简介:一套面向macOS平台的QQ音乐QMC格式转换工具源码包,适配qmcflac转flac、qmc0/qmc3转mp3以及mflac/mflac0转flac等常见格式处理场景。项目定位偏向教学与毕设案例,适合计算机、数学、电子信息等相关专业学生用于课程设计、学期末综合实践及毕业设计,也可作为Swift/macOS应用开发的入门参考。压缩包共43个文件,体积仅1.78MB,以Swift源码、Xcode工程文件(pbxproj、xcworkspace)、界面故事板(storyboard)及plist配置为核心,辅以png/gif示例图、README说明和LICENSE授权信息,目录结构清晰便于按模块阅读。已有62人学习浏览。源码内含QMC解码核心、TeaCipher加解密模块、图形界面窗口与单元测试,并带基础测试用例,读者可借此理解QMC格式的转码原理与macOS应用的组织方式,在此基础上扩展功能或优化界面。
1. 为什么QQ音乐下载的QMC文件在macOS上打不开:先搞懂这工具处理的是哪一层加密
在macOS上下载QQ音乐的会员歌曲,拿到的不是mp3也不是flac,而是一串以 qmcflac、qmc0、qmc3 结尾的加密文件。双击交给 QuickTime 只会得到一句“不支持的格式”,IINA 也放不出声音,因为文件头根本不是音频流的魔数,而是被逐字节异或过的乱码。这个转换工具要做的就是把加密壳剥掉:qmcflac 解密后是完整 FLAC 流,qmc0/qmc3 解密后是 MP3 流,再按需重新封装或转码。适合那些用 macOS 当主力系统、又不想开 Windows 虚拟机去折腾格式转换的人,目标是让命令行批量把 QMC 文件还原成可以正常导入音乐 App 的曲库。
2. 先看清QMC变种再动手:qmcflac与qmc0/qmc3的加密层差异
2.1 QMC是XOR加密不是转码:解密前后数据格式不变
很多第一次接触的人会把 QMC 当成一种新的音频编码,以为转换需要先解码成 PCM 再重新编码。这是最大的误解。QMC 不对音频做二次压缩,它只是在原始字节流(FLAC 或 MP3)上做了一层逐字节的 XOR 运算。加密后的数据依然保留着原始流的结构特征,只是每个字节的值被改写了。所以转换的本质是“解密”,不是“转码”,工作流可以简化为“读取字节 → 异或还原 → 验证魔数 → 按需封装”,而不是“QMC解码 → PCM → FLAC/MP3编码”这条笨重链路。
qmcflac、qmc0、qmc3 这三个后缀属于同一代加密算法,差别不在加密强度,而在加密前的原始容器:qmcflac 加密前是 FLAC 流,qmc0 和 qmc3 加密前是 MP3 流。老版本里 qmc0 和 qmc3 在加密实现上几乎一致,社区工具通常用同一份 128 字节静态密钥表去解,解完再靠文件头魔数决定输出扩展名。这会带来一个实际好处:你不需要为每个后缀各写一个转换器,一个能探测魔数的解密函数就能覆盖三个变种。
还要澄清一个概念:为什么说“解密后数据格式不变”?因为 FLAC 流的开头是固定的66 4C 61 43(ASCII 的“fLaC”),MP3 流的开头是49 44 33(ID3 标签)或FF FB(MPEG 帧头)。加密过程不会移动这些字节的位置,只是把它们异或成了别的值。解密算法正确时,这些魔数会原封不动地出现在文件开头;算法不对时,输出依然是乱码。这就是整个转换过程里最可靠的判断依据。
2.2 用file与hexdump识别变种:不靠后缀名猜格式
实际下载到的文件不一定叫.qmcflac。有些下载工具会顺手改扩展名,有些旧资源站把 qmc0 改成.qmc,靠后缀判断格式很容易误导自己。在 macOS 终端里,第一件事是看文件类型:
file /path/to/your-song.qmcflac输出大概率是data或者Apple Binary Property List,这是因为加密后的文件头不被系统识别。再用 hexdump 看前 16 字节,确认加密壳的存在:
hexdump -C -n 16 /path/to/your-song.qmc0正常会看到类似4a 30 12 f0 87 5d ...这样的乱序数据,越“随机”越说明字节已经被密钥流打乱。解密成功后,同一个位置应该出现上面说的魔数。这里可以做一个小对照表,方便在终端里快速比对:
| 加密前容器 | 解密后文件头 | hex 值 |
|---|---|---|
| FLAC | fLaC | 66 4C 61 43 |
| MP3(带ID3) | ID3 | 49 44 33 |
| MP3(无ID3) | MPEG音频帧头 | FF FB/FF F3/FF F2 |
我的习惯是正式批量转换前,先在目录里挑一个 qmcflac 和一个 qmc0 各做一次解密冒烟测试,用xxd对比解密前后的前 32 字节。确认魔数正确后再开始整库跑。这一步能省下后面大量“解出来但不认”的排错时间,比任何玄学手段都靠谱。
3. macOS上搭建转换环境:Homebrew装ffmpeg与Python依赖
3.1 安装基础工具链
macOS 自带 python3(一般是 3.9 或 3.11),但 ffmpeg 需要自己装。转换链路里 ffmpeg 承担两个关键角色:一是把解密后的文件重新封装成干净的 flac/mp3,二是提供 ffprobe 做完整性校验。用 Homebrew 一次性装齐:
brew install ffmpeg python@3.11 pip3 install --user mutagenmutagen是音频元数据处理库,后面回填专辑封面、检查标签都要用。至于pycryptodome,不是 QMC 老版本解密必需的,标准库就能完成 XOR 运算;如果你打算在解密后顺便处理某些加密封面块,可以一起装上:
pip3 install --user pycryptodome装完做两步验证,确认ffmpeg和 Python 包都指向预期版本:
ffmpeg -version | head -n 1 python3 -c "import mutagen; print(mutagen.version_string)"如果 Python 是 brew 装的,注意pip3和python3可能指向不同解释器。最常见的翻车是pip3 install --user mutagen装到了系统 Python 的 site-packages,而python3指向 brew Python,导致import mutagen报 ModuleNotFoundError。遇到这种情况,用python3 -m pip install --user mutagen强制绑定当前解释器即可。
3.2 准备Python解密脚本骨架
解密的核心就一个函数:读取文件全部字节,和 128 字节密钥流逐个异或。QMC 老版本用的是固定密钥循环,第 i 个字节异或的密钥下标是i % 128。先写一个最小可用骨架:
#!/usr/bin/env python3 # qmc_xor.py — QMC最小解密骨架 from pathlib import Path def qmc_decrypt(src: Path, key128: bytes) -> bytes: raw = src.read_bytes() out = bytearray(len(raw)) klen = len(key128) for i, b in enumerate(raw): out[i] = b ^ key128[i % klen] return bytes(out)这段代码的重点在out[i] = b ^ key128[i % klen]。i % klen使密钥流每 128 字节循环一次,这是旧版 QMC 静态密钥的核心特征。bytearray(len(raw))预先分配好输出缓冲区,避免bytes在循环里反复拼接造成的性能浪费;一首 20MB 的 FLAC 文件,这个函数跑完大概几十毫秒,不必优化。
这里必须说清楚:完整的 128 字节密钥表是 QMC 解密工具的命根子,它来自对 QQ 音乐旧版客户端的逆向提取,不会出现在我这段示范代码里。现成的 QMC 开源解密项目都会内置这份密钥表,你把它导入到key128变量就行。不要想着自己从零暴力破解密钥——128 字节的密钥空间根本不是算力能扛的,老老实实复用社区逆向成果才是正确路径。
3.3 用ffprobe验证解密效果
解密成功与否,不必用播放器听,ffprobe 一条命令就能判断。把解密后的字节临时写入一个文件:
ffprobe -v error -show_format -show_streams /tmp/decoded.bin能输出format_name=flac或format_name=mp3,说明加密壳已经剥干净。如果报Invalid data found,说明输出还是乱码,先检查密钥表是否完整,不要急着怀疑算法。这个验证步骤的价值在于把“感觉能放”变成“结构可验证”,后续所有批量流程都建立在 ffprobe 能识别的前提上。
还要提醒一句:解密后别急着删除源文件。把源.qmc*和 decoded 输出放在两个独立目录,全部转完并抽检后再清理,这是你操作失误时唯一的后悔药。
4. 从qmcflac到flac、qmc0/qmc3到mp3的完整转换流程
4.1 完整解密脚本:按魔数自动选择输出格式
有了骨架,再补一个魔数探测函数,就能做成一个完整可用的单文件转换器:
#!/usr/bin/env python3 # qmc_convert.py — 解密并按魔数自动输出flac或mp3 import sys from pathlib import Path from qmc_xor import qmc_decrypt def sniff(dec: bytes) -> str: if dec.startswith(b'fLaC'): return 'flac' if dec.startswith(b'ID3') or dec[:2] in (b'\xff\xfb', b'\xff\xf3', b'\xff\xf2'): return 'mp3' return 'unknown' def convert(src: Path, out_dir: Path) -> None: dec = qmc_decrypt(src, KEY128) # KEY128 从逆向项目导入 ext = sniff(dec) if ext == 'unknown': print(f'[失败] 未识别魔数: {src.name}') return out_dir.mkdir(exist_ok=True) out = out_dir / (src.stem + '.' + ext) out.write_bytes(dec) print(f'[成功] {src.name} -> {out.name}') if __name__ == '__main__': src = Path(sys.argv[1]) out_dir = Path(sys.argv[2]) if len(sys.argv) > 2 else Path('./output') convert(src, out_dir)逻辑说明:sniff()只检查前几个字节,FLAC 魔数fLaC必须在文件首字节;MP3 则同时接受 ID3 标签和 MPEG 帧头两种开头。out_dir / (src.stem + '.' + ext)用Path拼接输出路径,从源头避开字符串拼接带来的转义隐患。
参数说明:sys.argv[1]是输入文件,sys.argv[2]是输出目录,缺省时输出到当前目录下的output/。这个脚本的一个设计决策是不动源文件,原样保留在输入目录,转换结果单独落盘。跑法很简单:
python3 qmc_convert.py ./input/your-song.qmcflac4.2 重封装flac:为什么ffmpeg比直接改后缀更稳
解密后直接把.qmcflac改成.flac能不能用?分情况。大多数情况下播放器能认,但 macOS 自带的音乐 App 对违反 FLAC 规范的文件容忍度很低,容易出现时长显示错误、无法快进到曲尾、甚至导入失败。原因是解密后的 FLAC 字节流可能在尾部带了 QQ 音乐私有的 padding 块,或者 metadata block 里的采样数信息和实际帧数对不上。
所以 qmcflac 解密后我一般会过一道 ffmpeg 重封装:
ffmpeg -i song.flac -c:a flac -compression_level 5 -map_metadata 0 song.clean.flac-c:a flac是让 ffmpeg 把音频流重新编码为 FLAC。FLAC 是无损编码,这一步不会引入音质损失,但它会重建整个流结构,干掉尾部多余 padding,对齐 metadata 和帧边界。-compression_level 5是 FLAC 压缩等级,范围 0 到 8,数字越大体积越小但编码越慢。批量转几百首时,5 和 8 的体积差距通常在 1% 以内,但编码时间差距接近一倍,我一般推荐 5。-map_metadata 0是把原文件里的音轨标题、歌手、专辑信息带到新文件,避免重封装后标签丢失。
如果源文件是 24bit/96kHz 的高解析度 FLAC,这条命令会原样保留采样率。想统一采样位深可以加-sample_fmt s16,但无损转成 16bit 会牺牲动态范围,除非你的播放设备不支持 24bit,否则不建议加。
4.3 转码mp3:比特率、采样率与元数据保留
qmc0/qmc3 解密出来本身是 MP3 流,但并不意味着直接改名完事。先看原始码率再决定保留还是重压:
ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1 song.mp3如果输出bit_rate=128000,说明原文件是 128kbps,这个码率在 MP3 里属于偏低水平。自用建议统一转成 VBR Q2 质量等级:
ffmpeg -i song.mp3 -c:a libmp3lame -q:a 2 -map_metadata 0 song.final.mp3-q:a 2是 LAME 编码器的 VBR 质量参数,范围 0 到 9,0 质量最好文件最大,2 是大多数场景下体积和听感最平衡的档位,实际输出码率在 170-210kbps 之间波动。-map_metadata 0复制源文件元数据,转码后依然是带标签的文件。如果源文件有异常标签导致音乐 App 扫描出错,可以把这句换成-map_metadata -1整体丢弃,再主动补一套干净标签。
有一种情况不需要重新编码:原始 MP3 已经是高码率(比如 320kbps),并且 ffprobe 检查帧结构正常。这时用流拷贝更快:
ffmpeg -i song.mp3 -c:a copy -map_metadata -1 song.clean.mp3-c:a copy不重新编码,直接把音频帧复制到新文件,一台 2015 年的 MacBook Air 处理几十首歌也只要几秒。这里只清掉元数据,因为 QQ 音乐老文件里 ID3 标签写坏的概率不低。
5. 转换中的常见坑:文件名、封面与时长异常的排查
5.1 文件名里的特殊字符让脚本翻车
现象:qmc_convert.py跑在带空格或方括号的文件名上时,提示文件不存在,或者转出乱码文件。
原因:在 Shell 里直接把路径字符串传参时,空格把路径拆成了两个参数;文件名里的[和]在部分 Shell 里会被当成通配符展开,导致实际取到的路径对不上。
解决:脚本里坚持用Path对象处理路径,循环整目录时用Path.rglob()而不是 Shell 的find:
from pathlib import Path for src in Path('./input').rglob('*.qmc*'): convert(src, Path('./output'))rglob('*.qmc*')会递归匹配所有以.qmc开头的后缀文件,包括.qmcflac、.qmc0、.qmc3以及裸.qmc,这是覆盖各种命名风格最稳的方式。
5.2 解密成功但播放器不认:魔数偏移与ID3污染
现象:ffprobe 能识别输出是 MP3,但导入音乐 App 失败,或播放时拖动进度条出现卡顿。
原因:文件开头的 ID3 标签尺寸字段写错了,导致播放器解析音频帧起点时算错位置。QQ 音乐老版本写 ID3 的代码对 UTF-16 编码的字符串长度计算存在偏差,解密后原样保留了这个错误。
解决:解密后用 ffmpeg 剥掉 ID3 再重新封装:
ffmpeg -i song.mp3 -map_metadata -1 -c:a copy song.clean.mp3-map_metadata -1丢弃所有元数据;-c:a copy拷贝音频帧不重新编码,速度极快。丢掉的歌手歌名后面用 mutagen 补一遍,比修一个坏掉的标签省事得多。
5.3 封面图丢了的后悔药:用mutagen回填
现象:转换后的文件能正常播放,但音乐 App 里封面全灰。
原因:QQ 音乐把封面图单独放在加密容器外部,解密脚本只还原了音频流,封面 APIC 帧并没有跟着回来。
解决:如果加密前能拿到封面图文件,用 mutagen 回填是最直接的方式:
from mutagen.mp3 import MP3 from mutagen.id3 import APIC, ID3 mp3 = MP3('song.clean.mp3') if mp3.tags is None: mp3.add_tags() mp3.tags.add(APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=open('cover.jpg', 'rb').read())) mp3.save()encoding=3表示标签里的字符串用 UTF-8 编码;type=3是 ID3 规范里 Front Cover 的类型值,音乐 App 识别封面靠的就是这个字段,写成别的数字很可能不显示。FLAC 也有类似的PICTURE块,mutagen 的mutagen.flac.Picture可以直接构造,思路相同。
5.4 批量转换时源文件被覆盖
现象:跑完整库批量脚本,发现输入目录里的.qmcflac全不见了,只剩转好的文件,想重新处理时找不到源。
原因:输出路径和输入路径落在同一目录,且脚本用了类似src.with_suffix('.flac')的逻辑,直接把旧后缀换掉,等于用输出文件覆盖了原始文件。
解决:在脚本里强制输入输出目录分离,并加一个路径断言:
if src.resolve().parent == out_dir.resolve(): raise RuntimeError('输入输出目录必须不同')这行检查放在循环之前,一旦发现输入文件所在目录和输出目录相同,立刻中断,不给手滑留机会。
6. 用ffprobe做输出验证与批量处理的进阶玩法
6.1 验证输出完整性:一条命令批量体检
转换结束不等于转换成功。我的习惯是最后用一段 bash 对全部输出做一次性体检:
for f in ./output/*.flac ./output/*.mp3; do ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1 "$f" || echo "坏文件: $f" done这段循环逐个读取文件时长,能正确输出秒数说明结构完整,读不出来会打印“坏文件”。几百首歌曲跑下来,这个体检比挨个试听可靠得多,把“感觉应该没问题”变成可重复的机器校验。碰到报错的文件,就单独回到第 4 章的解密 + ffmpeg 流程重新处理一遍。
6.2 批量转换模板:从单文件到整目录
把第 4 章的convert()函数包装成接收两个目录参数的入口,就能处理整库:
python3 qmc_convert.py ./input ./output第一次跑建议先在test/目录放两个代表性文件,一首qmcflac、一首qmc0,确认输出都能被 ffprobe 识别后再对整库动手。我自己维护曲库的习惯是保留一份.flac的无损库,再拿这份库统一转成 320kbps 的 mp3 给移动设备。QMC 解密只是整个流程的第一步,后面每多一道 ffmpeg 工序,就多一次脚本化保障。这个流程走了快两年,最深的教训就是:永远让工具链可重跑,源文件单独留底,比相信任何一次“手动微调”都踏实。希望帮到你。
本文还有配套的精品资源,点击获取