This is one thing, not a stage in a pipeline. Do it whenever the user calls for it — right after editing, or again after deleting two more sentences.
It has only one prerequisite:the ledger already exists. Not because it comes after editing, but because without the ledger it doesn't know which sentences were kept and which second of the final cut each falls on.
需要 edit-list.json(账本)、transcript.json(逐词稿)
产出 subtitles.json
Stop when done,don't direct the user's next step。
First read and execute the Business Skill's stage contract, criteria in Subtitle Proofreading Rules, proper-noun spellings in AI Word Dictionary。
Who says what counts
This is the foundation of this section; everything else follows from it.
剪口播 说了算「留哪些话、在什么时候」 ← 时间的唯一真相
字幕 说了算「屏幕上显示什么字」 ← 显示的唯一真相
A subtitle screen storesa word id list + display text,not seconds. Timings are computed fresh from the ledger each time. So the two ledgers have no second timing that can drift apart, and invalidation is precise down to the screen: "the words of screens 7 and 12 were cut," rather than that vague "subtitles may be out of date" that no one can act on.
It is intentional that subtitles store their own text. The transcript answers "what he said", the subtitles answer "what to show people" — punctuation, removed filler words, the formal spelling of proper nouns: these were always meant to differ. Forcing one string to do both jobs would mean subtitles could never get a comma.
0. Do a Runtime precheck first, every time
Precisely obtain from Codex's enabled Plugin list the chengfeng-videocut of the source.path。
SKILL_DIR , find to guess the install directory:
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 --json
ready: continue.missing: the script shows "Installing from GitHub Release" once, then resumes automatically after verification completes.runtime_unhealthy, installation failure, or post-install doctor failure: report a structured diagnosis and stop. Stop means stop: do not continue the flow with a homemade review page, player, timeline, or any substitute interface. Any output produced while the product is unavailable is untrustworthy (real case: when Runtime was missing, the Agent hand-rolled a "screening room" web page whose review decisions were completely incompatible with the product's ledger format, and the user had to redo everything). The only correct action: give the user the structured diagnosis and point them to install or file an Issue.- During the precheck stage, do not start services, open Studio, or create projects.
For the detailed protocol see Runtime and Product Contract。
1. Entry assertions
node "$RUNNING" --json
node "$VC" inspect "$jobDir" --json
| Assertion | If not satisfied |
|---|---|
edit-list.json exists and has clips |
Explain clearly that the ledger is missing and have the user first call edit spoken-word video.Do not edit it yourself. |
transcript.json exists |
The project isn't ready; stop. Do not fabricate one yourself. |
Do not assert source_cut.mp4, nor transcribe. Subtitles need neither—see the next section.
2. Word corrections: dictionary first, transcript second
No need to re-transcribe. The original word-by-word transcript already carries timestamps, and the ledger knows which segments are kept— the second in the final cut where each word falls iscalculated, not obtained by listening again:
词在源片的第几秒 转录里就有
哪几段留着 账本里就有
词在成片的第几秒 两者一算就出来
Sending it through ASR again just listens to the same words one more time, costs money, takes time, and hears worse—proper nouns have to be corrected all over again.
(transcript retranscribe is a leftover from the previous design, when subtitles were going to get their timeline from transcribing the cut video.
Once subtitles switched to anchor-word ids, this problem went away.Don't use it for subtitles.)
Two different tools, both are needed.
# 词典:这个说话人的固定写法,不需要证据
node "$VC" transcript dictionary "$jobDir" --dictionary "$DICT" --json
# 文稿:作者手上有稿子时才做,逐处要证据
node "$VC" transcript align "$jobDir" --script "$scriptPath" --json
词典 永远这么写 不看上下文,直接改,列出改了哪几处
文稿 上下文对得上才这么写 逐处要证据,对不上的报「不敢定」,不猜
Why a dictionary is required and the transcript alone won't do: spoken takes get re-recorded. In a real project the same line was recorded four times, so contexts like "连接叉" and "叉是" all appear more than once, and align can only report "can't decide"— it doesn't guess, by rule, becausea wrong name is worse than a misheard one. Of the three instances of "叉" it dares to correct only one. The dictionary doesn't have this problem: in this speaker's work, "叉"is simply「X」。
There is only one dictionary, at the plugin level references/ai-term-dictionary.md: it corrects the transcript,
and cutting spoken takes and subtitles consume the same transcript. Don't create another one under this skill.
After correcting, look at that list. The dictionary doesn't look at context, so it can also make mistakes—for example, normalizing "Skills" (Claude Skills, a plural proper noun) that the speaker actually said into "Skill". If you see something wrong, fix the dictionary, don't patch things on the command side.
transcript align only reports, doesn't change. To apply its proposals, convert corrections into [{wordId, text}] and run again:
node "$VC" transcript correct "$jobDir" --file "$correctionsPath" --json
After correcting the transcript,subtitle screens that reference these words get rewritten in the same step, reporting subtitles.updated.
Those that can't be changed (that line was rewritten by someone) go into needsAttention, reported for a human to look at, no guessing.
When neither the dictionary nor the transcript has any spelling that can confirm the correct one, guessing is not allowed. Report it for the user to add to the dictionary— once added to the dictionary, the next video is automatically correct; guess right once and you'll have to guess again next time.
3. Splitting screens
node "$VC" subtitle build "$jobDir" --json
Four rules, in order:
① 这里删掉过话 删掉的两边在时间轴上挨着,意思上毫无关系。
删掉的是「静音」不算 —— 把句子中间的停顿剪掉,不能把句子劈开。
② 任何标点 火山给的,句号和逗号都算 —— **和剪口播分段是同一个粒度**,
两边断在同一处。标点比任何时间证据都强:说话人可以两句连着
说不喘气,也可以在一句中间停顿。
③ 段落边界 + 停顿 **只在这个词没有标点时才用**。它是「没标点时靠段落猜句子结束了」,
有标点就该由②和逗号那条管 —— 它们会考虑屏够不够长,③ 不会。
(在带标点的边界上也触发③,真实项目从 40 屏变 43 屏,切出更多碎屏)
④ 观众听到长停顿 句子内部要断,得有更长的静音才够格。
Cutting spoken takes and subtitles consume the same punctuation, at the same granularity: one comma per segment / per screen.
Fragments (one or two characters) get merged away, butnot merged across a period —"你看" starts a new sentence, not the tail of the previous one; merging backward gives "…调用Grok CLI你看", blurring the two sentences together.
Short sentences the speaker says quickly are kept (in a real project "每天早上" and "执行任务" were 0.6 seconds each). That's really how he said it, no alarm. One comma per screen inevitably produces such screens; alarming on it is alarming on normal speech—too many alarms and nobody looks at warnings anymore.
The rest, longer than one screen, are split evenly into several screens,one line per screen. If splitting produces a fragment of one or two characters, it means the boundary in ② isn't actually a period; merge the fragment back into the previous screen.
The command reports four things, all of which should be looked at:
stale 被剪辑改动的屏 —— 精确到第几屏、丢了哪几个字
tooFast 字太多、时间不够读
transcriptMoved 转录已经不是当初那一份了
When subtitles already exist—stop here and ask
Without adding --replace It will refuse outright.This is the only place in this skill that requires user consent,
because --replace wipes out split screens and wording that people spent time on, and the product itself cannot recover them.
用户想改几个字 不要 build。让他在 Studio 里直接改,或者告诉你改哪几屏。
用户要推倒重来 确认过了再加 --replace。
4. Review: look screen by screen in Studio
node "$VC" open "$jobDir" --json
After opening, switch to the **"Subtitles" tab** in the left column. One line per screen: the serial number and time on the left, the text on the right, click in to edit directly.
- Enter = start a new screen from the cursor position
- Backspace at the start of a line = merge into the previous screen
- In the right column "Parameters → Subtitles", choose one of the four presets, one style for the whole film
- Subtitles are visible directly on the picture, and position and font size are proportional to the picture—the preview and the final render use the same set of numbers
During review, at least have the user confirm: whether proper nouns are correct, whether any characters were swallowed, whether the sentence breaks read smoothly, and whether each screen stays long enough to finish reading.
5. Up to subtitles.json and no further
This segment is done. No physical cutting, no storyboarding, no animation, no rendering the final film,and do not push the user to the next step。
The report must be written separately: Product structured readback is API/readback PASS; the actual same-project browser frame review is the only visual frame PASS; when no one has actually looked at whether the subtitles match the picture, it is always human listening UNVERIFIED, and DOM, screenshots, or file probing must not be used as a substitute.
Boundary: burning into the picture is not this segment's job
The output method is set toburn into the picture, and the picture is drawn by the export segment. The output of subtitles is subtitles.json —
the dimensions inside are all percentages of the picture, and the export converts them to pixels according to the output resolution; both sides use the same set of numbers.
The mp4 exported today has no subtitles, that step has not been done yet. The report must state this consequence clearly, do not just say "subtitles are done" and leave it at that—the user will think they are in the final film.
Recovery and failures
subtitles_exist: subtitles already exist.Ask first, do not directly--replace。revision_conflict: another place is also writing. Re-read, do not overwrite automatically.- The dictionary reports "line N cannot be understood": that is a table row with a broken format on that line. The rule only recognizes table rows (
| 正确写法 | 常见误识别 |), and prose and lists are silently ignored. transcript correctreports "must not change any word id, time or gap flag": something that changes timing got mixed into a text-changing proposal. Changing text only changes text.runtime_unhealthy: do not reinstall in a loop.- No failure may present "transcription complete" as "subtitles are done".What ASR produces is raw material, not the product.