From b3083ccfb2faf427e243c51bb1c2738479521aeb Mon Sep 17 00:00:00 2001 From: Kissablecho Date: Tue, 8 Jul 2025 13:16:14 +0800 Subject: [PATCH 1/7] Update dev_package_and_upload.yml --- .github/workflows/dev_package_and_upload.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_package_and_upload.yml b/.github/workflows/dev_package_and_upload.yml index 0dbcc34..dc149f7 100644 --- a/.github/workflows/dev_package_and_upload.yml +++ b/.github/workflows/dev_package_and_upload.yml @@ -68,6 +68,6 @@ jobs: name: Artifact path: | ./dist/*.exe - retention-days: 90 + retention-days: 14 if-no-files-found: warn overwrite: true From fcf323bc03dfe5a65beb41bd46f5570ed73c92e6 Mon Sep 17 00:00:00 2001 From: Kissablecho Date: Tue, 8 Jul 2025 13:23:47 +0800 Subject: [PATCH 2/7] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00e25a9..7b17858 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ **禁止任何人将代码用于违法行为** -[![Auto Package and Release](https://github.com/God-2077/python-code/actions/workflows/auto_package_and_release.yml/badge.svg)](https://github.com/God-2077/python-code/actions/workflows/auto_package_and_release.yml)[![Dev Package and Upload(dev)](https://github.com/God-2077/python-code/actions/workflows/dev_package_and_upload.yml/badge.svg?branch=dev)](https://github.com/God-2077/python-code/actions/workflows/dev_package_and_upload.yml) +[![Nuitka Package and Release](https://github.com/God-2077/python-code/actions/workflows/nuitka_package_release.yml/badge.svg)](https://github.com/God-2077/python-code/actions/workflows/nuitka_package_release.yml) +[![Nuitka Package and Upload(dev)](https://github.com/God-2077/python-code/actions/workflows/dev_package_and_upload.yml/badge.svg)](https://github.com/God-2077/python-code/actions/workflows/dev_package_and_upload.yml) ## [网易云音乐歌单批量下载歌曲][1] From 0f3f5ad9695791a0ffb6e05a2c793ef86eda8c6b Mon Sep 17 00:00:00 2001 From: God-2077 Date: Tue, 8 Jul 2025 14:50:08 +0800 Subject: [PATCH 3/7] update nuitka_build.py --- .gitignore | 3 ++- package/nuitka_build.py | 12 +++++++++--- package/nuitka_config.yml | 10 +++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 352a4ed..8651081 100644 --- a/.gitignore +++ b/.gitignore @@ -198,4 +198,5 @@ cython_debug/ .temp temp/ .vscode/ -.idea/ \ No newline at end of file +.idea/ +**/*.bak \ No newline at end of file diff --git a/package/nuitka_build.py b/package/nuitka_build.py index d2ae578..fd2c9ec 100644 --- a/package/nuitka_build.py +++ b/package/nuitka_build.py @@ -6,6 +6,8 @@ import argparse from pathlib import Path # from zip import zip_files_and_folders +import uuid +import shutil def main(): print("="*50) @@ -92,7 +94,7 @@ def main(): '--mingw64', # '--mode', '--assume-yes-for-downloads', #自动下载外部代码 - '--show-memory' + # '--show-memory' ] @@ -120,8 +122,12 @@ def main(): # print(f"警告: UPX目录不存在 {upx_dir}") # else: # print("不使用UPX压缩") + # 添加主Python文件 - cmd.append(str(python_file)) + t_file = str(uuid.uuid4()) + ".py" + shutil.copy(str(python_file), t_file) + t_file_path = base_dir / t_file + cmd.append(str(t_file_path)) # 打印并执行命令 print("执行命令:", ' '.join(cmd)) @@ -137,7 +143,7 @@ def main(): result = subprocess.run(cmd) if result.returncode == 0: - print(f"(onefile)打包成功: {dist_path / output_name}") + print(f"打包成功: {dist_path / output_name}") success_count += 1 else: print(f"打包失败,退出码: {result.returncode}") diff --git a/package/nuitka_config.yml b/package/nuitka_config.yml index 43fb293..138db4b 100644 --- a/package/nuitka_config.yml +++ b/package/nuitka_config.yml @@ -13,7 +13,7 @@ icon: windows-disable-console: False distpath: 'dist' - timeout: 1200 + timeout: 2700 output-name-template: '{{name}}_{{version}}_nuitka_{{os}}_{{arch}}' - name: 'Keyboard_monitoring' @@ -29,7 +29,7 @@ icon: windows-disable-console: False distpath: 'dist' - timeout: 1200 + timeout: 2700 output-name-template: '{{name}}_{{version}}_nuitka_{{os}}_{{arch}}' - name: 'NetEase_Cloud_Music_Download' @@ -46,7 +46,7 @@ icon: windows-disable-console: False distpath: 'dist' - timeout: 1200 + timeout: 2700 output-name-template: '{{name}}_{{version}}_nuitka_{{os}}_{{arch}}' - name: 'ftp_server' @@ -60,7 +60,7 @@ icon: windows-disable-console: False distpath: 'dist' - timeout: 1200 + timeout: 2700 output-name-template: '{{name}}_{{version}}_nuitka_{{os}}_{{arch}}' - name: 'sunrise_sunset_info' @@ -74,5 +74,5 @@ icon: windows-disable-console: False distpath: 'dist' - timeout: 1200 + timeout: 2700 output-name-template: '{{name}}_{{version}}_nuitka_{{os}}_{{arch}}' From af30402d4fdee62dc8e9515fe3c8e2308b977651 Mon Sep 17 00:00:00 2001 From: Kissablecho Date: Tue, 8 Jul 2025 14:58:04 +0800 Subject: [PATCH 4/7] Update pyinstaller_package_and_release.yml --- .github/workflows/pyinstaller_package_and_release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pyinstaller_package_and_release.yml b/.github/workflows/pyinstaller_package_and_release.yml index 8b9dec5..1cfc4fa 100644 --- a/.github/workflows/pyinstaller_package_and_release.yml +++ b/.github/workflows/pyinstaller_package_and_release.yml @@ -72,6 +72,7 @@ jobs: generate_release_notes: true draft: false prerelease: false + overwrite_files: true files: | dist/*.exe @@ -83,4 +84,4 @@ jobs: # file: "./dist/*.exe" # release_id: ${{ steps.create_release.outputs.id }} # verbose: false - # overwrite: true \ No newline at end of file + # overwrite: true From 22c637ed559f985c882b8979d13fa5c40aa9de00 Mon Sep 17 00:00:00 2001 From: Kissablecho Date: Tue, 8 Jul 2025 14:58:50 +0800 Subject: [PATCH 5/7] Update nuitka_package_release.yml --- .github/workflows/nuitka_package_release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nuitka_package_release.yml b/.github/workflows/nuitka_package_release.yml index a76f7a1..490faf2 100644 --- a/.github/workflows/nuitka_package_release.yml +++ b/.github/workflows/nuitka_package_release.yml @@ -84,5 +84,6 @@ jobs: generate_release_notes: true draft: false prerelease: false + overwrite_files: true files: | dist/*.exe From cf048730c4f2614a6570983ecd3acbfa19d87ee8 Mon Sep 17 00:00:00 2001 From: Kissablecho Date: Tue, 8 Jul 2025 15:29:58 +0800 Subject: [PATCH 6/7] Update nuitka_build.py --- package/nuitka_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/nuitka_build.py b/package/nuitka_build.py index fd2c9ec..63e6644 100644 --- a/package/nuitka_build.py +++ b/package/nuitka_build.py @@ -125,8 +125,8 @@ def main(): # 添加主Python文件 t_file = str(uuid.uuid4()) + ".py" - shutil.copy(str(python_file), t_file) t_file_path = base_dir / t_file + shutil.copy(str(python_file), t_file_path) cmd.append(str(t_file_path)) # 打印并执行命令 From 9d9584cc408f4dd1b194fdbec145a68d408831a4 Mon Sep 17 00:00:00 2001 From: Kissablecho Date: Tue, 8 Jul 2025 16:06:48 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20NetEase=5FCloud=5FMusi?= =?UTF-8?q?c=5FDownload=20(#18)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dev_package_and_upload.yml (#17) * 更新 NetEase_Cloud_Music_Download --- README.md | 6 +- package/nuitka_config.yml | 4 +- .../README.md" | 8 +- .../v.25-07-08.py" | 261 ++++++++++++++++++ 4 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 "\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/v.25-07-08.py" diff --git a/README.md b/README.md index 7b17858..1ee898d 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,14 @@ ## [网易云音乐歌单批量下载歌曲][1] -最新版:[v.24-10-06][2] +使用 metting api 批量下载网易云音乐歌曲 + +注意,这程序严重依赖第三方的 metting api ## [键盘监听][4] -最新版:[v.24.07.16][5] +键盘监听 ## [psql_terminal][7] diff --git a/package/nuitka_config.yml b/package/nuitka_config.yml index 138db4b..ab84c07 100644 --- a/package/nuitka_config.yml +++ b/package/nuitka_config.yml @@ -33,8 +33,8 @@ output-name-template: '{{name}}_{{version}}_nuitka_{{os}}_{{arch}}' - name: 'NetEase_Cloud_Music_Download' - version: 'v.24-10-06' - python-file: '网易云音乐歌单批量下载歌曲\v.24-10-06.py' + version: 'v.25-07-08' + python-file: '网易云音乐歌单批量下载歌曲\v.25-07-08.py' install-requirements: [ 'quote', 'requests', diff --git "a/\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/README.md" "b/\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/README.md" index e5f88ad..1046f88 100644 --- "a/\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/README.md" +++ "b/\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/README.md" @@ -4,7 +4,7 @@ 注意,这程序严重依赖第三方的 metting api -最新版:[v.24-10-06][7] +最新版:[v.25-07-08][8] ## 说明 @@ -24,6 +24,9 @@ python ***.py ## 日志 +- [v.25-07-08][8] + - 删去 `#!/usr/bin/python` 指令 + - [v.24-10-06][7] - 小优化 - 添加了是否下载小于60秒音乐的选项 @@ -60,4 +63,5 @@ python ***.py [4]: https://github.com/God-2077/python-code/blob/main/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%AD%8C%E5%8D%95%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%AD%8C%E6%9B%B2/v.24-04-05.%E6%9C%80%E7%BB%88%E7%89%88.py [5]: https://github.com/God-2077/python-code/blob/main/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%AD%8C%E5%8D%95%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%AD%8C%E6%9B%B2/v.24-07-18.py [6]: https://github.com/God-2077/python-code/blob/main/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%AD%8C%E5%8D%95%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%AD%8C%E6%9B%B2/v.24-07-19.py -[7]: https://github.com/God-2077/python-code/blob/main/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%AD%8C%E5%8D%95%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%AD%8C%E6%9B%B2/v.24-10-06.py \ No newline at end of file +[7]: https://github.com/God-2077/python-code/blob/main/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%AD%8C%E5%8D%95%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%AD%8C%E6%9B%B2/v.24-10-06.py +[8]: v.24-10-06.py \ No newline at end of file diff --git "a/\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/v.25-07-08.py" "b/\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/v.25-07-08.py" new file mode 100644 index 0000000..eded536 --- /dev/null +++ "b/\347\275\221\346\230\223\344\272\221\351\237\263\344\271\220\346\255\214\345\215\225\346\211\271\351\207\217\344\270\213\350\275\275\346\255\214\346\233\262/v.25-07-08.py" @@ -0,0 +1,261 @@ +# -*- coding: UTF-8 -*- + +import os +import requests +from mutagen.mp3 import MP3 +import time +import signal +import sys +import re +from tabulate import tabulate + + +def download_file(url, file_path, file_type, index, total_files, timeout=10): + try: + response = requests.get(url, stream=True, timeout=timeout) + response.raise_for_status() + total_size = int(response.headers.get('content-length', 0)) + downloaded_size = 0 + + with open(file_path, "wb") as file: + for data in response.iter_content(chunk_size=4096): + downloaded_size += len(data) + file.write(data) + progress = downloaded_size / total_size * 100 if total_size > 0 else 0 + print(f"正在下载 [{index}/{total_files}][{file_type}] {file_path},进度:{progress:.2f}%\r", end="") + + print(f"下载完成 [{index}/{total_files}][{file_type}] {file_path}") + return True + except requests.exceptions.RequestException as e: + print(f"下载 [{index}/{total_files}][{file_type}] {file_path} 失败:{e}") + return False + + +def download_lyrics(url, lrc_path, song_index, total_songs): + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + with open(lrc_path, "w", encoding="utf-8") as lrc_file: + lrc_file.write(response.content.decode('utf-8')) + print(f"下载完成 [{song_index}/{total_songs}][LRC] {lrc_path}") + return True + except requests.exceptions.RequestException as e: + print(f"下载歌词失败:{e}") + return False + + +def safe_filename(filename): + invalid_chars = '\\/:*?"<>|' + for char in invalid_chars: + filename = filename.replace(char, '_') + return filename + + +def delete_file(file_path): + if os.path.exists(file_path): + os.remove(file_path) + print(f"删除文件:{file_path}") + + +def song_table(data): + table_data = [] + for idx, item in enumerate(data, start=1): + name = item['name'] + artist = item['artist'] + url_id = re.search(r'\d+', item['url']).group() + table_data.append([idx, name, artist, url_id]) + table_headers = ["序号", "标题", "艺术家", "ID"] + table = tabulate(table_data, table_headers, tablefmt="pipe") + print(table) + + +def exit_ctrl_c(sig, frame): + print("\n退出程序...") + sys.exit(0) + + +def welcome(): + print("welcome") + print("欢迎使用我开发的小程序") + print("Github Rope: https://github.com/God-2077/python-code/") + print("-------------------------") + print("网易云音乐歌单批量下载歌曲") + + +def download_playlist(playlist_id, download_path): + if not playlist_id.isdigit(): + print("歌单ID必须为数字") + return + + error_song_name = [] + error_song_id = [] + + api_urls = [ + "https://meting.qjqq.cn/?type=playlist&id=", + "https://api.injahow.cn/meting/?type=playlist&id=", + "https://meting-api.mnchen.cn/?type=playlist&id=", + "https://meting-api.mlj-dragon.cn/meting/?type=playlist&id=" + ] + + selected_api = None + data = None + + for api_url in api_urls: + try: + response = requests.get(f"{api_url}{playlist_id}", timeout=10) + # if requests.status_codes != 200: + # print("出错了...") + # print(f"状态码:{requests.status_codes}") + # return + response.raise_for_status() + data = response.json() + selected_api = api_url + if 'error' in data: + error = data.get("error", "") + print("出错了...") + print(f"错误详细:{error}") + return + break + except requests.exceptions.RequestException as e: + print(f"API {api_url} 请求失败:{e}") + continue + + if not data: + print("所有API都无法获取数据") + return + + os.makedirs(download_path, exist_ok=True) + + print(f"Meting API: {selected_api}") + + total_songs = len(data) + print(f"歌单共有 {total_songs} 首歌曲") + song_table(data) + envisage_size = total_songs * 7.7 + print(f"歌单共有 {total_songs} 首歌曲,预计歌曲文件总大小为 {envisage_size} MB") + chose = str(input("是否继续下载?(yes): ")) + if chose not in ["y", "", "yes"]: + print("退出程序...") + sys.exit(0) + chose = str(input("是否下载小于 60 秒的歌曲(可能为试听音乐)?(not): ")) + if chose not in ["y", "", "yes"]: + downtrymusic = 1 + else: + downtrymusic = 0 + successful_downloads = 0 + failed_downloads = [] + + def signal_handler(sig, frame): + print("\n检测到Ctrl+C, exiting gracefully...") + print(f"程序运行完成,共 {total_songs} 首歌曲,成功下载 {successful_downloads} 首歌曲") + if failed_downloads: + print(f"共有 {len(failed_downloads)} 首歌曲下载失败") + print("失败列表如下") + table_data = [[i + 1, error_song_name[i], error_song_id[i]] for i in range(len(error_song_name))] + print(tabulate(table_data, headers=["序号", "标题 - 艺术家", "ID"], tablefmt="grid")) + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + + for index, song in enumerate(data, start=1): + name = song.get("name", "") + artist = song.get("artist", "") + url = song.get("url", "") + lrc = song.get("lrc", "") + pic = song.get("pic", "") + + if not name or not artist or not url or not lrc: + print(f"歌曲信息不完整:{song}") + continue + + song_filename = f"{safe_filename(name)} - {safe_filename(artist)}.mp3" + song_name = f"{safe_filename(name)} - {safe_filename(artist)}" + song_path = os.path.join(download_path, song_filename) + + retry_count = 0 + while retry_count < 5: + if download_file(url, song_path, "MP3", index, total_songs): + successful_downloads += 1 + state = True + break + else: + retry_count += 1 + print(f"重试下载 [{index}/{total_songs}][MP3] {song_path},次数:{retry_count}") + state = False + time.sleep(1) + if state == False: + print("") + match = re.search(r'\d+', url) + error_song_id.append(int(match.group())) + error_song_name.append(song_name) + failed_downloads.append(match) + + if state == True: + try: + audio = MP3(song_path) + audio_duration = audio.info.length + if downtrymusic == 1: + if audio_duration < 60: + print(f"歌曲时长小于一分钟,删除歌曲和取消下载歌词和图片:{song_path}") + delete_file(song_path) + audio_duration_TF = False + successful_downloads -= 1 + match = re.search(r'\d+', url) + error_song_id.append(int(match.group())) + error_song_name.append(song_name) + else: + print(f"歌曲时长为 {audio_duration} 秒") + audio_duration_TF = True + else: + print(f"歌曲时长为 {audio_duration} 秒") + audio_duration_TF = True + except Exception as e: + print(f"无法获取歌曲时长:{e}") + print("应该为 VIP 单曲,删除歌曲文件和取消下载歌词和图片") + delete_file(song_path) + audio_duration_TF = False + successful_downloads -= 1 + match = re.search(r'\d+', url) + error_song_id.append(int(match.group())) + error_song_name.append(song_name) + failed_downloads.append(match) + + if audio_duration_TF: + lrc_filename = f"{safe_filename(name)} - {safe_filename(artist)}.lrc" + lrc_path = os.path.join(download_path, lrc_filename) + + retry_count = 0 + while retry_count < 5: + if download_lyrics(lrc, lrc_path, index, total_songs): + break + else: + retry_count += 1 + print(f"重试下载 [{index}/{total_songs}][LRC] {lrc_path},次数:{retry_count}") + time.sleep(1) + + pic_filename = f"{safe_filename(name)} - {safe_filename(artist)}.jpg" + pic_path = os.path.join(download_path, pic_filename) + + retry_count = 0 + while retry_count < 5: + if download_file(pic, pic_path, "PIC", index, total_songs): + break + else: + retry_count += 1 + print(f"重试下载 [{index}/{total_songs}][PIC] {pic_path},次数:{retry_count}") + time.sleep(1) + + print(f"程序运行完成,共 {total_songs} 首歌曲,成功下载 {successful_downloads} 首歌曲") + if failed_downloads or error_song_name: + print(f"共有 {len(failed_downloads)} 首歌曲下载失败") + print("失败列表如下") + table_data = [[i + 1, error_song_name[i], error_song_id[i]] for i in range(len(error_song_name))] + print(tabulate(table_data, headers=["序号", "标题 - 艺术家", "ID"], tablefmt="grid")) + + +if __name__ == "__main__": + signal.signal(signal.SIGINT, exit_ctrl_c) + welcome() + playlist_id = input("歌单ID:") + download_path = input(r"下载路径(默认为 ./down):") or r"./down" + download_playlist(playlist_id, download_path)