Skip to content
dsh.fish
Bundle

dsh-audio-converter

音频转换器:解密网易云 NCM / 酷我 KWM / 酷狗 KGM / QQ音乐 QMC 等平台加密音乐,mp3/flac/m4a/aac/wav/ogg/opus 互转,并提供响度归一化、EQ、动态压缩、重采样、位深等音质增强。带拖拽界面,也能打成单文件 exe。

Source
yousj666
License
MIT
Updated
Updated 24 days ago

Readme

# dsh-audio-converter

**音频转换器:解密各平台加密音乐,常规格式互转,并提供真实的音质增强。带拖拽界面。**

拖进去,出来就是能播的文件。

```
网易云 .ncm  ─┐
酷我   .kwm  ─┤
mp3 / flac   ─┼─→  解密 → 增强 → 转码 → 打标签 → 输出
m4a / wav    ─┤
ogg / opus   ─┘
```

---

## 加密格式支持情况(诚实版)

| 平台 | 扩展名 | 状态 | 说明 |
|---|---|---|---|
| **网易云音乐** | `.ncm` | ✅ **已支持** | 与已知正确结果**逐字节一致**(32 个断言验证) |
| **酷我音乐** | `.kwm` | ✅ **已支持** | 密钥恢复 + 容器嗅探验证(15 个断言) |
| **酷狗音乐** | `.kgm` `.kgma` `.vpr` | ✅ **已支持** | v1~v4 已实现(272 字节 MEND 表 + 17/16/272 三重异或),与独立参考实现跨语言对拍 |
| **QQ音乐 v1(老格式)** | `.tkm` `.bkc*` | ✅ **已支持** | 128 字节公开静态密钥;含 0x7FFF 边界规则,已跨语言对拍 |
| **QQ音乐 v2(mflac/mgg)** | `.mflac` `.mgg` `.qmcflac` | ✅ **已支持** | 尾包内嵌 EKey 的(QTag / PcV1Legacy)直接解;已跨语言对拍 |
| QQ音乐 v2(无内嵌密钥) | STag / MusicEx 尾包 | ⚠️ 需外部 EKey | 尾包里**只有资源元数据、没有密钥**,得从客户端数据库取 |
| **QQ音乐 v3(新版)** | `.mgg1/.mgg2/.mmp4` | ❌ **离线不可能** | 见下 |
| 酷狗 KGG | 加密版本 5 | ❌ 不可能 | 密钥在客户端密钥库里 |
| 虾米音乐 | `.xm` | ❌ 不可能 | 虾米已停止服务 |

### QMC v2 的四种尾包

QQ 音乐 v2 的密钥**跟着文件走**,藏在文件末尾的「尾包」里。四种形态:

| 尾包 | 有 EKey? | 能不能离线解 |
|---|---|---|
| `QTag` | ✅ 内嵌 | **能** —— 直接解 |
| `PcV1Legacy` | ✅ 内嵌 | **能** —— 直接解 |
| `STag` | ❌ 只有资源号 + mid | 需要外部 EKey |
| `MusicEx` | ❌ 只有文件名 | 需要外部 EKey |

后两种的密钥存在**客户端本地数据库**(安卓端 `player_process_db`)里,文件本身不含。
工具和界面上都有 **EKey 输入框**,把密钥填进去就能解。

> 判断「能不能解」用**尾包解析结果**,不是看标记名。
> 一开始我按 `STag`/`QTag` 标记判定,把 STag 误判成「新版无解」——
> 其实它和 QTag 一样都是 v2,区别只在**尾包里有没有密钥**。

### 为什么有两项是「不可能」而不是「还没做」

QQ 音乐新版采用**动态密钥分发 + 设备指纹绑定**:

```
播放请求 → 服务端验证账号与设备合法性 → 下发密钥 → 本地解密
```

**密钥根本不下发到本地文件里。** 所以拿到 `.mgg1` / `.mgg2` / `.mmp4` 之后,
本地**没有任何可用于解密的材料** —— 这不是实现难度问题,是设计上就不给。

