SuperAPI AI Directory

video-talkcraft

Design & creation

View repository

Updated 10-16 10:38

About this skill

video-talkcraft — talking-head video skill

Three major sources combined: Pipeline (voiceover→word-level timestamps→Remotion) + Vocabulary (108 motion recipe cards, all with playable demos + self-contained tsx) + Shots (seven-layer model anti-PPT system) + Visual language (Apple paradigm default version).

Core paradigm: narration drives the visuals, every sentence must have a live visual response (extremely slow camera push-pull/changes to existing elements), but new elements enter only at semantic beat boundaries, mechanically "one new element per sentence" is forbidden (one element per sentence is the institutional root of accumulation-style clutter; storyboarding is cut by semantic paragraphs, layout budget see cinematography.md §4.5); one beat has only one protagonist, and yields after speaking; subtitle sentence boundaries = full-film time anchors.

Process

① 文案 → ② 配音输入+时间戳(本机CPU) → ③ 素材 → ④ SHOTBOOK 层矩阵 → ⑤ 实现(全局系统 → 首镜先做先确认 → 其余镜头)
                                → ⑥ 渲染 → ⑦ 三重验收(机器闸全过 + 1 轮审片修 P0/P1)→ ⑧ 交付(可选续审 ≤3 轮)

⓪ Pre-work checkup: unify dependencies + aspect ratio and visual language

There is only one copy of dependencies: all talking-head projects' remotion/node_modules and workbench/node_modules are soft links pointing to <skill根>/runtime/node_modules , and the version is pinned only in runtime/package.json (@remotion/* all same version). The first thing when starting a new film (user locked on 2026-09-15: check for updates every time a new video is made):

bash <skill根>/runtime/check-runtime.sh --upgrade   # 装好共享依赖 + 共享无头浏览器 + 链好 workbench;Remotion 有新版就全家升级并冒烟渲 1 帧,不过自动回滚

Only when the last line [runtime] OK passes do you proceed; after upgrading, commit the changes to runtime/package.json / package-lock.json . Do not upgrade mid-production (master segment cache does not recognize dependency versions, segments rendered with mixed versions are inconsistent). In the project directory never run npm install (it will write into the shared directory); to add packages → add them to runtime/package.json,npm ci, then commit.

Aspect ratio and visual language (decide at start, referenced throughout)

  • Aspect ratio defaults to landscape 1920×1080 (user preference); use 1080×1920 only when explicitly publishing to Douyin/vertical channels
  • Visual language: if the user explicitly specifies a style, follow the user's (replace the entire token set); if not specified, first determine the domain from the talking-head script, derive this film's style tier (references/design-language.md §0: read the full script and answer "what is being said / to whom / in what tone" → domain → style tier table giving base color strategy / accent / font character / materials and card styling / chart language / asset character / energy tier, write into SHOTBOOK §0 G0 风格档), the skeleton is still the Apple paradigm (one accent color/one shadow/alternating base colors for act division/two font weights/ default act backgrounds: light pastel-mesh-flow / dark mesh-flow-dark——12 background types see §1.1, code template/motion-systems/backdrop.tsx), derive this film's tokens and save as theme.ts. The only rule that holds for any style: never pick colors casually scene by scene. The card is neutral UI; once it goes into the film it must be skinned: the demo / tsx of a motion card is a style-neutral presentation; after copying it into the project, change the skin according to the style profile (colors / fonts / corner radius / materials / chart axes and markers / placeholder graphics), but do not change the motion essentials (timing / easing / geometric proportions / hierarchy). For the contract and counterexamples, see design-language §0.4; in SHOTBOOK, write a skin line for every shot.

① Voiceover script

  • One information point per sentence, with the hook in the first sentence (number/conflict); 13 sentences ≈ 95s
  • All numbers must be in Chinese characters (timestamps are anchored character by character according to the text,197747 cannot be aligned with the pronunciation of "one hundred ninety-seven thousand"); write English brand words directly (mixed Chinese-English alignment has been verified)
  • Research and verify the facts first, and list a "fact red-line checklist" (numbers that must not be wrong / unverified data must not be cited)

② Voiceover input → pre-cut → word-level timestamps (default: local CPU; optional: Fish Audio)

Default to using a finished voiceover: live recording or any TTS is fine; input = one complete voiceover (wav/mp3) + a voiceover script that matches it word for word. If there is no recording and the user chooses synthesis, plan B below may be used; it will not replace existing voiceover or the default local CPU workflow.

②-0 Voiceover pre-cut (required for live recording; TTS only compresses breath pauses, so it can be skipped) — cut out filler words (um / uh / like...), stutters and repeated takes, and overly long pauses, before making timestamps (touching the audio after timestamps are done = timing / beats all become misaligned; pre-cut → timestamps → all subsequent steps, the order cannot be changed):

python3 scripts/voice_trim.py audio/raw.wav script.json --dry-run --words-out audio/asr_words.json   # 先只出报告:逐条 [filler / repeat / pause] 起止 + 共剪多少;ASR 结果落盘
python3 scripts/voice_trim.py audio/raw.wav script.json --words audio/asr_words.json --out audio/full.wav \
    [--video dh/host_raw.mp4 --video-out dh/host.mp4]               # 用户认可后落盘(吃回上一步的词表,不再跑一次 ASR);同一条录音的人物视频用同一 EDL 剪(音画逐帧同长)
  • The script is the ground truth; prefer missing something over making a mistake: only cut inserted segments that are not in the script in the ASR — those made entirely of a filler-word list (filler), and stutters / repeated takes equal to the immediately adjacent script text (repeat); never cut characters in the script that the ASR misheard (cutting them would cut the real pronunciation of that character); for repeated takes outside the script with changed wording, only report them,--cut-unmatched only then cut. The default source of the word list runs FireRed (the same backend as the timestamps below), and it also accepts word-level SRT exported from CapCut and similar tools (--srt) or word-level JSON (--words); sentence-level SRT cannot locate filler words, and the script will warn. With no script, only cut filler words whose entire token text is exactly in the conservative word list (um / uh / er / um...): compare by normalized text exactly, without recognizing homophones (五≠唔, 饿≠呃); with no script, only report adjacent repeated short words rather than cutting them (under word-level tokens, reduplication and stuttering cannot be distinguished). When cutting transparent webm from the same recording, it will decode one frame to check alpha; if the frame count matches but transparency is lost, it is also a FAIL.
  • Pauses: compress silences ≥0.7s to 0.35s (keep the quietest window of real room tone within the segment, do not fill in digital zero), and leave 0.25 / 0.5s of padding at the beginning and end; first find the energy valley at the cut point, then snap to the 30fps frame grid;cuts.json is the EDL (including the source → new timeline mapping table; cutting people videos and any later reconciliation use it).
  • The dry-run report must be shown to the user before writing to disk: for entries marked "cut point does not fall in silence, listen to this spot," have the user listen to that spot; the script only deletes, does not synthesize, and does not change speed or pitch. After cutting the people video, run the preflight.py --media-only from ③ to check the duration — the same EDL only holds for the same recording.

Word-level timestamps (for the pre-cut audio/full.wav):

# 方案 A(已有录音,本机 CPU 对齐):
pip install zhconv pypinyin sherpa-onnx soundfile numpy   # 默认后端 FireRedASR2-CTC int8 的全部依赖
# 首次:下载模型 767MB(model.int8.onnx + tokens.txt)放 ~/.cache/koubo/<模型名>/,地址见脚本头注释
python3 scripts/timestamps_cpu.py audio/full.wav script.json audio/timestamps.json
# 备选(免手动下模型):pip install faster-whisper 后加 --backend whisper(首跑自动下载 460MB)
python3 scripts/make_timing.py audio/timestamps.json remotion/src/timing.json

