# 蓝光外挂字幕调轴 SOP（杰科 G5300 + bdsub.py）

> 适用：手里只有物理蓝光碟、没有光驱、没有 ISO，需要把网上下载的 SRT 对齐到碟片播放时间轴。
> 工具：`bdsub.py`（同目录）。全程不需要联网、不需要装任何东西。

---

## 0. 准备（每部片 1 分钟）

1. 网上下载对应片的 SRT（**优先选 BluRay 版**，见第 6 节）。
2. 字幕放 U 盘**根目录**（物理碟场景只认根目录）。
3. Mac 上留一份同样的 SRT，例如 `~/Movies/字幕/星际穿越.srt`。

命名建议：`星际穿越.chs.srt`。校正后另存为 `星际穿越.chs.fixed.srt`，
**把原件从 U 盘移走**，只留一个文件，避免在碟机字幕菜单里选错。

---

## 1. 先判断错位类型（不用逐句看，30 秒）

### 1.1 片长法（最省事）

碟片总时长（碟盒背面 / 碟机信息页） vs 字幕末条时间：

```bash
./bdsub.py list 星际穿越.srt --tail 1
# 共 1876 条, 编码 utf-8, 首 00:00:12,340, 末 02:47:52,100
```

| 现象 | 判定 |
|---|---|
| 两者相差 < 1 分钟 | 基本对齐，或仅轻微平移 → 直接跳到第 4 节，用 1 个锚点 |
| 碟片比字幕长 4%~5% | **B 类 · PAL 加速**，可用 `--ratio 1.0416667` 一键解 |
| 碟片比字幕长很多（>10 分钟） | 可能是不同剪辑版，先看第 6 节换字幕 |
| 片长几乎相同但对不上 | **A 类 · 整体平移**，1 个锚点解决 |

> 注意：字幕末条通常停在正片最后一句对白，比碟片总时长早 1~3 分钟（后面是 credits）。
> 所以「碟片时长 − 字幕末条」在 1~3 分钟之间属正常，不要当成错位。

### 1.2 三点法（最准，也只需要 3 个点）

看片时记三个点，后面第 4 节直接喂给工具。判据：

- 三个点差值**基本恒定** → A 类平移
- 差值**随时间线性变大**，片尾/片头 ≈ 1.0417 → B 类 PAL
- 差值在**某个位置突然跳变**（通常是章节切换处）→ C 类结构错位，调轴收益为负，换字幕

---

## 2. 看片前先选好三句台词（关键，省一半时间）

**顺序很重要：先在字幕里选题，再去看片。** 否则看片时临时找台词，要来回切。

```bash
./bdsub.py find 星际穿越.srt 库珀          # 搜一句有辨识度的短台词
./bdsub.py list 星际穿越.srt --from 00:03:00 --to 00:06:00   # 或直接看某时间段
```

选题标准：

- 片头 3~6 分钟一句
- 片中一句（30~60 分钟区间，**不要**挑打斗/音乐段落）
- 片尾对白最后 5 分钟内一句 —— 注意是**最后一段对白**，不是字幕文件的最后一条
  （最后一条可能已经在 credits 里，碟上根本找不到对应画面）

三句都短、都有独特词（人名、数字、短句），一眼能认出。

把三句的**字幕时间码抄在纸上**：

```
① 04:38      ② 52:30      ③ 1:51:40
```

**末锚点必须离片尾足够远、首锚点必须离片头足够远。** 两点跨度 <20 分钟时，
算出来的速率比毫无意义（工具会提示）。

---

## 3. 看片采集碟片时间（每部片 5~8 分钟）

1. 碟机播放正片，**从菜单进正片后**开始记（不要从片头 logo 算，差多少都会被偏移量吃掉，无所谓）。
2. 听到第 ① 句台词出口的瞬间按**暂停**。
3. 按遥控器方向键 / 显示类按键调出 OSD，读**已播时间**（若显示的是剩余时间，用总长减一下）。
4. 记下来，写成 `碟=字` 的形式。

重复三次，得到：

```
--anchor 4:47=4:38      --anchor 54:38=52:30      --anchor 1:56:16=1:51:40
```

### 关于精度：别追求极致

按暂停总归会慢 0.3~0.8 秒，但**三次都慢同样的量**，这个恒定误差会被偏移量吸收，
真正会被放大的只有三点之间的相对误差。所以：

- 单点误差 1 秒以内 → 最终整体精度约 ±0.5 秒，肉眼看不出
- 为了 0.1 秒反复倒带重听，是纯浪费

