Turn a voiceover script into an script ↔ voiceover ↔ visuals sentence-by-sentence correspondence MP4:
script.txt ──tts.py──▶ voice.mp3 + timeline.json(逐句/逐词毫秒时间戳) + subtitles.srt
│
▼
composition.html(一句口播 = 一个场景,时间戳来自 timeline)
│ render.py(无头浏览器逐帧截图 = 确定性渲染)
▼
final.mp4(配音音轨已混入)
Core principles (must follow):audio is the clock, visuals follow the audio. Scene transitions, element appearances, and subtitle highlighting are all driven by timestamps in timeline.json; rendering is seek-style frame-by-frame screenshots, and the same input always renders the same every frame.
Working directory conventions
Each video gets its own independent directory, with outputs stored together:
<video_dir>/
├── script.txt 口播稿(纯文本)
├── kit.css engine.js 从技能 assets/ 拷贝
├── composition.html 你编写的合成页
├── build/ tts.py 产物: voice.mp3 / timeline.json / subtitles.srt
├── frames_final/ 渲染中间帧(默认渲完删除)
└── final.mp4 成片
Step 0 · Environment self-check
Check on first use or when errors occur (versions confirmed available on this machine: ffmpeg 6.1.1 / edge-tts 7.2.8 / playwright 1.55):
ffmpeg -version | head -1 && python -c "import edge_tts, playwright" \
&& python -m playwright install chromium --dry-run
If Chromium is missing, run python -m playwright install chromium. edge-tts needs access to Microsoft services (failures are usually network/proxy issues; retry or switch networks).
Step 1 · Voiceover script script.txt
- Plain text, one continuous passage, standard punctuation (sentence segmentation based on 。!?;!? and line breaks).
- If the user provided a script → use it as-is, only correcting obvious slips of the tongue/typos.
- If the user only provided a topic → first write a script based on the topic (colloquial short sentences, ≤ 40 characters per Chinese sentence),and proceed to composition only after user confirmation; Chinese speech rate is about 4.2 characters/second, which can be used to estimate the final video length.
- For long scripts (>5 minutes), warn the user about rendering time (1080p30 is about 6-10 times the video length); you can change to
--fastor 720p.
Step 2 · Generate voiceover and timeline
python <skill>/scripts/tts.py --script script.txt --out build --voice xiaoxiao
- Voice short names:
xiaoxiao(female · general default)xiaoyi(female · lively)yunxi(male · sunny)yunjian(male · steady)yunyang(male · news broadcast); or pass a full edge-tts voice name. Available--rate +10%to adjust speech rate. - The output
build/timeline.jsonis the "clock" for the whole workflow; read it first: confirm sentence segmentation is reasonable and durations match expectations, then proceed to Step 3. - User-provided voiceover/SRT (e.g. ListenHub export) → skip this step, and convert the SRT into timeline.json's
sentencesformat ({i,text,start,end,words?}, milliseconds; words can be omitted).
Step 3 · Write the composition page composition.html
- Choose a visual style: first read
references/styles.md(style catalog + layout DNA + selection rules). If the user specifies a style, use the corresponding kit (assets/kits/contains dark-hud / editorial / terminal / whiteboard / swiss / ink / glass / clay / blueprint / pixel / stage); if not specified, automatically match by subject matter; if unsure, use the defaultassets/kit.css(BlockFrame block style). When the visuals need embedded video assets, use the stage style +HF.bindVideo, convert assets to webm, and add--wait-videos。 - copy
assets/engine.js、assets/template.html(rename to composition.html) andthe selected kit(e.g.assets/kits/ink.css) to the video directory, and point composition.html's<link rel="stylesheet">to that kit filename. Note: the safety hook will intercept copying source files with Bashcp; use the Read + Write tools to complete the copy (write engine.js / kit contents as-is). - First read
references/composition-guide.md(alignment rules, animation properties, scene types, and the self-check list are all in there), key points:- one spoken line = one
.scene,data-start= sentencestart,data-end= sentencehold_end; - scene content must correspond to the current sentence; keyword appearance timing uses
data-delay ≈ 词时间 - 场景start; - follow the selected style's
references/styles.md"layout DNA" to rearrange the scene structure(layout skeleton/composition/decoration/motion signature change with the style); only swapping CSS colors does not count as meeting the bar; - subtitle data is introduced with
<script src="build/subtitles.js"></script>(produced by tts.py, i.e. the timeline's sentences; can also be manually pasted asconst SUBTITLES); - CSS animation/transition, setTimeout, and Math.random are prohibited (they break determinism).
- one spoken line = one
- Visuals: by default use kit.css's BlockFrame design system (cream background/candy colors/black outlines with hard shadows). If the user gives brand/style requirements → override
:rootvariables and component styles, but keep the whole set consistent.
Step 4 · Preview (required after compositing)
python -m http.server 8765 # 在视频目录下执行
Give http://localhost:8765/composition.html to the user, or self-check with browser tools:space to play, confirm (1) subtitles switch sentence by sentence and word highlighting keeps up with the speech rate; (2) the scene switches at the instant the sentence changes; (3) element appearance timing matches the semantics. If problems are found, go back to Step 3 and fix them (no rendering during the preview stage, so fixes are fastest).
Step 5 · Render the final video
python <skill>/scripts/render.py --html composition.html --audio build/voice.mp3 --out final.mp4
- Default 1920×1080 @30fps, CRF18. For drafts use
--fast, for samples use--width 1280 --height 720。 - Author attribution watermark:
--watermark laohaibao2025(small signature text in the lower right corner, injected at render time, does not modify the composition page; the engine also supportsHF.init({ watermark: "..." }))。 - Resume rendering from a breakpoint: after an interruption, rerunning the original command automatically skips existing frames;
--keep-frameskeep intermediate frames. - Estimated time ≈ video length × 6-10 (1080p30); for long videos, inform the user first.
- After rendering, use ffmpeg to spot-check frame alignment with the timeline:
ffmpeg -ss <句中时间> -i final.mp4 -frames:v 1 check.jpg, confirm that the frame/subtitles at that moment match the sentence.
Step 6 · Delivery
Report to the user:final.mp4(final video),build/subtitles.srt(external subtitles for platform upload),composition.html(source files, can be modified and re-rendered), duration/size. In one sentence, explain "which two steps to rerun after changing the script/changing the visuals".
Troubleshooting
| Symptom | Handling |
|---|---|
| edge-tts 429/timeout | Wait a few seconds and retry; switch networks; or the user brings their own TTS+SRT (using the timeline.json format) |
| Chromium fails to start | python -m playwright install chromium |
| Wrong fonts in the visuals/Chinese tofu blocks | Confirm Microsoft YaHei is available on this machine; kit.css's --f-body fallback chain already includes system fonts |
| Rendered visuals do not match the preview | Check whether CSS animation/random was used; all animations must go through data-anim / HF.on |
| Audio and video are half a beat off | render.py uses the ffprobe audio duration as authoritative; confirm that composition does not hardcode durationMs to override it |
| Frame rendering interrupted | Rerun the original command (auto-resume), or --from-frame N |