First Update
This commit is contained in:
parent
2d3782cbe2
commit
097ef5ed5f
49
main.py
Normal file
49
main.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author David-123
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from modules.raw_input import rinput
|
||||||
|
from modules.information import print_info
|
||||||
|
from modules.multi_download import mdl
|
||||||
|
from modules.one_download import download_one_lyric
|
||||||
|
from modules.settings import settings_menu
|
||||||
|
from modules.save_load_settings import load_settings
|
||||||
|
from modules.clear_screen import clear
|
||||||
|
|
||||||
|
|
||||||
|
class MainProcess(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.settings = load_settings()
|
||||||
|
if not os.path.exists(self.settings.lyric_path):
|
||||||
|
os.mkdir(self.settings.lyric_path)
|
||||||
|
self.version = "0.0.0"
|
||||||
|
|
||||||
|
def mainloop(self):
|
||||||
|
"""程序主循环"""
|
||||||
|
while True:
|
||||||
|
clear()
|
||||||
|
print(f"[NeteaseMusicLyricDownloader Reloaded] {self.version}\n"
|
||||||
|
"[程序主菜单]\n"
|
||||||
|
"[0] 退出程序\n[i] 程序信息\n[1] 单个歌曲的歌词下载\n[2] 多个歌曲的歌词下载\n[s] 进入设置")
|
||||||
|
r = rinput("请选择:")
|
||||||
|
|
||||||
|
if r == "1":
|
||||||
|
download_one_lyric(self.settings.lyric_path)
|
||||||
|
elif r == "2":
|
||||||
|
mdl(self.settings.lyric_path)
|
||||||
|
elif r == "0":
|
||||||
|
break
|
||||||
|
elif r == "i":
|
||||||
|
print_info(self)
|
||||||
|
elif r == "s":
|
||||||
|
settings_menu(self)
|
||||||
|
else:
|
||||||
|
input("请输入正确的选项\n按回车键继续...")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = MainProcess()
|
||||||
|
app.mainloop()
|
12
modules/clear_screen.py
Normal file
12
modules/clear_screen.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""包含一个函数,用来清空命令行的信息,自动判别系统"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def clear():
|
||||||
|
name = os.name
|
||||||
|
if name == "nt":
|
||||||
|
os.system("cls")
|
||||||
|
elif name == "posix":
|
||||||
|
os.system("clear")
|
||||||
|
else:
|
||||||
|
os.system("clear")
|
108
modules/get_song.py
Normal file
108
modules/get_song.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
"""集合 下载歌词 以及 获取歌曲信息 的功能"""
|
||||||
|
from json import loads
|
||||||
|
from requests import post
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
|
def wait_retry():
|
||||||
|
print("api提示操作频繁,等待恢复...")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
tmp = post(f"http://music.163.com/api/song/detail/?&ids=[1]")
|
||||||
|
except ConnectionError:
|
||||||
|
return "dl_err_connection"
|
||||||
|
else:
|
||||||
|
if loads(tmp.text)["code"] == 200:
|
||||||
|
return "continue"
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_song_info_raw(types: list, id: str):
|
||||||
|
"""获取歌曲信息
|
||||||
|
|
||||||
|
types 提供一个list,将会返回内部所有符合要求的信息类型\n
|
||||||
|
id 提供一个歌曲id(str),将会把歌曲的`types`信息返回"""
|
||||||
|
print("id:%s" % id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = post(f"http://music.163.com/api/song/detail/?&ids=[{id}]")
|
||||||
|
except ConnectionError:
|
||||||
|
return "dl_err_connection"
|
||||||
|
else:
|
||||||
|
info = loads(response.text)
|
||||||
|
if info["code"] == 406: # 判断当操作频繁时,继续获取,直到可以返回值为200为止
|
||||||
|
result = wait_retry()
|
||||||
|
if result == "continue":
|
||||||
|
pass
|
||||||
|
elif result == "dl_err_connection":
|
||||||
|
return "dl_err_connection"
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown exception...")
|
||||||
|
|
||||||
|
if not info.get("songs"): # 判断是否存在该歌曲
|
||||||
|
print("这首歌没有找到,跳过...")
|
||||||
|
return "song_nf"
|
||||||
|
else:
|
||||||
|
need = {}
|
||||||
|
for i in types: # 通过传入的变量 types 获取信息(根据返回的信息分析得到的下面这句语句)
|
||||||
|
need.setdefault(i, info["songs"][0][i])
|
||||||
|
return need
|
||||||
|
|
||||||
|
|
||||||
|
def get_song_lyric(id: str | int, path: str):
|
||||||
|
"""获取歌词
|
||||||
|
|
||||||
|
``id`` 提供一个歌曲id
|
||||||
|
``path`` 提供歌曲下载的路径"""
|
||||||
|
sinfo = get_song_info_raw(["name", "artists"], id)
|
||||||
|
if sinfo == "dl_err_connection": # 处理各式各样的事件
|
||||||
|
return "dl_err_connection"
|
||||||
|
elif sinfo == "song_nf":
|
||||||
|
return "song_nf"
|
||||||
|
else: # 整理歌曲数据,获取歌词
|
||||||
|
artists = ""
|
||||||
|
for i in sinfo["artists"]:
|
||||||
|
artists += f"{i['name']},"
|
||||||
|
artists = artists[:-1]
|
||||||
|
|
||||||
|
name = sinfo["name"]
|
||||||
|
replaces = { # 处理非法字符所用的替换字典(根据网易云下载的文件分析得到)
|
||||||
|
"|": "|",
|
||||||
|
":": ":",
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
"?": "?",
|
||||||
|
"/": "/",
|
||||||
|
"\\": "\",
|
||||||
|
"*": "*",
|
||||||
|
'"': """
|
||||||
|
}
|
||||||
|
for k, v in replaces.items():
|
||||||
|
name = name.replace(k, v)
|
||||||
|
|
||||||
|
print(f"歌曲:{name} - {artists}")
|
||||||
|
filename = f"{name} - {artists}.lrc"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = post(f"http://music.163.com/api/song/media?id={id}")
|
||||||
|
except ConnectionError:
|
||||||
|
return "dl_err_connection"
|
||||||
|
else:
|
||||||
|
info = loads(response.text)
|
||||||
|
if info["code"] == 406: # 此处与上方一样,防止因为请求限制而跳过下载
|
||||||
|
result = wait_retry()
|
||||||
|
if result == "continue":
|
||||||
|
pass
|
||||||
|
elif result == "dl_err_connection":
|
||||||
|
return "dl_err_connection"
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown exception...")
|
||||||
|
|
||||||
|
tmp = loads(response.text)
|
||||||
|
if tmp.get("nolyric") or not tmp.get('lyric'):
|
||||||
|
print("这首歌没有歌词,跳过...")
|
||||||
|
else:
|
||||||
|
with open(f"{path}{filename}", "w", encoding="utf-8") as f:
|
||||||
|
f.write(tmp["lyric"])
|
||||||
|
print(f"歌词下载完成!被保存在{path}{filename}")
|
16
modules/information.py
Normal file
16
modules/information.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""该程序的自述信息,调用即输出"""
|
||||||
|
from modules.clear_screen import clear
|
||||||
|
|
||||||
|
|
||||||
|
def print_info(self):
|
||||||
|
"""调用即输出,无返回值"""
|
||||||
|
clear()
|
||||||
|
print(f"""[NeteaseMusicLyricDownloader Reloaded]
|
||||||
|
版本: {self.version}
|
||||||
|
本软件开源,项目地址:<url>
|
||||||
|
作者:David-123
|
||||||
|
联系方式:
|
||||||
|
QQ:1826013250
|
||||||
|
E-mail:1826013250@qq.com(mainly)
|
||||||
|
mc1826013250@gmail.com""")
|
||||||
|
input("按回车键返回...")
|
28
modules/multi_download.py
Normal file
28
modules/multi_download.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""多文件下载"""
|
||||||
|
|
||||||
|
from modules.clear_screen import clear
|
||||||
|
from modules.raw_input import rinput
|
||||||
|
from modules.get_song import get_song_lyric
|
||||||
|
|
||||||
|
|
||||||
|
def mdl(path: str):
|
||||||
|
clear()
|
||||||
|
ids = []
|
||||||
|
print("输入歌曲id,用回车分开,输入s停止")
|
||||||
|
while True:
|
||||||
|
r = rinput()
|
||||||
|
if r == 's':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
int(r)
|
||||||
|
except ValueError:
|
||||||
|
print("该输入不合法")
|
||||||
|
else:
|
||||||
|
ids.append(r)
|
||||||
|
clear()
|
||||||
|
for i in range(0, len(ids)):
|
||||||
|
print("进度: %d/%d" % (i+1, len(ids)))
|
||||||
|
if get_song_lyric(ids[i], path) == "dl_err_connection":
|
||||||
|
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键继续任务(该任务会被跳过)...")
|
||||||
|
input("按回车键返回...")
|
22
modules/one_download.py
Normal file
22
modules/one_download.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# 导入自定义模块使用try,原因是如果在外部单独运行文件,无法通过modules索引到依赖的模块
|
||||||
|
# 直接单独运行会出现 ModuleNotFoundError 报错
|
||||||
|
|
||||||
|
from modules.raw_input import rinput
|
||||||
|
from modules.get_song import get_song_lyric
|
||||||
|
from modules.clear_screen import clear
|
||||||
|
|
||||||
|
|
||||||
|
def download_one_lyric(path: str):
|
||||||
|
"""单次下载歌词
|
||||||
|
|
||||||
|
``path: str`` 存储歌词的路径"""
|
||||||
|
clear()
|
||||||
|
song_id = rinput("请输入歌曲id:")
|
||||||
|
try:
|
||||||
|
int(song_id)
|
||||||
|
except ValueError:
|
||||||
|
input("不合法的id形式.\n按回车键返回...")
|
||||||
|
else:
|
||||||
|
if get_song_lyric(song_id, path) == "dl_err_connection":
|
||||||
|
input("下载发生错误!可能是连接被拒绝!请检查网络后再试\n按回车键返回...")
|
||||||
|
input("按回车键返回...")
|
11
modules/raw_input.py
Normal file
11
modules/raw_input.py
Normal file
@ -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()
|
51
modules/save_load_settings.py
Normal file
51
modules/save_load_settings.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""加载 or 保存设置文件"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(object): # 设定一个基础的存储设置信息的 class
|
||||||
|
def __init__(self, l_p="./out/", lang="en"):
|
||||||
|
self.lyric_path = l_p
|
||||||
|
self.language = lang
|
||||||
|
|
||||||
|
|
||||||
|
def class2dict(aclass): # 让 json.dumps 将 class 转化为一个 dict ,用于保存
|
||||||
|
return {
|
||||||
|
"lyric_path": aclass.lyric_path,
|
||||||
|
"language": aclass.language,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def dict2class(adict): # 让 json.load 将读取到的 dict 转化为我们所需要的 class
|
||||||
|
if len(adict) != 2: # 若检测到多余的设定将抛出异常
|
||||||
|
raise json.decoder.JSONDecodeError("Too many keys", "none", 0)
|
||||||
|
else:
|
||||||
|
return Settings(adict["lyric_path"], adict["language"])
|
||||||
|
|
||||||
|
|
||||||
|
def load_settings(): # 加载 的函数
|
||||||
|
"""加载设置
|
||||||
|
调用即可,无需参数
|
||||||
|
返回: 设置 class"""
|
||||||
|
if os.path.exists("settings.json"): # 判断目录下是否存在 settings.json ,若没有则创建,若有则读取
|
||||||
|
with open("settings.json", 'r', encoding="utf-8") as f:
|
||||||
|
try:
|
||||||
|
return json.load(f, object_hook=dict2class)
|
||||||
|
except json.decoder.JSONDecodeError: # 如果检测到文件无法读取,将会删除设置文件并重新创建
|
||||||
|
print("设置文件损坏,重新创建...")
|
||||||
|
os.remove("settings.json")
|
||||||
|
return load_settings()
|
||||||
|
else:
|
||||||
|
with open("settings.json", 'w', encoding="utf-8") as f:
|
||||||
|
f.write(json.dumps(Settings(), default=class2dict))
|
||||||
|
return Settings()
|
||||||
|
|
||||||
|
|
||||||
|
def save_settings(settings): # 保存 的函数
|
||||||
|
"""保存设置
|
||||||
|
``settings`` 传入一个 设置 class 将其序列化为json
|
||||||
|
返回 done 即为完成,理论上不存在报错所以暂时没有做其他处理"""
|
||||||
|
with open("settings.json", 'w', encoding="utf-8") as f:
|
||||||
|
f.write(json.dumps(settings, default=class2dict))
|
||||||
|
input("保存完成!按回车继续...")
|
63
modules/settings.py
Normal file
63
modules/settings.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"""集合设置参数"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from modules.clear_screen import clear
|
||||||
|
from modules.raw_input import rinput, cinput
|
||||||
|
from modules.save_load_settings import save_settings
|
||||||
|
|
||||||
|
|
||||||
|
def settings_menu(self):
|
||||||
|
"""设置菜单主循环"""
|
||||||
|
while True:
|
||||||
|
clear()
|
||||||
|
print(f"[NeteaseMusicLyricDownloader Reloaded] {self.version}\n"
|
||||||
|
"[设置菜单]\n"
|
||||||
|
"[0] 返回上级\n[1] 设置歌曲保存路径\n[2] 清空输出文件夹内的所有歌词\n[s] 将设置保存到文件")
|
||||||
|
r = rinput("请选择:")
|
||||||
|
if r == "0":
|
||||||
|
return
|
||||||
|
elif r == "1":
|
||||||
|
__set_lyric_path(self)
|
||||||
|
elif r == "2":
|
||||||
|
__save_settings(self)
|
||||||
|
elif r == "s":
|
||||||
|
__remove_lyric_files(self.settings.lyric_path)
|
||||||
|
else:
|
||||||
|
input("输入")
|
||||||
|
|
||||||
|
|
||||||
|
def __remove_lyric_files(path):
|
||||||
|
clear()
|
||||||
|
files = []
|
||||||
|
for i in os.listdir(path):
|
||||||
|
if re.match(r".*(\.lrc)$", i):
|
||||||
|
files.append(i)
|
||||||
|
if len(files) != 0:
|
||||||
|
for i in range(0, len(files)):
|
||||||
|
print("正在删除(%d/%d): %s" % (i+1, len(files), files[i]))
|
||||||
|
os.remove(path+files[i])
|
||||||
|
print("删除完毕!\n按回车继续...")
|
||||||
|
else:
|
||||||
|
print("文件夹内没有要删除的东西\n按回车继续...")
|
||||||
|
|
||||||
|
|
||||||
|
def __set_lyric_path(self):
|
||||||
|
print("允许使用相对路径和绝对路径,默认为\"./out/\"\n请避免使用反斜杠来确保通用性\n"
|
||||||
|
"当前值:%s\n请输入新的歌词保存路径:" % self.settings.lyric_path)
|
||||||
|
r = cinput()
|
||||||
|
if not r:
|
||||||
|
input("输入为空!\n按回车继续...")
|
||||||
|
if r[-1] != "/":
|
||||||
|
r += "/"
|
||||||
|
path = ""
|
||||||
|
for i in r.split("/"):
|
||||||
|
path += i+"/"
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.mkdir(path)
|
||||||
|
self.settings.lyric_path = r
|
||||||
|
input("设置成功!\n按回车继续...")
|
||||||
|
|
||||||
|
|
||||||
|
def __save_settings(self):
|
||||||
|
return save_settings(self.settings)
|
Loading…
Reference in New Issue
Block a user