OSD 不显示时间码时的替代方案：手机秒表，**从按下播放那一刻起计时，中途不暂停**，
听到台词时读秒表。误差约 1 秒，可以接受。

---

## 4. 拟合（1 分钟）

先只算不写文件，看判定：

```bash
./bdsub.py fit 星际穿越.srt \
  --anchor 4:47=4:38 \
  --anchor 54:38=52:30 \
  --anchor 1:56:16=1:51:40 \
  --dry-run
```

正常输出：

```
锚点数        3
缩放 a        1.041580  (+4.1580%)
偏移 b        -2.708 s
残差          +0.15s  -0.27s  +0.12s   (RMS 0.191s)
判定          B 类 · 速率漂移 a=1.041580 (+4.16%)  ≈ PAL 加速 24→25 (+4.167%)
```

逐项确认：

| 行 | 怎么看 |
|---|---|
| 残差 RMS | < 1.0 秒 → 线性拟合成立，放心用；> 1.0 秒 → 判 **C 类**，工具会**拒绝写文件**（确需强行套用加 `--force`） |
| 判定 | A 类只有偏移；B 类带比例；C 类说明锚点自相矛盾，应换字幕而非调轴 |
| 警告 | 出现「系数离谱」= 锚点大概率写反了 |

> 输出路径与源文件相同时会直接报错拒绝，不会覆盖你的原字幕。

**写反的典型表现**：`a=0.77` 或 `a=1.3` 这种明显不是常见帧率换算的数字。
正确方向永远是 `碟片时刻=字幕时刻`：字幕里 04:38 那句，在碟上 05:12 才出现 →
写 `--anchor 5:12=4:38`。含义是「每条字幕整体往后推 34 秒」。

确认无误后出文件：

```bash
./bdsub.py fit 星际穿越.srt \
  --anchor 4:47=4:38 --anchor 54:38=52:30 --anchor 1:56:16=1:51:40 \
  --out 星际穿越.chs.fixed.srt
```

### 快路径：已知是 PAL 加速

如果第 1 节判定为 PAL，比例是**精确值**，不用测，一个锚点就够：

```bash
./bdsub.py fit 星际穿越.srt --anchor 4:47=4:38 --ratio 1.0416667 --out 星际穿越.chs.fixed.srt
```

### 只有 A 类平移

```bash
./bdsub.py fit 星际穿越.srt --anchor 5:12=4:38 --out 星际穿越.chs.fixed.srt
```

一个锚点即可，工具会提示「解不出漂移」——这是正常的，A 类本来就没有漂移。

> **偏差只有零点几秒时，别用这一节的方法。** 锚点的测量误差就有 ±0.5 秒，
> 比要修的量还大。改用比较法扫值，见 **附录 A**。

---

## 5. 回盘验证（3 分钟，别跳过）

把 `_fixed.srt` 拷到 U 盘根目录，**移走原件**，插碟重放，检查三点：

- 片头那句：准不准
- 片中那句：准不准（这一步能抓出漂移）
- 片尾那句：准不准（这一步能抓出方向反了）

任一点偏差 > 2 秒，说明该点锚点采集有误，重采那一个点再跑一次。

---

## 6. 什么时候别调轴，去换字幕

| 情况 | 处理 |
|---|---|
| 两条字幕**条数不一样**（差几十条） | 不同剪辑版，`check` 会直接告诉你，别调，换字幕 |
| 残差 RMS > 1 秒，补锚点仍不降 | 同上 |
| 差值在某处突然跳变 | 加长版/剧场版之别，换对应版本 |
| 找不到 BluRay 版字幕 | 优先找**同片长**（秒级一致）的，其次才是同片名 |

```bash
./bdsub.py check 简体.srt 繁体.srt   # 条数 + 逐句差值趋势，判断是否同版本
```

**换字幕永远优于调轴。** 调轴是在补偿"拿错版本"，换对了就零成本。
十分钟能找到 BluRay 版，就别花十分钟拟合。

---

## 7. 命令速查

```bash
./bdsub.py find  字幕.srt 台词片段        # 查台词在字幕里的时间码
./bdsub.py find  字幕.srt 台词 --limit 30 # 匹配太多时提高上限
./bdsub.py list  字幕.srt --head 5        # 看开头几条
./bdsub.py list  字幕.srt --tail 5        # 看结尾几条（确认末条是否已进 credits）
./bdsub.py list  字幕.srt --from 5:00 --to 6:30
./bdsub.py fit   字幕.srt --anchor 碟=字 --anchor 碟=字 --dry-run
./bdsub.py fit   字幕.srt --anchor 碟=字 --ratio 1.0416667
./bdsub.py fit   字幕.srt --anchor ... --keep-duration   # 只移起点不改显示时长
./bdsub.py check a.srt b.srt
./bdsub.py convert 字幕.ass 字幕.srt      # ASS → SRT（去特效标签）
```

