diff --git a/vipsong.py b/vipsong.py index f5a3bec..3c81cbd 100644 --- a/vipsong.py +++ b/vipsong.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -import os, sys, glob, struct, base64, json +import os, sys, glob, struct, base64, json, io from pathlib import Path from typing import Optional from Crypto.Cipher import AES @@ -12,11 +12,53 @@ from pydub import AudioSegment NCM_MAGIC = b"CTENFDAM" CORE_KEY = b"hzHRAmso5kInbaxW" +META_KEY = bytes.fromhex("2331346C6A6B5F215C5D2630553C2728") META_PREFIX = b"163 key(D't modify):" +KUGOU_KEY_PATH = Path(__file__).parent / "kugou_key.xz" + +_FORMAT_MAP = { + "mp3": "mp3", + "flac": "flac", + "wav": "wav", + "ogg": "ogg", + "m4a": "mp4", + "mp4": "mp4", +} + +stats = {"success": 0, "failed": 0, "files": []} +kgm_decoder = None def _u32le(data, pos): return struct.unpack(b"= 3 and header[:3] == b"ID3": + return "mp3" + if len(header) >= 2 and header[0] == 0xFF and (header[1] & 0xE0) == 0xE0: + return "mp3" + if len(header) >= 4 and header[:4] == b"fLaC": + return "flac" + if len(header) >= 4 and header[:4] == b"OggS": + return "ogg" + if len(header) >= 12 and header[:4] == b"RIFF" and header[8:12] == b"WAVE": + return "wav" + if len(header) >= 12 and header[4:8] == b"ftyp": + return "mp4" + return None + +def _extension_for(fmt): + """将格式名称映射为文件后缀。""" + mapping = { + "mp3": "mp3", + "flac": "flac", + "wav": "wav", + "ogg": "ogg", + "m4a": "m4a", + "mp4": "m4a", + } + return mapping.get(fmt, "mp3") + def ncm_stream_bytes(key): sbox = bytearray(range(256)) j = 0 @@ -210,6 +252,16 @@ def _add_tags(output_path, tags, cover=None): except Exception: pass +def _convert_audio(raw, container, out_fmt, output_path): + """将解密后的原始音频转码为目标格式。""" + src_fmt = _FORMAT_MAP.get(container, container) + dst_fmt = _FORMAT_MAP.get(out_fmt, out_fmt) + if src_fmt == dst_fmt: + Path(output_path).write_bytes(raw) + else: + audio = AudioSegment.from_file(io.BytesIO(raw), format=src_fmt) + audio.export(output_path, format=dst_fmt) + def convert_file(file_path, output_fmt, delete_flag): global stats, kgm_decoder file_path = str(Path(file_path).resolve()) @@ -339,11 +391,11 @@ def main(): convert_file(f, output_fmt, delete_flag) print_stats() return - print("=* 50") + print("=" * 50) print(" vipsong - Music Format Converter") print(" Support: .ncm (NetEase) .kgm/.vpr (KuGou)") print(" Output: mp3 / flac / wav / ogg / m4a") - print("=* 50") + print("=" * 50) print("Commands:") print(" Convert file or glob pattern (e.g. *.ncm)") print(" -f Output format (default: mp3)")