# 方案 B(可选生成配音 + 时间戳,默认请求 s2.1-pro-free;可用性以 Fish Audio 为准):
pip install requests python-dotenv   # 还需 ffmpeg,生成前会检查
# 在 .env 配置 FISH_AUDIO_API_KEY=xxx 及可选 FISH_AUDIO_REFERENCE_ID=xxx(见 .env.example)
python3 scripts/tts_fishaudio.py script.json audio/full.wav audio/timestamps.json --timing-out remotion/src/timing.json
  • tts_fishaudio.py: by default --mode sentence one request per sentence, inserting --pause-sec the specified real silence (default 0.25 seconds);--mode stream Entire draft in one request, preserving natural pauses. Both receive UTF-8 SSE, and files are written only after generation finishes; streaming playback is not provided. Decode PCM first, then concatenate; calculate offsets by sample count. Error out on empty audio, missing/invalid alignment, or text mismatch; ignore only punctuation, case, and character width, and do not automatically convert number readings. Chinese uses interface character-level anchors, English interpolates within words;match/ok Only confirm the text mapping; listening is still required. If pre-cutting is done again after synthesis, CPU alignment must be rerun.
  • timestamps_cpu.py: ASR word-level timestamps → character-level alignment with the spoken script (CJK are reliable anchors, match key = traditional/simplified normalization + toneless pinyin, homophones are not counted as errors; Latin words are often misspelled by all ASR systems, interpolate between anchors) → quality-check each sentence's match, <0.90 flagged for manual listening verification. Default backend FireRedASR2-CTC (most stable at the tail, zero false positives; feeding in a whole ~200s segment crashes, memory grows quadratically, the script defaults to splitting into ≤75s segments by silence and then adding the offset back,--chunk-sec adjustable), alternative faster-whisper; cross-evaluation data for each backend and model download addresses are in the script header comments.
  • timestamps.json schema:{sr, total, sentences:[{i,text,start,end,match,ok,words:[{text,start,end}]}]} — words are CJK character-by-character + Latin whole-segment tokens (punctuation skipped); any alignment tool satisfying this schema can replace it.
  • make_timing.py: converts to timing.json (chars are 1:1 with the text character by character, punctuation has zero duration), for tSay/msSay anchor queries
  • Voice-over self-check (by ear): no popping/truncation/misreading; leave ~0.3s of breath between sentences, time anchors are more stable

②-1 Semantic annotation (required since 2026-09-22, owner references/semantic-annotation.md)

