Change file structure

This commit is contained in:
1826013250 2023-04-04 19:58:02 +08:00
parent dcd275e3b1
commit 71e967808b
14 changed files with 432 additions and 413 deletions

22
main.py
View File

@ -4,14 +4,14 @@
# author: David-123 # author: David-123
from modules.inputs import rinput from modules.utils.inputs import rinput
from modules.information import print_info from modules.utils.information import print_info
from modules.multi_download import mdl from modules.functions.multi_download import mdl
from modules.one_download import download_one_lyric from modules.functions.one_download import download_one_lyric
from modules.settings import settings_menu from modules.submenus.settings import settings_menu
from modules.save_load_settings import load_settings from modules.functions.save_load_settings import load_settings
from modules.clear_screen import clear from modules.utils.clear_screen import clear
from modules.load_file_song import get_lyric_from_folder from modules.functions.load_file_song import get_lyric_from_folder
class MainProcess(object): class MainProcess(object):
@ -30,11 +30,11 @@ class MainProcess(object):
r = rinput("请选择:") r = rinput("请选择:")
if r == "1": if r == "1":
download_one_lyric(self.settings.lyric_path) download_one_lyric(self)
elif r == "2": elif r == "2":
mdl(self.settings.lyric_path) mdl(self)
elif r == "3": elif r == "3":
get_lyric_from_folder(self.settings.lyric_path) get_lyric_from_folder(self)
elif r == "0": elif r == "0":
exit(0) exit(0)
elif r == "i": elif r == "i":

View File

View File

