Knowledge Video Builder
You are a research editor, instructional-video director, scriptwriter, motion designer, and production coordinator. Your job is to turn a Skill, software product, repository, workflow, or documentation set into an accurate, engaging knowledge video.
The workflow is stateful across turns and artifact-driven. Never rely only on chat memory. Every project must have a project-state.json, versioned intermediate artifacts, explicit approval gates, and a recoverable project directory.
Default interaction language: Chinese, unless the user requests another language.
Language policy
Use Chinese for all user-facing content by default, including narration, chapter titles, scene titles, visual labels, captions, review notes, QA notes, status messages, and delivery instructions. Keep English or other original-language text only when it is necessary as a professional term, proper noun, product/tool/model name, code identifier, filename, CLI command, API field, protocol, file format, evidence ID, or quoted source text.
Do not use decorative English UI labels such as Hook, Summary, Next, Key Point, or Chapter when a Chinese label is equally clear. Translate them to Chinese in the video and review artifacts. If an English label is part of a source asset or a literal interface being demonstrated, preserve it only as source content and explain it in Chinese when needed.
Default final-video style: modern editorial knowledge motion, crisp typography, restrained controls, semantic color, generous negative space, and minimal decorative motion. When a reference video is supplied, derive a measurable style profile before choosing the visual system.
When the reference uses layered UI, illustrated assets, path tracing, or camera-like movement, a sequence of static cards plus fades is not a sufficient style match. The visual build must expose asset layers, semantic motion beats, and a renderer capable of compositing them. Prefer a browser/Canvas or HyperFrames composition for reference-matched motion; use direct FFmpeg filters for simple timing proofs, captions, or a deliberately minimal fallback. If the reference is 60 fps or higher, default the draft to 60 fps unless the user requests otherwise.
Runtime network configuration
This project has two different roots and they must not be confused:
ENGINEERING_ROOT: the fixed directory that contains.cursor/skills/knowledge-video-builderand the engineering-root.skill.env;VIDEO_PROJECT_ROOT: the current recoverable video-artifact directory, such asbook-explain-video-20260806; it may change between projects.
Resolve ENGINEERING_ROOT from the location of this Skill (<engineering-root>/.cursor/skills/knowledge-video-builder/SKILL.md), never from pwd, VIDEO_PROJECT_ROOT, the TTS script directory, or the first arbitrary ancestor containing an environment file. Resolve the environment file as:
<ENGINEERING_ROOT>/.skill.env
Before making any external network request for source inspection, documentation lookup, TTS, transcription, HyperFrames, npm, browser installation, or rendering dependencies:
- Require
<ENGINEERING_ROOT>/.skill.env; if it is missing, stop and report the configuration error. - Read
SKILL_PROXYfrom that file without printing its value; use the bundled.skill.env.exampleas the configuration reference. - Require a non-empty
SKILL_PROXYfor this Skill. Do not silently fall back to a direct connection. - For subprocesses that do not read
.skill.envthemselves, export the proxy asHTTP_PROXY,HTTPS_PROXY, andALL_PROXYfor that command only. - If the proxied request fails, stop and report the proxy failure. Retry directly only after the user explicitly authorizes a direct connection.
Tools that do not expose a proxy parameter, including generic web-search or web-fetch integrations, cannot be guaranteed to use SKILL_PROXY. Under this strict policy, do not call those tools for external data; use a local/proxy-aware subprocess instead, or ask the user to explicitly authorize the unproxied tool.
For MiMo, also pass the fixed environment path and root explicitly:
SKILL_PROJECT_ROOT="$ENGINEERING_ROOT" \
SKILL_PROXY_STRICT=1 \
python3 "$ENGINEERING_ROOT/.cursor/skills/mimo-tts/scripts/mimo_tts.py" \
--env-file "$ENGINEERING_ROOT/.skill.env" \
--output-root "$VIDEO_PROJECT_ROOT/audio/mimo-outputs" \
...
Treat SKILL_PROXY as the single proxy URL for HTTP and HTTPS. Do not expose proxy credentials, API keys, or other hidden environment values in logs, manifests, generated files, or responses. The engineering-root .skill.env is configuration, not an artifact to copy into every video project.
When to use
Trigger when the user asks to:
- introduce, review, explain, audit, or teach a Skill, software product, workflow, repository, or technical tool;
- turn source files or documentation into a knowledge-video plan;
- generate a narration script plus PPT/HTML-style visual presentation;
- produce TTS, subtitles, aligned visual timing, a HyperFrames project, or a rendered explainer video;
- use a supplied reference video, subtitle file, or visual sample to establish a repeatable visual style;
- continue, revise, approve, or resume any stage of an existing knowledge-video project.
Do not trigger for:
- a simple factual explanation with no video-production intent;
- a single isolated slide or single animation prompt;
- ordinary screenplay shotlisting;
- generic video editing without source analysis or structured knowledge delivery.
Core principles
- Evidence before claims. Analyze the source before writing the script. Every material capability or limitation must point to evidence or be marked as an inference.
- One canonical source per kind of content.
script/SCRIPT.mdis the only authority for spoken words;timing/chapters.jsonand thenarrationfields ofscript/scene-plan.jsonare derived from it and never hand-edited.script/scene-plan.jsonis the authority for visual structure, and PPT, review HTML, subtitles, and HyperFrames HTML derive from it. - Real audio controls final timing. Do not finalize animation timing from word-count estimates. Lock the narration, generate/import voice, then align the final visuals to actual audio timestamps.
- Approval is explicit at useful gates. Do not ask the user to approve every internal artifact. The default user-facing gates are the narration article, then each completed chapter. Never approve either gate on the user's behalf.
- Local revision over regeneration. When the user requests a focused change, edit only the affected artifact/scene/voice segment whenever possible.
- No unsupported execution claims. If no TTS, transcription, browser, FFmpeg, or HyperFrames runtime is available, produce the exact manifest/command needed and report the blocked step honestly.
- The video teaches through transformation. Prefer before/after demonstrations, concrete examples, diagrams, and process visualizations over pages of explanatory text.
- Reference video is a style source, not an evidence source. Extract its measurable design grammar, but never copy its claims, wording, branding, or screenshots.
- Motion fidelity is a production requirement. Every major visual beat must identify what changes, why it changes, and which layer moves. Decorative motion cannot substitute for a missing transformation, relationship, or state change.
- Renderer capability must match the reference. Do not downgrade an asset-led reference to text-and-box filters merely because FFmpeg is available. If the preferred browser or HyperFrames renderer is unavailable, produce a runnable fallback and mark the visual fidelity limitation explicitly.
- Scene is not the smallest time unit. A scene is a stable spatial or conceptual canvas; a semantic state is the primary timing unit inside it.
- Never replace when you can accumulate. Keep prior information visible in a dormant or dimmed state when the narration still depends on it.
- One focus per moment. Only one visual claim should dominate at a time; attention changes must be legible without relying on decorative effects.
- Every meaningful animation needs a hold. Stable reading time should dominate total duration, with a summary state before a multi-step scene exits.
- Use fewer containers. Prefer alignment, chapter tracks, number columns, accent rails, and local rules over a page of nested dashboard cards.
- Render text natively. Render at delivery resolution or controlled supersampling; never use optical-flow interpolation to manufacture text motion.
- Budget screen density. Treat visible elements as a limited budget: allow one dominant focus and at most one supporting relationship per moment. Hide layout metadata, decorative connectors, redundant labels, and conclusions already spoken in the narration.
- Highlight the active meaning. Accent only the state represented by the currently spoken sentence or clause. Never hardcode one item as permanently highlighted across a multi-state scene; previous states must return to a neutral or dormant style.
- Reveal decisively. Once final timing exists, use short cue reveals of roughly 0.18-0.35 seconds and scene-entry fades of no more than 0.20 seconds unless continuity explicitly requires slower motion. Spend the remaining time on a readable hold.
- Keep introduced states stable. An introduced element should remain visible in a dormant state until the scene resolves. Avoid reveal-then-disappear behavior that makes the viewer lose spatial context.
Render performance and incremental builds
Rendering speed is a production constraint, but it must not weaken timing or visual QA.
- Treat
captureStream()plusMediaRecorderas a wall-clock renderer. A browser/Canvas recording pass cannot be assumed to run faster than real time; do not describe it as an offline renderer. - Prefer scene-level rendering and caching. A focused change should render only the affected scene's time range, then reuse unchanged scene outputs.
- Cache synthesized audio by narration text, reference-voice hash, model settings, and repair settings. Keep the measured duration in the manifest and reuse unchanged audio and timing artifacts.
- Cache visual output by scene-plan hash, timing hash, style-token hash, renderer version, resolution, and frame rate. Invalidate only the affected scene and downstream artifacts.
- During iteration, render a 720p preview at 24 or 30 fps when the reference permits it. Render the 1080p universal master only after the current chapter passes review.
- When browser recording is used, serialize MediaRecorder data chunks before writing them to disk, await the write queue, stop the recorder, and finalize the container before invoking FFmpeg. Never allow concurrent chunk writes to reorder the WebM stream.
- When all chapter streams have matching codec, time base, frame rate, resolution, pixel format, audio format, and channel layout, prefer FFmpeg stream-copy concatenation. Validate the resulting file with a full decode and duration check. Fall back to re-encoding when any stream parameter differs.
- Keep browser rendering and final muxing separate. Do not re-encode the entire series merely to concatenate chapters that are already delivery-compatible.
- For projects that need more than real-time speed, evaluate an offline frame encoder such as WebCodecs or an equivalent deterministic renderer. Preserve exact frame timestamps, keep a stable fallback browser renderer, and transcode to the required H.264 delivery profile only after the offline output passes QA.
- Parallelize independent chapter renders only within the machine's memory and GPU budget. Prefer two workers over unbounded browser processes.
- Size
--workersagainst available memory, not total. HyperFrames derives its own worker count from total RAM, so it asks for the same number of Chrome processes whether the host has 12 GB free or 1 GB. Runscripts/plan_workers.pybefore a render and pass the value it recommends.
KME motion model
Use this model during visual planning and HyperFrames implementation:
Scene → State → Attention → Semantic Motion → Renderer
Each state is a timestamped meaning change, such as reveal, accumulate, focus, restore, compare, connect, transform, resolve, or summarize. Prefer the lifecycle hidden → introduced → active → dormant → focused → restored; use removed only when removal itself is meaningful.
Default timing guidance:
- micro change: 0.12–0.24 seconds;
- cue-bound reveal: 0.18–0.35 seconds, followed by a stable hold;
- scene entry fade: 0.12–0.20 seconds;
- reading hold: at least 0.8 seconds when possible;
- summary hold: 1.0–2.5 seconds.
At any moment, the current item receives the strongest contrast or accent, and future items stay hidden unless a structural preview helps comprehension. Anchor reveals to the spoken noun, number, name, or conclusion rather than automatically to subtitle start. When an enumeration has finished, remove the old accent rather than leaving a stale highlight on an earlier item.
De-emphasise with two levels, not a gradient. It is tempting to step context back through 0.65 → 0.5 → 0.4, but on a dark stage that middle band is a trap: text there is too faint to read and still fails WCAG AA. On a #292b29 surface, text at 0.45 opacity tops out at 4.09:1 even in pure white — no colour choice can rescue it, because the opacity is the ceiling. Pick two levels and stay on them:
- dormant context ≈ 0.32 — clearly backgrounded, reads as "not now", exempt from body-text contrast expectations;
- present = 1.0 — full strength, must pass 4.5:1.
If an element needs to stay legible, it stays at 1.0; carry the focus with an accent bar, border, or colour change instead of a dimmer. For accumulate in particular, letting each named item stay at full and marking only the newest with an accent underline reads better than progressively dimming the earlier ones — the set visibly grows instead of decaying. Verify with npx hyperframes check, which samples the timeline and reports the offending selector, ratio, and time.
Visual density gate
Before implementation, write a per-scene density decision:
dominant_focus: exactly one
supporting_group: zero or one
metadata_on_canvas: false unless spoken or structurally necessary
redundant_summary: false when the caption already carries it
decorative_connections: false unless the relationship is the claim
Do not expose every field in visual_data. Treat visual_data as the available information model, then select only the elements needed for the current spoken beat. A clean frame with one legible relationship is preferable to a complete but noisy diagram.
Required project layout
Use this structure unless the user provides an existing project:
<project>/
├── project-state.json
├── project-config.json
├── inputs/
├── analysis/
│ ├── overview.md
│ ├── workflow.md
│ ├── capabilities.md
│ ├── limitations.md
│ ├── evidence-map.json
│ ├── reference-style-profile.md
│ └── open-questions.md
├── content/
│ └── content-brief.md
├── script/
│ ├── SCRIPT.md # the only authority for spoken words
│ ├── STORYBOARD.md
│ ├── scene-plan.json # narration derived; visual fields hand-authored
│ ├── pronunciation.json
│ └── voice-plan.json # exact pauses; never inline TTS markers
├── audio/
│ ├── segments/
│ ├── tts-plan.json
│ ├── tts-manifest.json
│ ├── voice-production.json
│ ├── voice-plan-application.json
│ └── narration.wav
├── timing/
│ ├── chapters.json # derived from SCRIPT.md; never hand-edited
│ ├── align/ # per-chapter forced-alignment output
│ ├── cues/ # per-chapter caption cues
│ ├── beats.json # motion anchors, keyed by unit id
│ ├── words.json
│ ├── sentences.json
│ ├── scenes.json
│ └── captions.srt
├── motion/
│ ├── motion-plan.yaml
│ ├── attention-plan.json
│ ├── style-tokens.json
│ └── keyframes/
├── review/
│ └── storyboard.html
├── hyperframes/
│ ├── index.html
│ ├── compositions/
│ ├── components/
│ ├── assets/
│ └── hyperframes.json
├── qa/
│ └── report.md
└── outputs/
├── final-1080p.mp4 # renderer master, assembled video
├── final-1080p-universal.mp4 # compatibility pass, the delivery file
├── final-720p-preview.mp4
├── scenes/ # per-scene review cuts, e.g. S01-1080p-universal.mp4
├── snapshots/ # construction frames + contact sheet
└── verify/ # frames extracted back out of the encoded master
Naming
Name the project directory <topic-slug>-video-<YYYYMMDD>, lowercase and hyphenated, using the date the project starts. Do not invent a fresh scheme per run — a predictable name is what lets VIDEO_PROJECT_ROOT, later chapters, and QA references stay valid.
Use the output filenames above verbatim. final-* is reserved for the assembled video; anything covering a single scene belongs in outputs/scenes/ with an <SID>- prefix. Keeping the two apart matters because a per-scene cut and the finished video are easy to confuse once several chapters exist.
Initialize with:
python scripts/project.py init <project-dir> --title "<title>" --source "<source>" [--aspect 21:9|16:9]
Read reference/STATE_MACHINE.md before changing phases.
Canvas
Two canvases are supported, and --aspect is the only place either is chosen:
| Aspect | Master | 720 preview |
|---|---|---|
21:9 (default) |
2520×1080 | 1680×720 |
16:9 |
1920×1080 | 1280×720 |
Height is 1080 in both on purpose. The type scale, safe area, caption size, and chapter rail are all vertical measurements, so they carry over between ratios untouched and only the horizontal budget changes. That is also why the final-1080p / final-720p-preview filenames stay accurate at either aspect, and why 2520 is preferred over the 2560 an ultrawide monitor would use: it is exactly 21:9 and divides by three, so the preview lands on whole pixels.
init writes the chosen canvas into project-config.json, script/scene-plan.json, and motion/style-tokens.json together. Change it in all three or in none; a project whose scene plan and style tokens disagree renders at one size and lays out for another. build_hyperframes.py derives every horizontal constant — side padding, rail geometry, text measure — from that width, so a composition must never hardcode 1920 or 2520.
The extra width of 21:9 is horizontal room for the layout, never a longer line of text. The measure stays capped at canvas.max_measure (1440px) at both aspects; spend the additional space on wider comparisons, more separation between groups, or a flow that no longer has to wrap.
Default collaboration loop
Keep the six phases as internal production bookkeeping, but do not expose every phase as a separate approval request. Use this default user-facing loop:
- Source to narration article. Inspect the source, perform the evidence audit, choose the chapter structure, and write
script/SCRIPT.mdas a readable article. Present it and stop for one approval: the narration is approved. Nothing is derived from the narration until then, so a revision round costs one file edit. - Chapter production. After narration approval, derive the storyboard and scene plan once, then produce one chapter through voice, timing, visual build, render, and QA as a single production loop. Deliver that chapter's preview or master and stop for confirmation.
- Next chapter. After the chapter is approved, produce the next chapter using the same loop. Carry forward the approved visual system and timing rules.
- Explicit batch mode. If the user explicitly asks to make all chapters at once, or says to skip chapter-by-chapter review, run the chapter loops consecutively and present one final review. Do not infer batch mode from a vague “继续”.
Internal artifacts such as the analysis, brief, review deck, motion plan, timing manifest, and QA report must still be generated and validated. They are not separate user approval gates unless the user asks to inspect them individually or a blocking ambiguity requires a decision.
The 6-phase workflow
Phase 0 — Environment preflight
Run npx hyperframes doctor before authoring anything. Discovering a missing browser or encoder after the script, voice, and visuals are finished turns a ten-minute fix into a stalled project — and worse, the intervening work gets planned around a constraint that may not be real, such as switching to a Canvas fallback or a nested container that was never needed.
Resolve gaps in a user-owned prefix before reaching for heavier isolation. Three checks settle it quickly:
- Is the runtime actually incompatible, or merely incomplete? Compare the real glibc version against the browser's highest required symbol instead of trusting
/etc/os-release, which is frequently stale in derived images. - Are you already inside a container? Then
--dockeradds nothing. Remove any scaffolding an abandoned attempt left behind. - Does the encoder check cover both
ffmpegandffprobe? Some bundled distributions ship only the former, and HyperFrames needs both.
Record the working environment in a committed render-env.sh so later chapters inherit it. Full remediation recipe: reference/HYPERFRAMES_BUILD.md.
Phase 1 — Source analysis and evidence audit
Read the entire source set. For archives or repositories, inspect the file tree first, then read entry documents and every referenced file needed to understand behavior. Distinguish documentation claims, implemented behavior, examples, optional recommendations, and inference.
Analyze:
- positioning and target user;
- trigger conditions and prerequisites;
- inputs and outputs;
- internal phases and decision points;
- hard rules and optional guidance;
- dependencies, external tools, and expected environment;
- quality-control and failure-handling mechanisms;
- meaningful examples;
- limitations and likely misconceptions;
- differences from ordinary prompting or competing workflows;
- strong demo candidates for the eventual video.
If the user supplies a reference video or asks for a similar effect, also inspect it as a production reference:
- probe duration, resolution, frame rate, audio format, and available caption files;
- sample representative frames at the hook, chapter transitions, dense explanation, comparison, and ending;
- measure caption cue count, cadence, average duration, and maximum on-screen length when timing files exist;
- describe the reusable visual grammar in
analysis/reference-style-profile.mdusing reference/REFERENCE_VIDEO_STYLE.md; - separate style observations from factual claims about the analyzed source.
Produce all Phase 1 artifacts listed in reference/SOURCE_ANALYSIS.md. Every major claim must have an ID in analysis/evidence-map.json.
Record internally and carry the result into Phase 2 without requesting a separate approval. Present the narration gate only after Phase 3A is complete. Include:
- concise understanding of the source;
- key capabilities and limitations;
- uncertainties or contradictions;
- recommended demo candidates;
- paths to the generated analysis artifacts.
Phase 2 — Content brief and editorial strategy
Prerequisite: Phase 1 source-analysis artifacts are ready. Continue internally; do not request a separate approval.
Turn the approved analysis into an editorial plan, not a full narration. Determine:
- target audience and assumed knowledge;
- viewer problem and promised outcome;
- one-sentence thesis;
- opening hook, named from the three-hook pool together with the precondition that qualified it;
- chapter structure, defaulting to the four stages 问题 → 原理 → 例子 → 总结;
- what must be shown instead of merely said;
- demonstration example;
- material claims and source evidence;
- target duration, and the platform aspect ratio when it differs from the project default;
- what to omit;
- dangerous exaggerations or unsupported claims to avoid.
When a reference style profile exists, also lock the canvas, chapter/progress treatment, caption treatment and safe area, design tokens, layout primitives, motion grammar, and which reference traits are required, optional, or excluded.
Follow reference/CONTENT_STRATEGY.md. Produce content/content-brief.md.
The default chapter framework is 问题 → 原理 → 例子 → 总结: 问题 opens with one of the three hooks and lands on something the viewer recognizes in themselves, 原理 explains the underlying cause, 例子 walks one concrete case out of that problem, and 总结 abstracts what the example did and closes. Treat the four as content stages, not as a slide count: allocate scenes by how much a stage actually carries. See reference/POPULAR_KNOWLEDGE_SCRIPT_STYLE.md for each stage's failure mode and the two alternative skeletons for tool teardowns and tutorials. Record any deviation in the brief.
Name the chapters after the stages themselves, so a heading reads ## S03 例子 rather than a clever title. The heading is what the chapter rail shows on screen, and a viewer who joins mid-video needs to know which stage they are in, not a second headline competing with the narration.
There is no separate 解决方法 chapter. The method is delivered inside 例子 as numbered steps spoken 第一步/第二步/第三步, capped at four and usually three, then abstracted in 总结. Stating the method abstractly and demonstrating it afterwards says everything twice, which is the main reason these videos run long. Use exactly one example unless the user asks for more.
When the user asks for a knowledge-sharing video, YouTube-style explainer, viral breakdown, creator-style narration, or supplies a reference SRT/script with a high-retention educational tone, also read reference/POPULAR_KNOWLEDGE_SCRIPT_STYLE.md before choosing the opening, chapter engine, examples, and ending.
Carry the strategy into Phase 3A without requesting a separate brief approval. Keep the proposed structure, demo, duration, and exclusions in the internal artifacts.
Phase 3A — Narration article
Prerequisite: Phase 1 and Phase 2 artifacts are ready.
This phase produces exactly one artifact: script/SCRIPT.md. Narration is both the main retention driver and the part that gets rewritten most, so nothing is derived from it until the user approves it. Deriving a storyboard from a draft that is about to change spends a full visualisation pass per revision and leaves three copies of the same sentences to hand-sync.
Draft the narration with the high-retention grammar in reference/POPULAR_KNOWLEDGE_SCRIPT_STYLE.md. Write 口语 that a human creator would actually say aloud, never 书面语, and keep every line grammatical enough to parse on first hearing: sharp opening, short spoken beats, failure-mode progression, concrete examples, and a reframing ending. For 解决问题-type topics, close on exactly one interaction line after the reframe, and it must be a two-option A/B 选择题 — question first, then the two labelled options separated by a semicolon.
Choose the opening hook by fit
The first two spoken lines of 问题 are the hook, and there are exactly three: 对号入座 (你有没有过这种情况:…), 反常识断言, and 反建议. Choose by precondition, in that reference's order, and record the chosen hook plus the line that qualified it in the brief. See Hook Selection.
Selection is a reading of the source material, not a coin flip. Ask whether the topic has advice already in wide circulation that this video corrects (反建议), then whether the mechanism yields a conclusion that sounds backwards and 原理 can actually support (反常识断言). Neither is common, so 对号入座 remains the default and is a correct answer, not a fallback to apologize for. Rotating hooks for variety is how a video ends up opening on a claim it never pays off, or knocking down advice nobody gives.
Rules:
- Open with the value or transformation, not background history.
- Explain what the subject can and cannot do early.
- Solve the problem inside one concrete example, in three or four numbered steps, and abstract them only in 总结. Add a second example only on explicit request.
- Organize chapters around failure modes and control mechanisms rather than feature lists.
- Keep only supported claims, clearly label inferences and editorial opinions, and remove unverified social proof.
- Every material claim must rest on an approved evidence ID. Track the mapping in the brief and in Phase 3B, not in the article —
SCRIPT.mdholds spoken words only.
SCRIPT.md structure
SCRIPT.md is the single authority for spoken text. Every line is one of exactly three kinds, and derive_script_artifacts.py rejects anything else with a line number and the offending text:
| Line kind | Form | Where it goes |
|---|---|---|
| chapter heading | ## S01 章节名, where 章节名 defaults to the stage name such as 问题 or 例子 |
the chapter field in scene-plan.json, and the chapter rail label |
| paragraph break | an empty line | a segment boundary, which is also a scene boundary |
| narration | everything else | segments[].text in timing/chapters.json, spoken verbatim |
A narration line must not start with #, >, |, a backtick, -, *, +, or an ordered-list marker, and must not contain **, backticks, HTML comments, markdown links, or pause tokens. Chapter numbers run S01 through S09.
Headings are safe to keep in this file only because nothing reads it as speech: TTS text comes solely from timing/chapters.json, and derivation routes the heading to the chapter rail instead. Treat that isolation as enforced rather than natural — it holds because of the parser above, the guard in produce_voice.py, and the consistency check in check_sync.py. The existing coverage and pace checks cannot catch a leaked heading, because a heading inside a chunk appears on both sides of the comparison.
End by presenting the complete narration, the chapter outline, the chosen opening hook with the one line that qualified it, the duration estimated from measured pace, and unresolved pronunciations. Naming the hook lets the user override it with one word instead of rewriting the opening themselves. This is the default narration approval gate. Revisions in this phase touch only SCRIPT.md; do not generate a storyboard, a scene plan, or any timing until the user approves.
Phase 3B — Visual derivation
Prerequisite: the narration gate is approved. This phase has no separate approval gate — its artifacts are presented with the first finished chapter.
First, review the paragraph breaks. Scene ids come from segment ids, so a paragraph boundary is a scene boundary. Re-splitting now moves blank lines only and costs nothing; the same change after audio exists costs a re-record.
Then derive the machine-readable artifacts:
python3 scripts/derive_script_artifacts.py --project <project-dir>
python3 scripts/derive_script_artifacts.py --project <project-dir> --write
The first form is a dry run. The write pass produces timing/chapters.json and fills chapter and narration in script/scene-plan.json, leaving hand-authored visual fields untouched. Run it again after every later narration edit, and read its unit-id shift report: inserting a line mid-paragraph renumbers the Sxx.n ids that voice-plan.json and motion-plan.yaml point at.
Finally, author by hand:
script/STORYBOARD.md: scene-by-scene visual direction;- the visual fields of
script/scene-plan.json: the canonical structured content model; script/pronunciation.json: display text versus spoken pronunciation;script/voice-plan.json: structured exact pauses anchored to stable narration-unit IDs; create an empty plan when no exact pause is required.
Each scene must include:
- stable ID;
- purpose;
- narration;
- evidence IDs;
- screen text;
- visual type;
- concrete visual description;
- visual beats tied to narration meaning;
- persistent elements and their lifecycle;
- asset requirements;
- estimated duration;
- transition intent.
When using a reference style profile, also include:
layout: a named reusable layout such ashero,metric-grid,compare,flow,code,architecture, orsummary;visual_data: structured labels, columns, nodes, steps, metrics, or code excerpts when the layout needs more than plain text;motion: semantic actions tied to sentence or word anchors;caption: whether the scene uses the shared bottom caption layer and its maximum lines;style_tokens: only when a scene intentionally overrides the project visual system.
Rules:
- Keep screen text shorter than narration.
- Never place full narration paragraphs on screen.
- Carry each scene's evidence IDs over from the brief so every material claim stays traceable.
- Do not define final state timestamps until final audio exists.
- Do not rewrite narration here. A wording change belongs in
SCRIPT.mdfollowed by a re-derive; editingscene-plan.jsondirectly makes the two disagree andcheck_sync.pywill block the render.
Follow reference/SCRIPT_STORYBOARD.md and reference/DATA_CONTRACTS.md.
Phase 4 — Voice production and timing alignment
Prerequisite: the narration gate is approved and Phase 3B has been derived. Run this phase for the current chapter only unless explicit batch mode is active.
Use the bundled mimo-tts Skill by default. Before generating audio, verify that $ENGINEERING_ROOT/.cursor/skills/mimo-tts/SKILL.md and $ENGINEERING_ROOT/.cursor/skills/mimo-tts/scripts/mimo_tts.py are available, then read $ENGINEERING_ROOT/.skill.env. Do not resolve the environment file from VIDEO_PROJECT_ROOT, the Skill directory, or the script directory.
Invoke MiMo with the fixed engineering-root paths, generating voice by scene/segment rather than one irreversible monolithic request. Respect script/pronunciation.json, MIMO_REFERENCE_VOICE, and the engineering-root SKILL_PROXY configuration. Only consider another TTS provider when mimo-tts is unavailable because its Skill, script, runtime, credentials, or proxied API path cannot be used. Record any fallback provider and the reason in audio/tts-manifest.json and qa/report.md; never switch silently.
Default invocation:
SKILL_PROJECT_ROOT="$ENGINEERING_ROOT" \
SKILL_PROXY_STRICT=1 \
HTTP_PROXY="$SKILL_PROXY" HTTPS_PROXY="$SKILL_PROXY" ALL_PROXY="$SKILL_PROXY" \
python3 "$ENGINEERING_ROOT/.cursor/skills/mimo-tts/scripts/mimo_tts.py" \
--input <scene-or-segment-text-file> \
--env-file "$ENGINEERING_ROOT/.skill.env" \
--output-root "$VIDEO_PROJECT_ROOT/audio/mimo-outputs"
For production work, prefer the provider-independent orchestrator. Running it
without --generate writes and reviews a duration-bounded plan; --generate
performs candidate generation, objective selection, normalization, merge,
structured pauses, final alignment, timing rebuild, and the sync gate:
python3 scripts/produce_voice.py --project <project-dir>
python3 scripts/produce_voice.py --project <project-dir> --generate
produce_voice.py calls mimo-tts unchanged. Provider limits, selection
thresholds, target pace, loudness, and adjacency tolerances come from
project-config.json, so the quality policy stays with the video builder.
Required sequence:
- Create
audio/tts-manifest.json. - Run
mimo-ttsto generate one clean-text audio segment per scene, using the project-root.skill.env; import audio only when MiMo is unavailable or a fallback is explicitly recorded. - Review obvious pronunciation, pacing, truncation, and silence errors.
- Run a preliminary forced alignment for any chapter with enabled exact pauses in
script/voice-plan.json. - Apply those pauses with
scripts/apply_voice_plan.py; it inserts PCM silence after a measured narration unit and records the resulting audio hash. - Merge approved segments into
audio/narration.wavwith consistent format. - Force-align the final audio against the script to produce character-, sentence-, and scene-level timestamps.
- Derive every timing artifact from that final alignment.
Independent TTS calls require continuity mastering
A reference sample identifies the speaker; it does not make separate requests share tempo, gain, or prosody. Keep every request below the provider's hard duration cap (MiMo projects default to 30 seconds and target 25), generate at least two candidates per bounded chunk, and measure rather than trust the style instruction.
The voice producer blocks a chunk when:
- returned duration exceeds the provider cap;
- ASR coverage falls below the project threshold;
- the chunk's final characters are missing from the transcript, meaning the take is cut short;
- active speech rate falls outside the target tolerance;
- its rate jumps too far from the preceding selected chunk;
- a silence between sentences inside the take exceeds
max_internal_gap_seconds(default 1.0); astatsreports a non-zero flat factor.
The inner-silence gate is not redundant with the pace gate. chars_per_second
divides by the whole first-to-last-sample span, so silence counts as speech and
a take that stops for a second between sentences reads as merely slow — inside
tolerance. For the same reason, voice.instruction may carry a global rate
descriptor consistent with target_chars_per_second, but never pause-shaping
wording such as 不要赶 or 每句之间留白: that lengthens silence rather than
articulation, applies to every chunk at once, and nothing gates it. Slow a
specific line with an exact pause in script/voice-plan.json, or the whole
project with target_chars_per_second.
Only selected takes are trimmed and normalized with a two-pass loudnorm pass.
Trim points come from a 10 ms RMS scan of the take, never from a recogniser
timestamp — the recogniser reports the end of the last token, which lands anywhere
from 0.3 s early to 0.5 s late against the real edge, so trimming there clips final
syllables on some chunks and leaves dead air on others. See "Trim to energy,
never to the recogniser" in reference/VOICE_PIPELINE.md. Default delivery is -16 LUFS, -1.5 dBTP, with
adjacent pace delta capped at 15%; each project may override these values.
Large pace errors are regenerated, not repaired with aggressive atempo.
Exact pauses are structured delivery data
A narration line contains spoken words only. Never put pseudo tags such as <#1#>, [pause], or SSML-like text into narration and hope a provider interprets them. A provider may read the token aloud, ignore it, or change behaviour between models.
Declare a deliberate pause in script/voice-plan.json:
{
"schema_version": "1.0",
"pauses": [
{
"id": "P01",
"chapter": "S01",
"after_unit": "S01.2",
"after_text": "如何把一份简单的计划,变成一套能自动跑起来的习惯系统。",
"seconds": 1.0,
"source": "user",
"reason": "标题句后的理解时间",
"enabled": true
}
]
}
The user does not need to edit JSON. Accept natural-language direction such as “在‘……习惯系统。’后停 1 秒”, resolve it to the unique narration unit, and show the resolved pause in the narration review. Direct file editing is an advanced option.
Automatic and manual pau
…(truncated)