**与其假装支持然后吐一个损坏文件,不如直接说清楚。**
界面和 `audio_capabilities` 工具都会明确告诉用户这一点,并给出可行替代方案
(用客户端重新下载为普通格式,或在客户端内导出)。

---

## 音质增强:会做什么、不会做什么

### 会做(真实有效的处理)

| 处理 | 效果 | 参数 |
|---|---|---|
| **响度归一化** | 让音量一致,避免忽大忽小 | EBU R128,默认 -16 LUFS / -1.5 dBTP |
| **夜间模式** | 压缩动态,小声也听得清 | `acompressor` ratio 4:1 |
| **暖声 EQ** | 减少刺耳感 | 200Hz +2dB / 4kHz -2dB |
| **明亮 EQ** | 提升清晰度 | 3kHz +3dB / 10kHz +2dB |
| **人声突出** | 播客/人声向 | 1kHz +2dB / 300Hz -2dB |
| **低频增强** | 补耳机低频 | 80Hz +5dB |
| **高通** | 去超低频浑浊 | 30Hz |
| **重采样 / 声道** | 匹配设备 | 任意采样率、单/立体声 |
| **位深** | 匹配设备或做后续处理 | 16 / 24 / 32 位(**只对 flac/wav 这类无损容器有意义**) |

### **不会**做

> **无法恢复有损编码已经丢掉的信息。**
> 128kbps 的 mp3 转成 FLAC **不会变好听** —— 它只是把同样的信息用更大的容器装起来。
> 界面和工具描述里都明确写了这一点,不做「一键变无损」这种假承诺。

---

## 常规格式互转

`mp3` · `flac` · `m4a` · `aac` · `wav` · `ogg` · `opus`

三档质量:体积优先 / 标准 / 质量优先。

---

## 装

```bash
dsh plugin --profile <profile> add dsh-audio-converter
# 或本地开发
dsh plugin --profile <profile> add link:/path/to/dsh-audio-converter
```

**依赖 ffmpeg**(必需)。程序会**按「能不能跑起来」**自动探测,不依赖任何固定路径:

```bash
winget install Gyan.FFmpeg        # Windows
brew install ffmpeg               # macOS
apt install ffmpeg                # Debian/Ubuntu
```

> ⚠️ 探测用的是「试跑 `-version`」而不是 `existsSync` —— 因为
> **WinGet 的应用执行别名(App Execution Alias)Node 的 `existsSync` 解析不了**,
> 而且 winget 有时会留下「清单还在、文件已删」的幽灵安装(本机就遇到了)。
> 详见知识库 P-017。

---

## 用

### 界面上用(推荐)

会话视图多一个 **「音频转换」** 页签,排在 **「上下文」「定时任务」** 后面
(`conversation.view`,order 40)。

```
把文件拖进虚线框  →  选格式/质量/增强  →  点「开始转换」
```

- **拖拽** 或 **选择文件**
- 任务走 job 模型,界面不卡;实时显示状态、耗时、输出体积
- 底部有**诚实的能力矩阵**,哪些能解、哪些不可能,一目了然
- **文件全程在本机处理,不上传任何服务器**

### 让模型帮你转

```
「把 G:\CloudMusic\VipSongsDownload 里的 ncm 全转成 mp3」
「看一下这个 .mgg 文件到底是什么格式,能不能解」
「把这批 flac 做响度归一化,再转成 m4a」
```

三个工具:

| 工具 | 作用 |
|---|---|
| `audio_convert` | 转换/解密文件或**整个目录**,可指定格式、质量、增强预设 |
| `audio_inspect` | 看一个文件到底是什么(**按 magic 不按后缀**)、能不能解 |
| `audio_capabilities` | 查支持矩阵与 ffmpeg 状态,处理前先确认 |

---

## 设计说明

### 格式识别:先看 magic,再看后缀

有些人只是把 FLAC 改成了 `.ncm` 后缀。只看后缀会把普通文件当加密文件处理,
白白报错。所以**先按文件头识别**:

```
CTENFDAM            → NCM
7C D5 32 EB …       → KGM
05 28 BC 96 …       → VPR
fLaC / ID3 / OggS / ftyp / RIFF  → 普通音频,无需解密
STag / QTag / musicex            → QQ音乐新版
```