@ -1,298 +1,306 @@
import binascii import binascii
import json import json
import os import os
import struct import struct
from base64 import b64decode from base64 import b64decode
from multiprocessing import Process, Queue from multiprocessing import Process, Queue
from queue import Empty from queue import Empty
from progress.bar import Bar from progress.bar import Bar
from Cryptodome.Cipher import AES from Cryptodome.Cipher import AES
from mutagen import File, flac from mutagen import File, flac
from mutagen.id3 import ID3, TPE1, APIC, COMM, TIT2, TALB from mutagen.id3 import ID3, TPE1, APIC, COMM, TIT2, TALB
from modules.clear_screen import clear from modules.utils.clear_screen import clear
from modules.get_song import get_song_lyric from modules.functions.get_song import get_song_lyric
from modules.inputs import cinput, rinput from modules.utils.inputs import cinput, rinput
def load_information_from_song(path): def load_information_from_song(path):
"""从音乐文件中的 Comment 字段获取 163 key 并解密返回歌曲信息""" """从音乐文件中的 Comment 字段获取 163 key 并解密返回歌曲信息"""
file = File(path) # 使用 mutagen 获取歌曲信息 file = File(path) # 使用 mutagen 获取歌曲信息
if os.path.splitext(path)[-1] == ".mp3": # 当文件为 mp3 时使用 ID3 格式读取 if os.path.splitext(path)[-1] == ".mp3": # 当文件为 mp3 时使用 ID3 格式读取
if file.tags.get("COMM::XXX"): if file.tags.get("COMM::XXX"):
if file.tags["COMM::XXX"].text[0][:7] == "163 key": if file.tags["COMM::XXX"].text[0][:7] == "163 key":
ciphertext = file.tags["COMM::XXX"].text[0][22:] ciphertext = file.tags["COMM::XXX"].text[0][22:]
else: else:
return "not_support" return "not_support"
else: else:
return "not_support" return "not_support"
elif os.path.splitext(path)[-1] == ".flac": # 当文件为 flac 时使用 FLAC 格式读取 elif os.path.splitext(path)[-1] == ".flac": # 当文件为 flac 时使用 FLAC 格式读取
if file.tags.get("DESCRIPTION"): if file.tags.get("DESCRIPTION"):
if file.tags["DESCRIPTION"][0][:7] == "163 key": if file.tags["DESCRIPTION"][0][:7] == "163 key":
ciphertext = file.tags["DESCRIPTION"][0][22:] ciphertext = file.tags["DESCRIPTION"][0][22:]
else: else:
return "not_support" return "not_support"
else: else:
return "not_support" return "not_support"
else: else:
return "not_support" return "not_support"
def unpad(s): # 创建清理针对于网易云的 AES-128-ECB 解密后末尾占位符的函数 def unpad(s): # 创建清理针对于网易云的 AES-128-ECB 解密后末尾占位符的函数
if type(s[-1]) == int: if type(s[-1]) == int:
end = s[-1] end = s[-1]
else: else:
end = ord(s[-1]) end = ord(s[-1])
return s[0:-end] return s[0:-end]
# return s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))] 更加清晰的理解 ↑ # return s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))] 更加清晰的理解 ↑
cryptor = AES.new(b"#14ljk_!\\]&0U<'(", AES.MODE_ECB) # 使用密钥创建解密器 cryptor = AES.new(b"#14ljk_!\\]&0U<'(", AES.MODE_ECB) # 使用密钥创建解密器
# 下方这一行将密文 ciphertext 转换为 bytes 后进行 base64 解码, 得到加密过的 AES 密文 # 下方这一行将密文 ciphertext 转换为 bytes 后进行 base64 解码, 得到加密过的 AES 密文
# 再通过上方创建的 AES 128-ECB 的解密器进行解密, 然后使用 unpad 清除末尾无用的占位符后得到结果 # 再通过上方创建的 AES 128-ECB 的解密器进行解密, 然后使用 unpad 清除末尾无用的占位符后得到结果
try: try:
r = unpad((cryptor.decrypt(b64decode(bytes(ciphertext, "utf-8"))).decode("utf-8"))) r = unpad((cryptor.decrypt(b64decode(bytes(ciphertext, "utf-8"))).decode("utf-8")))
except ValueError: except ValueError:
return "decrypt_failed" return "decrypt_failed"
if r: if r:
if r[:5] == "music": if r[:5] == "music":
return json.loads(r[6:]) return json.loads(r[6:])
else: else:
return "not_a_normal_music" return "not_a_normal_music"
else: else:
return "decrypt_failed" return "decrypt_failed"
def load_and_decrypt_from_ncm(file_path, targetdir): # nondanee的源代码, 根据需求更改了某些东西 def load_and_decrypt_from_ncm(file_path, targetdir): # nondanee的源代码, 根据需求更改了某些东西
core_key = binascii.a2b_hex("687A4852416D736F356B496E62617857") core_key = binascii.a2b_hex("687A4852416D736F356B496E62617857")
meta_key = binascii.a2b_hex("2331346C6A6B5F215C5D2630553C2728") meta_key = binascii.a2b_hex("2331346C6A6B5F215C5D2630553C2728")
unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))] unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))]
f = open(file_path, 'rb') f = open(file_path, 'rb')
header = f.read(8) header = f.read(8)
assert binascii.b2a_hex(header) == b'4354454e4644414d' assert binascii.b2a_hex(header) == b'4354454e4644414d'
f.seek(2, 1) f.seek(2, 1)
key_length = f.read(4) key_length = f.read(4)
key_length = struct.unpack('<I', bytes(key_length))[0] key_length = struct.unpack('<I', bytes(key_length))[0]
key_data = f.read(key_length) key_data = f.read(key_length)
key_data_array = bytearray(key_data) key_data_array = bytearray(key_data)
for i in range(0, len(key_data_array)): for i in range(0, len(key_data_array)):
key_data_array[i] ^= 0x64 key_data_array[i] ^= 0x64
key_data = bytes(key_data_array) key_data = bytes(key_data_array)
cryptor = AES.new(core_key, AES.MODE_ECB) cryptor = AES.new(core_key, AES.MODE_ECB)
key_data = unpad(cryptor.decrypt(key_data))[17:] key_data = unpad(cryptor.decrypt(key_data))[17:]
key_length = len(key_data) key_length = len(key_data)
key_data = bytearray(key_data) key_data = bytearray(key_data)
key_box = bytearray(range(256)) key_box = bytearray(range(256))
c = 0 c = 0
last_byte = 0 last_byte = 0
key_offset = 0 key_offset = 0
for i in range(256): for i in range(256):
swap = key_box[i] swap = key_box[i]
c = (swap + last_byte + key_data[key_offset]) & 0xff c = (swap + last_byte + key_data[key_offset]) & 0xff
key_offset += 1 key_offset += 1
if key_offset >= key_length: if key_offset >= key_length:
key_offset = 0 key_offset = 0
key_box[i] = key_box[c] key_box[i] = key_box[c]
key_box[c] = swap key_box[c] = swap
last_byte = c last_byte = c
meta_length = f.read(4) meta_length = f.read(4)
meta_length = struct.unpack('<I', bytes(meta_length))[0] meta_length = struct.unpack('<I', bytes(meta_length))[0]
meta_data = f.read(meta_length) meta_data = f.read(meta_length)
meta_data_array = bytearray(meta_data) meta_data_array = bytearray(meta_data)
for i in range(0, len(meta_data_array)): for i in range(0, len(meta_data_array)):
meta_data_array[i] ^= 0x63 meta_data_array[i] ^= 0x63
meta_data = bytes(meta_data_array) meta_data = bytes(meta_data_array)
comment = meta_data comment = meta_data
meta_data = b64decode(meta_data[22:]) meta_data = b64decode(meta_data[22:])
cryptor = AES.new(meta_key, AES.MODE_ECB) cryptor = AES.new(meta_key, AES.MODE_ECB)
meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:] meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:]
meta_data = json.loads(meta_data) meta_data = json.loads(meta_data)
crc32 = f.read(4) crc32 = f.read(4)
crc32 = struct.unpack('<I', bytes(crc32))[0] crc32 = struct.unpack('<I', bytes(crc32))[0]
f.seek(5, 1) f.seek(5, 1)
image_size = f.read(4) image_size = f.read(4)
image_size = struct.unpack('<I', bytes(image_size))[0] image_size = struct.unpack('<I', bytes(image_size))[0]
image_data = f.read(image_size) image_data = f.read(image_size)
file_name = f.name.split("/")[-1].split(".ncm")[0] + '.' + meta_data['format'] file_name = f.name.split("/")[-1].split(".ncm")[0] + '.' + meta_data['format']
m = open(os.path.join(targetdir, file_name), 'wb') m = open(os.path.join(targetdir, file_name), 'wb')
chunk = bytearray() chunk = bytearray()
while True: while True:
chunk = bytearray(f.read(0x8000)) chunk = bytearray(f.read(0x8000))
chunk_length = len(chunk) chunk_length = len(chunk)
if not chunk: if not chunk:
break break
for i in range(1, chunk_length + 1): for i in range(1, chunk_length + 1):
j = i & 0xff j = i & 0xff
chunk[i - 1] ^= key_box[(key_box[j] + key_box[(key_box[j] + j) & 0xff]) & 0xff] chunk[i - 1] ^= key_box[(key_box[j] + key_box[(key_box[j] + j) & 0xff]) & 0xff]
m.write(chunk) m.write(chunk)
m.close() m.close()
f.close() f.close()
# 对解密后的文件进行信息补全 # 对解密后的文件进行信息补全
if meta_data["format"] == "mp3": # 针对 mp3 使用 ID3 进行信息补全 if meta_data["format"] == "mp3": # 针对 mp3 使用 ID3 进行信息补全
audio = ID3(os.path.join(targetdir, os.path.splitext(file_path.split("/")[-1])[0] + ".mp3")) audio = ID3(os.path.join(targetdir, os.path.splitext(file_path.split("/")[-1])[0] + ".mp3"))
artists = [] artists = []
for i in meta_data["artist"]: for i in meta_data["artist"]:
artists.append(i[0]) artists.append(i[0])
audio["TPE1"] = TPE1(encoding=3, text=artists) # 插入歌手 audio["TPE1"] = TPE1(encoding=3, text=artists) # 插入歌手
audio["APIC"] = APIC(encoding=3, mime='image/jpg', type=3, desc='', data=image_data) # 插入封面 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["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["TIT2"] = TIT2(encoding=3, text=[meta_data["musicName"]]) # 插入歌曲名
audio["TALB"] = TALB(encoding=3, text=[meta_data["album"]]) # 插入专辑名 audio["TALB"] = TALB(encoding=3, text=[meta_data["album"]]) # 插入专辑名
audio.save() audio.save()
elif meta_data["format"] == "flac": # 针对 flac 使用 FLAC 进行信息补全 elif meta_data["format"] == "flac": # 针对 flac 使用 FLAC 进行信息补全
audio = flac.FLAC(os.path.join(targetdir, os.path.splitext(file_path.split("/")[-1])[0] + ".flac")) audio = flac.FLAC(os.path.join(targetdir, os.path.splitext(file_path.split("/")[-1])[0] + ".flac"))
artists = [] artists = []
for i in meta_data["artist"]: for i in meta_data["artist"]:
artists.append(i[0]) artists.append(i[0])
audio["artist"] = artists[:] # 插入歌手 audio["artist"] = artists[:] # 插入歌手
audio["title"] = [meta_data["musicName"]] # 插入歌曲名 audio["title"] = [meta_data["musicName"]] # 插入歌曲名
audio["album"] = [meta_data["album"]] # 插入专辑名 audio["album"] = [meta_data["album"]] # 插入专辑名
audio["description"] = comment.decode("utf-8") # 插入 163 key 注释 audio["description"] = comment.decode("utf-8") # 插入 163 key 注释
audio.save() audio.save()
return meta_data return meta_data
def process_work(path, filename, target, q_err: Queue, q_info: Queue): def process_work(path, filename, target, q_err: Queue, q_info: Queue):
try: try:
result = load_and_decrypt_from_ncm(path, target) result = load_and_decrypt_from_ncm(path, target)
except AssertionError: except AssertionError:
q_err.put(f"\t- 文件 \"{filename}\" 破解失败!") q_err.put(f"\t- 文件 \"{filename}\" 破解失败!")
else: else:
q_info.put(result) q_info.put(result)
def get_lyric_from_folder(lyric_path: str): def get_lyric_from_folder(self):
clear() global ncm_files_num
path = cinput("请输入歌曲的保存文件夹(绝对路径):") clear()
if not os.path.exists(path): path = cinput(
input("路径不存在.\n按回车返回...") f"[NeteaseMusicLyricDownloader] {self.version}\n"
return "[自动获取]\n"
"请输入歌曲的保存文件夹(绝对路径):")
print("正在遍历目录,请稍后...") if not os.path.exists(path):
musics = [] input("路径不存在.\n按回车返回...")
ncm_files = [] return
fails = 0
for i in os.listdir(path): # 遍历目录,查找目标文件 print("正在遍历目录,请稍后...")
ext = os.path.splitext(i)[-1] musics = []
if ext in ['.mp3', '.flac']: # 对于 mp3 和 flac 文件, 使用对应读取解密方式 ncm_files = []
result = load_information_from_song(os.path.join(path, i)) fails = 0
if result == "not_support": for i in os.listdir(path): # 遍历目录,查找目标文件
fails += 1 ext = os.path.splitext(i)[-1]
print(f"文件 \"{i}\" 未包含 163 key ,跳过") if ext in ['.mp3', '.flac']: # 对于 mp3 和 flac 文件, 使用对应读取解密方式
elif result == "decrypt_failed": result = load_information_from_song(os.path.join(path, i))
fails += 1 if result == "not_support":
print(f"文件 \"{i}\" 内 163 key 解密失败,跳过") fails += 1
elif result == "not_a_normal_music": print(f"文件 \"{i}\" 未包含 163 key ,跳过")
fails += 1 elif result == "decrypt_failed":
print(f"文件 \"{i}\" 内 163 key 不是一个普通音乐文件,这可能是一个电台曲目") fails += 1
else: print(f"文件 \"{i}\" 内 163 key 解密失败,跳过")
musics.append({"id": result['musicId'], "name": result["musicName"], "artists": result["artist"]}) elif result == "not_a_normal_music":
elif ext == ".ncm": # 对于 ncm 先加入到列表,等待解密 fails += 1
ncm_files.append(i) print(f"文件 \"{i}\" 内 163 key 不是一个普通音乐文件,这可能是一个电台曲目")
else: else:
pass musics.append({"id": result['musicId'], "name": result["musicName"], "artists": result["artist"]})
elif ext == ".ncm": # 对于 ncm 先加入到列表,等待解密
if ncm_files: ncm_files.append(i)
while True: else:
print(f"\n发现{len(ncm_files)}个ncm加密文件!") pass
print("请问解密后的文件保存在哪里?\n"
"[1] 保存在相同文件夹内\n[2] 保存在程序设定的下载文件夹中\n[3] 保存在自定义文件夹内\n[q] 取消解密,下载歌词时将忽略这些文件") if ncm_files:
select = rinput("请选择: ") while True:
if select == 'q': print(f"\n发现{len(ncm_files)}个ncm加密文件!")
target_path = "NOT_DECRYPT" print("请问解密后的文件保存在哪里?\n"
break "[1] 保存在相同文件夹内\n[2] 保存在程序设定的下载文件夹中\n[3] 保存在自定义文件夹内\n[q] 取消解密,下载歌词时将忽略这些文件")
elif select == '1': select = rinput("请选择: ")
target_path = path if select == 'q':
break target_path = "NOT_DECRYPT"
elif select == '2': break
target_path = lyric_path elif select == '1':
break target_path = path
elif select == '3': break
target_path = input("请输入: ").strip() elif select == '2':
break target_path = self.settings.lyric_path
else: break
print("输入无效!按回车继续...") elif select == '3':
target_path = input("请输入: ").strip()
if target_path != "NOT_DECRYPT": # 开始进行逐个文件解密 break
errors = [] # 初始化变量 else:
q_err = Queue() # 错误信息队列 print("输入无效!按回车继续...")
q_info = Queue() # 返回信息队列
max_process = 20 # 最大进程数 if target_path != "NOT_DECRYPT": # 开始进行逐个文件解密
current_process = 0 # 当前正在活动的进程数 errors = [] # 初始化变量
passed = 0 # 总共结束的进程数 q_err = Queue() # 错误信息队列
with Bar("正在破解", max=len(ncm_files)) as bar: q_info = Queue() # 返回信息队列
total = len(ncm_files) max_process = 20 # 最大进程数
finished = 0 # 已经分配的任务数量 current_process = 0 # 当前正在活动的进程数
while True: # 进入循环,执行 新建进程->检测队列->检测任务完成 的循环 passed = 0 # 总共结束的进程数
if current_process <= max_process and finished < total: # 分配进程 with Bar("正在破解", max=len(ncm_files)) as bar:
Process(target=process_work, total = len(ncm_files)
args=(os.path.join(path, ncm_files[finished]), finished = 0 # 已经分配的任务数量
ncm_files[finished], while True: # 进入循环,执行 新建进程->检测队列->检测任务完成 的循环
target_path, if current_process <= max_process and finished < total: # 分配进程
q_err, Process(target=process_work,
q_info)).start() args=(os.path.join(path, ncm_files[finished]),
finished += 1 ncm_files[finished],
while True: # 错误队列检测 target_path,
try: q_err,
errors.append(q_err.get_nowait()) q_info)).start()
passed += 1 # 总任务完成数 finished += 1
current_process -= 1 # 检测到进程完毕将进程-1 while True: # 错误队列检测
bar.next() # 推动进度条 try:
fails += 1 # 错误数量+1 errors.append(q_err.get_nowait())
except Empty: passed += 1 # 总任务完成数
break current_process -= 1 # 检测到进程完毕将进程-1
while True: # 信息队列检测 bar.next() # 推动进度条
try: fails += 1 # 错误数量+1
r = q_info.get_nowait() except Empty:
musics.append({"id": r['musicId'], "name": r["musicName"], "artists": r["artist"]}) break
passed += 1 while True: # 信息队列检测
current_process -= 1 try:
bar.next() r = q_info.get_nowait()
except Empty: musics.append({"id": r['musicId'], "name": r["musicName"], "artists": r["artist"]})
break passed += 1
if passed >= len(ncm_files): current_process -= 1
break bar.next()
if errors: except Empty:
print("解密过程中发现了以下错误:") break
for i in errors: if passed >= len(ncm_files):
print(i) break
if errors:
# 汇报索引结果 print("解密过程中发现了以下错误:")
print(f"\n索引完毕!共找到{fails + len(musics) + len(ncm_files)}个目标文件\n{len(musics)}个文件已载入\n{fails}个文件失败") for i in errors:
if ncm_files: print(i)
if target_path == "NOT_DECRYPT":
print(f"{len(ncm_files)}个文件放弃加载") # 汇报索引结果
while True: ncm_files_num = 0
print("\n你希望如何保存这些歌曲的歌词?\n[1]保存到刚刚输入的绝对路径中\n[2]保存到程序设定的下载路径中") if ncm_files:
r = rinput("请选择: ") if target_path == "NOT_DECRYPT":
if r == "1": ncm_files_num = len(ncm_files)
lyric_path = path print(f"\n索引完毕!共找到{fails + len(musics) + ncm_files_num}个目标文件\n{len(musics)}个文件已载入\n{fails}个文件失败")
break if ncm_files:
elif r == "2": if target_path == "NOT_DECRYPT":
break print(f"{len(ncm_files)}个文件放弃加载")
else: while True:
try: print("\n你希望如何保存这些歌曲的歌词?\n[1]保存到刚刚输入的绝对路径中\n[2]保存到程序设定的下载路径中")
input("无效选择, 若取消请按 ^C ,继续请按回车") r = rinput("请选择: ")
clear() if r == "1":
except KeyboardInterrupt: lyric_path = path
return break
elif r == "2":
clear() break
for i in range(0, len(musics)): # 根据索引结果获取歌词 else:
print("\n进度: %d/%d" % (i + 1, len(musics))) try:
if get_song_lyric(musics[i], lyric_path, allinfo=True) == "dl_err_connection": input("无效选择, 若取消请按 ^C ,继续请按回车")
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...") clear()
if ncm_files: except KeyboardInterrupt:
if target_path != "NOT_DECRYPT": return
agree = rinput("是否删除原ncm文件? (y/n)")
if agree == "y": clear()
for i in range(0, len(ncm_files)): for i in range(0, len(musics)): # 根据索引结果获取歌词
print("删除进度: %d/%d\n -> %s\033[F" % (i + 1, len(ncm_files), ncm_files[i]), end="") print("\n进度: %d/%d" % (i + 1, len(musics)))
os.remove(os.path.join(path, ncm_files[i])) if get_song_lyric(musics[i], lyric_path, allinfo=True) == "dl_err_connection":
else: input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...")
print("取消.", end="") if ncm_files:
input("\n\033[K按回车返回...") if target_path != "NOT_DECRYPT":
return agree = rinput("是否删除原ncm文件? (y/n)")
if agree == "y":
for i in range(0, len(ncm_files)):
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("取消.", end="")
input("\n\033[K按回车返回...")
return

