给剪好的口播做字幕:直接用已有的逐词稿加账本算出剪后时间(不必导出、不必重新转录)、用词典和作者文稿改写听错的专名、按句子分屏、在 Studio 里逐屏复核。用户说做字幕、加字幕、改字幕、重新分屏、字幕不对时使用。不要用于删词剪辑、物理剪切、分镜动画或成片渲染。
73
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
这是一件事,不是流程的一段。 用户什么时候喊它就什么时候做——刚剪完可以做, 做完又删了两句可以再做一遍。
它只有一个前提:账本已经存在。不是因为它排在剪辑后面,而是因为没有账本, 它不知道哪几句留着、各自落在成片的第几秒。
需要 edit-list.json(账本)、transcript.json(逐词稿)
产出 subtitles.json干完就停,不指挥用户下一步。
先读取并执行 业务 Skill 的阶段合同, 判据见 字幕校对规则,专名写法见 AI 用词词典。
这是这一段的地基,其余都从这里推出来。
剪口播 说了算「留哪些话、在什么时候」 ← 时间的唯一真相
字幕 说了算「屏幕上显示什么字」 ← 显示的唯一真相一屏字幕存的是词 id 列表 + 显示文字,不存秒数。时间每次从账本现算。 于是两本账没有第二份时间可以走偏,失效也能精确到屏:「第 7、第 12 屏的词被剪掉了」, 而不是那句谁也点不动的「字幕可能已过期」。
字幕存自己的文字是故意的。 转录回答「他说了什么」,字幕回答「给人看什么」—— 标点、去掉的口头禅、专名的正式写法,这些本来就该不一样。逼一份字符串同时干两件事, 字幕就永远加不了逗号。
从 Codex 已启用 Plugin 列表精确取得 chengfeng-videocut 的 source.path。
SKILL_DIR 不是 Codex 保证注入的变量;禁止依赖它、硬编码开发机路径或用 find 猜测安装目录:
PLUGIN_ROOT="$(codex plugin list --json | node -e 'let s=""; process.stdin.on("data", c => s += c); process.stdin.on("end", () => { const rows = JSON.parse(s).installed || []; const hit = rows.filter(x => x.enabled && x.name === "chengfeng-videocut" && x.source && x.source.path); if (hit.length !== 1) process.exit(1); process.stdout.write(hit[0].source.path); });')"
test -n "$PLUGIN_ROOT" && test -f "$PLUGIN_ROOT/.codex-plugin/plugin.json" || { echo "chengfeng-videocut enabled plugin root unavailable" >&2; exit 1; }
ENSURE="$PLUGIN_ROOT/scripts/ensure-runtime.cjs"
RUNNING="$PLUGIN_ROOT/scripts/ensure-running.cjs"
VC="$PLUGIN_ROOT/scripts/videocut-cli.cjs"
DICT="$PLUGIN_ROOT/references/ai-term-dictionary.md"
node "$ENSURE" --install-if-missing --jsonready:继续。missing:脚本只提示一次「正在从 GitHub Release 安装」,校验完成后自动续跑。runtime_unhealthy、安装失败或安装后 doctor 失败:报告结构化诊断并停止。详细协议见 Runtime 与产品契约。
node "$RUNNING" --json
node "$VC" inspect "$jobDir" --json| 断言 | 不成立时 |
|---|---|
edit-list.json 存在且有片段 | 说清楚缺账本,让用户先喊剪口播。不要自己去剪。 |
transcript.json 存在 | 项目没准备好,停止。不要自己造一份。 |
不要断言 source_cut.mp4,也不要去转录。 字幕两样都不需要——见下一节。
不需要重新转录。 原来那份逐词稿已经带着时间戳,账本知道哪几段留着—— 每个词落在成片的第几秒是算出来的,不是再听一遍听出来的:
词在源片的第几秒 转录里就有
哪几段留着 账本里就有
词在成片的第几秒 两者一算就出来再送一遍 ASR 只是把同样的话重新听一次,花钱、花时间,而且听得更差——专名要重新改一遍。
(transcript retranscribe 是上一版设计的遗留,那时字幕打算靠转写剪后视频拿时间轴。
字幕改成锚词 id 之后这个问题就没了。做字幕不要用它。)
两件不同的工具,都要用。
# 词典:这个说话人的固定写法,不需要证据
node "$VC" transcript dictionary "$jobDir" --dictionary "$DICT" --json
# 文稿:作者手上有稿子时才做,逐处要证据
node "$VC" transcript align "$jobDir" --script "$scriptPath" --json词典 永远这么写 不看上下文,直接改,列出改了哪几处
文稿 上下文对得上才这么写 逐处要证据,对不上的报「不敢定」,不猜为什么必须有词典,光靠文稿不行:口播会重录。真实项目里同一句录了四遍, 于是「连接叉」「叉是」这些上下文全都出现不止一次,align 只能报「不敢定」—— 它按规矩不猜,因为名字写错比听错更糟。三处「叉」它只敢改一处。 词典没有这个问题:在这个说话人的作品里,「叉」就是「X」。
词典只有一份,在插件级 references/ai-term-dictionary.md:它改的是转录,
而剪口播和字幕吃同一份转录。不要在这个 skill 下面再建一份。
改完要看那份清单。 词典不看上下文,所以它也会改错——比如把说话人真的说的 「Skills」(Claude Skills,复数专名)normalize 成「Skill」。看到不对就改词典, 不要在命令这边打补丁。
transcript align 只报不改。要应用它的提案,把 corrections 转成 [{wordId, text}] 再跑:
node "$VC" transcript correct "$jobDir" --file "$correctionsPath" --json改完转录,引用了这些词的字幕屏会在同一步里跟着改写,回报 subtitles.updated。
改不动的(那一行被人重写过)进 needsAttention,报出来让人看,不猜。
词典里没有、文稿里也没有任何一个写法能确认正确时,不许猜。 报出来让用户补词典—— 补进词典下一条视频就自动对了,猜对一次下次还要再猜。
node "$VC" subtitle build "$jobDir" --json四条规则,按顺序:
① 这里删掉过话 删掉的两边在时间轴上挨着,意思上毫无关系。
删掉的是「静音」不算 —— 把句子中间的停顿剪掉,不能把句子劈开。
② 任何标点 火山给的,句号和逗号都算 —— **和剪口播分段是同一个粒度**,
两边断在同一处。标点比任何时间证据都强:说话人可以两句连着
说不喘气,也可以在一句中间停顿。
③ 段落边界 + 停顿 **只在这个词没有标点时才用**。它是「没标点时靠段落猜句子结束了」,
有标点就该由②和逗号那条管 —— 它们会考虑屏够不够长,③ 不会。
(在带标点的边界上也触发③,真实项目从 40 屏变 43 屏,切出更多碎屏)
④ 观众听到长停顿 句子内部要断,得有更长的静音才够格。剪口播和字幕吃同一份标点,粒度也一样:一个逗号一段 / 一屏。
碎片(一两个字)会被并掉,但不跨句号并 —— 「你看」开启新的一句, 不是上一句的尾巴,往前并会得到「…调用Grok CLI你看」,两句糊在一起。
说话人说得快的短句会留下来(真实项目上「每天早上」「执行任务」各 0.6 秒)。 那是他真的这么说的,不报警。 一个逗号一屏必然产生这种屏, 为它报警等于对正常说话报警——报多了就没人看警告了。
剩下比一屏长的,均摊拆成几屏,一屏一行。切出一两个字的碎片,说明②那个边界 其实不是句号,把碎片并回上一屏。
命令回报四件事,都要看:
stale 被剪辑改动的屏 —— 精确到第几屏、丢了哪几个字
tooFast 字太多、时间不够读
transcriptMoved 转录已经不是当初那一份了不加 --replace 它会直接拒绝。这是这个 skill 里唯一要征求用户同意的地方,
因为 --replace 冲掉的是人花时间做的分屏和措辞,产品自己恢复不了。
用户想改几个字 不要 build。让他在 Studio 里直接改,或者告诉你改哪几屏。
用户要推倒重来 确认过了再加 --replace。node "$VC" open "$jobDir" --json打开后切到左栏的**「字幕」标签页**。一行一屏:左边序号和时间,右边就是字,点进去直接改。
复核时至少让用户确认:专名对不对、有没有吞字、断句读起来顺不顺、一屏停留够不够读完。
subtitles.json 为止这一段做完了。 不做物理剪切、不做分镜、不做动画、不渲染成片,也不催用户下一步。
报告必须分开写:Product 结构化 readback 为 API/readback PASS;真实同项目浏览器帧 审核才是 visual frame PASS;没有人实际看过字幕跟画面对不对时一律为 human listening UNVERIFIED,不得用 DOM、截图或文件探测替代。
产出方式定的是烧进画面,而画面是导出那一段画的。字幕的产出就是 subtitles.json ——
里面的尺寸全是画面的百分比,导出按输出分辨率换算成像素即可,两边同一套数。
今天导出的 mp4 里没有字幕,那一步还没做。报告里要写清楚这个后果, 不要说「字幕做好了」就完事——用户会以为成片里有。
subtitles_exist:已经有字幕了。先问,别直接 --replace。revision_conflict:另一处也在写。重新读取,不自动覆盖。| 正确写法 | 常见误识别 |),
散文和列表都会被安静忽略。transcript correct 报「must not change any word id, time or gap flag」:
改字的提案里混进了改时间的东西。改字就只改字。runtime_unhealthy:不要循环重装。0e991ed
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.