# CJK 字幕安全烧录：分别核对文本、字体映射与实际画面

本配方使用原创 3.6 秒几何测试画面和四条原创字幕，实际执行了本地 FFmpeg/libass 字幕烧录。**没有测试 MiniMax H3 或任何生成提供商**。1280×720 的静音短片是受控预检练习，不是完整视频交付脚本。

## 本次实际执行结果

- Node.js v24.14.0；FFmpeg 6.0 arm64；libass 0.17.0；HarfBuzz 6.0.0；FriBidi 1.0.12。可下载完整命令参数与渲染日志。
- 字体为未修改的 Noto Sans CJK SC Regular 2.004，固定官方 `Sans2.004` 提交 `523d033d6cb47f4a80c58a35753646f5c3608a78`，16,437,364 字节。字体 SHA-256：`2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b`。
- 本例 43 个不重复的非空白码点，在选中的 Unicode cmap format 12 中都有有效非零字形 ID。22 项预检场景与 7 项媒体断言通过；四张关键帧已经解码并另行目视核对。
- 该 FFmpeg 构建报告字体提供者为 CoreText。日志显示配套字体已加载，并选用了 `NotoSansCJKsc-Regular`。即使传入 fontconfig 文件，也不能据此宣称此构建完全隔离了系统字体。

## 1. 字体和许可一起保存

同时下载 `NotoSansCJKsc-Regular.otf`、未经改写的 `OFL.txt` 与 `FONT-COPYRIGHT.txt`。[官方字体许可](https://github.com/notofonts/noto-cjk/blob/523d033d6cb47f4a80c58a35753646f5c3608a78/LICENSE)允许在满足条件时随配方再分发，包括保留许可与版权，不单独出售字体。本配方没有裁剪或改名字体。原创媒体另见 `LICENSE-original-assets.txt`；未打包 FFmpeg 二进制。

[官方字体下载说明](https://github.com/notofonts/noto-cjk/blob/523d033d6cb47f4a80c58a35753646f5c3608a78/Sans/README.md)区分语言版本。SC 字体覆盖某个码点，不代表它采用日语、韩语或繁体中文场景偏好的地区字形。用于真实语言交付时，需选择对应字体，并重新核对字体族名、固定哈希与画面。

## 2. 先做编码、字幕和缺字预检

把脚本、`sample.srt` 与字体放在同一目录：

```sh
node --version
node run-preflight-tests.mjs
```

负向场景不会启动 FFmpeg。测试包含缺字、字体缺失、错误 UTF-8/UTF-16、时间重叠、ASS/HTML 标记、变体序列、字体哈希变化、目录穿越、软链接与命令参数边界。解析器只接受小范围纯文本 SRT：最多 32 条字幕，每条两行、每行 64 个码点，不能重叠，结束时间不超过一分钟，起止必须按 10 毫秒对齐。ASS 使用厘秒精度；本脚本拒绝更细的 SRT 边界，不把它们取整成短字幕或零时长字幕。

[OpenType cmap 规范](https://learn.microsoft.com/en-us/typography/opentype/spec/cmap)区分字符映射与代表缺字的零号字形。本检查器支持 Unicode cmap format 4/12，不证明变体序列、复杂整形或地区字形正确。遇到变体选择符或 ZWJ 会拒绝，不会悄悄认定已覆盖。

## 3. 在新目录实际烧录

使用你已有、带 libass 和 libx264 的 FFmpeg 6.0 二进制。脚本不安装或打包工具。传入绝对路径：

```sh
node run-recipe.mjs --ffmpeg /absolute/path/to/ffmpeg --out ./fresh-output
```

脚本先核对 SRT 和固定字体，再生成原创几何画面，把纯文本字幕转换为固定样式 ASS，实际烧录并解码四张 PNG。子进程通过参数数组调用，`shell:false`；用户文件名先复制到独立临时目录的固定名称，不会进入滤镜表达式。编码和滤镜各使用一个线程，子进程有一分钟期限和日志上限。已有输出会被拒绝，临时目录在 `finally` 清理。

配套 1280×720、3.6 秒源短片也能作为明确输入：

```sh
node run-recipe.mjs --ffmpeg /absolute/path/to/ffmpeg --out ./another-fresh-output --input ./sample-source.mp4 --srt ./sample.srt --font ./NotoSansCJKsc-Regular.otf
```

此练习只输出 3.6 秒且移除音频。处理真实交付文件前，要另行修改并检查时长、尺寸、音频映射和字幕位置；不能原样当作完整视频导出器。

## 4. 把执行证据与文字正确性分开验收

`coverage-report.json`列出每个码点和字形 ID；`command-log.json`、`ffmpeg-log.txt`保留真实命令、工具版本、选中字体与诊断。`execution-receipt.json`记录输出尺寸、SHA-256、关键帧检查与预检回执绑定。

逐一将 `keyframe-01.png` 至 `keyframe-04.png` 与 `sample.srt` 对照：第一张中英混排，第二、三张是简体/繁体，第四张是日语和韩语。`visual-review.json`只记录本例有限的目视读回，不代替熟悉该语言的排版审核。

脚本将字幕区域 x=0、y=600、1280×120 与无字幕源画面对比。这证明字幕区域有实际像素变化。PNG 非空、像素变化或 cmap 非零，**均不能单独证明文字正确**。还要分别检查方框缺字、标点、地区字形、裁切和时间。

## 根据失败定位下一步

| 失败 | 重试前调整 |
|---|---|
| `invalid_utf8` / UTF16-BOM 测试失败 | 明确导出为 UTF-8，不把未知字节直接改标签。 |
| `subtitle_precision_not_supported` | 将起止调整为 10 毫秒对齐；示例拒绝 201–209 毫秒，避免 ASS 折叠成 0.20–0.20 秒。 |
| `missing_glyph` | 找到覆盖报告里的准确 U+ 码点，选择合法且覆盖它的字体，重新核对族名、哈希与画面。 |
| `missing_input_file`、`font_hash_mismatch` | 恢复配套字体和许可，不依赖未记录的系统回退。 |
| `subtitle_markup_refused`、`unsupported_variation_sequence` | 使用受支持的纯文本样例，或另建并验证明确的 ASS/变体序列工作流。 |
| `no_visible_subtitle_difference`、`font_selection_unconfirmed` | 查看真实日志、字幕时间和解码帧；进程退出成功不足以验收。 |
| `output_exists`、路径边界失败 | 使用新建的受控目录和普通本地文件路径；脚本不覆盖文件、不下载网络媒体。 |

不同构建和平台即便工具版本号相同，也可能得到不同编码字节。配套哈希只标识本次真实本地运行，不承诺跨平台字节一致；没有宣称提供商生成、计费、生产任务或搜索流量结果。