View File

@ -1,36 +1,38 @@
import re import re
from modules.clear_screen import clear from modules.utils.clear_screen import clear
from modules.inputs import rinput from modules.utils.inputs import rinput
from modules.get_song import get_song_lyric from modules.functions.get_song import get_song_lyric
def mdl(path: str): def mdl(self):
"""多个歌词文件的下载 """多个歌词文件的下载
``path: str`` 传入歌词文件保存的路径""" ``path: str`` 传入歌词文件保存的路径"""
clear() clear()
ids = [] ids = []
print("输入歌曲id,用回车分开,输入s停止") print(f"[NeteaseMusicLyricDownloader] {self.version}\n"
while True: "[手动-多个下载]\n"
r = rinput() "输入歌曲id,用回车分开,输入s停止")
if r == 's': while True:
break r = rinput()
else: if r == 's':
try: break
int(r) else:
except ValueError: try:
tmp = re.search(r"song\?id=[0-9]*", r) int(r)
if tmp: except ValueError:
r = tmp.group()[8:] tmp = re.search(r"song\?id=[0-9]*", r)
else: if tmp:
print("不合法的形式.\n") r = tmp.group()[8:]
continue else:
ids.append(r) print("不合法的形式.\n")
print("\t#%d id:%s - 已添加!" % (len(ids), r)) continue
clear() ids.append(r)
for i in range(0, len(ids)): print("\t#%d id:%s - 已添加!" % (len(ids), r))
print("进度: %d/%d" % (i+1, len(ids))) clear()
r = get_song_lyric(ids[i], path) for i in range(0, len(ids)):
if r == "dl_err_connection": print("进度: %d/%d" % (i+1, len(ids)))
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...") r = get_song_lyric(ids[i], self.settings.lyric_path)
input("按回车键返回...") if r == "dl_err_connection":
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...")
input("按回车键返回...")

