diff --git a/main.py b/main.py index c474389..19315be 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -# ↑ For Linux & MacOS to run this program directly if the user currently installed python and third-party packages. +# ↑ For Linux & macOS to run this program directly if the user currently installed python and third-party packages. # -*- coding: utf-8 -*- # author: David-123 -from modules.raw_input import rinput +from modules.inputs import rinput from modules.information import print_info from modules.multi_download import mdl from modules.one_download import download_one_lyric diff --git a/modules/inputs.py b/modules/inputs.py new file mode 100644 index 0000000..276c9fb --- /dev/null +++ b/modules/inputs.py @@ -0,0 +1,11 @@ +"""该模块提供几个自定义处理输入函数""" + + +def rinput(string: str = ''): + """当调用该函数时,同input()一样,但是返回一个去除首位空格并全部小写的str""" + return input(string).strip().lower() + + +def cinput(string: str = ''): + """当调用该函数时,同input()一样,但是返回一个去除首尾空格的str""" + return input(string).strip() diff --git a/modules/load_file_song.py b/modules/load_file_song.py index b8350e1..10979f0 100644 --- a/modules/load_file_song.py +++ b/modules/load_file_song.py @@ -5,19 +5,31 @@ import struct from base64 import b64decode from Cryptodome.Cipher import AES -from mutagen import File +from mutagen import File, flac from mutagen.id3 import ID3, TPE1, APIC, COMM, TIT2, TALB from modules.clear_screen import clear from modules.get_song import get_song_lyric +from modules.inputs import cinput, rinput def load_information_from_song(path): """从音乐文件中的 Comment 字段获取 163 key 并解密返回歌曲信息""" - file = File(path) # 使用 TinyTag 获取歌曲信息 - if file.tags.get("COMM::XXX"): - if file.tags["COMM::XXX"].text[0][:7] == "163 key": - ciphertext = file.tags["COMM::XXX"].text[0][22:] + file = File(path) # 使用 mutagen 获取歌曲信息 + if os.path.splitext(path)[-1] == ".mp3": # 当文件为 mp3 时使用 ID3 格式读取 + if file.tags.get("COMM::XXX"): + if file.tags["COMM::XXX"].text[0][:7] == "163 key": + ciphertext = file.tags["COMM::XXX"].text[0][22:] + else: + return "not_support" + else: + return "not_support" + elif os.path.splitext(path)[-1] == ".flac": # 当文件为 flac 时使用 FLAC 格式读取 + if file.tags.get("DESCRIPTION"): + if file.tags["DESCRIPTION"][0][:7] == "163 key": + ciphertext = file.tags["DESCRIPTION"][0][22:] + else: + return "not_support" else: return "not_support" else: @@ -30,6 +42,7 @@ def load_information_from_song(path): end = ord(s[-1]) return s[0:-end] # return s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))] 更加清晰的理解 ↑ + cryptor = AES.new(b"#14ljk_!\\]&0U<'(", AES.MODE_ECB) # 使用密钥创建解密器 # 下方这一行将密文 ciphertext 转换为 bytes 后进行 base64 解码, 得到加密过的 AES 密文 @@ -114,23 +127,34 @@ def load_and_decrypt_from_ncm(file_path, targetdir): # nondanee的源代码, f.close() # 对解密后的文件进行信息补全 - audio = ID3(os.path.splitext(file_path)[0]+"."+meta_data["format"]) - artists = [] - for i in meta_data["artist"]: - artists.append(i[0]) - audio["TPE1"] = TPE1(encoding=3, text=artists) # 插入歌手 - audio["APIC"] = APIC(encoding=3, mime='image/jpg', type=3, desc='', data=image_data) # 插入封面 - audio["COMM::XXX"] = COMM(encoding=3, lang='XXX', desc='', text=[comment.decode("utf-8")]) # 插入 163 key 注释 - audio["TIT2"] = TIT2(encoding=3, text=[meta_data["musicName"]]) # 插入歌曲名 - audio["TALB"] = TALB(encoding=3, text=[meta_data["album"]]) # 插入专辑名 - audio.save() + if meta_data["format"] == "mp3": # 针对 mp3 使用 ID3 进行信息补全 + audio = ID3(os.path.join(targetdir, os.path.splitext(file_path.split("/")[-1])[0] + ".mp3")) + artists = [] + for i in meta_data["artist"]: + artists.append(i[0]) + audio["TPE1"] = TPE1(encoding=3, text=artists) # 插入歌手 + audio["APIC"] = APIC(encoding=3, mime='image/jpg', type=3, desc='', data=image_data) # 插入封面 + audio["COMM::XXX"] = COMM(encoding=3, lang='XXX', desc='', text=[comment.decode("utf-8")]) # 插入 163 key 注释 + audio["TIT2"] = TIT2(encoding=3, text=[meta_data["musicName"]]) # 插入歌曲名 + audio["TALB"] = TALB(encoding=3, text=[meta_data["album"]]) # 插入专辑名 + audio.save() + elif meta_data["format"] == "flac": # 针对 flac 使用 FLAC 进行信息补全 + audio = flac.FLAC(os.path.join(targetdir, os.path.splitext(file_path.split("/")[-1])[0] + ".flac")) + artists = [] + for i in meta_data["artist"]: + artists.append(i[0]) + audio["artist"] = artists[:] # 插入歌手 + audio["title"] = [meta_data["musicName"]] # 插入歌曲名 + audio["album"] = [meta_data["album"]] # 插入专辑名 + audio["description"] = comment.decode("utf-8") # 插入 163 key 注释 + audio.save() return meta_data def get_lyric_from_folder(lyric_path: str): clear() - path = input("请输入歌曲的保存文件夹(绝对路径):").strip() + path = cinput("请输入歌曲的保存文件夹(绝对路径):") if not os.path.exists(path): input("路径不存在.\n按回车返回...") return @@ -164,7 +188,7 @@ def get_lyric_from_folder(lyric_path: str): print(f"\n发现{len(ncm_files)}个ncm加密文件!") print("请问解密后的文件保存在哪里?\n" "[1] 保存在相同文件夹内\n[2] 保存在程序设定的下载文件夹中\n[3] 保存在自定义文件夹内\n[q] 取消解密,下载歌词时将忽略这些文件") - select = input("请选择: ").strip().lower() + select = rinput("请选择: ") if select == 'q': target_path = "NOT_DECRYPT" break @@ -182,28 +206,29 @@ def get_lyric_from_folder(lyric_path: str): if target_path != "NOT_DECRYPT": for i in range(0, len(ncm_files)): - print("破解进度: %d/%d ~ %s" % (i+1, len(ncm_files), ncm_files[i])) + print("破解进度: %d/%d ~ %s" % (i + 1, len(ncm_files), ncm_files[i])) try: result = load_and_decrypt_from_ncm(os.path.join(path, ncm_files[i]), target_path) except AssertionError: print(f"\t- 文件 \"{ncm_files[i]}\" 破解失败!\n\t 可能是文件不完整或者重命名了别的文件?跳过...") fails += 1 continue - print("\t--> %s" % os.path.splitext(ncm_files[i])[0]+"."+result["format"]) + print("\t--> %s" % os.path.splitext(ncm_files[i])[0] + "." + result["format"]) musics.append({"id": result['musicId'], "name": result["musicName"], "artists": result["artist"]}) # 汇报索引结果 - print(f"\n索引完毕!共找到{fails + len(musics) + len(ncm_files)}个目标文件\n{len(musics)}个文件已载入\n{fails}个文件失败") + print( + f"\n索引完毕!共找到{fails + len(musics) + len(ncm_files)}个目标文件\n{len(musics)}个文件已载入\n{fails}个文件失败") if ncm_files: if target_path == "NOT_DECRYPT": print(f"{len(ncm_files)}个文件放弃加载") while True: print("\n你希望如何保存这些歌曲的歌词?\n[1]保存到刚刚输入的绝对路径中\n[2]保存到程序设定的下载路径中") - r = input("请选择: ").strip().lower() + r = rinput("请选择: ") if r == "1": + lyric_path = path break elif r == "2": - path = lyric_path break else: try: @@ -215,16 +240,16 @@ def get_lyric_from_folder(lyric_path: str): clear() for i in range(0, len(musics)): # 根据索引结果获取歌词 print("\n进度: %d/%d" % (i + 1, len(musics))) - if get_song_lyric(musics[i], path, allinfo=True) == "dl_err_connection": + if get_song_lyric(musics[i], lyric_path, allinfo=True) == "dl_err_connection": input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...") if ncm_files: if target_path != "NOT_DECRYPT": - agree = input("是否删除原ncm文件? (y/n)").strip().lower() + agree = rinput("是否删除原ncm文件? (y/n)") if agree == "y": for i in range(0, len(ncm_files)): - print("删除进度: %d/%d ~ %s" % (i+1, len(ncm_files), ncm_files[i])) + print("删除进度: %d/%d\n -> %s\033[F" % (i + 1, len(ncm_files), ncm_files[i]), end="") os.remove(os.path.join(path, ncm_files[i])) else: - print("取消.") - input("按回车返回...") + print("取消.", end="") + input("\n\033[K按回车返回...") return diff --git a/modules/multi_download.py b/modules/multi_download.py index 236edb6..b106d30 100644 --- a/modules/multi_download.py +++ b/modules/multi_download.py @@ -1,6 +1,6 @@ import re from modules.clear_screen import clear -from modules.raw_input import rinput +from modules.inputs import rinput from modules.get_song import get_song_lyric @@ -18,9 +18,7 @@ def mdl(path: str): else: try: int(r) - except ValueError: - tmp = re.search(r"song\?id=[0-9]*", r) if tmp: r = tmp.group()[8:] @@ -31,7 +29,8 @@ def mdl(path: str): print("\t#%d id:%s - 已添加!" % (len(ids), r)) clear() for i in range(0, len(ids)): - print("\n进度: %d/%d" % (i+1, len(ids))) - if get_song_lyric(ids[i], path) == "dl_err_connection": + print("进度: %d/%d" % (i+1, len(ids))) + r = get_song_lyric(ids[i], path) + if r == "dl_err_connection": input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...") input("按回车键返回...") diff --git a/modules/one_download.py b/modules/one_download.py index 6b35a41..9838525 100644 --- a/modules/one_download.py +++ b/modules/one_download.py @@ -1,5 +1,5 @@ import re -from modules.raw_input import rinput +from modules.inputs import rinput from modules.get_song import get_song_lyric from modules.clear_screen import clear diff --git a/modules/settings.py b/modules/settings.py index 16063bb..0ec3b3d 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -1,9 +1,8 @@ """集合设置参数""" import os -import re from modules.clear_screen import clear -from modules.raw_input import rinput, cinput +from modules.inputs import rinput, cinput from modules.save_load_settings import save_settings @@ -13,33 +12,59 @@ def settings_menu(self): clear() print(f"[NeteaseMusicLyricDownloader] {self.version}\n" "[设置菜单]\n" - "[0] 返回上级\n[1] 设置歌曲保存路径\n[2] 清空输出文件夹内的所有歌词\n[s] 将设置保存到文件") + "[0] 返回上级\n[1] 歌曲保存路径\n[2] 清空输出文件夹内的内容\n[3] 歌词文件保存格式\n[4] 部分动态效果\n" + "[s] 将设置保存到文件") r = rinput("请选择:") if r == "0": return elif r == "1": __set_lyric_path(self) elif r == "2": - __remove_lyric_files(self.settings.lyric_path) + __remove_output_files(self) + elif r == "3": + pass + elif r == "4": + pass elif r == "s": __save_settings(self) else: input("输入无效!按回车键继续...") -def __remove_lyric_files(path): - clear() +def __remove_output_files(self): + while True: + clear() + print(f"[NeteaseMusicLyricDownloader] {self.version}\n" + "[设置菜单 - 删除文件]\n" + "[0] 返回上级\n[1] 清除歌词文件\n[2] 清除歌曲文件") + r = rinput("请选择:") # 选择清除的文件格式 + if r == "0": + return + elif r == "1": + dellist = [".lrc"] + break + elif r == "2": + dellist = [".mp3", ".flac"] + break + else: + input("输入无效!\n按回车键继续...") files = [] - for i in os.listdir(path): - if re.match(r".*(\.lrc)$", i): - files.append(i) + for i in os.listdir(self.settings.lyric_path): # 列出所有文件 + if os.path.splitext(i)[-1] in dellist: # 匹配文件 + files.append(i) # 将匹配到的文件加入到列表, 等待删除 if len(files) != 0: + if len(files) > 50: + special_text = "\033[F" + else: + special_text = "\n" for i in range(0, len(files)): - print("正在删除(%d/%d): %s" % (i+1, len(files), files[i])) - os.remove(path+files[i]) - input("删除完毕!\n按回车继续...") + print("删除进度: %d/%d\n -> %s%s" % (i+1, len(files), files[i], special_text), end="") # 删除进度提示 + os.remove(self.settings.lyric_path+files[i]) + input("\n\033[K删除完毕!\n按回车继续...") + return else: input("文件夹内没有要删除的东西\n按回车继续...") + return def __set_lyric_path(self): @@ -59,6 +84,11 @@ def __set_lyric_path(self): os.mkdir(path) self.settings.lyric_path = r input("设置成功!\n按回车继续...") + return + + +def __set_lyric_format(self): + pass def __save_settings(self):