From ef095c5b645de212b0e8eaec628e6d8c9c26257e Mon Sep 17 00:00:00 2001 From: 2061360308 <2061360308@qq.com> Date: Wed, 26 Feb 2025 22:05:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E7=AE=80=E6=98=93=E7=89=88=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E6=AD=8C=E5=8D=95=E6=89=B9=E9=87=8F=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playlists/README.md | 66 ++++++++++++++++++++ playlists/pack.py | 66 ++++++++++++++++++++ playlists/parse.py | 123 +++++++++++++++++++++++++++++++++++++ playlists/requirements.txt | 3 + playlists/settings.json | 8 +++ 5 files changed, 266 insertions(+) create mode 100644 playlists/README.md create mode 100644 playlists/pack.py create mode 100644 playlists/parse.py create mode 100644 playlists/requirements.txt create mode 100644 playlists/settings.json diff --git a/playlists/README.md b/playlists/README.md new file mode 100644 index 0000000..8c54adb --- /dev/null +++ b/playlists/README.md @@ -0,0 +1,66 @@ +## 介绍 + +该脚本能够批量下载歌单中的歌曲,并且将歌曲信息写入MP3文件的元数据中(仅支持MP3,标准音质和高音质) + +目前使用演示站点的url,后期可以会进一步完善。现在需要可以自行改动 + +## 项目配置 +项目根目录下运行该命令安装依赖 +```bash +pip install -r requirements.txt +``` + + +## 获取歌曲url +1. 浏览器打开[网易云官网](https://music.163.com/#/playlist) +2. 选择自己的歌单 +3. 在对应的歌单列表的某一首歌上右击,点检查 +4. 弹出开发者工具面板中切换到控制台页面(一般为第二个选项) +5. 将下面的脚本粘贴进控制台,回车 +6. 复制整个输出内容到当前项目下的 `settings.json`文件(替换原来的内容) +7. 可以自行更改`settings.json`中的`level`和`savePath`两个参数,以调整歌曲下载的音质和保存位置 +```js +const settings = { + "level": "exhigh", + "savePath": "./success", + "packPath": "./pack" +} +let spans; +let url = []; + +spans = document.querySelectorAll(".ttc>.txt"); +spans.forEach((span) => { + // 在每个 内选择 元素 + const link = span.querySelector("a"); + if (link) { + url.push(link.href) + } else { + console.log("No tag found in this tag."); + } +}); + +settings['songs'] = url +console.log(JSON.stringify(settings, null, 2)); +``` + +## 下载歌曲 +> 提示:前提条件,完成项目配置,以及settings.json的内容 +> + +执行parse.py +```bash +python parse.py +``` + +等待进度条走完后,检查新生成的`errorList.json`其中记录着失败的歌曲,`completedList.json`记录着全部成功的歌曲。 + +自行检查`errorList.json`、`completedList.json`中的数据,当前success下已经有了对应的歌曲文件 + +## 歌曲信息打包 +> 该步骤将success中下载的歌曲数据(封面-作者)等相关数据打包到MP3文件中 +> +> 如不需要相关信息可以跳过此步骤 + +```bash +python pack.py +``` \ No newline at end of file diff --git a/playlists/pack.py b/playlists/pack.py new file mode 100644 index 0000000..774962b --- /dev/null +++ b/playlists/pack.py @@ -0,0 +1,66 @@ +import os +import json +import shutil +import requests +from mutagen.easyid3 import EasyID3 +from mutagen.id3 import ID3, APIC, USLT +from tqdm import tqdm + +def update_mp3_metadata(mp3_path, data, folder_path): + audio = EasyID3(mp3_path) + audio['title'] = data['name'] + audio['artist'] = data['artist'] + audio['album'] = data['album'] + audio['albumartist'] = data['artist'] + audio.save() + + audio = ID3(mp3_path) + audio.update_to_v23() # 把可能存在的旧版本升级为2.3版本 + # 添加封面 + cover_path = os.path.join(folder_path, 'cover.jpg') + if os.path.exists(cover_path): + with open(cover_path, 'rb') as f: + cover_data = f.read() + audio['APIC'] = APIC( + encoding=0, # 3 is for utf-8 + mime='image/jpeg', # image/jpeg or image/png + type=3, # 3 is for the cover(front) image + desc='Cover', + data=cover_data + ) + # 添加歌词 + if 'lrc' in data: + audio['USLT'] = USLT( + encoding=3, + lang='eng', + desc='Lyrics', + text=data['lrc'] + ) + audio.save() + +def process_folder(folder_path, new_folder_path): + data_path = os.path.join(folder_path, 'data.json') + mp3_path = os.path.join(folder_path, 'song.mp3') + + if os.path.exists(data_path) and os.path.exists(mp3_path): + with open(data_path, 'r', encoding='utf-8') as f: + data = json.load(f) + new_mp3_path = os.path.join(new_folder_path, f"{data['name']}-{data['artist']}.mp3") + shutil.copy(mp3_path, new_mp3_path) + update_mp3_metadata(new_mp3_path, data, folder_path) + +def main(): + with open("settings.json", "r", encoding="utf-8") as f: + settings = json.load(f) + success_folder = settings.get("./success", "./success") + pack_folder = settings.get("packPath", "./pack") + if not os.path.exists(pack_folder): + os.makedirs(pack_folder) + + dirs = [d for d in os.listdir(success_folder) if os.path.isdir(os.path.join(success_folder, d))] + for dir_name in tqdm(dirs, desc="Processing folders"): + folder_path = os.path.join(success_folder, dir_name) + process_folder(folder_path, pack_folder) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/playlists/parse.py b/playlists/parse.py new file mode 100644 index 0000000..72373d9 --- /dev/null +++ b/playlists/parse.py @@ -0,0 +1,123 @@ +import os +import json +import time +import random +import requests +from pprint import pprint +from tqdm import tqdm + +headers = { + "accept": "application/json, text/plain, */*", + "accept-language": "zh-CN,zh;q=0.9", + "authorization": "Bearer def09b44baa4ea815d604f03a44993ce", + "content-type": "application/json", + "priority": "u=1, i", + "sec-ch-ua": "\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"133\", \"Chromium\";v=\"133\"", + "sec-ch-ua-full-version-list": "\"Not(A:Brand\";v=\"99.0.0.0\", \"Google Chrome\";v=\"133.0.6943.127\", \"Chromium\";v=\"133.0.6943.127\"", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-model": "\"\"", + "sec-ch-ua-platform": "\"Windows\"", + "sec-ch-ua-platform-version": "\"15.0.0\"", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + "Referer": "https://api.toubiec.cn/wyapi.html", + "Referrer-Policy": "strict-origin-when-cross-origin" + } + +url = "https://api.toubiec.cn/api/music_v1.php" + + +with open("settings.json", "r", encoding="utf-8") as f: + settings = json.load(f) + songs_to_download = settings["songs"] + level = settings.get("level", "lossless") + save_path = settings["savePath"] + +def download_file(file_url, output_location_path): + response = requests.get(file_url, stream=True) + with open(output_location_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + +def main(): + completed_list = [] + error_list = [] + + # 创建日志文件夹 + log_folder = "./log" + if not os.path.exists(log_folder): + os.makedirs(log_folder) + + for i, song_url in enumerate(tqdm(songs_to_download, desc="Downloading songs Task", unit="song")): + data = { + "url": song_url, + "level": level, + "type": "song", + "token":"e7519f587b3cf92583d7388639eae7ee" + } + + try: + result = requests.post(url, json=data, headers=headers) + + if result.status_code != 200: + raise Exception(f"Status code: {result.status_code}") + + # 解析结果 + resultData = result.json() + + # 校验 + if resultData['status'] != 200: + raise Exception(f"Status code: {resultData['status']}") + + # pprint(resultData) + + cover = resultData['song_info']['cover'] + name = resultData['song_info']['name'] + artist = resultData['song_info']['artist'] + alia = resultData['song_info']['alia'] + album = resultData['song_info']['album'] + song_original_url = resultData['url_info']['url'] + song_type = resultData['url_info']['type'] + lrc = resultData['lrc']['lyric'] + + song_data = { + "cover": cover, + "name": name, + "artist": artist, + "alia": alia, + "album": album, + "song_original_url": song_original_url, + "song_type": song_type, + "lrc": lrc + } + + output_folder_path = os.path.join(save_path, f"{name}-{artist}") + if not os.path.exists(output_folder_path): + os.makedirs(output_folder_path) + + mp3_path = os.path.join(output_folder_path, f"song.{song_type}") + download_file(song_original_url, mp3_path) + + cover_path = os.path.join(output_folder_path, "cover.jpg") + download_file(cover, cover_path) + + data_path = os.path.join(output_folder_path, "data.json") + with open(data_path, "w", encoding="utf-8") as data_file: + json.dump(song_data, data_file, ensure_ascii=False, indent=2) + + completed_list.append(song_url) + with open("./log/completedList.json", "w", encoding="utf-8") as f: + json.dump(completed_list, f, ensure_ascii=False, indent=2) + + # 随机等待 1 到 3 秒 + time.sleep(random.randint(1, 3)) + + + except Exception as error: + error_list.append({"url": song_url, "error": str(error)}) + with open("./log/errorList.json", "w", encoding="utf-8") as f: + json.dump(error_list, f, ensure_ascii=False, indent=2) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/playlists/requirements.txt b/playlists/requirements.txt new file mode 100644 index 0000000..d132358 --- /dev/null +++ b/playlists/requirements.txt @@ -0,0 +1,3 @@ +requests +tqdm +mutagen \ No newline at end of file diff --git a/playlists/settings.json b/playlists/settings.json new file mode 100644 index 0000000..566775f --- /dev/null +++ b/playlists/settings.json @@ -0,0 +1,8 @@ +{ + "level": "exhigh", + "savePath": "./success", + "packPath": "./pack", + "songs": [ + "https://music.163.com/song?id=2647373433" + ] + } \ No newline at end of file