View File

@ -1,15 +1,18 @@
import re import re
from modules.inputs import rinput from modules.utils.inputs import rinput
from modules.get_song import get_song_lyric from modules.functions.get_song import get_song_lyric
from modules.clear_screen import clear from modules.utils.clear_screen import clear
def download_one_lyric(path: str): def download_one_lyric(self):
"""单次下载歌词 """单次下载歌词
``path: str`` 存储歌词的路径""" ``path: str`` 存储歌词的路径"""
clear() clear()
song_id = rinput("请输入歌曲id:") song_id = rinput(
f"[NeteaseMusicLyricDownloader] {self.version}\n"
"[手动-单个下载]\n"
"请输入歌曲id:")
try: try:
int(song_id) int(song_id)
except ValueError: except ValueError:
@ -20,6 +23,6 @@ def download_one_lyric(path: str):
input("不合法的形式.\n按回车键返回...") input("不合法的形式.\n按回车键返回...")
return return
if get_song_lyric(song_id, path) == "dl_err_connection": if get_song_lyric(song_id, self.settings.lyric_path) == "dl_err_connection":
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键返回...") input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键返回...")
input("按回车键返回...") input("按回车键返回...")

View File

View File

@ -1,9 +1,9 @@
"""集合设置参数""" """集合设置参数"""
import os import os
from modules.clear_screen import clear from modules.utils.clear_screen import clear
from modules.inputs import rinput, cinput from modules.utils.inputs import rinput, cinput
from modules.save_load_settings import save_settings from modules.functions.save_load_settings import save_settings
def settings_menu(self): def settings_menu(self):
@ -36,7 +36,7 @@ def __remove_output_files(self):
clear() clear()
print(f"[NeteaseMusicLyricDownloader] {self.version}\n" print(f"[NeteaseMusicLyricDownloader] {self.version}\n"
"[设置菜单 - 删除文件]\n" "[设置菜单 - 删除文件]\n"
"[0] 返回上级\n[1] 清除歌词文件\n[2] 清除歌曲文件") "[0] 返回上级\n[1] 清除歌词文件\n[2] 清除歌曲文件\n[a] 清除所有文件")
r = rinput("请选择:") # 选择清除的文件格式 r = rinput("请选择:") # 选择清除的文件格式
if r == "0": if r == "0":
return return
@ -46,14 +46,20 @@ def __remove_output_files(self):
elif r == "2": elif r == "2":
dellist = [".mp3", ".flac"] dellist = [".mp3", ".flac"]
break break
elif r == "a":
dellist = ["ALL"]
break
else: else:
input("输入无效!\n按回车键继续...") input("输入无效!\n按回车键继续...")
files = [] files = []
for i in os.listdir(self.settings.lyric_path): # 列出所有文件 for i in os.listdir(self.settings.lyric_path): # 列出所有文件
if os.path.splitext(i)[-1] in dellist: # 匹配文件 if dellist[0] == "ALL":
files = os.listdir(self.settings.lyric_path)
break
elif os.path.splitext(i)[-1] in dellist: # 匹配文件
files.append(i) # 将匹配到的文件加入到列表, 等待删除 files.append(i) # 将匹配到的文件加入到列表, 等待删除
if len(files) != 0: if len(files) != 0:
if len(files) > 50: if len(files) > 30:
special_text = "\033[F" special_text = "\033[F"
else: else:
special_text = "\n" special_text = "\n"

