ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

macOS下QMC音频批量转FLAC/MP3:解密与转换实战指南

macOS下QMC音频批量转FLAC/MP3:解密与转换实战指南 简介macOS平台下的QQ音乐QMC加密格式批量转换工具是一款面向音频格式转换的桌面小工具适用于计算机、应用数学、电子工程等专业学生的课程设计、学期综合训练与毕业设计环节同时也适合初学者上手项目实践、学习工程组织与原型开发。工具实现了qmcflac/mflac转FLAC、qmc0/qmc3转MP3等常见格式转换包含可直接运行部署的完整工程源码并以图形界面提供批量文件选择与转换入口。资源包共43个文件以Swift源码、PNG界面素材、JSON/Plist配置、zbak备份文件为主压缩包约981KB目录划分清晰主工程模块、单元测试、说明文档、应用图标与成功状态图等一应俱全工程内的解码器、密码器与加解密组件覆盖密钥解码、格式还原的核心环节。目前已有121人学习下载适合需要快速拿到一个真实可用的转换工具、深入理解QMC格式解析与加解密实现细节的读者。1. QMC格式在macOS上为什么这么难搞先认清你手里的是哪种“加密”从QQ音乐下载的 SQ 无损回到访达里一看后缀是 qmcflac 或 mflac拖进播放器只会提示格式不支持。Windows 上有不少现成图形工具macOS 上想批量把 qmcflac/mflac 转成 FLAC、把 qmc0/qmc3 转成 MP3反而要自己折腾一套命令行链路。这些文件不是“改个后缀就能放”的普通加密而是 QQ 音乐客户端加密过的媒体容器要归档本地音乐就得先解密再做容器转换。这篇我按平时处理这类文件的顺序把格式差异、批量脚本、参数选择和翻车点一次讲透。适合手里攒了不少下载文件、想在 macOS 上整理成标准 FLAC/MP3 的人也适合想把这套流程自动化省事的开发者。2. 拆开QMC的壳QMC0/QMC3与QMCflac/Mflac的加密差异和容器真相2.1 看扩展名猜方案qmc0/qmc3/qmcflac/mflac到底各是什么处理之前先把后缀分类因为解密思路完全不同。常见 QMC 后缀可以分成老版固定密钥和新版动态掩码两代qmc0、qmc3、qmcflac 属于老版mflac、mflac0、mgg 属于新版。网上很多工具把 mgg 和 mflac 一起处理因为它们共用同一套新算法只是容器预判不同。扩展名加密代际解密后常见容器说明qmcflac老版固定密钥FLAC从名字看是 FLAC实际要以解密后探测结果为准qmc0老版固定密钥MP3 为主和 qmc3 一样历史抓取文件里偶尔出现 FLAC 内容qmc3老版固定密钥MP3 为主老格式部分文件实际容器是 FLACmflac / mflac0新版动态掩码FLAC新客户端下载的高音质文件通常带 QTag 尾巴mgg新版动态掩码FLAC / OGG与 mflac 同算法很多解密器默认一起支持只看后缀定方案翻车概率不低。早年 QQ 音乐客户端改过命名规则同一个文件在不同版本里可能被存成 qmcflac 或 mflac甚至 qmc0 后缀里包着 FLAC 流。我见过最典型的情况是按“qmc0 转 MP3”的思路去做解密完 ffprobe 一探里面是 FLAC最后输出文件被硬塞成 MP3 容器播放器直接罢工。所以第一步不是急着写转换脚本而是把“按后缀转码”改成“按真实容器转码”。真实容器只有解密之后才能确认但解密之前可以先看一眼文件头特征新老版本在文件开头和结尾的结构差别很大老版本文件通常整段都是密文新版本文件尾部能找到 QTag 这类可读的元数据块。拿到两个样本文件用十六进制查看器分别看开头几十字节和最后几百字节基本就能判断手里这批文件属于哪一代。这一步花五分钟后面能少踩一半的坑。2.2 加密不是AES异或表与STMask的差别以及为什么能直接解QMC 加密不是 AES 这类需要密钥协商的公钥体系本质上是本地客户端自带的对称变换密钥和掩码生成逻辑都在客户端二进制里。解密工具能流行起来正是因为有人从客户端里把这张表或生成规则挖了出来再用同样的逻辑做逆运算。老版 qmc0/qmc3/qmcflac 用的是固定密钥表异或。加密时把明文按字节和一张定长密钥表循环异或解密时再做一遍同样的异或即可因为异或是对称操作。概念代码可以写成这样def old_qmc_xor(data: bytes, key_table: bytes) - bytes: # 老版 QMC2 加密固定密钥表逐字节循环异或 # 解密 对密文再异或同一张表结果就是明文 out bytearray(len(data)) for i, b in enumerate(data): out[i] b ^ key_table[i % len(key_table)] return bytes(out)实际工程里老版还有跳距和分段处理不是每一代都是从头到尾简单循环但核心思路就是这样。做批量工具时最省事的做法是直接复用开源解密核心自己重新推导整张密钥表性价比很低尤其遇到跨版本文件时一张表认不全就是满盘噪声。我一般把密钥表当作黑匣子只要确认解密后文件头能对上 fLaC 或 ID3就认定当前选择正确。新版 mflac/mflac0 换成动态掩码但也不是复杂加密。文件里某个固定位置藏着两字节的 seed解密器用 seed 展开出一张 256 字节掩码表再对文件体逐字节异或。掩码生成的展开逻辑各解密器实现一致关键就是这个 seed 要和客户端版本匹配否则生成的掩码不对解密结果就是垃圾数据。示意如下def st_mask(seed: int) - bytes: # 用 seed 展开 256 字节掩码表具体展开规则以解密器实现为准 # 这里不展开完整算法不同客户端版本的展开细节有差异 mask bytearray(256) # 常见做法是用 seed 初始化一张表再做若干轮置换 return bytes(mask)这段代码只负责说明“新版不是直接拿 seed 异或而是先展开再异或”。真要写完整展开逻辑建议直接参考你选定的开源解密核心而不是自己从零推算因为版本错位很难排查。我在这上面吃过亏自己按老版固定表逻辑去解 mflac前几十个字节看起来像那么回事越往后越乱最后才发现是 seed 读取位置偏了两个字节。为什么本地能解因为播放时必须解密才能出声所以解密逻辑必然随客户端分发到本地逆向者只需要找到它并复刻。这不是什么高深的密码学对抗而是应用层加密的常态也是这类批量工具能在 macOS 上跑起来的前提。2.3 解密之后的容器校验FLAC与MP3的真实身份不能靠后缀解密完成只代表“壳没了”不代表“容器对了”。解密工具输出的是完整文件还是裸流完全取决于具体实现。有的工具会把 FLAC 的 fLaC 头一并还原有的只吐出纯音频流。输出扩展名是工具写死的不一定和真实容器一致。所以每转完一个文件我习惯立刻跑一次 ffprobe 看真实容器再决定下一步用 copy 重封装还是重新编码ffprobe -v error -show_entries streamcodec_name -of defaultnoprint_wrappers1 output.flac如果输出是flac说明容器和扩展名一致如果是mp3说明扩展名骗了你如果直接报错说明前面解密已经出了问题。macOS 自带的 afconvert 也能做简单探测但 ffprobe 能显示容器层级和流信息排查时更清楚后面转码也依赖它所以我会先把 ffmpeg 装好再说。容器判断还影响转码策略。真实容器是 FLAC 时直接流复制重封装即可保留全部信息真实容器是 MP3 时硬改成 flac 扩展名没有意义播放器照样识别不了。与其纠结扩展名不如让脚本以 ffprobe 的探测结果为准把输出扩展名修正成真实容器。3. 在macOS上搭一套能跑的批量转换链路FFmpeg、解密CLI与目录脚本3.1 先把工具装齐Homebrew安装FFmpeg与解密核心macOS 上转码首选 ffmpeg这条命令同时会装好 ffprobe。如果机器上还没装 Homebrew先装 Homebrew 再执行brew install ffmpeg装完后确认路径which ffmpeg ffprobemacOS 从 2020 款以后的机器上Homebrew 默认装在/opt/homebrew下如果which找不到说明 PATH 里没有把/opt/homebrew/bin加进.zshrc的 PATH 再 source 一次。不要从官网下编译包往/usr/local里塞后面升级维护都麻烦Homebrew 管着更新省心很多。解密核心的选择要提一下GitHub 上能搜到多个 qmc 解密项目有 Go 写的静态二进制也有 Node/Python 实现。macOS 上我建议直接用静态二进制不需要装 Node 环境拷贝到~/bin下就能跑mkdir -p ~/bin cp ~/Downloads/qmc-dec ~/bin/qmc-dec chmod x ~/bin/qmc-dec这里qmc-dec是我给解密器起的统称你实际用的那个二进制叫什么就换成什么。重点是把它的位置固定下来后面脚本统一引用这个路径。3.2 统一解密CLI的调用约定输入、输出与退出码的抽象实际工作中我发现不同解密器参数风格差异很大有的接受-i 输入 -o 输出有的直接decoder input output还有的只接受目录参数。为了让后面的批量脚本不跟着工具改我会先把调用方式收敛成一个统一接口。常见做法是写一个包装函数或小脚本把参数翻译成“输入文件路径 输出文件路径”两个位置参数。例如qmc-dec input.qmcflac output.flac注意查看退出码。很多工具解密失败时仍返回 0只在 stderr 里打印警告所以光看退出码不够还要检查输出文件是否真的生成、大小是否大于 0。批量转换最怕一个坏文件把整批带偏宁可多做这一步检查也不要贪快。3.3 批量循环脚本一段能直接抄走的Python调度代码单个文件跑通之后剩下就是批量。批量脚本我推荐用 Python 写而不是纯 bash因为文件名里可能带空格、引号、中文甚至换行Python 的subprocess用参数列表传参不经过 shell 解析能避开绝大多数文件名坑。这版脚本按扩展名映射输出先解密再调用后续的验证逻辑#!/usr/bin/env python3 import subprocess import sys from pathlib import Path DECODER Path.home() / bin / qmc-dec SRC_DIR Path(sys.argv[1]) if len(sys.argv) 1 else Path.cwd() OUT_DIR Path(sys.argv[2]) if len(sys.argv) 2 else SRC_DIR / converted # 扩展名 - 目标容器扩展名 EXT_MAP { .qmcflac: .flac, .mflac: .flac, .mflac0: .flac, .qmc0: .mp3, .qmc3: .mp3, } OUT_DIR.mkdir(parentsTrue, exist_okTrue) for src in SRC_DIR.rglob(*): suffix src.suffix.lower() if suffix not in EXT_MAP: continue out_name src.stem EXT_MAP[suffix] out_path OUT_DIR / out_name # 已经转过的直接跳过方便重跑 if out_path.exists() and out_path.stat().st_size 0: print(f[skip] {src.name}) continue print(f[dec] {src.name}) dec_path OUT_DIR / (src.stem .dec.bin) proc subprocess.run( [str(DECODER), str(src), str(dec_path)], capture_outputTrue, textTrue, ) # 重点解密器返回 0 且输出文件存在且大于 0 才算成功 if proc.returncode ! 0 or not dec_path.exists() or dec_path.stat().st_size 0: print(f[fail] {src.name}: {proc.stderr.strip()}, filesys.stderr) continue # 后续转码逻辑在下一节接入这段脚本的逻辑是遍历源目录下所有文件按下表匹配后缀跳过已经转出过的文件调用解密器生成临时中间文件。rglob(*)会递归子目录输出文件统一放到converted目录下避免和源文件混在一起。参数说明里最容易踩的是EXT_MAP的写法.mflac0要放在.mflac后面匹配吗Python 字典按键精确匹配不是前缀匹配所以顺序无所谓。真正要注意的是 qmc0 后缀不一定都是 MP3第 2 章说过真实容器要以解密后的探测为准这里只是先用扩展名定一个初值后面转码阶段会修正。3.4 转码与封装解密完直接交给FFmpeg出FLAC/MP3解密产生的.dec.bin是中间产物不能直接当结果用。下一步用 ffprobe 探测真实容器再决定如何交给 ffmpeg。核心原则是能流复制就不要重新编码。probe subprocess.run( [ ffprobe, -v, error, -show_entries, streamcodec_name, -of, defaultnoprint_wrappers1, str(dec_path), ], capture_outputTrue, textTrue, ) codec for line in probe.stdout.splitlines(): if line.startswith(codec_name): codec line.split(, 1)[1].strip() break if probe.returncode ! 0 or not codec: print(f[fail] probe {src.name}, filesys.stderr) continue if codec flac: final_out OUT_DIR / (src.stem .flac) cmd [ffmpeg, -y, -i, str(dec_path), -c:a, copy, -vn, str(final_out)] elif codec in (mp3, libmp3lame): final_out OUT_DIR / (src.stem .mp3) cmd [ffmpeg, -y, -i, str(dec_path), -c:a, copy, -vn, str(final_out)] else: # 其他编码统一转成 FLAC保证播放器兼容 final_out OUT_DIR / (src.stem .flac) cmd [ffmpeg, -y, -i, str(dec_path), -c:a, flac, -vn, str(final_out)] proc subprocess.run(cmd, capture_outputTrue, textTrue) if proc.returncode ! 0: print(f[fail] convert {src.name}: {proc.stderr.strip()}, filesys.stderr) continue print(f[ok] {out_name}) dec_path.unlink() # 删掉中间文件避免堆积这段逻辑说明探测到 FLAC 或 MP3 时直接-c:a copy流复制不重新编码速度极快也无损。只有遇到其他编码才降级转成 FLAC。-vn表示丢弃所有非音频流这一步顺手把解密中间文件里可能残留的视频帧或封面流清掉后面再单独处理封面。转码完立刻删除.dec.bin中间文件避免批量转几百个文件后磁盘被占满。这里的qmc0/qmc3转 MP3 的逻辑同样适用。解密后 ffprobe 显示为 MP3直接 copy 到.mp3因为源文件本来就是有损编码重新编码只会损失更多不会有任何收益。4. 转换参数怎么调FLAC压缩级别、MP3比特率、封面和文件名整理4.1 FLAC转码参数压缩级别、采样率与24bit处理的取舍FLAC 编码器参数其实很少最核心的是-compression_level范围 0 到 8。0 最快但压缩率最差8 最慢但体积最小。日常我固定用默认级别 5不追求级别 8因为本地音乐文件最不缺的就是那几十 MB 空间而压缩耗时和耗电是实打实的。如果机器是老 Intel Mac转大批量时建议降到级别 1速度快很多体积差异在可接受范围。碰到 96kHz/24bit 的源文件转码时格外注意采样格式。Flac 编码器在 ffmpeg 里会自动处理 24bit但不要手贱加-ar 44100降采样。把 96kHz 降成 44.1kHz 等于永久丢掉高频信息对本地无损归档没有意义。保持原采样率、原比特深度只调压缩级别ffmpeg -i input.flac -c:a flac -compression_level 5 -sample_fmt s32 output.flac-sample_fmt s32是显式声明 32bit 整型采样格式适用于 24bit 源文件。如果你的源是 16bit不需要加这个参数默认 s16 就行强行 s32 只会白白增大体积。4.2 MP3转码参数VBR档位和CBR场景的选择qmc0/qmc3 解密后多数是 MP3正常情况下直接 copy 就能用。只有当你确实需要把非 MP3 源转成 MP3 时才涉及编码参数选择。常见做法是 VBR用-q:a 2ffmpeg -i input.flac -c:a libmp3lame -q:a 2 output.mp3-q:a 2对应 LAME 的 VBR V2 档位大约 190-210kbps体积和音质平衡适合本地归档。追求极限音质用-q:a 0也就是 V0接近 245-260kbps体积会大一些。CBR 只留给兼容性要求高的场景比如老车载播放器对 VBR 时间轴支持不好这时用ffmpeg -i input.flac -c:a libmp3lame -b:a 320k output.mp3CBR 320k 是 MP3 里最高规格代价是体积大、编码慢。对我个人来说除非播放器明确不支持 VBR否则不会用 CBR因为现代播放器处理 VBR 毫无压力。4.3 封面和元数据从QMC尾巴里把专辑图抠出来再贴回去解密工具处理 QTag 的差异很大。QTag 是 QQ 音乐文件尾部附加的元数据块包含歌名、歌手、专辑、封面等。有的解密器会完整转换到 FLAC 的 Vorbis Comment 或 MP3 的 ID3v2 里有的直接丢掉。如果转换完发现封面没了先别急着手工补用 ffmpeg 把原解密文件的封面抽出来ffmpeg -v error -i decoded.flac -an -c:v copy cover.jpg-an丢弃音频流-c:v copy只提取封面图。抽出来之后再贴到目标文件上ffmpeg -i output.flac -i cover.jpg -map 0:a -map 1:v -c copy -metadata:s:v titleCover -metadata:s:v commentCover (front) output-with-cover.flac-map 0:a选音频流-map 1:v选封面流-c copy两个流都不重编码。MP3 文件贴封面时最好加-id3v2_version 3老播放器对 ID3v2.4 支持不好v2.3 兼容面最广。如果转换前发现大量文件缺封面说明解密核心默认丢了 QTag建议换一个保留元数据的解密版本省得每张图都手动处理。4.4 文件名与目录整理避免特殊字符和重复转换文件名整理放在转换之后不要在转换前改。原因很简单脚本按扩展名扫描源目录如果你把a.qmcflac改成01 - a.qmcflac之后想重跑脚本做增量转换匹配逻辑还能工作但如果你把源文件改成a.flac脚本会以为它是普通 FLAC不再解密原始密文也没了等于把后悔药扔掉。整理时用 ffprobe 读标签生成目标文件名比较可靠ffprobe -v error -show_entries format_tagsartist,title,album -of defaultnoprint_wrappers1 song.mp3拿到artist和title后用脚本拼成artist - title.ext。macOS 文件名里不能出现/:在 Finder 显示成/也很容易迷惑人需要先做替换。我一般在整理脚本里加一行替换表把/\:全部换成-。转换好的文件统一放converted目录按artist/album/两级目录归档比全堆在一个目录里好找得多。5. 常见问题与排查解密成功不等于转换成功五个翻车点5.1 解密后的文件依然打不开密钥版本和文件版本不匹配现象解密器提示成功输出文件也存在但 ffprobe 报Invalid data found when processing input打开音频还是提示损坏。原因你用的解密核心只实现了新版 mflac/mgg老版 qmc0/qmc3/qmcflac 的固定密钥表不一样或者反过来工具只认老版密钥碰到新版 mflac 就输出垃圾。这类问题最容易出现在“手里两种代际的歌曲混在一起”的时候。解决先用第 2 章的十六进制查看方法确认文件代际再选定对应版本。更省事的做法是找一个同时支持两代算法的解密核心别用只修了单一代际的临时脚本。如果已经转出一批坏文件宁可重新解密也不要在坏文件上继续折腾。5.2 播放只有沙沙声把裸流直接改名当成完整容器现象解密后文件能播放但全是白噪声波形像一个满幅度的随机信号时长有时也不对。原因解密器输出的是裸 FLAC 流前面没有 fLaC 容器头后面没有 STREAMINFO 块。直接改扩展名或者强行用-c:a copy封装FFmpeg 可能识别失败把裸流当原始 PCM 处理于是时长错乱、声音炸裂。解决对解密文件先ffprobe -show_entries streamcodec_name如果 codec 是 flac 但报错或时长异常大概率是裸流。用ffmpeg -f flac -i dec.bin -c:a copy out.flac强制指定输入格式为 flac再重封装成完整容器。MP3 裸流同理-f mp3强制输入格式。5.3 批量跑到一半停掉路径带空格、内存占满和输出目录套娃现象脚本处理了一百多个文件后突然终止报错信息指向某个文件名特别长、带空格的目录或者干脆提示No space left on device。原因一是用了 bash 的find xargs文件名里的空格被拆成多个参数导致解密器收到错误路径。二是输出目录设置在源目录内部脚本边遍历边生成converted子目录rglob(*)在下一次迭代时把输出文件也扫进来造成重复处理死循环或磁盘膨胀。解决批量脚本统一用 Pythonsubprocess参数列表传参不要经过 shell。输出目录放到源目录外面比如~/Music/converted。同时在循环里加文件大小检查输出文件超过源文件三倍还继续增长就要小心是不是套娃或解码异常。5.4 转换后时长错乱VBR MP3的时长字段被带进FLAC现象mp3 转 flac 后播放器显示时长 03:22实际播放 03:42快进后进度条回跳。原因MP3 是 VBR 编码旧 ID3v2 头的时长字段本来就和实际时长有偏差。转成 FLAC 时 FFmpeg 先读取了错误的元数据生成的 STREAMINFO 里写入错误时长。解决转码命令加-map_metadata 0之外再显式让 FFmpeg 重新扫描时长-fflags genpts。转换完成后统一跑一遍 ffprobe把时长和源文件对一遍偏差超过 2 秒的文件标记出来重转。这个坑比较隐蔽我在批量转老歌时碰到好几次最后是把重转校验写进脚本才根治。5.5 封面全部丢失QTag被当垃圾数据丢弃现象转出来的 FLAC/MP3 音质正常标签基本都在但所有文件都没有封面播放器显示默认灰色图标。原因QTag 在文件尾部很多精简版解密器实现时只恢复音频流把 QTag 当成尾部垃圾截掉了。封面就在这个尾巴里于是一整批全部丢图。解决如果只是封面丢失用 4.3 的方法先从原始加密文件的解密结果里抽图再贴回但工作量太大时不建议逐个补。更值得做的是换一个保留 QTag 的解密核心或者在解密前手动把尾部 QTag 提取出来存成文件。我现在的做法是批量前先抽查 3 个文件确认封面保留才放手跑全部免得转完才发现。6. 更顺手的用法把转换封装成“幂等命令”重跑和验证都省心前面的脚本每次都要手动指定目录重跑时虽然能跳过已转换文件但缺少完整日志。我会把流程收敛成一个可重复执行的入口叫qmc-convert。放进~/.zshrc之后打开终端直接输入命令加目录就能干活qmc-convert() { local src_dir${1:?用法: qmc-convert 源目录} local dst_dir${2:-$HOME/Music/converted} python3 $HOME/bin/qmc_convert.py $src_dir $dst_dir }配合的 Python 脚本沿用 3.3 的结构加三处改动。第一输出日志写到converted/convert.log每成功一个文件追加一行时间戳 | 源文件 | 输出文件 | 耗时。第二失败文件单独记到fail.log并且不中断整批任务。第三转换结束后用ffmpeg -v error -i output -f null -对每个输出文件做解码校验这条命令会完整解码整个文件遇到帧错误会报错相当于白盒验证。验证这一步不能省。我之前有次批量转完很顺利结果过了两周想播放某首歌发现转出来的 FLAC 在第十秒之后全是爆音因为源文件本身有坏帧流复制把坏帧也原样带了过去。后来我把校验固定成流程最后一步有坏文件就单独列出来再决定是重下还是保留原加密文件等新解密器。增量重跑也很重要。脚本检查输出文件存在且大小大于 0 就跳过这意味着转了一半中断后重新执行一次命令已经成功的文件不会重复劳动。配合日志我能清楚看到上次转到了第几个文件。如果你要归档的是几千首的大目录强烈建议先转 3 到 5 个样本逐个用播放器确认音质和封面正常再放开全量不然十几分钟转完才发现某一代文件全废又要从头再来。我现在处理新的 QMC 文件习惯永远是同一套顺序看扩展名抽一个样本解密ffprobe 确认真实容器调好参数小批试转全量执行最后校验一遍。听起来繁琐但每一步都在避免更大的坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表