时间格式都接受 `1:51:40`、`01:51:40,000`、`111:40` 这类写法，会自动补零。
`.srt` 的位置换成 `.ass` / `.ssa`，其余命令完全一样。

---

## 8. ASS / SSA 怎么处理

工具**直接支持** `.ass` 和 `.ssa`，所有命令都可以直接用，不需要先转换。

**写回方式**：只替换 `[Events]` 里每条 `Dialogue:` 行的 Start / End 两个字段，
其余字节（样式表、字体、颜色、`Comment:` 行、BOM、CRLF）原样保留。
已实测确认：一次校正后全文件仅 4 行变动，且变动只发生在时间字段上。

时间格式自动按 ASS 规范输出 `H:MM:SS.cc`（百分秒），不会写成 SRT 的 `,mmm`。
SSA v4（带 `Marked=` 字段）也能识别，字段顺序按文件自己的 `Format:` 行解析。

**什么时候该转成 SRT**：

| 情况 | 建议 |
|---|---|
| 字幕是纯对白，只有字体/颜色 | 保留 ASS，样式能用就用 |
| 有 `\pos` 强行定位、`\move` 滚动 | 建议 `convert` 成 SRT，碟机渲染常常不认，位置会飘 |
| 动漫特效字幕、屏幕字（招牌/短信） | 转 SRT 后这些内容会混进对白里，**不如换一份带中字的普通字幕** |

```bash
./bdsub.py convert 字幕.ass 字幕.srt      # 转完再 fit，或先 fit 再转都行
```

> `\pos` 是**空间**坐标，时间缩放不会影响它——这一点不用担心。
> 真正的问题是碟机对 ASS 的渲染支持有限，与时间无关。

---

## 9. 已知限制

- **只处理 SRT / ASS / SSA。** SUB（VobSub 图形）、IDX、PGS 不在此工具范围；
  这类需先 OCR 或换一份文本字幕。
- **不改帧率标记。** 工具只动时间戳，这正是我们要的。
- **编码自动识别** UTF-8 / GBK(GB18030) / BIG5（按常用字命中率择优），输出保持原编码；
  要强制改编码用 `--enc utf-8`。
- **碟机遥控器的"时移"能做的，工具都能做；工具能做的，遥控器做不了**
  （遥控器不能缩放速率、不能分段，且无时间回显）。
- **结构性错位无解。** 不同剪辑版之间不存在一个线性变换能对齐，别硬调。

---

## 10. 在 Windows 上用

程序是纯标准库单文件，**macOS / Linux / Windows 通用**，不需要装任何依赖。

1. 装 Python 3（python.org 官方安装包，勾选 "Add Python to PATH"）。
2. 把 `bdsub.py` 和字幕放同一个文件夹，在该文件夹地址栏输入 `cmd` 回车。
3. 把下面的 `python3` 换成 `py -3`：

```bat
py -3 bdsub.py list 字幕.srt --tail 5
py -3 bdsub.py fit 字幕.srt --anchor 4:47=4:38 --anchor 54:38=52:30 --dry-run
```

注意事项：

- 路径含空格或中文要加引号：`py -3 bdsub.py fit "D:\字幕\星际穿越.srt" ...`
- 控制台是 GBK 时中文正常显示；极老的英文 Windows 控制台可能显示成 `?`，
  只是**显示**问题，不影响计算和输出文件，想看正常中文用 Windows Terminal。
- 输出文件的换行符与 SRT/ASS 各自规范一致，不受平台影响。

---

## 11. 什么时候该考虑买光驱


出现以下任一情况，说明手工锚点已不划算，再考虑 LG BU40N 类 UHD 光驱 + MakeMKV：

- 想校正的字幕数量 > 20 部
- 碟片对白极少（纯音乐、纪录片旁白稀疏）→ 锚点难找
- 遇到 C 类，需要拿碟内 PGS 时间轴当 reference 走 `pgs2ref.py` + `alass`

否则，一部片 10 分钟的人工成本，比 1000 元光驱 + 每部片 1 小时的读盘时间划算得多。

---

# 附录 A：只有零点几秒的偏差怎么调

## A.1 先明确一件事：这类偏差不能用锚点法测