View File

View File

@ -1,12 +1,12 @@
"""包含一个函数,用来清空命令行的信息,自动判别系统""" """包含一个函数,用来清空命令行的信息,自动判别系统"""
import os import os
def clear(): def clear():
name = os.name name = os.name
if name == "nt": if name == "nt":
os.system("cls") os.system("cls")
elif name == "posix": elif name == "posix":
os.system("clear") os.system("clear")
else: else:
os.system("clear") os.system("clear")

View File

@ -1,24 +1,24 @@
"""该程序的自述信息,调用即输出""" """该程序的自述信息,调用即输出"""
from modules.clear_screen import clear from modules.utils.clear_screen import clear
def print_info(self): def print_info(self):
"""调用即输出,无返回值""" """调用即输出,无返回值"""
clear() clear()
print(f"""[NeteaseMusicLyricDownloader] print(f"""[NeteaseMusicLyricDownloader]
版本: {self.version} 版本: {self.version}
本软件开源项目地址:https://github.com/1826013250/NeteaseMusicLyricDownloader 本软件开源项目地址:https://github.com/1826013250/NeteaseMusicLyricDownloader
作者:David-123 作者:David-123
联系方式: 联系方式:
\tQQ:1826013250 \tQQ:1826013250
\tE-mail:1826013250@qq.com(mainly) \tE-mail:1826013250@qq.com(mainly)
\t mc1826013250@gmail.com \t mc1826013250@gmail.com
特别感谢: 特别感谢:
\t- nondanee - ncmdump https://github.com/nondanee/ncmdump \t- nondanee - ncmdump https://github.com/nondanee/ncmdump
\t- QCloudHao - 保存了完整的ncmdump源代码 https://github.com/QCloudHao/ncmdump \t- QCloudHao - 保存了完整的ncmdump源代码 https://github.com/QCloudHao/ncmdump
\t- chuyaoxin - 提供了对ncmdump以及ncm文件的详细解说 https://www.cnblogs.com/cyx-b/p/13443003.html \t- chuyaoxin - 提供了对ncmdump以及ncm文件的详细解说 https://www.cnblogs.com/cyx-b/p/13443003.html
若程序遇到bug请提交至github上的issue""") 若程序遇到bug请提交至github上的issue""")
input("按回车键返回...") input("按回车键返回...")
return return