### 结果结构必须一致

`processFile` 有两条返回路径(只解密 / 解密+转码)。第一版里提前返回那条
**漏了 `srcInfo` 和 `meta`** —— 调用方得按分支处理,很容易漏字段。
现在两条路径返回完全相同的结构,并且**有测试盯着**这条约束。

### 解密正确性怎么验证的

**不是「自己加密再自己解密」** —— 那只证明两者互逆,证明不了跟真实格式一致。

- **NCM**:拿真实 `.ncm` 解密,跟一份已知正确的转换结果
  **在去掉 ID3 标签之后逐字节比对** → 完全一致
- **KWM**:自造样本往返(5 轮随机内容)+ 三条密钥恢复路径 + 认不出时必须抛错

> 顺带一个坑:直接比整个文件的哈希会**误判** —— 不同工具解密后会把封面
> **重新嵌进 ID3**,一个 1KB 的标签能变成 795KB,但**音频载荷是一样的**。
> 详见知识库 P-018。

---

## 测试

```bash
npm test                 # 一次跑完 8 个测试文件

node test-ncm.mjs        # NCM 解密器           32 个断言
node test-kwm.mjs        # KWM 解密器           15 个断言
node test-kgm-qmc.mjs    # KGM + QMC v1         41 个断言(跨语言对拍)
node test-qmc2.mjs       # QMC v2               46 个断言(跨语言对拍)
node test-ffmpeg.mjs     # ffmpeg 封装          75 个断言
node test-pipeline.mjs   # 格式识别与管线       57 个断言
node test-host.mjs       # host 半边            60 个断言
node test-client.mjs     # 浏览器半边          34 个断言
```

**合计 360 个断言。** 不需要装进 DSH、也不需要浏览器(除 host/client 用 mock ctx)。

**只需 Node ≥ 22.19,没有构建步骤。** ffmpeg 可选 —— 没装的话用到它的测试会跳过。

`test-ncm.mjs` 的第 4 节需要你自己的 `.ncm` 素材(用环境变量指定,见
[CONTRIBUTING.md](./CONTRIBUTING.md));没设就整节跳过,其余断言照常跑。

亮点:
- **真实素材 + 外部锚点**:真 `.ncm` 文件 → 与已知正确结果**在去掉 ID3 之后**逐字节比对
- **跨语言对拍**:黄金值由一份**独立写的 Python 参考实现**算出(不 import 原项目),
  JS 侧翻译错了就对不上。QMC 的 `0x7FFF` 边界、TEA 的 float32 语义、
  Map/RC4 的流密码全都靠这个办法验证
- **实测增强效果**:用 ffmpeg 的 `volumedetect` 量处理前后的响度,确认不是空转
- **回读产物校验**:转完再探测一遍输出,比对采样率/时长/编码 —— 采样率被滤镜悄悄改掉就是这么抓到的
- **回退路径也测**:没有 ffprobe 时解析 `ffmpeg -i` 的 stderr,22 个断言确保结果和 ffprobe 一致
- **安全栅栏**:非回环 Host / 跨站发起一律 403;上传时的路径穿越防护
- **诚实性断言**(源码级):界面必须写明「无法恢复有损信息」「离线做不到」

---

## 已知边界

- **QMC v2 的 STag / MusicEx 尾包不含密钥** —— 需要用界面上的 EKey 输入框手动提供
- **酷狗 KGG(加密版本 5)不支持** —— 密钥在客户端密钥库
- **QQ音乐 v3(新版)离线无解** —— 不是实现问题,是密钥不下发到本地
- **酷狗公钥表是截断版**(8 MB,覆盖 128 MB 音频)。要处理更大的文件,跑 `node scripts/expand-kugou-key.mjs` 生成完整表(69.77 MB)
- 上传文件上限 512MB(拖拽走 HTTP,超大文件建议用工具传路径)
- 批量处理是**串行**的(避免一次转几十首把机器打满);一次提交几十首会排队
- 解密后**立即写入 ffmpeg 能读的临时文件**,转换完不会自动清理临时目录
- **QMC/KGM 没有官方测试素材** —— 验证靠「自造合法样本 + 跨语言对拍」,
  不是靠真实文件。如果你手上有真实文件跑出问题,欢迎反馈
- Windows 路径分隔符按反斜杠处理;其他平台未实测

---

## 三种用法

### ① DSH 插件(本项目的主体)

会话视图里多一个「音频转换」页签(`conversation.view`,order 40),拖文件进去就行。
同时给模型注册 3 个工具,可以直接说「把某个文件夹里的 ncm 全转成 mp3」。

### ② 命令行(需要 Node + ffmpeg)

```bash
node convert.mjs <文件或文件夹...> [--format mp3] [--enhance loudness] [--out 目录]
node convert.mjs --list      # 看支持哪些格式
node convert.mjs --dry       # 只识别格式,不真转
```

Windows 上也可以双击 `convert.bat`,或者把文件拖到它上面。

### ③ 单文件 exe(不需要任何依赖)

```bash
npm run build-exe
```

打出一个约 **197 MB** 的独立 exe(内嵌 Node + ffmpeg):

- **拖文件上去** → 直接转成 mp3
- **双击** → 本地网页设置界面,能选格式/质量/位深/增强

拷到任何 Windows 上都能跑,**不需要装 Node、不需要装 ffmpeg**。

> 体积构成:node 88 MB + ffmpeg 100 MB + 酷狗公钥表 8 MB。
> **不带 ffprobe** —— 它是另一个约 100 MB 的静态二进制,
> 而 `probe()` 已经能在没有它时解析 `ffmpeg -i` 的 stderr(22 个断言盯着这条路径)。

---

## 文件

```
lib/crypto/ncm.js               NCM 解密器(AES-128-ECB + RC4 变体)
lib/crypto/kwm.js               KWM 解密器(32 字节循环 XOR + 密钥恢复)
lib/crypto/kgm.js               KGM 解密器(MEND 表 + 三重异或)
lib/crypto/qmc.js               QMC v1(128 字节静态密钥 + 0x7FFF 边界规则)
lib/crypto/qmc2.js              QMC v2(尾包解析 + TEA + Map/RC4 流密码)
lib/crypto/assets/kugou_key.bin 酷狗公钥表(8 MB,覆盖 128 MB 音频)
lib/crypto/assets/kugou_key.xz  同一张表的完整版压缩源(94 KB)
lib/ffmpeg.js                   ffmpeg 封装:探测 / 转码 / 增强 / 打标签
lib/pipeline.js                 格式识别 → 解密 → 转换 → 增强 的调度
lib/resources.js                资源解析:磁盘文件 / exe 内嵌 两种模式
lib/index.js                    host 半边:3 个工具 + 5 条 HTTP 路由 + job 队列
lib/client.js                   浏览器半边:拖拽页签(手写,无构建)
convert.mjs                     命令行入口
convert.bat                        Windows 拖拽入口
exe/main.mjs                    单文件 exe 的入口(拖拽 + 本地界面)
exe/build.mjs                   打包脚本(esbuild → SEA → postject)
scripts/expand-kugou-key.mjs    从 .xz 生成完整公钥表
test-*.mjs                      8 个测试文件,360 个断言
diag-ncm.mjs                    NCM 诊断工具(排查"解出来对不对"用)
```

## 参与开发

见 [CONTRIBUTING.md](./CONTRIBUTING.md)。两条硬规矩:

1. **解密正确性必须用外部锚点证明** —— 自己加密再自己解密只能证明互逆
2. **做不到的如实标注** —— 宁可报错也不要吐出损坏文件

## 许可

MIT —— 见 [LICENSE](./LICENSE)。

解密算法的参考资料、酷狗公钥表的来源与性质、FFmpeg 的许可说明,
都写在 [THIRD-PARTY-NOTICES.md](./THIRD-PARTY-NOTICES.md) 里。

## 免责声明

本项目仅用于**个人已购音乐**的格式转换 —— 把你花钱买的歌从专有容器里拿出来,
在你自己拥有的设备上播放。请勿用于传播受版权保护的内容。

Install

dsh plugin --profile web add github:yousj666/dsh-audio-converter

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source