1153 lines
43 KiB
Markdown
1153 lines
43 KiB
Markdown
# `split_cue.py` 使用手册
|
||
|
||
批量用 CUE 文件把"整轨大文件 + CUE"式专辑切成分轨的 Python 脚本,支持断点续跑、CJK 编码 CUE、多种源格式,以 shntool + cuetools 作为切割/标签后端。
|
||
|
||
设计上是 [`transcode_music.py`](./transcode_music.md) 的**前置伴生工具**:先跑 `split_cue.py` 把整轨拆开,再跑 `transcode_music.py` 转 MP3,两者组合无缝衔接。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [1. 简介](#1-简介)
|
||
- [2. 系统要求](#2-系统要求)
|
||
- [3. 快速上手](#3-快速上手)
|
||
- [4. 命令行参数详解](#4-命令行参数详解)
|
||
- [5. 常用使用场景](#5-常用使用场景)
|
||
- [6. Manifest 文件说明](#6-manifest-文件说明)
|
||
- [7. 支持的源格式与依赖矩阵](#7-支持的源格式与依赖矩阵)
|
||
- [8. CUE 检测与匹配逻辑](#8-cue-检测与匹配逻辑)
|
||
- [9. 分轨命名与元数据](#9-分轨命名与元数据)
|
||
- [10. 与 transcode_music.py 的组合工作流](#10-与-transcode_musicpy-的组合工作流)
|
||
- [11. 错误处理与恢复](#11-错误处理与恢复)
|
||
- [12. 已知局限](#12-已知局限)
|
||
- [13. FAQ](#13-faq)
|
||
|
||
---
|
||
|
||
## 1. 简介
|
||
|
||
**这个工具解决什么问题?**
|
||
|
||
你有很多"整轨型"专辑:一张 CD 打包成一个大 FLAC / WAV / APE 文件,配一个 CUE 索引,例如:
|
||
|
||
```
|
||
王菲 - 唱游 [FLAC+CUE]/
|
||
├── CDImage.flac (整张 CD 的一个 40 分钟大文件)
|
||
└── CDImage.cue (12 个 TRACK 条目, 标注每首歌的起止)
|
||
```
|
||
|
||
这类文件不方便用普通播放器逐首播放(除非播放器专门支持 CUE),也不方便直接转成 MP3(`transcode_music.py` 会检测出这种情况并报警跳过)。
|
||
|
||
**`split_cue.py` 就是用来把这类专辑批量拆成分轨的**:扫描整个源目录树,找出所有"整轨 CUE"型专辑,用 `shnsplit` 按 CD 帧边界(1/75 秒精度)切成 `01 - 红豆.flac`、`02 - 催眠.flac` 这样的分轨文件,用 `cuetag` 写入 CUE 里的标题/艺人/专辑等元数据,全部**原地切割**、**保留原文件不动**。
|
||
|
||
**核心特性**:
|
||
|
||
| 特性 | 说明 |
|
||
|---|---|
|
||
| 原地切割 | 分轨文件写在原专辑目录里,与整轨大文件并存 |
|
||
| 保留原文件 | 整轨的 `CDImage.flac` 从不被删改,可事后自己决定去留 |
|
||
| 保持源格式 | FLAC → FLAC 无损、WAV → WAV、APE → FLAC(ffmpeg 无 APE 编码器) |
|
||
| Sample-exact 切割 | shntool 按 CD 帧对齐,每首歌起止精确到 1/75 秒 |
|
||
| 完整元数据 | cuetag 自动写入 TITLE / ALBUM / ARTIST / track / TRACKTOTAL / DATE / GENRE |
|
||
| CJK 支持 | CUE 编码自动识别(utf-8/utf-8-sig/gbk/big5/shift_jis/cp936),中日韩音源都能处理 |
|
||
| 断点续跑 | `.split_cue_manifest.json` 记录状态,任意时刻中断都能从上次位置继续 |
|
||
| 幂等 | 已完成的专辑再次运行秒跳过;新增专辑增量处理 |
|
||
| 自动排除已分轨 | 若目录里已有分轨文件(situation C),自动 SKIP 不误切 |
|
||
| 依赖自适应 | 启动时 probe 依赖,缺 `mac` 时把 APE 源标 UNSUPPORTED 而非崩溃 |
|
||
|
||
---
|
||
|
||
## 2. 系统要求
|
||
|
||
| 组件 | 版本 | 必需/可选 | 说明 |
|
||
|---|---|---|---|
|
||
| Python | 3.10+ | 必需 | 只用标准库,无需 pip install |
|
||
| shntool | 3.0+ | **必需** | 提供 `shnsplit` 切割命令 |
|
||
| cuetools | 1.4+ | **必需** | 提供 `cuetag` 元数据写入命令 |
|
||
| flac | 1.3+ | 处理 FLAC 时必需 | shntool 编码 FLAC 输出时调用 |
|
||
| mac (Monkey's Audio) | 4.x+ | 处理 APE 时必需 | shntool 解码 APE 输入时调用 |
|
||
| ffprobe | 6.0+ | 可选 | 仅用于源文件元数据展示(时长、采样率),不影响切割 |
|
||
|
||
**检查环境**:
|
||
|
||
```bash
|
||
python3 --version
|
||
shnsplit -v | head -1
|
||
cuetag --help 2>&1 | head -1
|
||
flac --version | head -1
|
||
mac 2>&1 | head -1 # 无输出/命令不存在 = APE 源将标 UNSUPPORTED
|
||
ffprobe -version | head -1
|
||
```
|
||
|
||
**安装依赖(Ubuntu / Debian / WSL)**:
|
||
|
||
```bash
|
||
sudo apt install shntool cuetools flac
|
||
# APE 支持(可选):Ubuntu 官方仓库通常没有,需要第三方 PPA 或源码编译
|
||
sudo apt install monkeys-audio # 如果仓库有的话
|
||
```
|
||
|
||
**macOS**:
|
||
|
||
```bash
|
||
brew install shntool cuetools flac
|
||
# APE:需从 https://monkeysaudio.com 下载或用其他方式
|
||
```
|
||
|
||
**Windows**:直接跑不推荐(有 `os.fsync(dir_fd)` 的 POSIX 依赖),建议 WSL2。
|
||
|
||
**依赖缺失时的行为**:
|
||
|
||
启动时脚本会 probe 所有依赖并打印状态。缺 `shnsplit` 或 `cuetag` 直接退出(exit code 3)。缺 `mac` 或 `flac` 只影响对应格式,其他格式仍能处理。
|
||
|
||
---
|
||
|
||
## 3. 快速上手
|
||
|
||
**最常用的三个命令**:
|
||
|
||
```bash
|
||
# 1. 先分析看看哪些专辑需要切割 (不实际切)
|
||
python3 split_cue.py /mnt/z/我的音乐 --analyze-only
|
||
|
||
# 2. 确认没问题后正式切割 (中断可再跑, 自动 resume)
|
||
python3 split_cue.py /mnt/z/我的音乐
|
||
|
||
# 3. 切完了继续转 MP3 (transcode_music.py 会自动跳过整轨大文件)
|
||
python3 transcode_music.py /mnt/z/我的音乐 /mnt/x/mp3输出
|
||
```
|
||
|
||
就这么简单。整轨型专辑切成分轨后,`transcode_music.py` 会自动识别为"situation C"(CUE 引用整轨但存在分轨),跳过整轨文件只转分轨——两个脚本天然衔接。
|
||
|
||
---
|
||
|
||
## 4. 命令行参数详解
|
||
|
||
### 完整语法
|
||
|
||
```
|
||
split_cue.py [SOURCE] [选项...]
|
||
```
|
||
|
||
或
|
||
|
||
```
|
||
split_cue.py --source SOURCE [选项...]
|
||
```
|
||
|
||
### 参数列表
|
||
|
||
| 参数 | 短选项 | 类型 | 是否必需 | 默认 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `SOURCE` | — | 位置参数 | 必需¹ | — | 源目录,含整轨型专辑的位置 |
|
||
| `--source` | `-s` | 字符串 | 必需¹ | — | 源目录(与位置参数 SOURCE 等价) |
|
||
| `--analyze-only` | — | 开关 | 可选 | 关 | 只分析生成 manifest,不实际切割 |
|
||
| `--force` | — | 开关 | 可选 | 关 | 忽略现有 manifest,全部重新分析并**覆盖**已有分轨 |
|
||
| `--verbose` | `-v` | 开关 | 可选 | 关 | 显示 DEBUG 级别日志(含跳过项) |
|
||
| `--shnsplit` | — | 字符串 | 可选 | `shnsplit` | shnsplit 可执行路径 |
|
||
| `--cuetag` | — | 字符串 | 可选 | `cuetag` | cuetag 可执行路径 |
|
||
| `--ffprobe` | — | 字符串 | 可选 | `ffprobe` | ffprobe 可执行路径 |
|
||
| `--split-timeout` | — | 整数 | 可选 | `1800` | 单个专辑 shnsplit 超时秒数 |
|
||
| `--help` | `-h` | 开关 | — | — | 显示帮助并退出 |
|
||
|
||
¹ 源目录必须提供,可用位置参数或 flag 两种形式:
|
||
|
||
```bash
|
||
# 位置参数 (更简洁)
|
||
split_cue.py /mnt/z/music
|
||
|
||
# flag 长形式
|
||
split_cue.py --source /mnt/z/music
|
||
|
||
# flag 短形式
|
||
split_cue.py -s /mnt/z/music
|
||
```
|
||
|
||
注意:**`split_cue.py` 不需要目标目录**,因为它是原地切割——分轨文件直接写到原专辑目录里。这与 `transcode_music.py` 需要 `-s / -t` 两个参数不同。
|
||
|
||
### 参数详细说明
|
||
|
||
#### `SOURCE` / `--source` / `-s`
|
||
|
||
**源目录**。脚本会递归扫描此目录及所有子目录,找出每个"整轨 CUE"型专辑处理。
|
||
|
||
- 支持含空格、中文、特殊字符的路径
|
||
- 网盘挂载(`/mnt/z/`、`/mnt/x/` 等)都可以
|
||
- Manifest 文件 `.split_cue_manifest.json` 写入此目录
|
||
|
||
**特殊约定**:如果 SOURCE 本身就是一个专辑目录(而不是一堆专辑的父目录),也能正常工作。脚本会把源目录自身也作为分析候选。
|
||
|
||
#### `--analyze-only`
|
||
|
||
**只分析,不切割**。用途:
|
||
|
||
- 首次跑一个新的大目录时,先看看总共有几个专辑需要切、切成几轨
|
||
- 检查 CUE 解析是否有问题(编码、缺 FILE 引用等)
|
||
- 生成 manifest 后可以手动查看/编辑
|
||
|
||
**强烈推荐**每次对新目录都先跑一次 `--analyze-only`。切割是**原地写文件**的操作,先看清楚计划再动手更安全。
|
||
|
||
#### `--force`
|
||
|
||
**强制重跑**。忽略现有 manifest,重新分析并**覆盖**已存在的分轨文件。
|
||
|
||
**什么时候用**:
|
||
- 之前切错了想重来
|
||
- CUE 修改过了,想重新按新 CUE 切
|
||
- 想清理并重新开始
|
||
|
||
**警告**:`--force` 会覆盖已存在的同名分轨文件。如果之前有你手动编辑过的分轨,会丢失。**不确定时先备份**。
|
||
|
||
**边界情况**:如果专辑的 status 已经因为"situation C"被判为 `skipped`(因为目录里已有分轨),`--force` 也**不会**自动 un-skip。因为脚本无法可靠区分"我上次生成的分轨"和"用户已有的分轨"——安全起见不动。想强制重切,请先手动删掉旧的分轨文件再跑。
|
||
|
||
#### `--verbose` / `-v`
|
||
|
||
打开 DEBUG 级别日志。默认 INFO 级别只显示每首切割和错误。加上 `-v` 会额外显示:
|
||
|
||
- `[已完成 skip] ...` 每个跳过的专辑
|
||
- `[SKIP (无需切割): ...]` 每个 skipped 目录
|
||
- `[已存在, 保留] ...` 每个已有分轨文件
|
||
|
||
大量输出,通常只在排查问题时用。
|
||
|
||
#### `--shnsplit` / `--cuetag` / `--ffprobe`
|
||
|
||
指定可执行文件路径。默认从 `PATH` 查找。
|
||
|
||
**什么时候用**:
|
||
- 装了多个版本想指定用哪个
|
||
- 二进制不在标准 PATH 里
|
||
- 用自己编译的版本
|
||
|
||
示例:
|
||
|
||
```bash
|
||
split_cue.py /src --shnsplit /opt/shntool-3.0.10/bin/shnsplit
|
||
```
|
||
|
||
#### `--split-timeout`
|
||
|
||
单个专辑 shnsplit 命令的超时时间(秒),默认 `1800`(30 分钟)。超时算失败,写入 manifest 后继续下一个。
|
||
|
||
**通常不需要调**。除非你有超长专辑(比如一个 3 小时的整轨 audiobook)在慢网盘上,可以调大到 3600 或更多。
|
||
|
||
注意:shnsplit 是**单次调用切完整个专辑**的,不像 ffmpeg 是逐轨调用,所以超时值应该覆盖整个专辑的切割时间。
|
||
|
||
---
|
||
|
||
## 5. 常用使用场景
|
||
|
||
### 场景 1:第一次处理新目录(推荐流程)
|
||
|
||
假设你刚下载了一堆 `[FLAC+CUE]` 到 `/mnt/z/新收藏/`。
|
||
|
||
**第一步:先分析**
|
||
|
||
```bash
|
||
python3 split_cue.py /mnt/z/新收藏 --analyze-only
|
||
```
|
||
|
||
输出会告诉你:
|
||
|
||
- 总共扫了几个目录
|
||
- 有几个专辑属于"整轨型"需要切割(`analyzed`)
|
||
- 有几个源格式不支持(`unsupported`)
|
||
- 有几个已经是分轨的(`skipped`)
|
||
|
||
**第二步:查看切割计划**
|
||
|
||
```bash
|
||
python3 -c "
|
||
import json
|
||
d = json.load(open('/mnt/z/新收藏/.split_cue_manifest.json'))
|
||
for rel, a in d['albums'].items():
|
||
if a['status'] == 'analyzed':
|
||
print(f'{rel}: {a[\"source_audio\"]} → {len(a[\"tracks\"])} 轨')
|
||
for t in a['tracks'][:3]:
|
||
print(f' · {t[\"target_name\"]}')
|
||
if len(a['tracks']) > 3:
|
||
print(f' ... 及另外 {len(a[\"tracks\"]) - 3} 轨')
|
||
"
|
||
```
|
||
|
||
**第三步:确认无误后正式切割**
|
||
|
||
```bash
|
||
python3 split_cue.py /mnt/z/新收藏
|
||
```
|
||
|
||
已经 `analyzed` 的专辑会开始切割,`skipped` / `unsupported` 的自动跳过。
|
||
|
||
**第四步:接着跑 transcode_music.py 转 MP3**
|
||
|
||
```bash
|
||
python3 transcode_music.py /mnt/z/新收藏 /mnt/x/music/新收藏
|
||
```
|
||
|
||
`transcode_music.py` 会自动识别切出来的分轨(situation C),跳过整轨大文件只转分轨。
|
||
|
||
### 场景 2:中断后恢复
|
||
|
||
切到一半按 Ctrl+C 中断(或断电、脚本崩溃)。只需要**再跑同样的命令**:
|
||
|
||
```bash
|
||
python3 split_cue.py /mnt/z/新收藏
|
||
```
|
||
|
||
脚本会读取 manifest,跳过所有 `completed` 状态的专辑,从上次中断的地方继续。不需要任何特殊参数。
|
||
|
||
### 场景 3:增量同步(新增了整轨专辑)
|
||
|
||
以后又下载了几个 `[FLAC+CUE]` 到 `/mnt/z/新收藏/`:
|
||
|
||
```bash
|
||
python3 split_cue.py /mnt/z/新收藏
|
||
```
|
||
|
||
已完成的秒跳过,只有新目录会被分析和切割。最舒服的用法。
|
||
|
||
### 场景 4:修复失败的专辑
|
||
|
||
如果某个专辑因为 CUE 解析错误、shnsplit 崩溃等失败了(`status: failed`),修好后:
|
||
|
||
```bash
|
||
# 直接再跑, failed 的会被自动重试
|
||
python3 split_cue.py /mnt/z/新收藏
|
||
```
|
||
|
||
已成功的不动,只重试失败的。
|
||
|
||
### 场景 5:只切一个专辑
|
||
|
||
比如只想切某一张:
|
||
|
||
```bash
|
||
python3 split_cue.py "/mnt/z/新收藏/王菲 - 唱游 [FLAC+CUE]"
|
||
```
|
||
|
||
Manifest 会写到那个专辑目录里,其他专辑不受影响。
|
||
|
||
### 场景 6:想重新切某个已完成的专辑
|
||
|
||
比如切完发现 CUE 元数据有错,改了 CUE 想重切:
|
||
|
||
**方法一:删掉分轨文件 + 编辑 manifest**
|
||
|
||
删除该专辑目录里的分轨文件(`01 - Title.flac` 等),然后编辑 `.split_cue_manifest.json`,把该专辑的 status 改成 `analyzed`、每个 track 的 status 改成 `pending`。再跑一次即可。
|
||
|
||
**方法二:全局 --force**
|
||
|
||
```bash
|
||
python3 split_cue.py /mnt/z/新收藏 --force
|
||
```
|
||
|
||
会重新分析所有专辑并覆盖分轨。但注意:**如果目录里已有旧分轨且脚本无法区分**,可能会被判为 situation C 而 skip。最保险的做法还是先手动删旧分轨。
|
||
|
||
### 场景 7:排查某个专辑的问题
|
||
|
||
某个专辑 status=failed,用 verbose 模式跑:
|
||
|
||
```bash
|
||
python3 split_cue.py "/mnt/z/新收藏/有问题的专辑" -v
|
||
```
|
||
|
||
输出会显示 shnsplit 的完整错误、cuetag 警告等。
|
||
|
||
### 场景 8:切完不满意,回滚
|
||
|
||
因为原文件从不被删改,回滚很简单:
|
||
|
||
```bash
|
||
# 删掉所有分轨文件, 只留原整轨 + CUE
|
||
cd "/mnt/z/新收藏/王菲 - 唱游"
|
||
rm -f *.flac # 删所有 flac
|
||
# 找回原整轨 (脚本没删过它, 只是你上面 rm 一起删了; 如果只想删分轨:)
|
||
# 或更精确: 只删符合 "NN - Title.flac" 命名的
|
||
```
|
||
|
||
更精确的清理:
|
||
|
||
```bash
|
||
# 只删 "01 - ...", "02 - ..." 这种格式的文件
|
||
find "/mnt/z/新收藏/王菲 - 唱游" -maxdepth 1 -regex '.*/[0-9]\{2\} - .*\.\(flac\|wav\)$' -delete
|
||
|
||
# 编辑 manifest 把该专辑 status 改回 analyzed
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Manifest 文件说明
|
||
|
||
### 位置
|
||
|
||
Manifest 保存在**源目录**下,文件名 `.split_cue_manifest.json`(隐藏文件)。
|
||
|
||
### 顶层结构
|
||
|
||
```json
|
||
{
|
||
"manifest_version": 1,
|
||
"created_at": "2026-09-06T13:41:03+08:00",
|
||
"updated_at": "2026-09-06T13:41:04+08:00",
|
||
"source_root": "/mnt/z/新收藏",
|
||
"stats": {
|
||
"total_albums": 4,
|
||
"by_album_status": {"completed": 1, "skipped": 3},
|
||
"by_track_status": {"completed": 3},
|
||
"total_tracks": 3
|
||
},
|
||
"albums": {
|
||
"专辑相对路径1": { ... },
|
||
"专辑相对路径2": { ... }
|
||
}
|
||
}
|
||
```
|
||
|
||
### 单个专辑 (album) 结构
|
||
|
||
```json
|
||
{
|
||
"status": "completed",
|
||
"source_audio": "CDImage.flac",
|
||
"cue": "CDImage.cue",
|
||
"album_title": "唱游",
|
||
"album_performer": "王菲",
|
||
"album_date": "1998",
|
||
"album_genre": "Chinese Pop",
|
||
"source_codec": "flac",
|
||
"source_sample_rate": 44100,
|
||
"source_channels": 2,
|
||
"duration_sec": 40.0,
|
||
"output_format": "flac",
|
||
"tracks": [
|
||
{
|
||
"num": 1,
|
||
"shnsplit_index": 1,
|
||
"title": "红豆",
|
||
"performer": "王菲",
|
||
"target_name": "01 - 红豆.flac",
|
||
"total_tracks": 3,
|
||
"status": "completed",
|
||
"error": null,
|
||
"converted_at": "2026-09-06T13:41:04+08:00"
|
||
},
|
||
{ "num": 2, "shnsplit_index": 2, ... },
|
||
{ "num": 3, "shnsplit_index": 3, ... }
|
||
],
|
||
"issues": [],
|
||
"started_at": "2026-09-06T13:41:03+08:00",
|
||
"completed_at": "2026-09-06T13:41:04+08:00"
|
||
}
|
||
```
|
||
|
||
**字段含义**:
|
||
|
||
| 字段 | 含义 |
|
||
|---|---|
|
||
| `status` | 专辑级状态,见下表 |
|
||
| `source_audio` | 检测到的整轨大文件名(相对路径) |
|
||
| `cue` | 匹配的 CUE 文件名 |
|
||
| `album_title` / `album_performer` | 从 CUE 的顶级 TITLE / PERFORMER 解析 |
|
||
| `album_date` / `album_genre` | 从 CUE 的 REM DATE / REM GENRE 解析 |
|
||
| `source_codec` / `source_sample_rate` / `source_channels` / `duration_sec` | 由 ffprobe 探测(仅用于展示) |
|
||
| `output_format` | shnsplit `-o` 参数(`flac` 或 `wav`) |
|
||
| `tracks[]` | 切割计划,每首歌一项 |
|
||
| `tracks[].num` | CUE 里声明的 TRACK 编号 |
|
||
| `tracks[].shnsplit_index` | shnsplit 内部计数器(1-based,用于对应 tmp 文件名) |
|
||
| `tracks[].title` / `tracks[].performer` | 从 CUE 的 TRACK 级 TITLE / PERFORMER 解析 |
|
||
| `tracks[].target_name` | 最终文件名(已 sanitize) |
|
||
| `tracks[].status` | 分轨级状态 |
|
||
| `issues[]` | 人类可读的警告/说明 |
|
||
|
||
### 状态值 (status)
|
||
|
||
**专辑级状态**:
|
||
|
||
| status | 说明 | 下次运行时行为 |
|
||
|---|---|---|
|
||
| `pending` | 初始状态(一般用不到) | 重新分析 |
|
||
| `analyzed` | 已识别为整轨 CUE 型,切割计划就绪 | 执行切割 |
|
||
| `processing` | 正在切割(若看到,说明上次中断了) | 重新切割整个专辑 |
|
||
| `completed` | 所有分轨都成功输出 | **秒跳过** |
|
||
| `failed` | 切割失败(shnsplit / cuetag / 重命名任一失败) | 重试整个专辑 |
|
||
| `skipped` | 无需切割(无 CUE / 已分轨 situation C / CUE 引用多文件 / 引用不存在) | 跳过 |
|
||
| `unsupported` | 源格式当前依赖无法处理(如 APE 但 `mac` 未装、DSF) | 跳过 |
|
||
|
||
**分轨级状态**(tracks[] 里):
|
||
|
||
| status | 说明 |
|
||
|---|---|
|
||
| `pending` | 待处理 |
|
||
| `completed` | 已完成 |
|
||
| `failed` | 失败(`error` 字段有原因) |
|
||
|
||
### 手动干预 manifest
|
||
|
||
Manifest 是纯 JSON,可以用文本编辑器直接改。
|
||
|
||
**例子 1:让某个专辑重新切割**
|
||
|
||
```json
|
||
"status": "completed" → "status": "analyzed"
|
||
```
|
||
|
||
同时把该专辑内所有 track 的 status 从 `completed` 改成 `pending`,删掉现有分轨文件,再跑脚本。
|
||
|
||
**例子 2:跳过某个有问题的专辑**
|
||
|
||
```json
|
||
"status": "analyzed" → "status": "skipped"
|
||
```
|
||
|
||
**例子 3:删除某个专辑的记录重新扫描**
|
||
|
||
删除对应的 album 条目,下次运行会自动重新扫描添加。
|
||
|
||
**改完保存直接跑**:
|
||
|
||
```bash
|
||
python3 split_cue.py /mnt/z/新收藏
|
||
```
|
||
|
||
### Manifest 损坏怎么办?
|
||
|
||
如果 JSON 损坏(编辑错、盘故障),脚本启动时会自动检测,把坏的 manifest 重命名为 `.split_cue_manifest.json.corrupt-<时间戳>`,然后**重新生成新的 manifest**。
|
||
|
||
已切出来的分轨文件不会丢,但脚本会重新扫描分析。因为已切分的目录会呈现为 situation C(有分轨 + 有整轨),会自动 SKIP,不会重复切割。**幂等性保护**。
|
||
|
||
---
|
||
|
||
## 7. 支持的源格式与依赖矩阵
|
||
|
||
### 支持矩阵
|
||
|
||
| 源格式 | 输出格式 | 依赖 | 备注 |
|
||
|---|---|---|---|
|
||
| `.flac` | `.flac` | `shntool` + `flac` | 无损 re-encode,sample-exact |
|
||
| `.wav` | `.wav` | `shntool` 原生 | 无外部依赖,PCM 样本直切 |
|
||
| `.ape` | `.flac` | `shntool` + `mac` + `flac` | mac 解码 APE,flac 编码输出(ffmpeg 无 APE 编码器) |
|
||
| `.dsf` / `.dff` | — | 无 | **不支持** (DSD)。请先手动转 FLAC 再切 |
|
||
|
||
### 依赖启动时 probe
|
||
|
||
启动时会打印所有依赖状态:
|
||
|
||
```
|
||
未找到 mac (APE 解码器) → APE 源将标记 UNSUPPORTED. 安装可选: apt install monkeys-audio
|
||
支持源格式: ['.flac', '.wav']
|
||
```
|
||
|
||
**缺 shnsplit 或 cuetag**:直接退出(exit 3)。这是硬依赖。
|
||
|
||
**缺 flac**:FLAC 源会被标为 UNSUPPORTED。只有 WAV 源能处理。
|
||
|
||
**缺 mac**:APE 源会被标为 UNSUPPORTED,其他格式不受影响。
|
||
|
||
**缺 ffprobe**:警告但不影响切割,只是 manifest 里 `source_codec` / `duration_sec` 等字段是空的。
|
||
|
||
### 为什么不支持 DSD?
|
||
|
||
`shntool` 本身不支持 DSD 格式。理论上可以用 ffmpeg 走另一条路,但:
|
||
|
||
1. DSD 打包成整轨很少见(DSD 音源通常出货就是分轨的)
|
||
2. DSD 解码涉及低通滤波、降采样等,输出到 FLAC 会显著改变数据
|
||
3. 处理逻辑复杂度不值得
|
||
|
||
如果你确实有 DSD 整轨 + CUE,建议先手动用 ffmpeg 转成 FLAC,再跑 split_cue.py:
|
||
|
||
```bash
|
||
ffmpeg -i album.dsf -c:a flac -ar 88200 album.flac
|
||
# 然后修改 CUE 里的 FILE 指向 album.flac
|
||
split_cue.py /path/to/album
|
||
```
|
||
|
||
---
|
||
|
||
## 8. CUE 检测与匹配逻辑
|
||
|
||
### 什么样的目录会被识别为"整轨 CUE 型"?
|
||
|
||
严格判定条件(**全部满足**才算):
|
||
|
||
1. 目录里**至少**有 1 个 CUE 文件
|
||
2. 目录里**至少**有 1 个无损音频文件(FLAC/WAV/APE/DSF/DFF)
|
||
3. 某个 CUE 里 `FILE "xxx" WAVE` 条目**恰好 1 个**
|
||
4. 这个 FILE 引用能匹配到目录里的音频文件
|
||
5. 目录里**没有其他无损音频文件**(除了这个被引用的)
|
||
6. CUE 里**有 TRACK 条目**(不能是空 CUE)
|
||
|
||
只要有一项不满足,就不会被切割:
|
||
|
||
| 情况 | 判定 | 原因 |
|
||
|---|---|---|
|
||
| 无 CUE | `skipped` | 没有切割依据 |
|
||
| 无无损音频 | `skipped` | 没东西可切 |
|
||
| CUE 无 FILE 条目 | `skipped` + issues 记录 | CUE 无效 |
|
||
| CUE 有多个 FILE (>1) | `skipped` | 已经是分轨 CUE 索引 |
|
||
| CUE 引用的文件不存在 | `skipped` + issues 记录 | 可能是错配的 CUE |
|
||
| CUE 引用整轨但同时存在分轨 | `skipped` (situation C) | 已切过或用户手动分轨了 |
|
||
| 匹配成功但源格式依赖缺失 | `unsupported` | 例如 APE 但 mac 未装 |
|
||
|
||
### CUE FILE 引用的匹配策略
|
||
|
||
CUE 里的 `FILE "album.wav" WAVE` 未必和实际文件名完全一致。脚本用三级 fallback:
|
||
|
||
1. **精确匹配**:文件名完全一致
|
||
2. **不区分大小写**:`ALBUM.FLAC` 匹配 `album.flac`
|
||
3. **忽略扩展名**:`FILE "album.wav" WAVE` 匹配到 `album.flac`(假设目录里只有一个 `album.*` 无损文件)
|
||
|
||
三级都不匹配才判为"引用不存在"。
|
||
|
||
### CUE 编码识别
|
||
|
||
CUE 文件常见的编码:
|
||
|
||
- `utf-8-sig`(有 BOM 的 UTF-8)
|
||
- `utf-8`
|
||
- `gbk`(简体中文)
|
||
- `big5`(繁体中文)
|
||
- `shift_jis`(日文)
|
||
- `cp936`(GBK 别名)
|
||
- `latin1`(最后兜底)
|
||
|
||
脚本按顺序尝试解码,第一个成功的即采用。中日韩音源基本都能识别。
|
||
|
||
### CUE 里 TITLE / PERFORMER 的语义
|
||
|
||
CUE 里 `TITLE "..."` 和 `PERFORMER "..."` 可能出现在两个位置:
|
||
|
||
- **顶级**(在任何 TRACK 之前):整张专辑的标题和艺人
|
||
- **TRACK 块内**:这一首歌的标题和艺人
|
||
|
||
脚本区分这两种上下文,分别写入 `album_title` / `album_performer` 和每首歌的 `title` / `performer`。
|
||
|
||
### REM 元数据
|
||
|
||
CUE 里 `REM DATE "1998"` 和 `REM GENRE "Chinese Pop"` 在顶级出现时,会被解析为专辑的年份和流派,写入 manifest 并通过 cuetag 传给分轨的 tag。
|
||
|
||
### CUE 与 transcode_music.py 的区别
|
||
|
||
`transcode_music.py` 也有 CUE 检测逻辑(在其文档第 8 节详述了三种情况 A/B/C)。它的目的是**报警**"这个专辑需要 CUE split",而 `split_cue.py` 是**执行**这个 split。
|
||
|
||
两者对 CUE 的看法一致:
|
||
|
||
| 情况 | transcode_music.py 视角 | split_cue.py 视角 |
|
||
|---|---|---|
|
||
| A: CUE + 整轨大文件 | `needs_cue_split` 报警跳过 | **`analyzed`,执行切割** |
|
||
| B: CUE + 分轨 | 正常转码 | `skipped` |
|
||
| C: CUE 引用整轨 + 分轨都在 | 跳过整轨只转分轨 | `skipped` |
|
||
|
||
这就是为什么它们能天然衔接:`split_cue.py` 把 A 转成 C,然后 `transcode_music.py` 处理 C。
|
||
|
||
---
|
||
|
||
## 9. 分轨命名与元数据
|
||
|
||
### 文件命名规则
|
||
|
||
分轨文件名格式:**`NN - Title.ext`**
|
||
|
||
- **NN**:CUE 里 TRACK 声明的编号,零填充到至少 2 位(如果总轨数 ≥ 100 会用 3 位)
|
||
- **Title**:CUE 里 TRACK 块内的 TITLE,经过文件名净化
|
||
- **ext**:由源格式决定的输出扩展名(见第 7 节表格)
|
||
|
||
**Title 净化规则**:
|
||
|
||
1. 替换文件系统非法字符 `<>:"/\|?*` 以及 ASCII 控制字符(`\x00-\x1f`)为 `_`
|
||
2. 折叠连续空白为单个空格
|
||
3. 去掉结尾的点号和空格(Windows 要求)
|
||
4. 截断到最多 200 字符(避免文件系统限制)
|
||
5. 净化后为空则用 `untitled` 兜底
|
||
|
||
**示例**:
|
||
|
||
| CUE 里的 TITLE | 输出文件名(假设 num=2) |
|
||
|---|---|
|
||
| `红豆` | `02 - 红豆.flac` |
|
||
| `催眠 / 静夜` | `02 - 催眠 _ 静夜.flac`(`/` → `_`) |
|
||
| `"Track: A"` | `02 - _Track_ A_.flac`(`"` 和 `:` → `_`) |
|
||
| (无 TITLE) | `02 - Track 02.flac`(兜底) |
|
||
|
||
**注意**:**文件名**里的非法字符被替换,但**元数据 tag**(写入文件内部)保留 CUE 里的原样。所以 `催眠 / 静夜.flac` 里的 `TITLE` tag 仍然是 `催眠 / 静夜`(含 `/`),播放器显示正常。
|
||
|
||
### 命名冲突处理
|
||
|
||
极少数情况下多首歌的净化后名字撞车(比如两首歌都叫 `未命名` 而且 num 不同但被截断)。脚本会在冲突时追加 `(2)`、`(3)`:
|
||
|
||
- `05 - 未命名.flac`
|
||
- `06 - 未命名 (2).flac`
|
||
|
||
### 元数据 (Tag) 映射
|
||
|
||
由 `cuetag` 自动写入。对 FLAC 输出,写的是 Vorbis Comment tags:
|
||
|
||
| Tag | 来源 |
|
||
|---|---|
|
||
| `TITLE` | CUE 里 TRACK 块内的 `TITLE` |
|
||
| `ARTIST` / `PERFORMER` | CUE 里 TRACK 块内的 `PERFORMER`(若无则用专辑 PERFORMER) |
|
||
| `ALBUM` | CUE 里顶级 `TITLE` |
|
||
| `track` | CUE 里 TRACK 编号(补零至 2 位,如 `01`) |
|
||
| `TRACKTOTAL` | CUE 里总 TRACK 数 |
|
||
| `DATE` | CUE 里顶级 `REM DATE` |
|
||
| `GENRE` | CUE 里顶级 `REM GENRE` |
|
||
|
||
**注意**:cuetag 的行为是**按位置对应**:`cuetag album.cue file1.flac file2.flac file3.flac` 会把 CUE 的 TRACK 1 写到 file1,TRACK 2 写到 file2,以此类推。脚本按 shnsplit_index 严格排序传参,保证对应正确。
|
||
|
||
### cuetag 失败的后果
|
||
|
||
如果 cuetag 因某种原因失败(罕见),脚本会**警告但不算专辑失败**——分轨音频文件本身是完整的,只是没有元数据 tag。可以事后手动跑一次:
|
||
|
||
```bash
|
||
cd "专辑目录"
|
||
cuetag *.cue 01*.flac 02*.flac 03*.flac # 按顺序传
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 与 transcode_music.py 的组合工作流
|
||
|
||
### 典型完整流程
|
||
|
||
```bash
|
||
# ┌─ 前期整理 (split_cue.py)
|
||
python3 split_cue.py /mnt/z/新收藏 --analyze-only
|
||
python3 split_cue.py /mnt/z/新收藏
|
||
|
||
# └─ 转码到便携格式 (transcode_music.py)
|
||
python3 transcode_music.py /mnt/z/新收藏 /mnt/x/mp3输出 --analyze-only
|
||
python3 transcode_music.py /mnt/z/新收藏 /mnt/x/mp3输出
|
||
```
|
||
|
||
### 两个脚本对同一个专辑的看法
|
||
|
||
举例:`王菲 - 唱游 [FLAC+CUE]/` 目录里有 `CDImage.flac` + `CDImage.cue`(12 轨)。
|
||
|
||
**只跑 transcode_music.py**:
|
||
|
||
```
|
||
[!] 1 个专辑需先手动 CUE split (已跳过转码):
|
||
• 王菲 - 唱游 [FLAC+CUE]
|
||
CDImage.cue: 整轨型 (单文件 'CDImage.flac', 12 轨) — 需先手动 CUE split 再转码
|
||
```
|
||
|
||
**先跑 split_cue.py 再跑 transcode_music.py**:
|
||
|
||
```
|
||
[split_cue.py]
|
||
[1/1] 切割: 王菲 - 唱游 [FLAC+CUE]
|
||
源: CDImage.flac → 12 轨 (flac)
|
||
✓ [01/12] 01 - 红豆.flac
|
||
✓ [02/12] 02 - 催眠.flac
|
||
...
|
||
|
||
[transcode_music.py]
|
||
CDImage.cue: 引用 'CDImage.flac' 但存在 12 个分轨 → 跳过 'CDImage.flac' 只转分轨
|
||
转码: 01 - 红豆.flac (flac, 44100Hz)
|
||
转码: 02 - 催眠.flac (flac, 44100Hz)
|
||
...
|
||
```
|
||
|
||
`transcode_music.py` 自动检测到 situation C,只转 12 个分轨、不动整轨,产生 12 个 MP3。
|
||
|
||
### Manifest 隔离
|
||
|
||
两个脚本的 manifest 文件名不同:
|
||
|
||
- `split_cue.py` → `.split_cue_manifest.json`
|
||
- `transcode_music.py` → `.transcode_manifest.json`
|
||
|
||
互不干扰。可以随时来回跑。
|
||
|
||
### 输出结构对比
|
||
|
||
**运行前**:
|
||
|
||
```
|
||
/mnt/z/新收藏/
|
||
└── 王菲 - 唱游 [FLAC+CUE]/
|
||
├── CDImage.flac (整张 CD 一个大文件)
|
||
└── CDImage.cue
|
||
```
|
||
|
||
**运行 split_cue.py 后**:
|
||
|
||
```
|
||
/mnt/z/新收藏/ ← 源目录, 分轨写这里
|
||
├── .split_cue_manifest.json ← split_cue 状态
|
||
└── 王菲 - 唱游 [FLAC+CUE]/
|
||
├── CDImage.flac ← 原文件保留
|
||
├── CDImage.cue ← 保留
|
||
├── 01 - 红豆.flac ← 新: split_cue 输出
|
||
├── 02 - 催眠.flac
|
||
└── ... (12 个分轨)
|
||
```
|
||
|
||
**接着运行 transcode_music.py 后**:
|
||
|
||
```
|
||
/mnt/z/新收藏/
|
||
├── .split_cue_manifest.json
|
||
├── .transcode_manifest.json ← transcode_music 状态
|
||
└── 王菲 - 唱游 [FLAC+CUE]/ (未变)
|
||
|
||
/mnt/x/mp3输出/ ← 目标目录
|
||
└── 王菲 - 唱游 [FLAC+CUE]/
|
||
├── CDImage.cue ← CUE 也复制过去
|
||
├── 01 - 红豆.mp3 ← 320k CBR
|
||
├── 02 - 催眠.mp3
|
||
└── ... (12 个 MP3)
|
||
```
|
||
|
||
注意:`CDImage.flac` 不会转成 MP3(因为 situation C 检测跳过);`CDImage.cue` 会被复制到目标目录(作为资源)。
|
||
|
||
### 关于 CUE 的一个细节
|
||
|
||
切完后,`CDImage.cue` 里的 `FILE "CDImage.flac" WAVE` 仍指向原整轨。如果用户在目标目录里想用 CUE 播放分轨 MP3,这个 CUE 是不正确的——它指向整轨、但目标目录里没有那个整轨。
|
||
|
||
如果你实际会用 CUE 索引,考虑:
|
||
|
||
1. 手动编辑复制到目标的 CUE,把 FILE 指向 MP3(但 CUE 传统上不支持多 FILE 分轨映射,改起来麻烦)
|
||
2. 直接删除目标目录里的 CUE(分轨 MP3 有 tag,播放器不需要 CUE 也能显示正确信息)
|
||
|
||
**推荐做法**:直接删。分轨 MP3 的 ID3 tag 已经包含所有 CUE 元数据。
|
||
|
||
---
|
||
|
||
## 11. 错误处理与恢复
|
||
|
||
### 错误捕获原则
|
||
|
||
**单个专辑失败不影响其他**。shnsplit / cuetag / 重命名任一失败时:
|
||
|
||
1. 该专辑内所有未完成的 track 状态设为 `failed`,错误消息写入 `error` 字段
|
||
2. 该专辑整体状态设为 `failed`
|
||
3. `issues[]` 追加人类可读的说明
|
||
4. **继续处理**下一个专辑
|
||
5. 最后在报告里列出所有失败项
|
||
|
||
### 再次运行时
|
||
|
||
`failed` 状态的专辑会**自动重试**(整个专辑重切,因为 shnsplit 是全轨一次调用的原子操作,无法只重切失败的分轨)。
|
||
|
||
```bash
|
||
# 修好源文件后, 直接再跑
|
||
python3 split_cue.py /mnt/z/新收藏
|
||
```
|
||
|
||
### 常见错误
|
||
|
||
**"shnsplit 失败: unknown format"**
|
||
|
||
CUE 里 `FILE "xxx" TYPE` 的 TYPE 值 shntool 不认。常见于 `FLAC` / `WAVE` / `MP3` / `BINARY`,其他值可能有问题。检查 CUE 内容并手动修正。
|
||
|
||
**"shnsplit 失败: cannot open file"**
|
||
|
||
CUE 的 FILE 引用文件名和实际文件名不一致(尽管脚本会 fallback 匹配到实际文件,但 shnsplit 用的是 CUE 里的原字符串)。可能因为脚本 fallback 匹配到了但 shntool 找不到。手动把 CUE 里的 FILE 名改成实际文件名,再跑。
|
||
|
||
**"cuetag 失败, 音频仍可用但可能无标签"**
|
||
|
||
cuetag 有时对某些 CUE 或 FLAC 版本行为异常。不影响切割本身,可事后手动跑 cuetag 或用别的 tagger(Picard、mp3tag 等)补 tag。
|
||
|
||
**"预期临时文件缺失/过小"**
|
||
|
||
shnsplit 返回 0 但实际没写全所有分轨。极罕见,通常是磁盘满或权限问题。检查目标目录空间和权限。
|
||
|
||
**"临时目录创建失败"**
|
||
|
||
无法在专辑目录里创建 `.split_cue_tmp_<pid>`。通常是目录只读或磁盘满。
|
||
|
||
**"源文件消失"**
|
||
|
||
分析阶段之后、切割阶段之前源文件被删/移动。删除对应专辑 manifest 条目重新扫描即可。
|
||
|
||
### 中断(Ctrl+C)
|
||
|
||
脚本捕获 SIGINT,保存 manifest 后退出。**中断时临时目录 `.split_cue_tmp_<pid>` 里的 shnsplit 中间产物会保留**(因为 finally 清理只在 split_album 函数正常返回时执行)——下次跑会重建。中断专辑的状态可能停留在 `processing`,重跑时会被视为需要重切。
|
||
|
||
如果发现有遗留的 `.split_cue_tmp_*` 目录(比如脚本被 kill -9 强杀),可以手动清理:
|
||
|
||
```bash
|
||
find /mnt/z/新收藏 -maxdepth 2 -type d -name '.split_cue_tmp_*' -exec rm -rf {} +
|
||
```
|
||
|
||
### 查看失败列表
|
||
|
||
```bash
|
||
# 用 jq 快速查看失败的专辑
|
||
jq -r '.albums | to_entries[] | select(.value.status=="failed") | .key' \
|
||
/mnt/z/新收藏/.split_cue_manifest.json
|
||
|
||
# 查看某专辑的具体失败原因
|
||
jq '.albums["专辑名"] | {status, issues, tracks: [.tracks[] | select(.status=="failed") | {num, error}]}' \
|
||
/mnt/z/新收藏/.split_cue_manifest.json
|
||
```
|
||
|
||
### 关于 unsupported
|
||
|
||
`unsupported` 状态的专辑不算失败,是主动跳过。原因写在 `issues[]` 里,例如:
|
||
|
||
- `缺少依赖: mac (APE 解码器)` → 装 `mac` 后再跑,会自动切
|
||
- `shntool 不支持 DSD, 请先手动转 FLAC` → 手动预处理
|
||
|
||
装好依赖后,需要用 `--force` 或删掉对应 manifest 条目才会重新分析。
|
||
|
||
---
|
||
|
||
## 12. 已知局限
|
||
|
||
- **不支持 DSD**(`.dsf` / `.dff`):shntool 不识别 DSD。若有整轨 DSD + CUE,需先手动 ffmpeg 转 FLAC 再切。
|
||
- **shnsplit 是一次调用切完整个专辑**:无法只重切失败的某一首歌。失败时整个专辑重切(幂等,无副作用)。
|
||
- **不做 replaygain / 章节标记 / 高级 tag**:只做 cuetag 支持的标准 tag(TITLE/ARTIST/ALBUM/DATE/GENRE/track/TRACKTOTAL)。
|
||
- **不支持并行切割**:为保证 manifest 状态一致性,专辑串行处理。可对不同子目录同时跑多个脚本实例。
|
||
- **CUE 里 track 编号必须连续从 1 开始**:极少数非标准 CUE(比如从 TRACK 03 开始)会导致 shnsplit 内部计数器和 CUE 编号错位。目前未特殊处理,可能造成 tag 错乱。
|
||
- **网盘 mtime 不能保留**:分轨是新建文件,mtime 是当下时间。原文件不动。
|
||
- **--force 不会自动清理旧分轨**:如果目录里已有旧分轨且脚本判为 situation C 而 skip,`--force` 也不会强制切。需手动删旧分轨。
|
||
- **shnsplit 依赖 `flac` 二进制来编码 FLAC**:这是 shntool 3.x 的架构选择(它调用外部 encoder)。装了 `flac` 才能切 FLAC 源;装了 `mac` 才能切 APE 源。
|
||
- **APE 输出为 FLAC,不能原格式**:ffmpeg 和 shntool 都没 APE 编码器(APE 是私有格式)。所以 APE → FLAC 是唯一路径。
|
||
- **Windows 直接跑未测试**:脚本用了 POSIX `os.fsync(dir_fd)`,Windows 上会 best-effort。理论可用,实际推荐 Linux / WSL2 / macOS。
|
||
|
||
---
|
||
|
||
## 13. FAQ
|
||
|
||
**Q: 为什么要单独一个 split_cue.py?直接让 transcode_music.py 顺便切了不好吗?**
|
||
|
||
A: 分离关注点。切割是**修改源目录**的操作(写新文件),转码是**读源目录写目标目录**的操作。两者错误影响面不同:切错了改源,转错了改目标。分开可以:
|
||
1. 先充分 review 切割计划再动手(`--analyze-only`)
|
||
2. 切割失败不牵连转码,反之亦然
|
||
3. 用户可以只切不转(本地播放器直接消费分轨 FLAC)
|
||
4. 用户可以直接手动切好再让 transcode_music 转(跳过 split_cue)
|
||
|
||
**Q: 切完 CUE 文件删不删?**
|
||
|
||
A: 保留。原文件(`CDImage.flac`)也保留。用户想删的话手动来,脚本不做破坏性操作。删原文件前建议:
|
||
1. 先播放几首分轨确认没问题
|
||
2. 用 `metaflac -l 01*.flac` 确认 tag 完整
|
||
3. 备份 CUE(换新 CUE 想重切时有用)
|
||
|
||
**Q: 切割精度和音质怎么样?**
|
||
|
||
A: shntool 是按 CD 帧(1/75 秒)对齐切的,与原始 CUE INDEX 完全一致。对 FLAC → FLAC,音频数据无损(decode + re-encode 是数学等价的 PCM 转换)。对 WAV → WAV 是纯样本操作,字节级精确。对 APE → FLAC,音频数据也无损(FLAC 压缩是无损的),只是容器格式变了。
|
||
|
||
**Q: 分轨没有 gapless 播放会不会有咔嗒声?**
|
||
|
||
A: 用了 shntool 的默认 `--append-gaps` 模式,pregap 追加到上一轨结尾。相邻曲目之间的过渡应该 sample-exact 无缝,播放器如支持 gapless 就无咔嗒声。
|
||
|
||
**Q: 切完后音频总长度对不上原文件?**
|
||
|
||
A: 应该是精确对上的(每首歌的 duration 加起来 = 原文件 duration)。如果偏差 < 1 秒,可能是取整误差(ffprobe 显示的 duration 有精度限制)。偏差大就是有 bug,请提 issue。
|
||
|
||
**Q: 中文 / 日文 / 韩文的 CUE 处理有问题吗?**
|
||
|
||
A: 应该没问题。脚本会按 `utf-8 / gbk / big5 / shift_jis / cp936 / latin1` 顺序尝试解码,绝大部分 CJK CUE 都能识别。切割输出的 tag(Vorbis Comment)总是 UTF-8。如果发现某个 CUE 解码错乱,可以用别的工具(比如 `iconv` 或文本编辑器)先把 CUE 转成 UTF-8 再跑脚本。
|
||
|
||
**Q: `cuetag` 输出 warning 说什么 metaflac 不 exist?**
|
||
|
||
A: 有些 cuetools 版本的 cuetag 是 Python 或 shell 脚本,内部调用 `metaflac`(来自 `flac` 包)。装了 `flac` 就有 `metaflac`。如果 `cuetag` 抱怨 metaflac 不存在,装 `flac` 包即可(`sudo apt install flac`)。
|
||
|
||
**Q: 我的 CUE 有奇怪的编码/结构 shntool 处理不了怎么办?**
|
||
|
||
A: 先用文本编辑器把 CUE 转成 UTF-8 无 BOM,去掉不必要的 REM 行,看 TRACK / FILE / INDEX 结构是否规范(每个 TRACK 至少要有 `INDEX 01 MM:SS:FF`)。规范化后重试。
|
||
|
||
**Q: 能不能自定义分轨文件名格式?比如 "%p - %t.flac"?**
|
||
|
||
A: 目前不能。命名格式固定为 `NN - Title.ext`。这是刻意的:
|
||
1. 保证目录内文件按 track 顺序排列
|
||
2. 简化文件名,避免复杂替换的 bug
|
||
3. 元数据 tag 里已经有完整信息(艺人、专辑等),不需要塞进文件名
|
||
|
||
**Q: 支持 track 里带 gap(HTOA / hidden track)吗?**
|
||
|
||
A: 部分支持。shnsplit 默认 `--append-gaps` 会把 gap 归到前一轨末尾。TRACK 01 之前的 pregap(HTOA)目前算作 TRACK 01 的开头。如果你需要独立提取 HTOA,得手动调 shnsplit 参数(脚本目前不暴露此选项)。
|
||
|
||
**Q: 切错了想撤回?**
|
||
|
||
A: 因为**原文件从不被删改**,撤回很简单:找出符合 `NN - Title.ext` 格式的新生成分轨全部删除即可。示例见场景 8。
|
||
|
||
**Q: 支持嵌套目录(比如多碟 CD1/CD2/)吗?**
|
||
|
||
A: 支持。脚本会 `os.walk` 全递归,每一层子目录独立分析。多碟专辑的每个 CDx 目录都会被视为一个独立的专辑单元。
|
||
|
||
**Q: 我可以在 Docker 里跑吗?**
|
||
|
||
A: 可以。写个简单 Dockerfile:
|
||
|
||
```dockerfile
|
||
FROM debian:stable-slim
|
||
RUN apt-get update && apt-get install -y \
|
||
python3 shntool cuetools flac ffmpeg \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
COPY split_cue.py /usr/local/bin/
|
||
ENTRYPOINT ["python3", "/usr/local/bin/split_cue.py"]
|
||
```
|
||
|
||
然后:
|
||
|
||
```bash
|
||
docker run --rm -v /mnt/z/music:/music my-split-cue /music --analyze-only
|
||
```
|
||
|
||
**Q: 我想只切某一个格式(比如只 FLAC 不 APE)怎么办?**
|
||
|
||
A: 目前没提供 filter 参数。可以用 shell 循环:
|
||
|
||
```bash
|
||
find /mnt/z/新收藏 -type d | while read d; do
|
||
if ls "$d"/*.flac >/dev/null 2>&1; then
|
||
python3 split_cue.py "$d"
|
||
fi
|
||
done
|
||
```
|
||
|
||
**Q: 生成的 manifest 会很大吗?**
|
||
|
||
A: 一般不大。50 个专辑的 manifest 约 30 KB。上千个专辑估计几 MB。不影响使用。
|
||
|
||
**Q: transcode_music.py 和 split_cue.py 能同时跑吗?**
|
||
|
||
A: 不建议。两者都会写源目录(split_cue 写分轨、transcode_music 写自己的 manifest)。虽然写的是不同文件不会冲突,但 transcode_music 分析时看到 split_cue 正在切的中间状态可能判断错误。**串行跑更安全**:split_cue 先跑完,transcode_music 再跑。
|
||
|
||
---
|
||
|
||
## 附录 A:完整命令示例集
|
||
|
||
```bash
|
||
# 分析 (不切割)
|
||
python3 split_cue.py /mnt/z/music --analyze-only
|
||
|
||
# 正式切割 (自动 resume)
|
||
python3 split_cue.py /mnt/z/music
|
||
|
||
# 详细日志
|
||
python3 split_cue.py /mnt/z/music -v
|
||
|
||
# 强制重跑 (覆盖已有分轨)
|
||
python3 split_cue.py /mnt/z/music --force
|
||
|
||
# 使用长参数
|
||
python3 split_cue.py --source /mnt/z/music
|
||
|
||
# 使用短参数
|
||
python3 split_cue.py -s /mnt/z/music
|
||
|
||
# 组合: 位置 + 选项
|
||
python3 split_cue.py /mnt/z/music -v --force
|
||
|
||
# 指定 shnsplit / cuetag 路径
|
||
python3 split_cue.py /mnt/z/music \
|
||
--shnsplit /opt/shntool-3/bin/shnsplit \
|
||
--cuetag /opt/cuetools/bin/cuetag
|
||
|
||
# 加大超时 (超长专辑)
|
||
python3 split_cue.py /mnt/z/music --split-timeout 3600
|
||
|
||
# 只处理某一个专辑
|
||
python3 split_cue.py "/mnt/z/music/王菲 - 唱游 [FLAC+CUE]"
|
||
|
||
# 完整工作流: split 然后 transcode
|
||
python3 split_cue.py /mnt/z/music && \
|
||
python3 transcode_music.py /mnt/z/music /mnt/x/mp3
|
||
```
|
||
|
||
## 附录 B:文件结构示例
|
||
|
||
**运行前**(典型的整轨型音乐盘):
|
||
|
||
```
|
||
/mnt/z/新收藏/
|
||
├── 王菲 - 唱游 [FLAC+CUE]/
|
||
│ ├── CDImage.flac ← 整张 CD 一个大文件, 40 分钟
|
||
│ └── CDImage.cue ← 12 个 TRACK
|
||
├── 蔡琴 - 老歌 [FLAC+CUE]/
|
||
│ ├── album.flac ← 整张 CD 一个大文件
|
||
│ ├── album.cue
|
||
│ ├── cover.jpg
|
||
│ └── booklet.pdf
|
||
├── 张学友精选 [已分轨]/ ← 已经是分轨的, 会 SKIP
|
||
│ ├── 01 吻别.flac
|
||
│ ├── 02 一路上有你.flac
|
||
│ ├── 03 情网.flac
|
||
│ └── (无 CUE)
|
||
└── Beatles - White Album [WAV+CUE]/ ← WAV 源, 会切成 WAV
|
||
├── disc1.wav
|
||
└── disc1.cue
|
||
```
|
||
|
||
**运行 `split_cue.py /mnt/z/新收藏` 后**:
|
||
|
||
```
|
||
/mnt/z/新收藏/
|
||
├── .split_cue_manifest.json ← 状态记录
|
||
├── 王菲 - 唱游 [FLAC+CUE]/
|
||
│ ├── CDImage.flac ← 保留不动
|
||
│ ├── CDImage.cue ← 保留
|
||
│ ├── 01 - 红豆.flac ← 新: 分轨输出 (含 tag)
|
||
│ ├── 02 - 催眠.flac
|
||
│ ├── 03 - 无常.flac
|
||
│ ├── 04 - 童 (国语).flac
|
||
│ ├── ... (共 12 个)
|
||
│ └── 12 - 精彩.flac
|
||
├── 蔡琴 - 老歌 [FLAC+CUE]/
|
||
│ ├── album.flac ← 保留
|
||
│ ├── album.cue ← 保留
|
||
│ ├── cover.jpg ← 保留
|
||
│ ├── booklet.pdf ← 保留
|
||
│ ├── 01 - 恰似你的温柔.flac ← 新: 分轨
|
||
│ ├── 02 - 你的眼神.flac
|
||
│ └── ... (12 个)
|
||
├── 张学友精选 [已分轨]/ ← 无变化 (SKIP)
|
||
│ ├── 01 吻别.flac
|
||
│ ├── 02 一路上有你.flac
|
||
│ └── 03 情网.flac
|
||
└── Beatles - White Album [WAV+CUE]/
|
||
├── disc1.wav ← 保留 WAV 整轨
|
||
├── disc1.cue ← 保留
|
||
├── 01 - Back In The U.S.S.R..wav ← 新: WAV 分轨
|
||
├── 02 - Dear Prudence.wav
|
||
└── ... (30 个)
|
||
```
|
||
|
||
**接着运行 `transcode_music.py /mnt/z/新收藏 /mnt/x/mp3`**:
|
||
|
||
```
|
||
/mnt/z/新收藏/ ← 未变
|
||
├── .split_cue_manifest.json
|
||
├── .transcode_manifest.json ← 新: transcode 状态
|
||
└── ... (专辑目录未变)
|
||
|
||
/mnt/x/mp3/ ← 目标目录
|
||
├── 王菲 - 唱游 [FLAC+CUE]/
|
||
│ ├── CDImage.cue ← CUE 复制过来
|
||
│ ├── 01 - 红豆.mp3 ← 320k CBR
|
||
│ ├── 02 - 催眠.mp3
|
||
│ └── ... (12 个 MP3)
|
||
├── 蔡琴 - 老歌 [FLAC+CUE]/
|
||
│ ├── album.cue
|
||
│ ├── cover.jpg
|
||
│ ├── booklet.pdf
|
||
│ ├── 01 - 恰似你的温柔.mp3
|
||
│ └── ...
|
||
├── 张学友精选 [已分轨]/
|
||
│ ├── 01 吻别.mp3
|
||
│ ├── 02 一路上有你.mp3
|
||
│ └── 03 情网.mp3
|
||
└── Beatles - White Album [WAV+CUE]/
|
||
├── disc1.cue
|
||
├── 01 - Back In The U.S.S.R..mp3
|
||
└── ... (30 个 MP3)
|
||
```
|
||
|
||
**注意**:
|
||
- 整轨大文件(`CDImage.flac`、`album.flac`、`disc1.wav`)**不会**被转成 MP3,因为 transcode_music.py 检测到 situation C 会跳过它们
|
||
- 所有 CUE 文件会被复制到目标目录(作为资源文件)
|
||
- 分轨 MP3 保留完整 ID3 tag(继承自分轨 FLAC 的 Vorbis Comment)
|
||
|
||
**Manifest 位置**:
|
||
|
||
```
|
||
/mnt/z/新收藏/.split_cue_manifest.json ← split_cue 的状态
|
||
/mnt/z/新收藏/.transcode_manifest.json ← transcode_music 的状态
|
||
```
|
||
|
||
两个 manifest 都在源目录里,文件名不同,互不冲突。
|