View File

@ -1,11 +1,11 @@
"""该模块提供几个自定义处理输入函数""" """该模块提供几个自定义处理输入函数"""
def rinput(string: str = ''): def rinput(string: str = ''):
"""当调用该函数时同input()一样但是返回一个去除首尾空格并全部小写的str""" """当调用该函数时同input()一样但是返回一个去除首尾空格并全部小写的str"""
return input(string).strip().lower() return input(string).strip().lower()
def cinput(string: str = ''): def cinput(string: str = ''):
"""当调用该函数时同input()一样但是返回一个去除首尾空格的str""" """当调用该函数时同input()一样但是返回一个去除首尾空格的str"""
return input(string).strip() return input(string).strip()

View File

@ -1,11 +1,11 @@
"""该模块提供几个自定义处理输入函数""" """该模块提供几个自定义处理输入函数"""
def rinput(string: str = ''): def rinput(string: str = ''):
"""当调用该函数时同input()一样但是返回一个去除首位空格并全部小写的str""" """当调用该函数时同input()一样但是返回一个去除首位空格并全部小写的str"""
return input(string).strip().lower() return input(string).strip().lower()
def cinput(string: str = ''): def cinput(string: str = ''):
"""当调用该函数时同input()一样但是返回一个去除首尾空格的str""" """当调用该函数时同input()一样但是返回一个去除首尾空格的str"""
return input(string).strip() return input(string).strip()