subtitles:修复 Premiere Pro 字幕
一次性设置:参见工具包根目录下的 INSTALL.md。使用插件时,设置命令为
bash "${CLAUDE_PLUGIN_ROOT}/install.sh" --data "${CLAUDE_PLUGIN_DATA}";手动安装时(将四个 skills/ 文件夹并列复制到 ~/.claude/skills/ 中),在工具包根目录下运行 bash install.sh。
脚本位于此技能的 scripts/ 文件夹中。此技能指向另一个技能,路径为 ../rough-cut/...。
技能功能
Premiere 生成的自动转录(.json 导出文件)通常包含大量拼写错误,尤其是专有名词(例如,摄影师的名字可能被拆分成两个不相关的词)。whisper.cpp 的转录质量要好得多。
此技能会重新在本地转录视频,然后重建一个与 Premiere 精确格式相同的 .json 文件(可直接重新导入):借助参考脚本纠正名称,恢复标点和大小写,为作品标题添加引号。最后,通过强制对齐将时间戳与音频重新对齐(whisper 的时间戳存在漂移)。
- 是:本地转录、名称纠正、标点大小写、作品标题加引号、音频时间戳重新对齐(强制对齐)、与 Premiere 完全相同的格式,可直接重新导入。
- 否:不执行视频的剪切或粗剪(那是
rough-cut的功能)、不翻译、不重新同步已经正确的字幕文件、不处理.srt/.vtt格式的字幕(此处的格式是 Premiere 的原生 JSON)。
预期输入
- 从 Premiere 导出的视频(
.mp4/.mov):它包含需要转录的音频。 - 从 Premiere 导出的字幕
.json文件:它提供了需要克隆的目标格式(键、speakerUUID、语言)。 - 视频的参考脚本,可以是任何可读形式(纯文本、Markdown、Google 文档、Notion 页面、粘贴的文本):它是专有名词拼写的权威来源(人物、作品、地点、品牌)。如果没有,请索要,或者只纠正明显的拼写错误,对于任何不确定的名称,保留 whisper 的拼写。
语言
排版(标点符号周围的间距)、重音字母、标点符号集、引号标记和拼写规范化规则位于 languages/<code>.json 文件中(内置了 fr 和 en),绝不会出现在代码中。语言通过 --lang <code> 选择,否则使用 Premiere JSON 的语言,再否则使用 whisper 转录记录的语言,最后默认使用 en。文件的工作方式以及如何添加语言:../rough-cut/references/languages.md(相同机制;此技能有自己的 languages/ 文件夹)。
要求(每台机器只需设置一次)
需要 whisper-cli + ffmpeg + 模型 ~/Models/whisper/ggml-large-v3-turbo.bin。请参见 ../rough-cut/references/whisper-setup.md(共享设置)。检查:which whisper-cli ffmpeg && ls ~/Models/whisper/。
强制对齐(将时间戳与音频重新对齐,步骤 6b):
pip3 install --break-system-packages ctc-forced-aligner torch torchaudio unidecode
一个约 1.2 GB 的 ONNX 模型会在首次运行时下载(缓存到 ~/ctc_forced_aligner/)。检查:python3 -c "import ctc_forced_aligner"。
工作流程
下面的
scripts/...命令假设你在技能文件夹中。工作目录可以在两次 shell 调用之间重置:要么使用cd进入技能文件夹一次,要么在每个脚本前加上其绝对路径。工作文件(音频、转录、修正)应放在任何同步文件夹之外的临时文件夹中。
1. 探查视频和输入格式
首先:备份原始文件。 Premiere 可能会在重新导出时重写输入的 .json 文件,而它是检查步骤 7 中时间戳的唯一同步事实依据:
cp "<Reel.json>" /tmp/subs_original_premiere.json
ffprobe -v error -show_entries format=duration:stream=codec_type,r_frame_rate \
-of default=noprint_wrappers=1 "<video>"
注意时长(最后一个单词必须落在时长内)和 fps。然后检查输入的 Premiere JSON 以确认格式:
python3 scripts/inspect_format.py "<Reel.json>"
它会打印顶级/片段/单词的键、speaker UUID、语言、片段数量以及逐片段的当前文本(=需要修复的拼写错误列表)。预期的格式在 references/premiere-format.md 中有文档说明:如果检查显示不常见的键,请阅读该文档。
2. 本地转录(whisper.cpp)
python3 scripts/extract_audio.py "<video>" /tmp/subs_audio.wav
python3 scripts/transcribe.py /tmp/subs_audio.wav /tmp/subs_transcript.json --model large-v3-turbo --language <code>
在 Apple Silicon 上,3 分钟的视频大约需要 8 秒。转录结果包含带有 text(包含标点和大小写)以及带时间戳的 words 的片段。
2b. 如果 whisper 没有添加标点(连续交付):重新添加标点
对于快速的独白,whisper 有时会返回没有标点和大小写的流(片段在句子中间断开,没有 ./,)。明显的症状:build_subs.py 报告 eos: 0。没有标点的字幕是无法阅读的。快速检查:python3 scripts/words_of.py /tmp/subs_transcript.json 0 1 2 --plain。
如果是这样,请自行重新添加标点(这是 LLM 的任务,不是 whisper 的):重写每个片段的 text,添加标点和句子开头大写,保持 whisper 的单词不变:仅添加标点和大小写;词汇修正(名称、缩写)保留在 corrections.json 中。标点始终附在单词上(so?,而不是 so ?):前面有空格的标点会变成一个孤立的 token,对齐会悄悄地丢弃它(曾经一次丢失了 114 个 ?/!)。build_subs.py 会自动恢复语言的间距(对于法语,是不换行空格)。将结果保存为 JSON 格式 {index: "punctuated text"},然后:
python3 scripts/repunctuate.py --transcript /tmp/subs_transcript.json --punct /tmp/punct.json --output /tmp/subs_transcript_punct.json
如果你添加、删除或更改了一个单词,该脚本会拒绝(它会显示差异):这可以保持单词与时间戳的对齐。注意片段之间的流畅性:延续一个句子的片段以小写开头,开始一个句子的片段以大写开头。在后续步骤(步骤 6)中使用此添加了标点的转录(--transcript)。
同时删除任何片尾幻觉:在最后的静音部分,whisper 经常编造出一行文字,例如字幕致谢或“Thanks for watching”(每种语言的典型行都列在 languages/<code>.json 的 whisper_hallucinations 下)。使用规则 seq_rule 将其 N 改成 0 来删除它。另一种幻觉:连续的片段具有相同的文本,并且其单词的持续时间接近零(start==end),这是在静音部分重复编造出来的(例如“here we go”重复 3 次)。检测方法:片段 i 和 i+1 具有相同的文本且单词持续时间为 0。修复:在构建之前从转录中删除它们(del segs[i:j])。
3. 阅读 whisper 转录和参考脚本
逐片段打印转录的 text,并与输入的 Premiere 文本(步骤 1)进行比较。whisper 已经修复了很多问题,但名称上的拼写错误仍然存在。阅读参考脚本以获取专有名词及其引用的作品标题的规范拼写。
4. 构建 corrections.json(智能核心)
这是决定质量的关键步骤。通过将 whisper 听到的内容与脚本中的名称进行比较来构建修正文件。检查受影响片段的逐词拆分,以编写精确的规则:
python3 scripts/words_of.py /tmp/subs_transcript.json 2 9 38 # 要检查的片段的索引 token
输出索引了每个单词(9:Smyth 10:Jones):这让你可以编写精确的规则,而无需猜测分词方式(一个名称占用多少个 token,是否保留连字符等)。
corrections.json 的格式(所有键都是可选的):
{
"quote_char": "\"",
"normalize_cotes": true,
"seq_rules": [
[["jon","smyth"], ["John","Smith"]],
[["train","park"], ["Trent","Parke"]],
[["arthus","bertrand"], ["Arthus-Bertrand"]]
],
"word_rules": {"macdum": "Magnum", "scholler": "Schoeller"},
"titles": [
{"match": ["the","decisive","moment"], "canonical": ["The","Decisive","Moment"]},
{"match": ["errance"], "canonical": ["Errance"], "sep_comma": true}
]
}
seq_rules:多词名称,或 1 对 N / N 对 1 的替换。匹配基于单词的小写形式,并去除标点。用于任何跨越 2 个或更多 whisper 单词的名称,或者当单词数量发生变化时。大小写:目标单词按原样使用:编写你想要的最终大小写,包括在句子中的位置(如果单词以句子开头,则使用["Today","we",...],而不是["today",...])。源/目标长度相同:标点(内部逗号)和时间戳会按索引保留;长度不同:时间戳会重新分配,并且只有第一个/最后一个单词的标点得以保留(被替换单词的内部逗号会丢失:在步骤 7 中重新读取这些片段)。删除:空的目标[["uh"], []]会删除该单词(仅用于 whisper 加倍了的真正孤立的噪音;对于转录错误的单词,应该纠正而不是删除,否则你会失去句子的一部分)。word_rules:单个词的名称,强制大小写(小写键映射到精确值)。titles:引用的作品(书籍、系列、有标题的照片)会自动添加引号(在第一个单词前添加开始引号,最后一个单词后添加结束引号),以及一个用于标题正确大小写的canonical。sep_comma: true会在标题后添加逗号(当多个标题连续出现时很有用)。将作品放在引号中,而不是人物名称或品牌。normalize_cotes:应用语言文件的拼写规范化规则(normalize_words),用于 whisper 不一致地书写的变体。除非你设置它,否则默认为关闭。french_typography:语言排版间距的开关(名称是历史遗留的;它适用于正在使用的任何语言)。默认值取自语言文件:对于法语,在双标点符号和闭引号之前以及开引号之后使用不换行空格(U+00A0);对于英语,则没有。仅当你想使用附加标点时,才设置为false。当你在步骤 2b 中重新添加标点时,标点必须附加(Flop:);build_subs.py会自动恢复间距。
5. 与用户一起确定不确定项(绝不猜测)
whisper 有时会产生一个不明确或难以理解的非名称单词。不要默默地猜测。 列出这些点及其时间码,并询问用户实际说了什么,如果上下文或脚本有提示,可以提供假设。区分:
- 直接纠正:由脚本确认的专有名词;明显且确定的拼写错误。
- 先询问:不明确的非名称单词,或任何改变含义的修正(例如,whisper 的“who feels”与脚本的“without”:含义相反,即使脚本能确定,也需要标记)。
不要重写口语风格(俚语、填充短语、重复):字幕必须与音频一致。
在等待答案期间在文件中写入什么(你不需要阻止生成):生成一个应用了所有安全修正以及你对常见不明确单词的最佳假设(并标记它们)的初始版本,但对于脚本中不存在的不确定名称,保留 whisper 的原始拼写:绝不要编造一个专有名词。交付包含要验证的点列表(时间码)的版本,然后在收到答案后更新 corrections.json 并重新生成。这是正常流程:生成,提出问题和展示结果,完善。
6. 生成修正后的文件
python3 scripts/build_subs.py \
--transcript /tmp/subs_transcript.json \
--original "<Reel.json>" \
--corrections /tmp/corrections.json \
--output "<folder>/<name>_corrige.json" [--lang <code>]
该脚本会克隆原始文件的格式(speaker UUID、语言、排序的键、最小化的 UTF-8),恢复标点和大小写,应用修正,添加引号,重新计算 eos,并打印一个验证报告。
将 <name>_corrige.json 写在原始文件旁边,永远不要覆盖原始导出文件(它是用户的源文件)。_corrige 后缀是脚本依赖的文件名:请保留它。
6a. 每次检查 build_subs.py 的两个常见陷阱
以下是多个视频中出现的两个错误,每次构建后都必须检查:
- 小数重复。 whisper 将
6.6拆分为 2 个 token(6+.6;同理1,33或4 AM被拆分为4+AM);标点重新注入会导致输出中出现重复(1.4 .4,6.6. .6),并且seq_rules匹配错误(规范化不会剥离内部的./,)。检测:在最终输出中 grep 模式\d[.,]?\s+[.,]\d。修复:在构建之前对转录进行手术:将 2 个 token 合并为一个({'text':'1.4','start':a.start,'end':b.end,...};whisper 单词 schema =text/start/end/confidence,而不是duration),然后重新构建。 - 孤立的句末标点被丢弃。 一个存在于 whisper 片段
text中但没有对应单词 token 的?/!/:会消失(How did we do it ?中的?会丢失)。检测:比较 whisper 文本和输出中的count('?')(以及!、:)。修复:手动编辑输出 JSON:在之前的 token 后重新附加?(使用语言的间距)。
如果检测到陷阱 1,请修复转录并重新构建(步骤 6)。如果检测到陷阱 2,请直接修补输出。
6b. 将时间戳与音频重新对齐:强制对齐(始终执行)
whisper 的单词时间戳会漂移(在快速、多次录制的独白中,测量到的漂移高达提前 7 秒,且是非线性的):whisper 压缩了录制之间的静音,导致声音在字幕之后出现。典型的抱怨是:“当‘Ah’出现时,我已经听到下一句话了”。DTW(-dtw)无济于事(相同的偏移量),而在稀疏锚点上进行时间扭曲会扭曲原本正确的时间区域。whisper 的文本是好的,只有它的时间戳是错误的,所以这部分完全专注于修复时间戳。
python3 scripts/realign.py \
--original "/tmp/subs_original_premiere.json" \
--corrected "<folder>/<name>_corrige.json" \
--audio /tmp/subs_audio.wav \
--output "<folder>/<name>_corrige.json"
该脚本会对齐每个单词。它在覆盖模式下运行:它就地修改修正后的文件,添加精细的时间戳。对于 3 分钟的视频,这需要大约 15 秒。
如果需要:你也可以使用 --scrub-to-audio 将整个字幕文件覆盖到音频上(如果你只想保留由强制对齐决定的新的分段),但这很少需要。
7. 在 Premiere 中验证(必须)
在 Premiere 中重新导入 <name>_corrige.json。将时间轴放大到片段级别,并对照原始备份 JSON 检查前 30 秒的单词时间戳(/tmp/subs_original_premiere.json)。确认:
- 字幕与音频对齐。
- 没有单词重复(陷阱 1)。
- 句子末尾的标点符号存在(陷阱 2)。
- 引号围绕标题。
- 最后一个单词落在视频的时长内。
如果对齐看起来有点偏差,realign.py 有一个 --offset 参数(以秒为单位的浮动值,例如 --offset 0.3)可以将整个字幕调整到正确的时间。抵销的原因:realign.py 可能错误地检测到了单词边界。
附录
文件名约定
- 原始 Premiere 导出:
<Reel>.json - 修正的文件:
<Reel>_corrige.json(与原始文件放在同一文件夹中) - 临时工作文件:
/tmp/subs_*
语言代码
en:英语fr:法语
每个语言文件包含特定于该语言的排版规则、标点符号、引号以及常见的 whisper 幻觉。要添加新语言,请参阅 ../rough-cut/references/languages.md 并创建一个新的 <代码>.json 文件。