COLORFUL UPDATE
Make output more and more and more colorful! Change file structure and README.md
This commit is contained in:
parent
e7d19d6892
commit
94149eabea
20
main.py
20
main.py
@ -3,15 +3,18 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# author: David-123
|
# author: David-123
|
||||||
|
|
||||||
|
from sys import exit
|
||||||
|
|
||||||
|
from colorama import init
|
||||||
|
|
||||||
from modules.utils.inputs import rinput
|
from modules.utils.inputs import rinput
|
||||||
from modules.utils.information import print_info
|
from modules.utils.information import print_info
|
||||||
from modules.functions.multi_download import mdl
|
from modules.functions.mainly.multi_download import mdl
|
||||||
from modules.functions.one_download import download_one_lyric
|
from modules.functions.mainly.one_download import download_one_lyric
|
||||||
from modules.submenus.settings import settings_menu
|
from modules.submenus.settings import settings_menu
|
||||||
from modules.functions.save_load_settings import load_settings
|
from modules.functions.settings.save_load_settings import load_settings
|
||||||
from modules.utils.clear_screen import clear
|
from modules.utils.clear_screen import cls_stay
|
||||||
from modules.functions.load_file_song import get_lyric_from_folder
|
from modules.functions.mainly.load_file_song import get_lyric_from_folder
|
||||||
|
|
||||||
|
|
||||||
class MainProcess(object):
|
class MainProcess(object):
|
||||||
@ -22,10 +25,8 @@ class MainProcess(object):
|
|||||||
def mainloop(self):
|
def mainloop(self):
|
||||||
"""程序主循环"""
|
"""程序主循环"""
|
||||||
while True:
|
while True:
|
||||||
clear()
|
cls_stay(self, "[程序主菜单]")
|
||||||
print(f"[NeteaseMusicLyricDownloader] {self.version}\n"
|
print("[0] 退出程序\n[1] 单个歌曲的歌词下载\n[2] 多个歌曲的歌词下载\n[3] 从网易云下载的歌曲中获取歌词"
|
||||||
"[程序主菜单]\n"
|
|
||||||
"[0] 退出程序\n[1] 单个歌曲的歌词下载\n[2] 多个歌曲的歌词下载\n[3] 从网易云下载的歌曲中获取歌词"
|
|
||||||
"\n[s] 进入设置\n[i] 程序信息")
|
"\n[s] 进入设置\n[i] 程序信息")
|
||||||
r = rinput("请选择:")
|
r = rinput("请选择:")
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ class MainProcess(object):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
init(autoreset=True)
|
||||||
app = MainProcess()
|
app = MainProcess()
|
||||||
try:
|
try:
|
||||||
app.mainloop()
|
app.mainloop()
|
||||||
|
0
modules/functions/mainly/__init__.py
Normal file
0
modules/functions/mainly/__init__.py
Normal file
@ -1,48 +1,55 @@
|
|||||||
"""集合 下载歌词 以及 获取歌曲信息 的功能"""
|
"""集合 下载歌词 以及 获取歌曲信息 的功能"""
|
||||||
import os
|
import os
|
||||||
from json import loads
|
|
||||||
from requests import post
|
from requests import post
|
||||||
from requests.exceptions import ConnectionError
|
from requests.exceptions import ConnectionError
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
from colorama import Fore, Style
|
||||||
|
|
||||||
|
from modules.utils.bar import CompactBar, bprint
|
||||||
|
|
||||||
|
|
||||||
def wait_retry():
|
def wait_retry(kind, identify, bar=None):
|
||||||
print("api提示操作频繁,等待恢复...")
|
bprint("api提示操作频繁,等待恢复...", bar)
|
||||||
|
if kind == "information":
|
||||||
|
url = f"https://music.163.com/api/song/detail/?&ids=[{identify}]"
|
||||||
|
elif kind == "lyric":
|
||||||
|
url = f"https://music.163.com/api/song/media?id={identify}"
|
||||||
|
else:
|
||||||
|
return "unknown_kind"
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
tmp = post(f"http://music.163.com/api/song/detail/?&ids=[1]")
|
tmp = post(url).json()
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
return "dl_err_connection"
|
return "dl_err_connection"
|
||||||
else:
|
else:
|
||||||
if loads(tmp.text)["code"] == 200:
|
if tmp["code"] == 200:
|
||||||
return "continue"
|
return tmp
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
|
|
||||||
def get_song_info_raw(types: list, id: str):
|
def get_song_info_raw(types: list, identify: str, bar: CompactBar = None):
|
||||||
"""获取歌曲信息
|
"""获取歌曲信息
|
||||||
|
|
||||||
types 提供一个list,将会返回内部所有符合要求的信息类型\n
|
``types`` 提供一个list,将会返回内部所有符合要求的信息类型\n
|
||||||
id 提供一个歌曲id(str),将会把歌曲的`types`信息返回"""
|
``identify`` 提供一个歌曲id(str),将会把歌曲的`types`信息返回"""
|
||||||
print("id:%s" % id)
|
bprint(Fore.CYAN + "ID:%s" % identify, bar)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = post(f"http://music.163.com/api/song/detail/?&ids=[{id}]")
|
info = post(f"https://music.163.com/api/song/detail/?&ids=[{identify}]").json()
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
return "dl_err_connection"
|
return "dl_err_connection"
|
||||||
else:
|
else:
|
||||||
info = loads(response.text)
|
|
||||||
if info["code"] == 406: # 判断当操作频繁时,继续获取,直到可以返回值为200为止
|
if info["code"] == 406: # 判断当操作频繁时,继续获取,直到可以返回值为200为止
|
||||||
result = wait_retry()
|
result = wait_retry("information", identify, bar=bar)
|
||||||
if result == "continue":
|
if type(result) == dict:
|
||||||
pass
|
info = result
|
||||||
elif result == "dl_err_connection":
|
elif result == "dl_err_connection":
|
||||||
return "dl_err_connection"
|
return "dl_err_connection"
|
||||||
else:
|
else:
|
||||||
raise Exception("Unknown exception...")
|
raise Exception("Unknown exception...")
|
||||||
|
|
||||||
if not info.get("songs"): # 判断是否存在该歌曲
|
if not info.get("songs"): # 判断是否存在该歌曲
|
||||||
print("这首歌没有找到,跳过...")
|
bprint(Fore.LIGHTBLACK_EX + "\t-> 这首歌没有找到,跳过...", bar)
|
||||||
return "song_nf"
|
return "song_nf"
|
||||||
else:
|
else:
|
||||||
need = {}
|
need = {}
|
||||||
@ -51,17 +58,19 @@ def get_song_info_raw(types: list, id: str):
|
|||||||
return need
|
return need
|
||||||
|
|
||||||
|
|
||||||
def get_song_lyric(id: str | int | dict, path: str, allinfo: bool = False):
|
def get_song_lyric(identify: str | int | dict, path: str, allinfo: bool = False, bar: CompactBar = None):
|
||||||
"""获取歌词
|
"""获取歌词
|
||||||
|
|
||||||
``id`` 提供一个歌曲id
|
``identify`` 提供一个歌曲id
|
||||||
``path`` 提供歌曲下载的路径
|
``path`` 提供歌曲下载的路径
|
||||||
``allinfo`` 若此项为 True ,则提供的id格式必须为 {"id": int | str, "name": str, "artists": [[str, ...], ...]} (dict)"""
|
``allinfo`` 若此项为 True ,则提供的identify格式必须为存储在网易云下载文件中meta_data的格式
|
||||||
|
``bar`` 若获取歌词时下方有进度条, 则应当传入此参数"""
|
||||||
if allinfo:
|
if allinfo:
|
||||||
sinfo = id
|
sinfo = identify
|
||||||
id = id["id"]
|
identify = identify["id"]
|
||||||
|
bprint(Fore.CYAN + f"ID: {identify}", bar)
|
||||||
else:
|
else:
|
||||||
sinfo = get_song_info_raw(["name", "artists"], id)
|
sinfo = get_song_info_raw(["name", "artists"], identify, bar)
|
||||||
if sinfo == "dl_err_connection": # 处理各式各样的事件
|
if sinfo == "dl_err_connection": # 处理各式各样的事件
|
||||||
return "dl_err_connection"
|
return "dl_err_connection"
|
||||||
elif sinfo == "song_nf":
|
elif sinfo == "song_nf":
|
||||||
@ -79,7 +88,7 @@ def get_song_lyric(id: str | int | dict, path: str, allinfo: bool = False):
|
|||||||
|
|
||||||
name = sinfo["name"]
|
name = sinfo["name"]
|
||||||
if not name:
|
if not name:
|
||||||
print("歌曲错误!这是网易云的问题,请不要找作者")
|
bprint(Fore.RED + "歌曲错误!这是网易云的问题,请不要找作者", bar)
|
||||||
return "song_err"
|
return "song_err"
|
||||||
replaces = { # 处理非法字符所用的替换字典(根据网易云下载的文件分析得到)
|
replaces = { # 处理非法字符所用的替换字典(根据网易云下载的文件分析得到)
|
||||||
"|": "|",
|
"|": "|",
|
||||||
@ -96,30 +105,28 @@ def get_song_lyric(id: str | int | dict, path: str, allinfo: bool = False):
|
|||||||
name = name.replace(k, v)
|
name = name.replace(k, v)
|
||||||
artists = artists.replace(k, v)
|
artists = artists.replace(k, v)
|
||||||
|
|
||||||
print(f"歌曲:{name} - {artists}")
|
bprint(Fore.YELLOW + "\t-> 歌曲:" + Style.RESET_ALL + f"{name} - {artists}", bar)
|
||||||
filename = f"{name} - {artists}.lrc"
|
filename = f"{name} - {artists}.lrc"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = post(f"http://music.163.com/api/song/media?id={id}")
|
info = post(f"https://music.163.com/api/song/media?id={identify}").json()
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
return "dl_err_connection"
|
return "dl_err_connection"
|
||||||
else:
|
else:
|
||||||
info = loads(response.text)
|
|
||||||
if info["code"] == 406: # 此处与上方一样,防止因为请求限制而跳过下载
|
if info["code"] == 406: # 此处与上方一样,防止因为请求限制而跳过下载
|
||||||
result = wait_retry()
|
result = wait_retry("lyric", identify, bar=bar)
|
||||||
if result == "continue":
|
if type(result) == dict:
|
||||||
pass
|
info = result
|
||||||
elif result == "dl_err_connection":
|
elif result == "dl_err_connection":
|
||||||
return "dl_err_connection"
|
return "dl_err_connection"
|
||||||
else:
|
else:
|
||||||
raise Exception("Unknown exception...")
|
raise Exception("Unknown exception...")
|
||||||
|
|
||||||
tmp = loads(response.text)
|
if info.get("nolyric") or not info.get('lyric'):
|
||||||
if tmp.get("nolyric") or not tmp.get('lyric'):
|
bprint(Fore.LIGHTBLACK_EX + "\t--> 这首歌没有歌词,跳过...\n", bar)
|
||||||
print("这首歌没有歌词,跳过...")
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
with open(os.path.join(path, filename), "w", encoding="utf-8") as f:
|
with open(os.path.join(path, filename), "w", encoding="utf-8") as f:
|
||||||
f.write(tmp["lyric"])
|
f.write(info["lyric"])
|
||||||
print(f"歌词下载完成!被保存在{os.path.join(path, filename)}")
|
bprint(Fore.GREEN + "\t--> 歌词下载完成!被保存在" + Style.RESET_ALL + f"{os.path.join(path, filename)}\n", bar)
|
||||||
return
|
return
|
@ -9,13 +9,15 @@ from time import sleep
|
|||||||
|
|
||||||
import mutagen.mp3
|
import mutagen.mp3
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
|
from Cryptodome.Util.Padding import unpad
|
||||||
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 colorama import Fore, Style
|
||||||
|
|
||||||
from modules.utils.clear_screen import clear
|
from modules.utils.clear_screen import cls_stay
|
||||||
from modules.functions.get_song import get_song_lyric
|
from modules.functions.mainly.get_song import get_song_lyric
|
||||||
from modules.utils.inputs import cinput, rinput
|
from modules.utils.inputs import cinput, rinput
|
||||||
from modules.utils.bar import CompactBar
|
from modules.utils.bar import CompactBar, CompactArrowBar
|
||||||
|
|
||||||
|
|
||||||
def load_information_from_song(path) -> str | dict:
|
def load_information_from_song(path) -> str | dict:
|
||||||
@ -43,20 +45,12 @@ def load_information_from_song(path) -> str | dict:
|
|||||||
else:
|
else:
|
||||||
return "not_support"
|
return "not_support"
|
||||||
|
|
||||||
def unpad(s): # 创建清理针对于网易云的 AES-128-ECB 解密后末尾占位符的函数
|
|
||||||
if type(s[-1]) == int:
|
|
||||||
end = s[-1]
|
|
||||||
else:
|
|
||||||
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) # 使用密钥创建解密器
|
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"))), 16).decode("utf-8")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return "decrypt_failed"
|
return "decrypt_failed"
|
||||||
|
|
||||||
@ -72,9 +66,6 @@ def load_information_from_song(path) -> str | dict:
|
|||||||
def load_and_decrypt_from_ncm(file_path, target_dir) -> dict: # nondanee的源代码, 根据需求更改了某些东西
|
def load_and_decrypt_from_ncm(file_path, target_dir) -> dict: # 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")
|
||||||
|
|
||||||
def unpad(s):
|
|
||||||
return 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'
|
||||||
@ -87,7 +78,7 @@ def load_and_decrypt_from_ncm(file_path, target_dir) -> dict: # nondanee的源
|
|||||||
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), 16)[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))
|
||||||
@ -112,7 +103,7 @@ def load_and_decrypt_from_ncm(file_path, target_dir) -> dict: # nondanee的源
|
|||||||
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), 16).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]
|
||||||
@ -171,11 +162,8 @@ def process_work(path, filename, target, q_err: Queue, q_info: Queue):
|
|||||||
|
|
||||||
|
|
||||||
def get_lyric_from_folder(self):
|
def get_lyric_from_folder(self):
|
||||||
clear()
|
cls_stay(self, "[自动获取 - 加载文件]")
|
||||||
path = cinput(
|
path = cinput("请输入歌曲的保存文件夹(绝对路径):")
|
||||||
f"[NeteaseMusicLyricDownloader] {self.version}\n"
|
|
||||||
"[自动获取]\n"
|
|
||||||
"请输入歌曲的保存文件夹(绝对路径):")
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
input("路径不存在.\n按回车返回...")
|
input("路径不存在.\n按回车返回...")
|
||||||
return
|
return
|
||||||
@ -239,8 +227,8 @@ def get_lyric_from_folder(self):
|
|||||||
max_process = 20 # 最大进程数
|
max_process = 20 # 最大进程数
|
||||||
current_process = 0 # 当前正在活动的进程数
|
current_process = 0 # 当前正在活动的进程数
|
||||||
passed = 0 # 总共结束的进程数
|
passed = 0 # 总共结束的进程数
|
||||||
with CompactBar(f"正在破解 %(index){len(str(len(ncm_files)))}d/%(max)d",
|
with CompactArrowBar(f"正在解锁 %(index){len(str(len(ncm_files)))}d/%(max)d",
|
||||||
suffix="", max=len(ncm_files), color="blue", width=9999) as bar:
|
suffix="", max=len(ncm_files), color="green", width=9999) as bar:
|
||||||
total = len(ncm_files)
|
total = len(ncm_files)
|
||||||
allocated = 0 # 已经分配的任务数量
|
allocated = 0 # 已经分配的任务数量
|
||||||
while True: # 进入循环,执行 新建进程->检测队列->检测任务完成 的循环
|
while True: # 进入循环,执行 新建进程->检测队列->检测任务完成 的循环
|
||||||
@ -252,7 +240,7 @@ def get_lyric_from_folder(self):
|
|||||||
target_path,
|
target_path,
|
||||||
q_err,
|
q_err,
|
||||||
q_info)).start()
|
q_info)).start()
|
||||||
bar.print_onto_bar("已分配: %s" % ncm_files[allocated])
|
bar.print_onto_bar(Fore.CYAN + "已分配: " + Style.RESET_ALL + "%s" % ncm_files[allocated])
|
||||||
allocated += 1
|
allocated += 1
|
||||||
current_process += 1
|
current_process += 1
|
||||||
while True: # 错误队列检测
|
while True: # 错误队列检测
|
||||||
@ -270,16 +258,17 @@ def get_lyric_from_folder(self):
|
|||||||
musics.append({"id": r['musicId'], "name": r["musicName"], "artists": r["artist"]})
|
musics.append({"id": r['musicId'], "name": r["musicName"], "artists": r["artist"]})
|
||||||
passed += 1
|
passed += 1
|
||||||
current_process -= 1
|
current_process -= 1
|
||||||
bar.print_onto_bar(f"\"{r['musicName']} - "
|
bar.print_onto_bar(Fore.YELLOW +
|
||||||
|
f"\"{r['musicName']} - "
|
||||||
f"{''.join([x + ', ' for x in [x[0] for x in r['artist']]])[:-2]}"
|
f"{''.join([x + ', ' for x in [x[0] for x in r['artist']]])[:-2]}"
|
||||||
"\" 已完成!")
|
"\"" + Fore.GREEN + " 已完成!")
|
||||||
bar.next()
|
bar.next()
|
||||||
except Empty:
|
except Empty:
|
||||||
break
|
break
|
||||||
if passed >= len(ncm_files):
|
if passed >= len(ncm_files):
|
||||||
break
|
break
|
||||||
if errors:
|
if errors:
|
||||||
print("解密过程中发现了以下错误:")
|
print(Fore.LIGHTRED_EX+"解锁过程中发现了以下错误:")
|
||||||
for i in errors:
|
for i in errors:
|
||||||
print(i)
|
print(i)
|
||||||
|
|
||||||
@ -304,18 +293,20 @@ def get_lyric_from_folder(self):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
input("无效选择, 若取消请按 ^C ,继续请按回车")
|
input("无效选择, 若取消请按 ^C ,继续请按回车")
|
||||||
clear()
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
return
|
return
|
||||||
|
|
||||||
clear()
|
cls_stay(self, "[自动获取 - 下载歌词]")
|
||||||
|
with CompactArrowBar(f"进度: %(index){len(str(len(musics)))}d/%(max)d",
|
||||||
|
suffix="", max=len(musics), color="yellow", width=9999) as bar:
|
||||||
for i in range(0, len(musics)): # 根据索引结果获取歌词
|
for i in range(0, len(musics)): # 根据索引结果获取歌词
|
||||||
print("\n进度: %d/%d" % (i + 1, len(musics)))
|
if get_song_lyric(musics[i], lyric_path, allinfo=True, bar=bar) == "dl_err_connection":
|
||||||
if get_song_lyric(musics[i], lyric_path, allinfo=True) == "dl_err_connection":
|
bar.print_onto_bar(Fore.RED + "下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...")
|
||||||
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...")
|
input()
|
||||||
|
bar.next()
|
||||||
if ncm_files:
|
if ncm_files:
|
||||||
if target_path != "NOT_DECRYPT":
|
if target_path != "NOT_DECRYPT":
|
||||||
agree = rinput("是否删除原ncm文件? (y/n)")
|
agree = rinput(Fore.RED + "是否删除原ncm文件? (y/n)")
|
||||||
if agree == "y":
|
if agree == "y":
|
||||||
for i in range(0, len(ncm_files)):
|
for i in range(0, len(ncm_files)):
|
||||||
print("删除进度: %d/%d\n -> %s\033[F" % (i + 1, len(ncm_files), ncm_files[i]), end="")
|
print("删除进度: %d/%d\n -> %s\033[F" % (i + 1, len(ncm_files), ncm_files[i]), end="")
|
42
modules/functions/mainly/multi_download.py
Normal file
42
modules/functions/mainly/multi_download.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import re
|
||||||
|
from colorama import Fore
|
||||||
|
|
||||||
|
from modules.utils.clear_screen import cls_stay
|
||||||
|
from modules.utils.inputs import rinput
|
||||||
|
from modules.functions.mainly.get_song import get_song_lyric
|
||||||
|
from modules.utils.bar import CompactArrowBar
|
||||||
|
|
||||||
|
|
||||||
|
def mdl(self):
|
||||||
|
"""多个歌词文件的下载
|
||||||
|
|
||||||
|
``path: str`` 传入歌词文件保存的路径"""
|
||||||
|
cls_stay(self, "[手动-多个下载]")
|
||||||
|
ids = []
|
||||||
|
print("输入歌曲id,用回车分开,输入s停止")
|
||||||
|
while True:
|
||||||
|
r = rinput()
|
||||||
|
if r == 's':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
int(r)
|
||||||
|
except ValueError:
|
||||||
|
tmp = re.search(r"song\?id=[0-9]*", r)
|
||||||
|
if tmp:
|
||||||
|
r = tmp.group()[8:]
|
||||||
|
else:
|
||||||
|
print("不合法的形式.\n")
|
||||||
|
continue
|
||||||
|
ids.append(int(r))
|
||||||
|
print("\t#%d id:%s - 已添加!" % (len(ids), r))
|
||||||
|
cls_stay(self, "[手动-多个下载]")
|
||||||
|
with CompactArrowBar(f"进度: %(index){len(str(len(ids)))}d/%(max)d",
|
||||||
|
suffix="", max=len(ids), color="yellow", width=9999) as bar:
|
||||||
|
for i in range(0, len(ids)):
|
||||||
|
r = get_song_lyric(ids[i], self.settings.lyric_path, bar=bar)
|
||||||
|
if r == "dl_err_connection":
|
||||||
|
bar.print_onto_bar(Fore.RED + "下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...")
|
||||||
|
input()
|
||||||
|
bar.next()
|
||||||
|
input("按回车键返回...")
|
@ -1,6 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from modules.utils.inputs import rinput
|
from modules.utils.inputs import rinput
|
||||||
from modules.functions.get_song import get_song_lyric
|
from modules.functions.mainly.get_song import get_song_lyric
|
||||||
from modules.utils.clear_screen import clear
|
from modules.utils.clear_screen import clear
|
||||||
|
|
||||||
|
|
||||||
@ -23,6 +23,6 @@ def download_one_lyric(self):
|
|||||||
input("不合法的形式.\n按回车键返回...")
|
input("不合法的形式.\n按回车键返回...")
|
||||||
return
|
return
|
||||||
|
|
||||||
if get_song_lyric(song_id, self.settings.lyric_path) == "dl_err_connection":
|
if get_song_lyric(int(song_id), self.settings.lyric_path) == "dl_err_connection":
|
||||||
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键返回...")
|
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键返回...")
|
||||||
input("按回车键返回...")
|
input("按回车键返回...")
|
@ -1,38 +0,0 @@
|
|||||||
import re
|
|
||||||
from modules.utils.clear_screen import clear
|
|
||||||
from modules.utils.inputs import rinput
|
|
||||||
from modules.functions.get_song import get_song_lyric
|
|
||||||
|
|
||||||
|
|
||||||
def mdl(self):
|
|
||||||
"""多个歌词文件的下载
|
|
||||||
|
|
||||||
``path: str`` 传入歌词文件保存的路径"""
|
|
||||||
clear()
|
|
||||||
ids = []
|
|
||||||
print(f"[NeteaseMusicLyricDownloader] {self.version}\n"
|
|
||||||
"[手动-多个下载]\n"
|
|
||||||
"输入歌曲id,用回车分开,输入s停止")
|
|
||||||
while True:
|
|
||||||
r = rinput()
|
|
||||||
if r == 's':
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
int(r)
|
|
||||||
except ValueError:
|
|
||||||
tmp = re.search(r"song\?id=[0-9]*", r)
|
|
||||||
if tmp:
|
|
||||||
r = tmp.group()[8:]
|
|
||||||
else:
|
|
||||||
print("不合法的形式.\n")
|
|
||||||
continue
|
|
||||||
ids.append(r)
|
|
||||||
print("\t#%d id:%s - 已添加!" % (len(ids), r))
|
|
||||||
clear()
|
|
||||||
for i in range(0, len(ids)):
|
|
||||||
print("进度: %d/%d" % (i+1, len(ids)))
|
|
||||||
r = get_song_lyric(ids[i], self.settings.lyric_path)
|
|
||||||
if r == "dl_err_connection":
|
|
||||||
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...")
|
|
||||||
input("按回车键返回...")
|
|
0
modules/functions/settings/__init__.py
Normal file
0
modules/functions/settings/__init__.py
Normal file
@ -5,8 +5,9 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
class Settings(object): # 设定一个基础的存储设置信息的 class ,并设置形参用于 json 导入设置
|
class Settings(object): # 设定一个基础的存储设置信息的 class ,并设置形参用于 json 导入设置
|
||||||
def __init__(self, l_p="./out/", lang="en"):
|
def __init__(self, l_p="./out/", l_f="", lang="en"):
|
||||||
self.lyric_path = l_p
|
self.lyric_path = l_p
|
||||||
|
self.lyric_format = l_f
|
||||||
self.language = lang
|
self.language = lang
|
||||||
|
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ def dict2class(adict): # 让 json.load 将读取到的 dict 转化为我们所
|
|||||||
return Settings(adict["lyric_path"], adict["language"])
|
return Settings(adict["lyric_path"], adict["language"])
|
||||||
|
|
||||||
|
|
||||||
def load_settings(): # 加载 的函数
|
def load_settings() -> Settings: # 加载 的函数
|
||||||
"""加载设置
|
"""加载设置
|
||||||
调用即可,无需参数
|
调用即可,无需参数
|
||||||
返回: 设置 class"""
|
返回: 设置 class"""
|
@ -1,18 +1,16 @@
|
|||||||
"""集合设置参数"""
|
"""集合设置参数"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from modules.utils.clear_screen import clear
|
from modules.utils.clear_screen import cls_stay
|
||||||
from modules.utils.inputs import rinput, cinput
|
from modules.utils.inputs import rinput, cinput
|
||||||
from modules.functions.save_load_settings import save_settings
|
from modules.functions.settings.save_load_settings import save_settings
|
||||||
|
|
||||||
|
|
||||||
def settings_menu(self):
|
def settings_menu(self):
|
||||||
"""设置菜单主循环"""
|
"""设置菜单主循环"""
|
||||||
while True:
|
while True:
|
||||||
clear()
|
cls_stay(self, "[设置菜单]")
|
||||||
print(f"[NeteaseMusicLyricDownloader] {self.version}\n"
|
print("[0] 返回上级\n[1] 歌曲保存路径\n[2] 清空输出文件夹内的内容\n[3] 歌词文件保存格式\n[4] 部分动态效果\n"
|
||||||
"[设置菜单]\n"
|
|
||||||
"[0] 返回上级\n[1] 歌曲保存路径\n[2] 清空输出文件夹内的内容\n[3] 歌词文件保存格式\n[4] 部分动态效果\n"
|
|
||||||
"[s] 将设置保存到文件")
|
"[s] 将设置保存到文件")
|
||||||
r = rinput("请选择:")
|
r = rinput("请选择:")
|
||||||
if r == "0":
|
if r == "0":
|
||||||
@ -33,10 +31,8 @@ def settings_menu(self):
|
|||||||
|
|
||||||
def __remove_output_files(self):
|
def __remove_output_files(self):
|
||||||
while True:
|
while True:
|
||||||
clear()
|
cls_stay(self, "[设置菜单 - 删除文件]")
|
||||||
print(f"[NeteaseMusicLyricDownloader] {self.version}\n"
|
print("[0] 返回上级\n[1] 清除歌词文件\n[2] 清除歌曲文件\n[a] 清除所有文件")
|
||||||
"[设置菜单 - 删除文件]\n"
|
|
||||||
"[0] 返回上级\n[1] 清除歌词文件\n[2] 清除歌曲文件\n[a] 清除所有文件")
|
|
||||||
r = rinput("请选择:") # 选择清除的文件格式
|
r = rinput("请选择:") # 选择清除的文件格式
|
||||||
if r == "0":
|
if r == "0":
|
||||||
return
|
return
|
||||||
@ -74,7 +70,7 @@ def __remove_output_files(self):
|
|||||||
|
|
||||||
|
|
||||||
def __set_lyric_path(self):
|
def __set_lyric_path(self):
|
||||||
clear()
|
cls_stay(self, "[设置菜单 - 保存路径]")
|
||||||
print("允许使用相对路径和绝对路径,默认为\"./out/\"\n请*不要*使用反斜杠来确保通用性\n"
|
print("允许使用相对路径和绝对路径,默认为\"./out/\"\n请*不要*使用反斜杠来确保通用性\n"
|
||||||
"当前值:%s\n请输入新的歌词保存路径:" % self.settings.lyric_path)
|
"当前值:%s\n请输入新的歌词保存路径:" % self.settings.lyric_path)
|
||||||
r = cinput()
|
r = cinput()
|
||||||
@ -93,6 +89,7 @@ def __set_lyric_path(self):
|
|||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
self.settings.lyric_path = r
|
self.settings.lyric_path = r
|
||||||
|
save_settings(self.settings)
|
||||||
input("设置成功!\n按回车继续...")
|
input("设置成功!\n按回车继续...")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -100,6 +97,3 @@ def __set_lyric_path(self):
|
|||||||
def __set_lyric_format(self):
|
def __set_lyric_format(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def __save_settings(self):
|
|
||||||
return save_settings(self.settings)
|
|
||||||
|
@ -67,3 +67,57 @@ class CompactBar(Bar):
|
|||||||
line = ''.join([message, suffix])[:display_length]+"..."
|
line = ''.join([message, suffix])[:display_length]+"..."
|
||||||
shorten = len_abs(line)
|
shorten = len_abs(line)
|
||||||
self.writeln(line, shorten=shorten)
|
self.writeln(line, shorten=shorten)
|
||||||
|
|
||||||
|
|
||||||
|
class CompactArrowBar(CompactBar):
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
覆写原有的update方法,自适应终端宽度
|
||||||
|
支持中文
|
||||||
|
"""
|
||||||
|
filled_length = int(self.width * self.progress)
|
||||||
|
empty_length = self.width - filled_length
|
||||||
|
|
||||||
|
message = self.message % self
|
||||||
|
s = "=" * filled_length
|
||||||
|
if s:
|
||||||
|
s = s[:-1] + ">"
|
||||||
|
bar = color(s, fg=self.color)
|
||||||
|
empty = self.empty_fill * empty_length
|
||||||
|
suffix = self.suffix % self
|
||||||
|
line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix,
|
||||||
|
suffix])
|
||||||
|
# 以上为原本update代码
|
||||||
|
term_size = get_terminal_size().columns
|
||||||
|
shorten = False
|
||||||
|
if len_abs(line) > term_size: # 检测完整长度是否小于终端长度
|
||||||
|
if len_abs(line) - len_abs(''.join([bar, empty])) <= term_size: # 检测无进度条时是否小于终端长度
|
||||||
|
width = term_size - (len_abs(line) - len_abs("".join([bar, empty]))) - 1
|
||||||
|
filled_length = int(width * self.progress)
|
||||||
|
empty_length = width - filled_length
|
||||||
|
s = "=" * filled_length
|
||||||
|
if s:
|
||||||
|
s = s[:-1] + ">"
|
||||||
|
bar = color(s, fg=self.color)
|
||||||
|
empty = self.empty_fill * empty_length
|
||||||
|
line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix,
|
||||||
|
suffix])
|
||||||
|
shorten = len_abs(line)
|
||||||
|
elif len_abs(''.join([message, suffix])) <= term_size: # 检测仅有前缀后缀时是否小于终端长度
|
||||||
|
line = ''.join([message, suffix])
|
||||||
|
shorten = len_abs(line)
|
||||||
|
else: # 全部不符合时,以仅有前缀后缀的模式,直接截断
|
||||||
|
display_length = term_size - get_more_length(''.join([message, suffix])[:term_size]) - 3
|
||||||
|
if display_length < 0:
|
||||||
|
display_length = 0
|
||||||
|
line = ''.join([message, suffix])[:display_length] + "..."
|
||||||
|
shorten = len_abs(line)
|
||||||
|
self.writeln(line, shorten=shorten)
|
||||||
|
|
||||||
|
|
||||||
|
def bprint(content, bar: CompactBar = None):
|
||||||
|
"""添加对进度条的支持的print, 若传入bar参数, 则使用print_onto_bar参数来打印内容"""
|
||||||
|
if bar:
|
||||||
|
bar.print_onto_bar(content)
|
||||||
|
else:
|
||||||
|
print(content)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""包含一个函数,用来清空命令行的信息,自动判别系统"""
|
"""用来清空命令行的信息,自动判别系统"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
@ -10,3 +10,10 @@ def clear():
|
|||||||
os.system("clear")
|
os.system("clear")
|
||||||
else:
|
else:
|
||||||
os.system("clear")
|
os.system("clear")
|
||||||
|
|
||||||
|
|
||||||
|
def cls_stay(self, custom=""):
|
||||||
|
"""保留版本号清除屏幕"""
|
||||||
|
clear()
|
||||||
|
print(f"[NeteaseMusicLyricDownloader] {self.version}")
|
||||||
|
print(custom)
|
@ -2,3 +2,4 @@ requests
|
|||||||
mutagen
|
mutagen
|
||||||
pycryptodomex
|
pycryptodomex
|
||||||
progress
|
progress
|
||||||
|
colorama
|
Loading…
Reference in New Issue
Block a user