锚点法的测量误差来自"按暂停的反应延迟"，约 ±0.5 秒。要修正的量若只有 0.2~0.4 秒，
**测量误差比被测量还大**，算出来的结果纯属噪声——你会得到一个"看起来精确"的错数，
改完反而更糟。这是测量学的基本问题：被测量小于仪器误差时，必须换方法。

正确方法是**比较法（试错）**，即固定步长扫值，靠人眼挑。原因见 A.5。

## A.2 第一步：先判断要不要改

字幕早/晚，观感完全不同：

| 情况 | 处理 |
|---|---|
| 字幕比声音**早**出现 0.3 秒内 | **不要改。** 观众是"听着声音读字"，眼睛需要提前量，字幕略早是行业惯例，甚至更舒服 |
| 字幕比声音**晚** 0.2 秒内 | 多数人可接受，敏感的人会觉得"话说完了字才出来" |
| 字幕比声音**晚**超过 0.3 秒 | 值得改 |
| 方向在不同段落**不一致** | 不是固定偏移，是字幕本身质量问题，整体平移会拆东墙补西墙，别改 |

参考阈值：音画唇音同步的可察觉窗口是"声音超前 45 ms ～ 声音滞后 125 ms"
（ITU-R BT.1359-1），超出这个窗口才谈得上"不同步"。
字幕比唇音同步更宽容，因为它走的是阅读通道，不是唇形匹配通道。

## A.3 第二步：扫值，不要一个一个改

```bash
./bdsub.py sweep 星际穿越.srt --from -0.4 --to 0.4 --step 0.1
```

生成 9 个文件，一次全拷到 U 盘根目录：

```
星际穿越_0.0s.srt   ← 原时间轴，留作基准
星际穿越_m0.1s.srt  m0.2s  m0.3s  m0.4s   ← 字幕往前提
星际穿越_p0.1s.srt  p0.2s  p0.3s  p0.4s   ← 字幕往后推
```

（`m` = minus 提前，`p` = plus 推后；这样命名是为了在碟机文件列表里按字母序正好连续排列。）

播放到选定片段，用遥控器【字幕】键在菜单里逐一切换，选最贴的那一个。

**这一步的关键是把 N 次"改文件→拷 U 盘→插碟→重播"的往返压成 1 次。**
单次往返 3 分钟，9 次就是半小时；批量扫值只需一次。

## A.4 第三步：需要更细就再扫一轮

```bash
./bdsub.py sweep 星际穿越.srt --from 0.1 --to 0.3 --step 0.05 --dir 细调
```

但先说清楚**精度的物理上限**：

- 23.976 fps 一帧 = 41.7 ms
- 碟机字幕渲染本身还有 1~2 帧延迟
- 所以 **±0.1 秒（约 2.4 帧）已经是实际可达的极限**，再往下调没有意义

做到 ±0.1 就收工。

## A.5 为什么"试错"反而比"测量"准

人眼判断**先后顺序**的精度，远高于人判断**绝对时间差**的精度。

- 问"这句字幕和这个人开口，哪个先？"——你答得对，误差 <100 ms
- 问"差了多少毫秒？"——你答不出，只能靠猜

所以把"测量"换成"比较"（这一版 vs 上一版，哪个更贴），是把一个高误差任务换成了低误差任务。
这也是为什么遥控器的「时移」功能在实践中总调不准——它让你凭记忆比较绝对值，
而记忆是不可靠的。

## A.6 想精确知道数值的唯一办法：拍慢动作视频

如果就是想得到一个确切的数字（强迫症场合）：

1. 手机开**慢动作录像**（240 fps），对着屏幕录包含目标台词的那几秒
2. 回放时逐帧看：找到"字幕出现"那一帧和"声音起始/嘴型张开"那一帧
3. 两帧之差 × 4.17 ms = 真实偏差

字幕和声音在同一段录像里，相对关系被完整保留，所以这个测量是真的准。
代价是要逐帧数，一部片做一次就够，不用每部都做。

## A.7 命令速查

```bash
./bdsub.py sweep 字幕.srt --from -0.4 --to 0.4 --step 0.1   # 一组候选
./bdsub.py sweep 字幕.srt --offset 0.3 --out 最终.srt        # 单个值，直接出成品
./bdsub.py sweep 字幕.ass --from -0.2 --to 0.2 --step 0.1    # ASS 同样支持
```

注意 `--from -0.4` 的负号必须紧挨数字，不能写成 `--from=-0.4` 之外的 `-0.4:0.4` 形式
（那会被命令行解析成选项名）。