Annotate the spoken script sentence by sentence with "what this sentence is doing", saved to the project root semantics.json— this is the demand-side artifact for card selection; without it, card selection is left with only input type as a filter, and the remaining dozens of cards are picked by impression (that 2026-09-21 film's "I am Wanli" thus got the text card with the lowest energy in the entire library, and nobody found the P0 person-name entry in the library).

python3 <skill根>/scripts/semantic_annotate.py --init     # 从 timestamps.json 出骨架(带词法提示 hint)
#   逐句填 sem(26 词封闭词表,见 taxonomy.md「语义索引」)/ weight(main 主句 = 允许进新元素、sub 陪衬句)/ entities / need
python3 <skill根>/scripts/semantic_annotate.py --stats    # 校验:与时间戳逐字一致 · 词表封闭 · 词法硬规(我是X→自我介绍、点赞订阅→号召、数量词→数据)· 每镜至少一个 main

Annotation is a derived artifact: pre-cut → timestamps → annotation → everything after that; after revising the script or re-cutting, if text no longer matches, the annotation is invalidated, rerun --init. ②-1 comes before the storyboard, so the shot field is empty at this point (SHOTBOOK has not been written yet): after ④ is written it must be backfilled once, otherwise downstream shot-level coverage cannot check anything —

python3 <skill根>/scripts/semantic_annotate.py --sync-shots   # ④ 之后:按 SHOTBOOK 标题的起止秒(或 shots.json)回填 shot,只动 shot,标注不丢

③ Materials

  • The materials list is generated from the semantic annotation in ②-1, not by guesswork:need is 证据 sentences to capture real pages / find sources,身份 needs headshots and account information,量化 needs charts or data sources,entities the person names / brands / places / URLs are the search terms.
  • First assign a material mode to each shot (multiple choice, combinable):B-roll(live-action video) / 图片(photo / poster / illustration) / 截图(webpage / interface evidence screen) / 纯动效— e.g. "B-roll base + screenshot evidence card". For news/information topics, evidence takes priority: Playwright live screenshots are more informative than generic B-roll. The four tiers correspond to the input type codes in taxonomy.md V / image / screenshot / text, and SHOTBOOK writes one line per shot 素材:V(路径)· 图(路径)· 文(format see ④)
  • Live-action materials are a required item, not optional (hard rule as of 2026-09-06): zero B-roll / images in the entire film = only motion graphics + the on-camera presenter = what the audience sees is "presenting a PPT". preflight.py Reconciliation SHOTBOOK: Zero V / image shots directly FAIL; V / image shot ratio < 1/3 WARN (basis must be given in SHOTBOOK, e.g. evidence-type topics are mainly screenshots). "This film does not use B-roll" is not allowed to be written as a design decision; if no video source can be found, downgrade to images (Pexels photos original image → Pixabay); only if there are no images either does it go into the "unfinished / not collected list". Images and videos share the same source and same key; collection specifications (minimum resolution, disk directory, registration) see references/broll-sources.md"Supporting image collection"
  • Hard rule for real images: When the topic has real pages that can be screenshotted (product official website / GitHub / docs / gallery), browser/page-type shots in the final film are forbidden from using code mockups to pass as screenshots—the gray-bar fake UI in card demos is placeholder material, and in the final film it must be replaced as a whole according to the card's "reuse guide" with <img> real images; mock is only allowed for indicative UI with no real counterpart, and SHOTBOOK must annotate shot by shot "why there is no real image". Extension: When the voiceover talks about "this kind of final film / effect", the example footage must be a real final-film clip (<OffthreadVideo> embedding an existing final film / real-device screen recording crop, muted); when talking about "long pages / dashboards / parameter pages", use Playwright full-page long screenshots (magnifier / cruise-type motion graphics directly consume real-image coordinates). Annotation coordinates on real images must always be machine-measured, never visually estimated: page elements use DOM getBoundingClientRect, and rendered images use pixel-by-pixel measurement (a visual estimate off by ±30px will put the ring around a different element); for real images that scroll/move, annotations (ring/box/pill) must be pinned to the content coordinate system and move with the content; pinning to a fixed screen position is the root cause of misalignment
  • Single-video shots must have a thematic frame (user-finalized 2026-09-07): When the only subject in a shot is a piece of video (screen recording / single B-roll / someone else's final film or interview), posting it bare full-frame is forbidden, placing it bare on a white card is forbidden, and installing a fake player is forbidden (progress bar / play button / timecode: none at all—if it is not a real player, it is lying); the video area must be wrapped in a layer of template/components/theme-frame.tsx 's <ThemeFrame kind>: eight styles (retro browser window / magazine photo frame / 35mm film / Polaroid / engineering drawing / notebook / stamp perforation edge / double hairline), choose one style according to the film's tone, use only one style per film, and write it into the SHOTBOOK skin row; multi-video cards (bed-echo-blur foreground / split-60-40-story left cell / gallery-wall-dolly) also wrap their video areas in the same style. The frame only handles styling and its own decorative relay; overall entrance / exit / very slow push belong to the shot layer; the image inside the frame gets zero processing (no filter / no scaling / no fade in or out). There are only two exceptions: when video serves as a bed rather than the subject (bed-echo-blur / §1.2 live-action bed), and product interface cards (chat-gpt / claude-code type, where the skin is the content). Static images do not go into frames (a frame says "this is a recording"; if the image does not move, it looks fake at a glance; images use media-pop-in / slow-push-in). Rules and the eight-style table: design-language §1.3
  • Webpage capture does not paste images: Webpages judged usable when looking for information/material are forbidden from being pasted on screen as static screenshots in the final film; they must be "filmed" like handheld footage—scroll (evidence-scroll-tour◆: constant-speed upward scroll ≈10% page height/s; when reaching a key item, slow down in advance and stop for 1~2s) / cruise (stage-keyframe-tour◇: long page lies still on the stage, camera stops at points of interest one by one) / magnify (magnifier-detail circular magnifier take one look and withdraw,pip-zoom-box◈ pull it out and leave it hanging long-term) / mark (highlighter-sweep sweep a whole sentence,ink-underline◇ mark one word, scribble-annotation circle-annotate arrows,corner-bracket-frame◈ Frame area); a page that fits in one screen should at least use slow-push-in noise floor. One shot, one main subject: within a scroll/cruise segment, do not pop up a magnifier or draw lines on the spot; the sequence is "scroll to → stop → draw/magnify → scroll again"; for shot method selection, see shot-design.md §2④ "Web Page Shooting". Materials are written to disk according to the "Web Page Shooting Material Collection" spec in broll-sources.md: Playwright full-page 2× long screenshots + JSON of measured target coordinates from the same session DOM (the magnifier/drawing/stop points all consume these coordinates, connecting to the previous "no eyeballing"). Shooting is always done by Remotion on long screenshots (seek-safe, can be word-anchored), do not use browser screen recording (unstable frame rate, lazy loading and sticky headers breaking through, cannot match word-level timestamps). The only exception: a small screenshot that fits in one screen and serves only as a supporting element (one of multiple stacked in media-pop-in) may be placed statically into the card, but it still has Ken Burns and must not be the main subject of the shot
  • For shot rows marked B-roll, run 2–3 English visual concept words through Pexels + Pixabay API dual-source in parallel, and save candidates to assets/broll/; source tiering and licensing red lines (only use no-attribution sources) see references/broll-sources.md
  • Research bookkeeping: take and archive screenshots of each source page carrying key facts; insources.md links correspond one-to-one with local screenshots (it is forbidden to keep only links without evidence); when citing in the final video, prefer archived screenshots as visual evidence + micro-tier source line
  • When there is B-roll/screenshot + corresponding voiceover character material (screen recording/digital human finished product), characters are always downgraded to a persistent corner badge— circular avatar stamp (host-shrink-to-chip◆) or cutout person pasted in the corner, placed in the lower left / lower right corner; you must not cut the person away, and the person must not fill the frame. For the two routing options and all hard constraints, references/host-footage.md §5 is authoritative; for shot presets see shot-design.md §2⑦
  • Manim charts:--transparent --format=mov then must be converted to VP9 webm (-c:v libvpx-vp9 -pix_fmt yuva420p)
  • Everything is written to public/; pulling remote resources at render time is forbidden
  • ③ At wrap-up, always run the material health check (turn the hard entry rules from host-footage / broll-sources into assertions; the answers were already written in the reference, but no one checked at the right moment— this is the common failure mode of the two biggest pitfalls in the 2026-09-06 retrospective—so instead of adding more docs, make it mandatory before starting work):
python3 scripts/preflight.py --media-only --host remotion/public/dh/host.webm --fps 30 --voice audio/full.wav
# 查:人物素材 r/avg 帧率一致(VFR)· 源片无重复帧签名 · 素材 fps = 成片 fps(不等→成片改成素材 fps,禁 -r 直转)·
#     时长与配音逐帧对齐 · 真实宽高比 · 素材盘点(实拍/图片/网页长图数)· sources.md 在册。任一 FAIL 不进 ④

④ SHOTBOOK (must be produced, reviewed before implementation)

First use references/shot-design.md to fill in a three-plane layered worksheet for each shot (background plane/subject plane/text plane + motion effects for each plane

  • seven shot type presets), then expand into a layer matrix according to references/cinematography.md §4, example references/shotbook-example.md. For each scene: one sentence of intent + subject relay line + beat-by-beat layer matrix (beats anchored to word-level timestamps; every action must be able to answer "with whom it coordinates"). Every shot must have a "Materials:" line (machine-readable, preflight stats each file):- 素材:V(public/broll/gpu.mp4)· 图(public/stills/a.jpg, public/stills/b.jpg)· 截图(public/pages/gh/page.png)· 文— V / image / screenshot must give a path in parentheses; writing "to be collected" = FAIL; pure motion-effect shots must also write 素材:文. Required "## Unfinished / Not Collected Checklist" section: any "this video will not do X" either-or choice—either put it in the §0 design decisions (+ rationale), or put it in this section (+ blocking reason + whether fallback sources were tried); writing "none" is allowed, but omitting the section is not. This section specifically blocks "unfinished work being packaged as a design principle and thereby becoming a premise no longer questioned" (the real failure mode in the 2026-09-06 retrospective). G0 writes one line "sample shot: sNN" (for ⑤-1: do and confirm the first shot first): default s01; when s01 is a chapter card / empty shot or another shot without subject motion effects, choose the first shot that has a card, subtitles, characters, or source material. Beats must be machine-verifiable: every visual accent lands as remotion/beats.json ({t, anchor, sentence, what}, and t is always looked up from timing.json/atChar() , hand-typing approximate seconds is prohibited— hand-typed errors cannot be seen in still-frame QA), the SHOTBOOK beat table must match beats.json, and the machine gate uses scripts/beat_lint.py to validate against timestamps.json |Δ|≤0.1s. Not yet at the beat, do not reveal: numbers/graphics whose word anchors have not yet arrived must be completely invisible (opacity 0), dimming/graying out as a "preview" is prohibited; in-line numbers must be gated together with the characters that follow them (a hollow "just __ category" hanging there is likewise a defect). No empty stage at shot open: an empty window from the start of the shot to the first motion-effect anchor >1.5s must have carrying visuals (real b-roll / continuation of elements from the previous shot / a real asset wall); an empty canvas waiting for the word anchor is not allowed. Shot-end guard band: if a word-anchor motion-effect landing point is <0.7s from the shot's out point, either move it earlier or move it into the next shot—the landing point will be swallowed by the transition; beat_lint.py --shots shots.json the machine checks the hard floor of ≥0.5s. Act-level transition events must also go into beats.json: the occlusion peak moment of shape wipe / act change is also generated from word anchors and entered into the table— a transition event table with hand-typed absolute seconds floats outside the machine-verifiable system; neither still-frame QA nor beat_lint can see it. Layout budget (full table cinematography.md §4.5): split the storyboard by semantic paragraphs, one primary visual job per shot; text-only shots must be paired with accompanying graphics (2026-09-07 user feedback: "only text motion effects piling up is too monotonous"): for shots whose asset row has only "text" (except chapter cards), the layer matrix must add one row "G5 line-art diagram ← explaining X so draw Y" (references/schematic.md, code template/motion-systems/schematic.tsx), and preflight gives a WARN for text-only shots missing that row; the motion effect of a pivot sentence (a "but this time it's not X"-style turn/question) belongs to the next shot that it opens; at any moment, on-screen subject groups ≤3 (de-emphasized holdovers count), and each shot must leave at least one empty quadrant; one hero design per screen. Layout specification (full table references/layout.md): the budget governs "how much to place", and the specification governs "where to place it, how big, and how to align it"— SHOTBOOK must write a layout row for each shot (column span + group bounding box + alignment reference + type scale), and the keyframe opens debugOverlay to check nine items; any failure = P1; a single-sentence hero is centered but must not cover faces (including faces in B-roll; for vertical, choose a rule-of-thirds line outside the face). Layout rotation (2026-09-21 user: the final video has "the person in the lower left, assets in the middle square, text on the right—too fixed"; full table cinematography.md §4.5 item 9): before writing the per-shot matrix, first write the "layout rhythm table" in G0 (| 镜 | 人物形态·方位 | 素材容器 | 主卡 |, format cinematography.md §4) — the same presentation card (material presentation / data / camera movement type) must not be used for 3 consecutive shots, and across the whole film ≤1/3 (for films with ≥6 shots); among character form (half-body / lower-left corner badge / lower-right corner badge / inside split-screen cell / cutout person pasted in corner / brief exit) and material container (full-bleed / framed / split-screen cell / base bed / multi-image arrangement / 3D wall / long page), at least one must change across 3 consecutive shots; the same B-roll must not enter two adjacent shots. A single B-roll is chosen according to the seven forms in shot-design.md §2⑦, with adjacent shots using different forms, and multiple materials according to the relationship table in §2④′; preflight checks according to the rhythm table + skin-row check (consecutive use / proportion FAIL). Rotation is changing composition, not adding motion — within each form there is still only extremely slow camera push/pull. Each shot must write a “card selection line” (the semantics of ②-1 → card; candidates are provided by the machine):
python3 <skill根>/scripts/card_match.py --out qa/card-candidates.md   # 语义 × 素材行 × 卡索引 → 每镜每个主句语义的可行候选 + 排序理由 + 落选理由

Before running, first semantic_annotate.py --sync-shots backfill the shot (at ②-1 there is no storyboard yet). Besides listing candidates by semantics, the candidate table also gives a separate line for shots that declared materials: material continuation candidates — if V / image / screenshot is declared but there is no presentation / camera movement card to continue it = bare material pasting, and preflight judges FAIL. SHOTBOOK writes for each shot - 选卡行:<语义> → <卡>、<语义> → <卡>(the cards in the card selection line must actually land in the skin row; preflight reconciles this); selecting a card outside the candidates requires writing a line in that shot - 语义偏离:自我介绍 ← 开场已报身份,s11 不再重复(reason ≥4 characters; preflight recognizes this line and lets it pass). Add a semantics column to the beat row of the layer matrix (values taken from semantics.json, do not coin new words), so that “what this beat is doing” is machine-readable in the storyboard. Selecting a card requires reading the card experience: for each selected card, copy the “known pitfalls” and “placement self-check” of references/cards/<slug>.md item by item into the self-check column of that shot's layer matrix, and check item by item after implementation (example: for framing / circled annotation / underline cards, always check whether the annotation encloses the target;gooey-morph is only used for images, not text, and is centered when there are no characters;chapter-title-card each chapter has one theme color + one line-art motif related to the content of this chapter, and SHOTBOOK writes the chapter theme line — four chapter cards with the same color and same pattern are "here we go again", not "turning the page") — not putting card experience into SHOTBOOK is equivalent to not having read it. Motion vocabulary is selected from the 108 recipe cards; both filters check references/taxonomy.md 's machine-generated index (the source is the five fields of each card's frontmatter: “input / semantics / material form / position / props”,scripts/cards_index.py --write generation,--check validation, manual index edits are invalid): first filter by the input of this shot (person / V / image / screenshot / text / interface / scene — “input type index”), then filter by the semantics of this spoken line (self-introduction / data / comparison / enumeration / quotation / call to action……26-word closed vocabulary — “semantics index”; a card is listed only under the semantics it is specifically made for); cards with ◦ in the index have no prop whose content can be swapped (they expose only resource / skin type, or expose nothing), and changing copy / data requires changing tsx (the props line in each card's reuse guide says where to change). The two batches of new cards ◉◎ (29 cards) have at the beginning of the md another “input type” table + four “common scenarios”. Then references/taxonomy.md classified index → references/cards/<slug>.md parameters and pitfalls → template/cards/<slug>.tsx self-contained Remotion source code (implementation is based on it; copy it into the project and change CONFIG to use it);demos/<slug>/index.html is the HTML preview of the same visual (open gallery/index.html view in one screen, demo auto-plays when scrolled into view; the production master for ★ practical cards is also in template/motion-systems|components). Fidelity iron rule: every card used must truly exist in the project src/cards/<slug>.tsx (copied from template and modified CONFIG) — writing a "similar-looking" simplified version by hand just from the card name after only reading the md is the biggest source of failure (bounce/slap/density all lost, viewfinder bracket direction drawn reversed, business card color blocks changed), the machine gate uses scripts/card_lint.py Validate existence and similarity slug by slug (≥0.55; modified CONFIG/copy stays within tolerance). Reskinning is not rewriting: a copied card is neutral UI and must be reskinned per the SHOTBOOK §0 style tier — swap all colors to theme tokens, font stack and font weight, corner radius / stroke / shadow / material, chart card axes / grid / markers / numeral font, card-type placeholder graphics swapped for real assets or stylized graphics — only change the skin layer, not timing / easing / geometric ratios / hierarchy; next to each shot's layer matrix write a skin row (卡名 → 改了什么皮), cards of the same type within the same shot share one skin; for contracts, exceptions (product UI cards are not reskinned, semantic colors do not change hue) and counterexamples see design-language §0.4. card_lint's 0.55 is exactly the margin left for reskinning: a reskinned card passes, only rewriting the motion drops it below. The iron rule of three phases: entrance 0.2~0.8s → hold (staying still is enough — the life in the frame is handled by the scene camera's extremely slow push/pull) → exit 0.15~0.5s; the entrance always uses more force than the exit; there can only be one accent on screen at any given moment. If you choose a motion effect you must bring its sound effect with it: each card has a cue table at demos/_lib/sfx-map.js ({t, name, vol, rate?, clip?}, where t is card-relative seconds) — when selecting cards in the SHOTBOOK copy the cue into that shot's layer matrix (converted to absolute seconds; vol is relabeled per final-cut convention ≤0.35, the demo library's 0.65 cap is an audition convention, not a final-cut convention). At implementation time follow the sfx steps in ⑤. Coverage convention: full coverage of primary motion entrances, aligned with the demo library density (2~6 hits per card ≈ 0.4 hits/s, about 40~50 hits for a 100s film) — "few but precise" governs not stacking two hits at a single point, restrained volume (≤0.35), continuous-reveal types (slow push / focus / character-by-character rise) and pure-text punchline cards, logo curtain-call whitespace, transitions configured only with buildup and no landing, no closing ding or heavy thud; it is not about cutting coverage. Real samples (pk- prefix) take priority. **The cue's file name follows ls public/sfx/ ** (pk: the colon in the key name is exported as pk-, some keys carry a prefix and produce double-segment names like pk-transition-transition-soft — a wrong name makes rendering fail with a direct 404).

④→⑤ gate: after finishing the SHOTBOOK pass the full preflight first, then move to implementation (the asset check in ③ + SHOTBOOK reconciliation; any FAIL blocks ⑤):

python3 scripts/preflight.py --shotbook SHOTBOOK.md --host remotion/public/dh/host.webm --fps 30 --voice audio/full.wav --shots remotion/shots.json --semantics semantics.json
# SHOTBOOK 对账:每镜有「素材:」行 · V/图/截图 的文件都在盘上 · 零 V/图 镜头 FAIL · 占比 <1/3 WARN · 「未完成 / 未采集清单」节在册 · shots.json 与镜头 id 一致
#           · 版式轮换:同卡连用 ≥3 镜 / 呈现卡 >1/3 / 节奏表连续 3 镜同形态同容器 FAIL;版式行复制 / 素材相邻复用 / 缺节奏表 WARN

⑤ Implementation (Remotion)

Install the global system first, then write the scenes (code template/motion-systems/, the canonical spec is cinematography.md §2, subtract from the motion): install only G1 CameraRig (one extremely slow push-in or pull-out scale curve per scene, 1.00→1.04~1.06 or reverse, no x/y/rotation/blur,impulses left empty, table-driven by shots.ts) and G3 yield state machine (Live demoteAt = next subject anchor / Defocus,idle off, stays still once settled; demoteAt means staying on with reduced weight, not exiting — after an element is dimmed and shrunk it still occupies its original slot and counts toward the on-screen budget, and a new subject must not be placed in its position; the old name retireAt is still usable but deprecated, on 2026-09-06 a P0 text collision occurred because the name was misleading); G2 parallax, G4 act color temperature optional, not installed by default; subject idle / ambient breathing vignette / light sweep / exposure pulse / camera pulse are all omitted. G5 line-art diagram (schematic.tsx + icons.ts) only for pure graphic mirrors: DrawPath / DrawIcon / Connector / Node / Plate / Panel / Cross / Tick / Traveller / Label, all abs-second driven, machine drawn in one stroke, lights up wherever the line reaches, one skin; whatever the icon lacks, run python3 scripts/fetch_icons.py <slug,...> --merge --out <工程里 icons.ts 的路径>(without --out it's written as in-library template/motion-systems/icons.ts; Iconify lucide = ISC + partial Feather MIT, root directory THIRD_PARTY_NOTICES.md copied into the project together with icons.ts and registered in sources.md).

Every shot boundary must have an explicit transition treatment, bare cuts are forbidden: the six movement-continuation forms (lead/tail overlap 12–16 frames + ShotFade, code template/motion-systems/transitions.tsx) or the lightweight caret/shape-wipe forms, for selection see cinematography.md §3; one boundary uses only one form. Spatial/process narrative passages may instead use a long-take world canvas (longtake.tsx,cinematography.md §3.5)。 template/components/ are ready-to-use pieces: Subtitles full-sentence hard-appear version (chunks injected via props) / FlowerWord floral text / SmashWord smash text / HighlightSweep highlighter / PencilDraw pencil hand-drawing / Mascot mascot / NumberRoll. Bottom subtitles: plain typography, no punctuation (official master design-language.md §5): follow-along subtitles add no motion effects at all, appear as whole sentences hard-on, contain no sentence punctuation whatsoever (except the half-width dot between numbers/model numbers; pauses are handled by splitting cards); the only exception keyword-pop-highlight keyword pops up and throughout the whole film ≤3 times (motion-systems version keywords prop has this upper-limit self-check). Sound effects implementation:node scripts/sfx_dump.mjs remotion/public/sfx decode the library samples into mp3 → the cue table copied from SHOTBOOK becomes a sfx.ts(absolute seconds), in the scene <Audio src={staticFile(...)} startFrom/volume> placed one by one; sound-effect level ~12dB below the voice, at most one cue per frame. anime.js v4 / three.js go through the anime-remotion.ts / three-anime.ts bridge (seek-safe, for the project iron rules see cinematography.md §6: zero Math.random, initial opacity:0, lead compensation converged in one place).

⑤-1 Do the first shot first and confirm it first (user-finalized 2026-09-09 — a mandatory gate before the other shots start work)

No more "finish the whole film then look at the first shot": the film-wide commonalities (skinning / subtitle style / camera amplitude / card density / sound-effect level) can be judged on one shot, and changing after 13 shots are done is 13 times the rework; the urge to "render the whole thing after changing one line" is also concentrated in this segment (2026-09-08 issue #24: all 9 whole-film direct renders happened after production was finished and before acceptance).

  1. First build the full-composition skeleton:remotion/ after the directory is created, first bash <skill根>/runtime/link-runtime.sh <本片工程>(node_modules symlink to the unified dependencies,package.json dependency versions copied from runtime — do not self-build, do not npm install), then entry / whole-film duration / shots.ts + shots.json full table (the times come from SHOTBOOK and timestamps, write them all out at this point) / global systems (G1 camera, G3 yield, curtain bottom, subtitles, theme) / src/sfx.ts first build an empty table (export const CUES: SfxCue[] = []) — the six files of the ⑤-2 decomposition contract (shots / scenes/index / Subtitles / sfx / timing / camera) must be complete already in the skeleton phase, once the workbench is connected it is multi-track, and afterwards each cue placed adds another block on the track; without sfx.ts you cannot enter multi-track, and the bottom-right of the workbench will name which contract module is missing (@kbsrc/* the alias of real / stub is decided when the dev server starts, files added later require rerunning link-project.sh and restarting); all other shots are placeholders without exception —MainVideo-example.tsx inside SCENES[shot.id] if not found it falls to PlaceholderScene(Only show the lower-third base + subtitles; do not write any motion effects). The skeleton is built out in full so that render_shots' segment table / duration assertions take effect as-is, and no exemption is opened for the first shot.
  2. Implement only the template shot (SHOTBOOK G0 'template shot' row, default s01): land it in full per SHOTBOOK — skinning rows, card tsx copy, sound-effect cues, transition handling — do not do 'rough first, refine later'.
  3. Render one single-shot preview with audio for the user to see: render only this segment's video and this segment's audio, not the whole audio track (the other shots are all placeholders, so the whole audio track is meaningless; the 2026-09-09 demo v4 measured a 6s first shot with a 47s preview render: segment 34s + segment audio 10s):
# 在工程 remotion/ 目录下
node <skill根>/scripts/render_shots.mjs --shots shots.json --only s01 --seg-audio --preview-dir out/preview
open out/preview/s01.mp4        # 打开给用户看

--seg-audio segment audio exists only in this preview (deleted immediately after rendering); assembly / delivery still goes through --audio the whole audio track, so Discipline A is not broken. 4. Ask once, wait for the answer (AskUserQuestion-type tool, two options, do not decide for the user): 'Template confirmed, continue with the remaining shots' / 'Revise the template first'. After revising, re-render the same preview and ask again; before the template is confirmed, do not touch the remaining shots. Once confirmed, the template shot's skinning / subtitles / camera amplitude become the baseline for the whole film, and the remaining shots must not start a separate set.

Layout red lines (numeric table design-language.md §5; geometry master outline references/layout.md)

  • Subtitle position / width / font size / persistent-element placement follow the design-language §5 table by aspect ratio (vertical-screen persistent elements must be bottom-left; the right edge is the Douyin like bar)
  • Horizontal-screen content main column ≤1440px centered, margins action-safe 96 / title 160 (design-language §3; column span and snapping see layout.md §1)
  • General: after a cut, the main visual must enter within ≤10 frames; dark scenes hide the global top title; text must not overlap screenshot text (add a white background card); card text must be protected from cropping (reserve padding)
  • Person present: first run scripts/face_bbox.py to measure the face safe zone (spec host-footage.md §3), no text/card/subtitle or its background may enter it at any time; place the main information panel on the opposite side of the person

⑤-2 Workbench real-time dashboard (open as soon as the skeleton is built, keep it open throughout production)

Move the 'open the workbench only after delivery' from ⑧ forward to here: as soon as the ⑤-1 composite skeleton is built (shots.json + Main.tsx written to disk), connect and open the workbench, and from then on the user can see at any time 'how far along it is, what state each shot is in', can play the current real-time cut, and can click a single-shot audio preview; each time the agent saves to disk, the preview refreshes, no need to wait for the final cut (workbench/docs/live-pipeline.md). Opening it means multiple tracks (2026-09-21 user final version: the in-production dashboard and the delivery surface are the same multi-track workbench, not a single-track 'progress station') — as soon as the skeleton is built, the timeline has subtitles / transition markers / all shots (placeholder shots drawn with diagonal stripes) / lower-third base / voiceover / per-item sound effects as separate tracks; each time the agent lands a cue, implements a shot, or changes a subtitle, it appears on the corresponding track within ≤3s (shots / scenes / sfx.ts / subtitles are automatically synced by stable id as soon as they land on disk; user-modified ones are not overwritten); the progress track + each shot clip's corner badge shows placeholder / implemented / rendered / passed-gate.

bash <skill根>/runtime/check-runtime.sh                                # 依赖统一在 runtime/,这一步顺手把 workbench/node_modules 链好(不在 workbench 里 npm install)
cd <skill根>/workbench
bash scripts/link-project.sh <本片工程>       # kbsrc → remotion/src;public/ 先清掉指向别的工程的旧链接再逐项软链;末行必须打印「拆解契约 OK」——否则多轨进不去,它会说缺哪个文件
npm run dev &                                                          # 已在跑就跳过;链接变了要重跑一次 npm run gen;契约文件是后补的要重启 dev server(别名启动时定)
sleep 4 && curl -s http://localhost:5199 | grep -q '动效工作台' && echo "工作台 OK" || echo "FAIL: 工作台未起"
open 'http://localhost:5199/?tracks'                                   # 进来就是多轨(字幕 / 转场 / 逐镜 / 幕底 / 配音 / 音效 + 进度轨),之后自动跟盘;?live 同义
                                                                       # 契约不全不会装任何工程,右下角点名缺哪个模块——回 ⑤-1 补齐;单轨"成片(实时)"2026-09-21 已下线
  • Status manifest pipeline.json (project root): the workbench dev server derives in real time each shot's status from on-disk artifacts (placeholder / implemented / rendered / passed-gate; scene newer than segment = stale), without relying on the agent remembering to write it; the agent only records a note for things that cannot be derived from disk, all via node <skill根>/scripts/pipeline_state.mjs(run at the project root or under remotion/):

    Timing Command Description
    ⑥⑦ a shot's machine gate + review passed --pass s03,s04 progress track turns green;--pass all
    Review found unresolved defects --issue "s07|P1|字幕带压到人脸安全区 12.3–12.8s" progress block red dot + shot panel lists them; after fixing --clear-issues s07
    When the stage does not match the inference --stage ⑥⑦ Not usually needed; the stage is automatically inferred from artifacts
    Whenever you want to verify no arguments recalculates and writes to disk, prints a summary on the last line
  • Do not cover the page with work-in-progress: Vite errors no longer cover the entire workbench; a notification appears in the lower right + the screen stays on the previous version; if a single clip render errors, only that cell is drawn red; if the project is broken when the page opens, the finished-video card automatically recovers after fixing (load failures are not cached). Only tolerant inside Player——render_shots / when the workbench export goes through the Remotion CLI, any card throwing an error is a render failure, and the red error screen will not be treated as the finished video.

  • The kanban canvas = the project's native size (Root.tsx's width / height / fps must be written as numeric literals; using variables will default to 1920×1080@30 and warn at npm run gen ).

  • --pass is bound to the artifact at that time: if the segment is deleted / the scene is changed afterward → that shot returns to the inferred state and prompts "previously passed"; after re-rendering and rechecking, then --pass。

  • Do not call getInputProps()in the Main component (it will definitely throw in the Remotion Player, and that cell on the kanban will turn red): switches like debug / sfxSolo should be changed to component props (Composition defaultProps), or guard typeof window !== 'undefined' && !(window as any).remotion_isPlayer。

  • The kanban only views, does not drive: it does not provide an interface to "click a button to trigger a step"; the skill is still the main controller.

  • Decomposition contract (the workbench multitrack + per-shot parameter tuning consume these exports;template/motion-systems/ all have examples): shots.ts exports SHOTS(each shot lead / tail frame count,hardOut)+ shotSequence;scenes/index.ts exports SCENES(shot id → scene component) and SCENE_PARAMS(shot id → that scene's PARAMS); each scene export const PARAMS = [...] as const satisfies readonly ParamField[], within the scene const p = useParams(shot.id, PARAMS) values——the table only contains context-level parameters (copy / color / font size / position / entrance direction); word-anchor timing, duration, easing, geometric proportions, and layering are the crux and do not go into the table (same line as design-language §0.4); Subtitles.tsx exports Subtitles + phrases()(all visible subtitle segments: silence regions already subtracted, display mapping already applied) + SubtitleLine(single-sentence static render; Subtitles itself also uses it for drawing); Environment.tsx exports Environment(act base canvas, drawn below all shots) and Overlays(act-level overlays: black shock cut frames / curtain call, etc., drawn above shots and below subtitles);Main.tsx takes from here; params.ts copy from the template (useParams / ParamsProvider / OVERRIDES),remotion/overrides.json built as {}; Put voiceover files in public/narration.wav (the convention of template MainVideo-example.tsx ; the workbench multitrack parses voiceover blocks by narration / full, do not name them anything else——as tested on 2026-09-21, naming them incorrectly silences the entire multitrack and the export loses the voice); for live-action base beds use template/components/bed.tsx in Bed(root element className="wb-bed")——when right-click exporting the transparent channel in the workbench, use this marker to remove the base bed and keep only the motion graphics body. overrides.json Writes go to the workbench, agent never modifies: The keys the user changes in the panel take precedence; the agent only changes the tsx defaults—this is the solution to the dual-write conflict; Rendering (render_shots / workbench export) reads the same copy, so before finalizing, check whether it is empty (if the user has adjusted parameters, deliver according to the adjusted version, and state this in the delivery notes). Whether the contract is complete is checked by the machine gate python3 scripts/workbench_contract_lint.py <工程根> (one of the six gates ⑥⑦; at the template shot stage add --allow-placeholder).

⑥⑦ Rendering + triple acceptance (all machine gates pass → 1 round of review → delivery)

Iteration discipline (governs all loops in this section)

  • When the gate reports FAIL, first read how the gate measures, then make changes (the gate source code is in scripts/): motion_check's stillness determination is freezedetect n=0.003 d=0.8 (only motion with a per-frame average pixel difference >0.3% counts as movement)—smooth gradients, drifting backgrounds, and pure opacity breathing produce no pixel change; only camera zoom changes every pixel; sfx_check --mix measures [t−0.05, t+0.45] 0.5s window. Reading the gate also lets you determine whether the gate itself is reachable (when the inter-sentence breath is <0.3s and the sound effect level upper limit is −22dB, UNMASKED is mathematically unreachable)—this kind of conclusion that "the input determines it, and it is not fixable in this film" must be reached early.
  • Stills first:render_stills.mjs about 10s per batch, which can answer 90% of "is this change correct" (landing point/occlusion/copy/color scheme/layering/framing); Full renders are reserved only for running machine gates and temporal defects that stills cannot catch (jitter/flicker/audio-video/freezedetect).
  • Render only the changed segment, and batch review changes before rendering again:--only s2,s4 / --changed sNN only render the changed segment, During review, check timestamps segment by segment to confirm "the changed segments are new, and unchanged segments use the cache," do not habitually --all; accumulate multiple batches of P0/P1 into 1–2 batches before rendering again.

⑥-0 Static pre-check before rendering (zero master cost)—three checks that stop rework before the master:

python3 scripts/beat_gap_check.py remotion/beats.json remotion/shots.json   # 空台预检(advisory,从节拍表推算)
# 每条 WARN 都要答得出"这窗里什么在动":正常答案只有一个——该镜的相机极缓推拉覆盖了这窗(不补 idle/呼吸层);相机确在动就 --ok 声明
cd remotion && python3 ../scripts/freeze_probe.py --shots shots.json          # 静止探针:每镜 3 个时点渲「相隔 0.8s 的两帧」,按 freezedetect 同款 yuv420p 均差判
# 量的是真实合成像素(beat_gap_check 看不见持续运动);15 镜 90 张静帧实测 ~2 分钟,替掉"渲 10~20 分钟母版才知道 S06 过不了静止闸"。
# 2026-09-06 v4 实测:45 个采样点与母版 freezedetect 一致 98%(1 个 mafd 0.72 的边界点多报);是单点粗筛,不是全覆盖

List 2 · State transition windows: every half↔chip transition point on the character track and every wipe moment ±0.5s are included as still sampling points— the crossing conflict between subtitle band repositioning and character geometric transition (black text over black clothing) is hidden only in such windows; sentence-level/anchor frame sampling both miss it. Subtitle band repositioning must wait until the geometric transition is complete before switching (half→chip is delayed by ~0.45s before settling).

⑥-1 Still sampling (batch-render once in a bundle, an order of magnitude faster than one by one npx remotion still ):

# 两个 node 渲染脚本都必须在工程 remotion/ 目录下执行(Remotion 模块从工程自己的 node_modules 解析,
# 并自动加载工程的 remotion.config.ts——webpack alias / publicDir / browserExecutable 与 CLI 渲染一致;
# 吃 inputProps 的合成给 --props @props.json;素材是符号链接时 --public-dir 指向解引用同步后的目录)。
# 首跑没装浏览器会联网下载 Chrome Headless Shell(~95MB);离线机先 npx remotion browser ensure,或 --browser 指向本机 headless shell
# 中间帧格式:renderMedia 不读 remotion.config.ts,此前永远是默认 JPEG-80——render_shots 现在透传配置里显式设的
# setVideoImageFormat / setJpegQuality / setCrf,命令行 --image-format png|jpeg --jpeg-quality 95 --crf 18 再覆盖,
# 生效值打在日志首行 `encode: …`。默认建议 jpegQuality 95:2026-09-06 白底片同段三格式实测,jpeg95 对 png 的 PSNR 47.9dB、
# jpeg80 46.0dB,静态窗噪声底三者相同(2.83/2.83/3.07),渲染时间差异小于本机运行间噪声——格式按画质定不按速度定;
# 深底渐变片的色带(用户实测 raw_mean 0.67→0.39)本次没有对应素材复测,深底风格档仍按 png。
node <skill根>/scripts/render_stills.mjs --times 2.0,7.2,...   # 抽样点=每镜入/出+关键锚点+状态切换窗

⑥-1.5 Set the rendering rhythm (the template shot was already confirmed by the user in ⑤-1; do not separately render the first shot for the user to view here; the first render is still forbidden to directly --all—ask about rhythm first): If the global system (theme / subtitles / camera / backdrop) was changed after ⑤-1, first --only s01 --seg-audio --preview-dir out/preview re-output the template shot, self-check that it matches the confirmed version, and there is no need to ask the user again. Then ask once (an AskUserQuestion-type tool, two options, do not decide for the user):

  1. Full-film render: ⑥-2's --all --parallel 4 --concat … --audio … --mux …;
  2. Per-shot rhythm:--only s02 --audio … --preview-dir out/preview render one shot, open it for the user to view one shot, wait for the user to say "continue / change" before the next shot, For changes use --changed sNN; after all are viewed, then --concat + --mux assemble. Both rhythms must finally pass ⑥-2's frame-count assertion and ⑦'s triple acceptance—"the user has viewed it" does not equal "it has passed acceptance"; the machine gates and independent review are still performed.

⑥-2 Segmented rendering master system—split into segments by shot and render continuously in a single process within each segment (the raster within a segment is self-consistent; multiple tabs rendering concurrently will produce periodic phase jitter), Between segments --parallel 4 Measured 1.3–1.8× faster than single-process (on this machine, under different loads, 253→139s and 307→230s respectively, 4 shots 900 frames); the entire audio track has no rasterization issues,--audio-concurrency 4(default) measured 419s→175s (116s clip),imageFormat:'none' saves only 6%—frame-by-frame seek in the browser is the main cost of audio track rendering; segment boundaries are all shot cut points, K segments in parallel:

# 在工程 remotion/ 目录下执行。首渲:K 段并行 + 拼装 + 整条音轨 + 混音
node <skill根>/scripts/render_shots.mjs --shots shots.json --all --parallel 4 \
     --concat out/assembled.mp4 --audio out/full-mix.wav --mux out/vN.mp4
# 改一个镜头 → 只重渲该段±邻段(lead/tail 交叠波及邻镜边缘)再拼装
node <skill根>/scripts/render_shots.mjs --shots shots.json --changed s14 \
     --concat out/assembled.mp4 --audio out/full-mix.wav --mux out/vN+1.mp4
npx remotion render src/entry.ts <Comp> out/sfx-solo.wav --props='{"sfxSolo":true}' --codec=wav

Three hard rules for audio-video alignment (assertions built into the script; miss one and it will be misaligned): the audio track is never segmented as a whole (all video segments are muted, the audio track is rendered once and mixed in at delivery—attaching AAC to each segment and concatenating will cause progressive misalignment due to encoder priming delay); segment boundary rounding follows the same rule as Sequence (off by 1 frame = the entire visual rhythm shifts by 33ms); frame count assertion (actual frames per segment + total assembled frame count must be exactly equal, otherwise FAIL). The audio track cache is reused only after passing three checks: duration == composite duration, the three fingerprints of assets/inputProps/timing config files (beats.json, cues.json, src/sfx.ts etc., filenames containing sfx|cue|beat|audio|sound|timing) unchanged, and not a truncated temp file; for changes invisible to fingerprints (volume constants written inside components), use --force-audio. Fix verification likewise only renders the affected segments and passes the gate (freezedetect can run on a single segment), no full render.

# —— 关卡 1 机器闸:六条命令一次跑完,全 PASS 才进关卡 2 独立审片 ——
python3 scripts/motion_check.py out/vN.mp4 --baseline remotion/public/dh/host.webm --window <t>,<人物区 W:H:X:Y>
                                                  # 画面健康:静止段 + 抖动。抖动先查重复帧签名(人物区周期性近零差 = 素材帧率病,
                                                  # 处方在 preflight,--concurrency=1 治不了),再查并发光栅;--baseline 同窗量源片,
                                                  # 素材自带的噪声降 WARN——有人物素材的片必给,人物区窗必加
python3 scripts/sfx_check.py out/sfx-solo.wav cues.json                  # 音效在场(峰值 ≥−45dBFS)
python3 scripts/sfx_check.py --mix out/vN.mp4 audio/full.wav cues.json --timestamps audio/timestamps.json
                                                  # 音效可听(掩蔽分级);--timestamps 数气口,UNMASKED 门槛不超过气口数并打印"不可达"原因
python3 scripts/card_lint.py remotion/src <slug,slug,...>                # 卡片保真(复制自 template/cards)
python3 scripts/beat_lint.py remotion/beats.json audio/timestamps.json --shots remotion/shots.json --anchors anchors.json
                                                  # 词落点 |Δ|≤0.1s + 镜尾保护带 ≥0.5s + label 只许 [A-Za-z0-9_-](进文件名/JS 字符串)
python3 scripts/workbench_contract_lint.py <本片工程根>                      # 工作台拆解契约(⑤-2):六个契约文件 · SCENES/SCENE_PARAMS 与 shots.json 对账 ·
                                                  # 每镜 PARAMS + useParams · params.ts / overrides.json · Subtitles 的 phrases/SubtitleLine——
                                                  # 漏一个导出 = 用户打开工作台才发现按钮灰 / 面板空;⑤-1 只有样板镜时加 --allow-placeholder
# 评审材料抽帧:每句 2 帧 + 动效锚点帧(anchors.json 从 beats.json 导出)
# 连拍三帧对只抽 anchors.json 里标了 "burst": true 的锚点——状态切换(两态翻转/换场/砸入落位)
# 与高风险区域必须标;其余锚点只抽定妆帧。
# motion_check 的抖动闸只量 ≤12 个 18s 间隔的固定裁剪窗、快速运动窗还跳过——窗外的短闪烁它看不见,
# 所以连拍不是可选项;--bursts 全抽只在抖动闸报警需人眼定位时用。同一份 anchors.json 也喂给 motion_check
python3 scripts/qa_extract.py out/vN.mp4 audio/timestamps.json /tmp/qa_vN 540 anchors.json
python3 scripts/motion_check.py out/vN.mp4 --anchors anchors.json   # 锚点 t+0.6s 各加一窗,结尾打印实际覆盖窗数
# 评审拼图:帧目录拼 3×4 网格(评审先整版浏览、可疑帧再回原目录单张放大)
python3 scripts/contact_sheet.py /tmp/qa_vN /tmp/qa_vN_sheets

Machine gate criteria memo: the two sound-effect checks require the project's main audio track to support {!getInputProps().sfxSolo && <Audio .../>}; audibility MASKED>50% or UNMASKED fewer than max(3, clip length/30s) means FAIL ("81/81 present but all masked by vocals" is a typical failure), good-product criteria: transition/boundary cues sound during the ~0.3s breath between phrases; shots.json = the storyboard table exported as [{"id","start","end"}](same source as shots.ts).

Gate 2 independent review (the protocol proper is references/review-protocol.md, and the review subagent must read it first when dispatched): A subagent with a brand-new context must be dispatched—no self-review by the creator, no forking/reusing the production conversation as a "review", no followup re-review of the same review (a forked review inherits the creator's perspective, and the reference is the SHOTBOOK written by the creator themselves, forming a self-justifying closed loop). Dispatch / wait / liveness / re-dispatch follow the harness-agnostic primitive table in review-protocol §1.6 (PACKET / DISPATCH / FAN-OUT / WAIT / LIVENESS / RE-DISPATCH, with one column each for Claude Code · Codex · headless); WAIT is based on the completion behavior of the persisted REVIEW.md , not on the subagent's completion notification. The creator's own first-round layout review is also delegated to a subagent (returns only a text defect list; the image tokens of dozens of images do not enter the main context). Prepare the five-piece material set from protocol §1.2; the review produces a P0/P1/P2 list per the rubric, and only after fixing P0 + P1 is it considered passing; for rework, provide measurement numbers per protocol §3 and render only the affected segments. Gate 3 rule compliance: check the eight items in cinematography.md §5 shot by shot + final check before delivery (debug overlay off, final cut readable when scaled to 390px wide); for the items see review-protocol.md §2. Review loop: after all machine gates pass, do only 1 round of independent review → fix P0/P1 → deliver immediately, and at the same time ask the user whether to continue reviewing (automatic rounds capped at 3) and open the motion workbench (⑧); the remaining P2 list accompanies the deliverable. For details, review-protocol.md §4.

⑧ Delivery

Two-pass loudnorm (a single pass is dynamic mode, which compresses sound-effect transients; moreover loudnorm internally upsamples to 192kHz, and without adding -ar 96k will leak into AAC—measured 2026-09-06):

# 第一遍量测(只看 stderr 的 JSON)
ffmpeg -i out/final.mp4 -af "loudnorm=I=-15:TP=-1.5:LRA=11:print_format=json" -f null - 2>&1 | sed -n '/^{/,/^}/p' > /tmp/ln.json
# 第二遍线性归一(measured_* 从 /tmp/ln.json 抄:input_i / input_tp / input_lra / input_thresh / target_offset)
ffmpeg -i out/final.mp4 -c:v copy \
  -af "loudnorm=I=-15:TP=-1.5:LRA=11:measured_I=<input_i>:measured_TP=<input_tp>:measured_LRA=<input_lra>:measured_thresh=<input_thresh>:offset=<target_offset>:linear=true" \
  -ar 48000 -c:a aac -b:a 192k delivery.mp4

Listen once to confirm the voiceover has no popping/truncation and the sound effects do not overpower the vocals or stack frames (the relative level of sound effects changes after loudnorm)— the agent itself cannot listen to the finished product,sfx_check.py --mix is the audio counterpart machine: it must rerun delivery.mp4 once before delivery; The introduction includes a source line (if in-library samples are used, add the sfx source, see demos/_lib/sfx/ATTRIBUTION.md).

At delivery time, the workbench should already have been open since ⑤-2 (if not, connect it and open it as described in ⑤-2; do not wait for the user to ask; provide it at the same time as the question "whether to continue automatic review and revision", see the ⑥⑦ review loop policy) — give the user a CapCut-like interface for manual fine-tuning, and pipeline_state.mjs --pass … pin the passing shots green:

bash <skill根>/runtime/check-runtime.sh          # 统一依赖体检 + 链好 workbench/node_modules(不在 workbench 里 npm install)
cd <skill根>/workbench
bash scripts/link-project.sh <本片工程>          # 链接本片工程(机器本地符号链接,不进库;手写 ln -sfn 循环不会替换指向旧工程目录的链接——2026-09-15 实测 logos 仍指上一支片)
mkdir -p public && for f in <本片工程>/remotion/public/*; do ln -sfn "$f" "public/$(basename "$f")"; done
npm run dev &                                     # 浏览器打开 http://localhost:5199/?tracks 并告知用户(多轨面;?live 同义)
sleep 4 && curl -s http://localhost:5199 | grep -q '动效工作台' && echo "工作台 OK" || echo "FAIL: 工作台未起——禁止用 remotion studio 代替"

Mistake prevention: the interface delivered to the user can only be this workbench (the page title is "TalkCraft Workbench · Motion Workbench"; the assertion line above is the verification). npx remotion studio(inside the project) or the workbench's npm run studio is the developer parameter-tuning entry point, not the delivery surface, and must not be used in place of the workbench; npm run dev must be executed from <skill根>/workbench (do not start it in this film's project directory).

The workbench open (?tracks) is multitrack: sentence-by-sentence subtitles (text editable) / act-level overlays / transition markers / per-shot parameterized shots / act backgrounds / voiceover / per-item sound effects — it has been there since ⑤-2, and at delivery it merely all turns green; The delivery surface must be this multitrack surface, not the single-track "final cut (real-time)" (2026-09-21 user feedback: after the final cut was done, what opened was still the single-track progress console, making it impossible to edit background / motion / subtitles / sound effects by track); inside a shot, each scene's PARAMS declared copy / color / font size / position / entrance direction is individually adjustable (word-anchored beats, duration, easing, camera fixed), changes are written back to this film's remotion/overrides.json, and rendering reads the same copy; when done, click "Export Final Cut" (built-in Remotion rendering, following the single-concurrency discipline). See workbench/README.md / GUIDE.md ⑥. When connecting, resolve by real path, and if a contract module is missing, degrade only that one (workbench/kbsrc.map.mjs); projects produced by this skill can be decomposed by writing the six files in ⑤ "Decomposition Contract" — if the decompose button is grayed out, first check the last line of npm run gen to see which contract file is missing, and do not go create promo-style PromoScenes / Host to fill it in. When publishing, it is recommended (not required) to @ this skill's author in the introduction — it is the best support for the author: X @VincentWei93 · Douyin @Vincent · Xiaohongshu @Vincent。 For suggestions and feedback, you are welcome to scan the QR code in the "WeChat Discussion Group" section of the README to join the discussion group.

Directory Routing

What do you want to do Where to look
Define the visual language (palette / type scale / spacing / subtitle spec) · 12 act backgrounds references/design-language.md(Apple paradigm default version; §1.1 act background menu → template/motion-systems/backdrop.tsx)
Typography: where to place / how big / how to align (grid · spacing tokens · centering · type scale · collision · nine checks) references/layout.md
Design background / subject / text layers for a shot references/shot-design.md(three-sided work order + seven-type presets)
Shot methodology / anti-PPT / SHOTBOOK format / acceptance references/cinematography.md(+ shotbook-example.md)
Review: Level 2 five-piece material set / rubric / defect grading · Level 3 · rework discipline · review loop references/review-protocol.md(required reading for the review subagent)
Transitions (six-style code) / long takes template/motion-systems/transitions.tsx / longtake.tsx(cinematography.md §3, §3.5)
Pure text shot with line-art diagram (G5: vocabulary · semantic graphic dictionary · beat discipline · placement self-check) references/schematic.md → template/motion-systems/schematic.tsx + icons.ts(scripts/fetch_icons.py Grab Iconify lucide)
Video container borders in eight styles (a single-video shot is not bare-pasted and does not have a fake player installed; the video area of any card can be wrapped) template/components/theme-frame.tsx(rules and selection style: design-language §1.3)
Choose animation/check parameters and pitfalls references/taxonomy.md(two generated indexes: input type / semantics) → references/cards/ → template/cards/(tsx source code) + demos//gallery/(preview)
Sentence-by-sentence semantic annotation of the voiceover script (②-1) · card candidate table · card index generation references/semantic-annotation.md · scripts/semantic_annotate.py · scripts/card_match.py · scripts/cards_index.py
Find assets · collect supporting images · collect webpage capture assets (full-page 2× long image + DOM coordinate JSON) references/broll-sources.md
Pre-start health check (③ asset phase --media-only / ④→⑤ full gate: person asset frame rate · duplicate frames · duration · aspect ratio + SHOTBOOK asset reconciliation · unfinished checklist) scripts/preflight.py
Static probe (use real composited frame differences to predict freezedetect before the master) scripts/freeze_probe.py
Person assets (input specs / CPU matting / face safe area) · how to place them on the same screen as B-roll references/host-footage.md + scripts/face_bbox.py
Add recipe card references/demo-spec.md, validate node scripts/verify-demo.mjs <slug>
Copyable code template/cards/(108 cards, each card self-contained tsx),template/motion-systems/(very slow push-pull camera/yield/bridge),template/components/(subtitles/styled text/pencil/mascot)
Manual fine-tuning / export after the final cut workbench/(CapCut-style workbench: multi-track timeline + full-card parameterization + final-cut breakdown + Remotion render export)
Real-time dashboard throughout production (always on from ⑤-2, opens to multi-track and automatically follows the disk: progress track / shot status badges / stage bar / single-shot preview / work-in-progress does not cover the page) · status checklist workbench/docs/live-pipeline.md · scripts/pipeline_state.mjs(--pass / --issue / --stage; status is automatically advanced by artifacts)
Word-level timestamps (local CPU) scripts/timestamps_cpu.py(FireRedASR2-CTC default / faster-whisper alternative, + word-by-word alignment with the voiceover script) → scripts/make_timing.py
One-click dubbing + timestamps (Fish Audio free tier) scripts/tts_fishaudio.py(based on s2.1-pro-free streaming TTS, generates full.wav + timestamps.json + optional timing.json)
Dubbing pre-cut (filler words / stutter restatements / overly long pauses, run before timestamps; the same EDL cuts the person video) scripts/voice_trim.py(②-0; the script is the ground truth, only cut inserted segments outside the script; word list source ASR / word-by-word SRT / word-level JSON;cuts.json EDL includes timeline mapping)
What to do when the gate report FAILs · how to burn fewer masters ⑥⑦ “iteration discipline” three points—first read how the gate measured before changing · prioritize still frames · render only the segment you changed, and batch review changes
Machine gates (visual health / fidelity / word landing points + shot tails / sound effects) scripts/motion_check.py(static segment + concurrent raster jitter dual determination) / scripts/card_lint.py(cards must be copied from template/cards) / scripts/beat_lint.py(word landing points against timestamps + --shots shot-tail guard band) / scripts/sfx_check.py(solo present + --mix audibility)
Render speedup (segmented master / batch still frames / empty-stage precheck / review montage) · do the first shot first and confirm it (⑤-1) scripts/render_shots.mjs(segment render + assembly + audio track mix-in + frame count assertion;--changed sNN single-shot iteration 53s;--only s01 --seg-audio --preview-dir sample shot with sound preview, without rendering the entire audio track (⑤-1); ⑥-1.5 ask the user whether to do the whole film or shot by shot) / scripts/render_stills.mjs(one bundle batch still) / scripts/beat_gap_check.py(Empty stage pre-check before rendering) / scripts/contact_sheet.py(QA frames assembled into a 3×4 grid)
Motion-matched sound effects Per-card cue sheet demos/_lib/sfx-map.js(for flavor discipline see references/demo-spec.md"Demo Hard Requirements" item 8); production side node scripts/sfx_dump.mjs export sampling