diff --git a/moedatabase.sh b/moedatabase.sh index de872a9..fb4da8f 100644 --- a/moedatabase.sh +++ b/moedatabase.sh @@ -6,14 +6,15 @@ # History: # 2017/9/18 3usi9 - +# 没什么逻辑,功能比较散 DATABASE_DIR=$MOEFM_DATABASE DATABASE="$DATABASE_DIR" DATABASE+="/database" OPT_DIR="$HOME/moefm_export" +# 输出的路径 - +# 检查是否定义了数据库 if [ "$MOEFM_DATABASE" = "" ]; then echo -e "\e[1m\e[36mYou haven't set a database direction!\e[0m" echo -e "After set database direction, please \e[1m\e[31mRESTART\e[0m the terminal" @@ -26,8 +27,9 @@ if [ "$MOEFM_DATABASE" = "" ]; then dab=${dab/'~'/''$HOME''} echo "export MOEFM_DATABASE=$dab" >> "$HOME/.bashrc" source "$HOME/.bashrc" - mkdir $dab - touch $dab/database + mkdir "$dab" + touch "$dab/database" + touch "$dab/filter" exit 0 fi @@ -38,12 +40,15 @@ remove_sing() local sid=$* echo $sid sed -i '/^'"$sid"'/d' $DATABASE + # 删除数据库中的条目 local path="$DATABASE_DIR" path+="/$sid.mp3" rm "$path" + # 删除文件 echo -e "ID为\e[1m\e[33m$sid\e[0m的歌曲已从数据库中移出" } +# 见moefm.sh中的说明 switch_display_to_save() { local str=$*; @@ -54,6 +59,7 @@ switch_display_to_save() echo "$str" } +# 见moefm.sh中的说明 switch_save_to_display() { local str=$*; @@ -67,23 +73,32 @@ switch_save_to_display() clean() +# 传入参数:清理到数据库的大小(MB) { local size=$* local tomb=1024 size=$((size*tomb)) - - local file_tab=$(ls -altur --time-style=iso "$DATABASE_DIR" | grep "^-" | grep -v "database" | awk '{print $8}') + + local file_tab=$(ls -altur --time-style=iso "$DATABASE_DIR" | grep "^-" | grep -v "database" | grep -v "filter" | awk '{print $8}') + # 按照访问时间顺序从后往前列出数据库中的条目 + # ls -a:all + # ls -lt: list by change time + # ls -u: (use with -lt) list by access time + # ls -r: reverse (from older to newer) + for i in $file_tab do local cursize=$(du "$DATABASE_DIR" | awk '{print $1}') + # 数据库现在的大小 local file_id=$(echo $i | awk -F '.' '{print $1}') - # sed -i '/^'"$sid"'/d' $DATABASE + # 要删除的歌曲ID if [ $cursize -lt $size ]; then break fi + rm "$DATABASE_DIR/$i" sed -i '/^'"$file_id"'/d' $DATABASE done @@ -91,7 +106,7 @@ clean() } clear_database() -# haven't test +# 清空数据库 { echo -e "这项操作会\e[1m\e[31m删除\e[0m本地数据库中的\e[1m\e[31m所有歌曲\e[0m\n并且\e[1m\e[31m不可撤销\e[0m!" echo -e "请确保\e[1m\e[33mmoefm.sh没有运行\e[0m" @@ -104,6 +119,7 @@ clear_database() rm -r "$DATABASE_DIR/" mkdir "$DATABASE_DIR" touch "$DATABASE_DIR/database" + touch "$DATABASE_DIR/filter" break elif [ "$ans" = "no" ]; then echo -e "取消操作..." @@ -115,6 +131,7 @@ clear_database() } dump_all() +# 导出数据库中的所有歌曲 { clear if [ ! -d "$OPT_DIR" ]; then @@ -126,7 +143,10 @@ dump_all() local db=$(cat $DATABASE) local cnt=1 local tot=$(cat $DATABASE | grep -c "####") + # grep -c : count + echo -e "正在导出第 `tput sc`\e[1m\e[32m$cnt\e[0m 首歌曲,共有 \e[1m\e[33m$tot\e[0m 首歌曲" + # 采用tput控制光标位置 for i in $(echo $db) do echo -e "\e[1m\e[32m`tput rc`$cnt\e[0m" @@ -139,21 +159,28 @@ dump_all() tit=${tit//'%20'/' '} alb=${alb//'%20'/' '} art=${art//'%20'/' '} + # 这里要修改成switch_to_display... + # 下次再改,这次只加注释... + local path="$DATABASE_DIR/$id.mp3" mp3info -t "$tit" -a "$art" -l "$alb" "$path" - # Added mp3 metadata.... + # 把metadata写入导出的mp3中(待加入专辑封面写入功能) + tit=${tit//'/'/'/'} alb=${alb//'/'/'/'} + # 把'/'放在文件名里,转义起来很麻烦..直接改成全角'/' cp "$path" "$OPT_DIR/$tit - $alb.mp3" - + # 歌曲名 - 专辑名.mp3 done } dump_one() +# 导出包含关键字的歌曲 +# 传入参数:关键字 { arg=$* clear @@ -163,6 +190,7 @@ dump_one() echo -e "导出路径为\e[1m\e[33m$OPT_DIR\e[0m" fi son=$(cat $DATABASE | grep -i "$arg") + # grep -i: Ignore upper case and lower case if [ "$son" = "" ]; then echo "该歌曲不存在..." @@ -176,6 +204,8 @@ dump_one() local tit=$(echo $i | awk -F '####' '{print $2}') local alb=$(echo $i | awk -F '####' '{print $3}') local art=$(echo $i | awk -F '####' '{print $4}') + + # 改成switch id=${id//'%20'/' '} tit=${tit//'%20'/' '} alb=${alb//'%20'/' '} @@ -183,7 +213,7 @@ dump_one() local path="$DATABASE_DIR/$id.mp3" mp3info -t "$tit" -a "$art" -l "$alb" "$path" - # Added mp3 metadata.... + # mp3 metadata echo -e "正在导出:\e[1m\e[33m$tit\e[0m" tit=${tit//'/'/'/'} alb=${alb//'/'/'/'} @@ -193,6 +223,8 @@ dump_one() } search_database() +# 检索database中的条目 +# 传入参数:关键字 { arg=$* arg=$(switch_display_to_save "$arg") diff --git a/moefm.sh b/moefm.sh index 89aadf0..51e6e19 100755 --- a/moefm.sh +++ b/moefm.sh @@ -8,13 +8,60 @@ # 2015/3/3 Desmond Ding # 2017/9/17 3usi9 +# 工作逻辑: + +# #######搜索###################### +# 1.查找数据库 +# search() 查找网络数据库 +# search_local() 查找本地数据库 + +# 2.将找到的结果压入队列 +# push_playq() 在队尾追加元素 +# pop_playq() 弹出队头元素 +# change_playq_front() 修改队头元素 + +# #######播放##################### + +# 3.取出队头元素,检查title是否已决定(song_id已知),若没有,从moefm上下载歌曲信息 + +# 4.检查是否打开下载开关,如果打开,下载这首歌 + +# 5.播放队头歌曲,检查是否打开Last.fm同步开关,若打开,将播放记录上传到Last.fm + +# 6.弹出队头,如果队列非空,继续取出下一个队头元素 + +# ########下载################### + +# 检查本地数据库是否存在 +# 若存在,检查文件是否完整 +# 若完整,直接返回,并将队头的播放路径改为本地歌曲路径 +# 否则,从网络上下载这首歌曲 +# 若不存在,从网络上下载这首歌曲 + +# 数据库条目格式: +# song_id####song_title####song_album####song_artist +# 比如: +# 歌曲ID为 1234 ,title为 Hello world , album为 Goodbyeworld , artist为I'm world +# 数据库中的条目: +# 1234####Hello%20world####Goodbyeworld####I'm%20world + + + + + + # URL part BASE_URL='http://moe.fm/listen/playlist?api=json' +# moefm play API(所谓'专用接口') SEARCH_URL='http://api.moefou.org/search/sub.json?sub_type=song' +# moefm search API(通用借口,搜索[子条目(单首歌曲)]) +# TODO: 增加搜索wiki(专辑/相关列表)的功能 API_KEY='&api_key=4e229396346768a0c4ee2462d76eb0be052592bf8' DATABASE_DIR=$MOEFM_DATABASE DATABASE="$DATABASE_DIR" DATABASE+="/database" +# DATABASE里保存了下载到本地的歌曲和metadata + # UI part black=`tput setaf 0` red=`tput setaf 1` @@ -44,6 +91,7 @@ playq_front=0 push_playq() { + # 压入队尾 playq_url[$playq_back]=$1 playq_tit[$playq_back]=$2 playq_alb[$playq_back]=$3 @@ -56,11 +104,13 @@ push_playq() pop_playq() { + # 弹出队头 playq_front=$(($playq_front+1)) } change_playq_front() { + # 修改队头 playq_url[$playq_front]=$1 playq_tit[$playq_front]=$2 playq_alb[$playq_front]=$3 @@ -69,10 +119,11 @@ change_playq_front() playq_size[$playq_front]=$6 cover_url[$playq_back]=$7 } -# for dynamically resolving +# 一会儿搜索的时候需要用到修改队头的功能 check_que_empty() { + # 检查队列是否空 if [ "$playq_front" = "$playq_back" ]; then echo "1" else @@ -82,6 +133,7 @@ check_que_empty() check_str_empty() { + # 检查字符串是否空 local param=$* local str_empty="未知" @@ -93,13 +145,17 @@ check_str_empty() } switch_net_to_save() +# 将网络上的文件名改为本地保存的文件名(数据库里不能有' ', 这是for .. in .. 的分隔符,所以把所有的' '替换为'%20') { local str=$*; str=${str//' '/'%20'}; echo "$str" } + switch_save_to_display() +# 将数据库中的数据转换为可视数据 +# 数据库里的条目应当避免出现 ' < > '_'(空格)等字符 { local str=$*; str=${str//'''/"'"} @@ -111,6 +167,7 @@ switch_save_to_display() } switch_display_to_save() +# 将可视数据转换为数据库中可保存的数据 { local str=$*; str=${str//"'"/'''} @@ -120,13 +177,22 @@ switch_display_to_save() echo "$str" } -show_cover() -{ +show_cover() +# 展示专辑封面(测试中...不对,待测试...) +# 传入参数:专辑封面的网址 +{ local url=$* + + echo "HIT!" + # delete it after tested + wget -O "$DATABASE_DIR/opt.jpg" "$url" + # 应对网络不稳定的情况,所以先下载到本地再显示... pos=$(xwininfo -id $(xprop -root | awk '/_NET_ACTIVE_WINDOW\(WINDOW\)/{print $NF}') | grep "\-geometry" | awk '{print $2}') + # Terminal窗口的位置 + # 详细信息见Xorg geometry文档 echo $pos @@ -139,6 +205,7 @@ show_cover() if [ "$ycnt" = "" ]; then ycnt="0" fi + # 如果某个位置是0,它不会返回任何值... if [ "$xcnt" = "" ]; then xcnt="0" fi @@ -149,8 +216,10 @@ show_cover() nx=$(($nx+$xcnt)) ny=$(($hei*2)) ny=$(($ny+$ycnt)) + # 要展示的图片的位置 pic=$(identify "$DATABASE_DIR/opt.jpg" | awk '{print $3}') + # 图片的大小信息 echo "nx:$nx ny:$ny" px=$(echo $pic | awk -F 'x' '{print $1}') @@ -159,16 +228,19 @@ show_cover() echo "px=$px, py=$py" feh -g "$pic+$nx+$ny" -x "$DATABASE_DIR/opt.jpg" + # 使用feh作为看图工具(display什么的...其实个人不太喜欢) } block_song() +# 过滤歌曲的函数...如果传入参数中包含filter的敏感词就返回false,否则返回true { local keywd="$*" keywd=$(switch_display_to_save $keywd) local blocked="0" + # 逐行读取filter for line in $(<"$DATABASE_DIR/filter") do res=$(echo "$keywd" | grep -i "$line") @@ -190,12 +262,15 @@ block_song() love_track() +# Last.fm Love a song... +# 传入数据:song_id { local id=$* local url=$BASE_URL url+="&song=$id" url+=$API_KEY local moefm_json=$(curl -s -A moefm.sh echo $url) + # 请求json local mp3_url=$(echo $moefm_json | jq -M -r ".response.playlist[0].url") local title=$(echo $moefm_json | jq -M -r ".response.playlist[0].sub_title") @@ -218,12 +293,23 @@ love_track() artist=$(switch_save_to_display "$artist") python3 -c 'import scrobble; scrobble.Love_one("'"$title"'", "'"$album"'", "'"$artist"'")' >/dev/null 2>&1 + # 调用python库.., 见scrobble.py echo -e "\e[1m\e[33m$title\e[0m - \e[1m\e[32m$artist\e[0m loved" + # 其实个人还是喜欢手写ASCII Character... + # echo -e 表示使用ASCII Escape符号 + # \e表示Escape + # \e[0m 恢复无属性 + # \e[1m 粗体字 + # \e[31m 红色前景(字体) + # \e[41m 红色背景(会把背景图片遮住所以很不爽) + } pure_download() +# moefm.sh -D 选项,只下载一首歌曲 +# 传入歌曲ID { clear local id=$* @@ -253,10 +339,12 @@ pure_download() local path="$DATABASE_DIR" path+="/$id.mp3" + # 歌曲以id作为名称保存到数据库中 + title=$(check_str_empty $title) album=$(check_str_empty $album) artist=$(check_str_empty $artist) - + echo -e "正在下载的歌曲: " local TITLE="曲名: ${bold}${COLOR_ARG}%s\n${reset}" @@ -269,30 +357,47 @@ pure_download() title=$(switch_display_to_save "$title") album=$(switch_display_to_save "$album") artist=$(switch_display_to_save "$artist") - + # 转换为数据库条目格式 + local vect="$id####$title####$album####$artist" + # 见文件头部的说明 + local res=$(cat $DATABASE | grep "$id") if [ "$res" = "" ]; then - ### database didn't have this song... + # 数据库中没有这首歌曲 echo $vect >> $DATABASE + # 将其(metadata)添加到数据库 + wget "$mp3_url" -O "$path" --progress=bar:force 2>&1 | tail -f -n +8 echo "下载完成!" # Add the entry else - ### Entry exists + # 数据库中有这首歌曲 local file_size=$(du -a "$path" | awk '{print $1}') + # 本地文件大小 + # $song_siz 的定义在这个函数前面title的那部分...值为服务器上保存的歌曲的文件大小 + local delta=$(($file_size-$song_siz)) delta=${delta#-} - # There is always faults... + # 取绝对值 if [ $delta -gt 10 ]; then + # -gt 为greater than ### FILE_BROKEN! rm "$path" + # 删除文件 + sed -i '/^'"$id"'/d' $DATABASE - # delete database entry + # 删除数据库中的条目 + + echo $vect >> $DATABASE + # 新增这个条目到数据库 + # (不要问我为什么先删后增...我也想代码复用啊[这就是你把后面的代码粘贴过来的理由?]) wget "$mp3_url" -O "$path" --progress=bar:force 2>&1 | tail -f -n +8 + # tail命令截取wget中进度条的一部分 + echo "下载完成!" else echo "条目已存在!" @@ -303,6 +408,8 @@ pure_download() show_ui() +# 展示UI +# 传入歌曲的metadata { TITLE="曲名: ${bold}${COLOR_ARG}%s\n${reset}" ALBUM="专辑: ${bold}${COLOR_ARG}%s\n${reset}" @@ -320,8 +427,7 @@ show_ui() fi } - - +# 用这个奇怪的队列之后,我其实已经不知道怎么打乱顺序了... # # Fisher-Yates shuffle # shuffle() # { @@ -338,14 +444,18 @@ show_ui() # done # } + search_local() +# 搜索本地数据库 +# 传入值:keyword { local keywd=$* keywd=$(switch_display_to_save "$keywd"); - # I have no choice... + res=$(cat $DATABASE | grep -i "$keywd") for i in $(echo $res) + # 这里i的分隔是用空格(或换行符)做标识符的,所以要把歌曲名中的空格改为%20 do local sid=$(echo $i | awk -F '####' '{print $1}') local stit=$(echo $i | awk -F '####' '{print $2}') @@ -361,10 +471,13 @@ search_local() } search() +# 搜索网络数据库 +# 传入值:keyword { local keywd=$* keywd=${keywd//' '/'%20'} + # 相当于switch_display_to_net, 本着不改代码的原则放着不动 SEARCH_URL+="&keyword=" SEARCH_URL+=$keywd @@ -376,6 +489,7 @@ search() for((i=0;i/dev/null 2>&1 & echo $vect >> $DATABASE fi echo "${playq_url[$playq_front]}" + # return else + # 数据库中有条目 local file_size=$(du -a "$path" | awk '{print $1}') local delta=$(($file_size-${playq_size[$playq_front]})) delta=${delta#-} - # There is always faults... if [ $delta -lt 10 ]; then + # 文件是完整的 echo "$path" else rm "$path" @@ -572,14 +703,17 @@ download() play() # Need to check empty before play +# 播放队头的歌曲 { local file_path="$DATABASE_DIR" file_path+="/${playq_id[$playq_front]}.mp3" if [ "$ABS_LOCAL" = "1" ]; then + # -L 选项,本地查找(因为没有进行完整性检查所以可能播放一半...但总比什么都听不到要好) mpg123 -q -C "$file_path" else local final_path=$(download $file_path) + # 得到最终决定的播放地址[见download()] mpg123 -q -C "$final_path" fi } @@ -587,9 +721,10 @@ play() # Need to check empty before play play_a_song() { - +# 显示,播放并同步队头的歌曲 if [ "${playq_tit[$playq_front]}" = "to_be_resolved" ]; then resolve_json "${playq_url[$playq_front]}" + # dynamically resolving fi local sid=${playq_id[$playq_front]} @@ -604,14 +739,17 @@ play_a_song() ass=$(block_song "$stitle") + # 判断是否在filter列表中 if [ "$ass" = "true" ]; then + # 不在filter列表中 stitle=$(switch_save_to_display "$stitle") salbum=$(switch_save_to_display "$salbum") sartist=$(switch_save_to_display "$sartist") if [ "$SCRO_OPT" = "1" ]; then + # -U 选项,同步到last.fm nohup python3 -c 'import scrobble; scrobble.Scrobble_one("'"$stitle"'", "'"$salbum"'", "'"$sartist"'")' >/dev/null 2>&1 & fi @@ -626,7 +764,7 @@ play_a_song() } - +# 检查是否设置了数据库路径 if [ "$MOEFM_DATABASE" = "" ]; then echo -e "\e[1m\e[36mYou haven't set a database direction!\e[0m" echo -e "After set database direction, please \e[1m\e[31mRESTART\e[0m the terminal" @@ -648,7 +786,9 @@ fi while true; do if [ "$SEAR_OPT" = "1" ]; then + # -S 选项,搜索 if [ "$ABS_LOCAL" = "1" ]; then + # -L 选项,本地搜索 search_local $KEYWORDS else search $KEYWORDS @@ -656,39 +796,55 @@ while true; do fi if [ -n "$ALBUM_ARG" ]; then + # -a 选项,播放指定专辑 require_list "&music=$ALBUM_ARG" fi if [ -n "$SONG_ARG" ]; then + # -s 选项,播放单首歌曲 require_list "&song=$SONG_ARG" fi if [ -n "$RADIO_ARG" ]; then + # -r 选项,播放个人电台 require_list "&radio=$RADIO_ARG" fi if [ -n "$FREE_OPT" ]; then + # -X 选项,自由播放 require_list "" fi if [ "$*" = "" ]; then + # 什么都没有输入... echo -e "Please enter an argument, use ./moefm.sh -h to get help" exit 0 fi if [ "$(check_que_empty)" = "1" ]; then + # 队列里空空如也.. echo "There isn't any song..." exit 0 fi while [ "$(check_que_empty)" = "0" ]; do - play_a_song done if [ "$REPT_OPT" != 1 ]; then + # -R 选项,循环播放 exit 0 fi done + + +# 下一步计划: +# 1.用Qt和C++重写播放的核心功能与UI(明明想做GUI嘛~不要违背自己的意愿嘛~) +# 采用OOP范式(函数式要写一个大一点儿的项目其实真的力不从心...) +# 2.扩展模块采用Python写,交互采用Shell(这才是Shell派上用场的时候!) +# 3.不知道下次填这个坑是啥时候了... + +# Mail me: Templ_1@outlook.com +# github的账号是小号, 不经常上,可能不能很快答复 diff --git a/scrobble.py b/scrobble.py index 21f3e61..2e33d2e 100644 --- a/scrobble.py +++ b/scrobble.py @@ -129,7 +129,7 @@ def Love_one(title, album, artist): print(title); buf = ''; - # Clip every word into search_vector + for i in range(0, len(title)): if title[i] == ' ': @@ -142,7 +142,7 @@ def Love_one(title, album, artist): sear_vec.append(buf); - # last one + for i in range(0, len(sear_vec)): print(sear_vec[i]); @@ -162,32 +162,14 @@ def Love_one(title, album, artist): if len(albums) > 0: art = albums[0].get_artist(); - - # Traceback and search track... - # search = network.search_for_track(art, sear_vec[0]); - # tracks = search.get_next_page(); - # # In round search - # if len(tracks) > 0: - # network.scrobble(tracks[0].get_artist(), tracks[0].get_name(), time.time(), tracks[0].get_album()); - # print("Round 2+ FOUND!"); - # return; - # VERY DANGEROUS... - - # Note: this part of search hardly hit the target... - # So I removed it... - - - - # Still can't find track... Force add... loved = pylast.Track(albums[0].get_artist(), title, network); loved.love(); - # このなまえ.. print("Round 3 FOUND!"); return; - # If still can't fetch......... - # Huhuhuhuhu~~~~~~Generate Album Information + # Generate Round + if album != '未知': loved = pylast.Track(artist, title, network); else: