Turn atopicor avoiceover script, ina single sessioninto directly publishablecomplete set of assets: a BlockFrame final cut where the visuals follow the audio + covers in all aspect ratios + platform copy. This skill is the listenhub-tts(upstream: text→audio+subtitles) and producing-video(downstream: audio+subtitles→final cut)orchestration layeron top of them; it is additionally responsible for the thing most easily missed — completeness of the delivery checklist。
Iron rule: the deliverables checklist allows nothing missing. The output of each video is achecklist, not an mp4. Covers must bein all aspect ratios(not just a main cover and done), and platform copy must be provided for both YouTube + Bilibili. Before finishing, validate with check-deliverables.sh validation,any ✗ means it is not complete. The reason this skill exists is: previously users had to manually remind "also need a 4:3 cover"; now the checklist is hard-coded and automatically completed.
Why this layer is needed (motivation)
listenhub-tts + producing-video can already produce videos, but they only handle "text→audio→one mp4". Every time a human has to watch and fill in: how many cover ratios to make? Was platform copy written? How to render the vertical 4K? This skill turns theseinto a fixed process and checklist, and runs them all at once by format branch, with no more verbal instructions.
Division of labor
| Who | What they do |
|---|---|
| This skill (you, orchestration) | Determine topic/format → call listenhub-tts → assemble BlockFrame composition per SRT → render per format (including 3:4 4K supersampling) → complete covers in all aspect ratios → write platform copy → checklist acceptance |
listenhub-tts(upstream) |
Voiceover text → narration-full.mp3 + narration.srt(VerySmallWoods cloned voice, use the same voice throughout the series) |
cover-design(covers) |
codex+gpt-image generates hand-drawn editorial-style covers: topic→composition recipe + layout for each aspect ratio + three checks for platform safe area/1:1 crop/120px — this skill does not duplicate it, directly delegates |
producing-video(downstream core) |
All iron rules for HyperFrames video production (animations attached to tl, transitions only use clip-path reveal, local woff2 fonts, loudness -14 LUFS, blank frame self-check, 4K supersampling) — this skill does not duplicate these, copy and comply |
To learn about video production details, read
producing-video's SKILL.md; to learn about dubbing/subtitles, readlistenhub-tts. This skill only fills in the "complete set of assets" orchestration between them.
Dependency check (pre-flight)
npx hyperframes doctor # Node ≥ 22 · FFmpeg · Chrome(出片用)
codex --version # 封面出图用(cover-design)
echo "${LISTENHUB_API_KEY:?需要 LISTENHUB_API_KEY}" >/dev/null # 上游配音用
Skills that need to be present:listenhub-tts、producing-video、cover-design(full cover set), and producing-video required by hyperframes / hyperframes-cli; when the official logo is used there is also brand-icons(optional, see Step 2). The cover uses cover-design 's codex image generation (uses Codex subscription auth, does not read OPENAI_API_KEY). Keys all go through environment variables,never committed to the repo。
format config (all differences are in this table)
short/long two formatsgo through the same pipeline, only these few cells differ:
| short (mobile vertical, default) | long (horizontal) | |
|---|---|---|
main canvas #root |
3:4 · 1080×1440 | 16:9 · 1920×1080 |
| main cover | cover-3x4.png |
cover-16x9.png |
| cover layout | cover-design 's 3:4 vertical layout |
cover-design 's 16:9 horizontal layout |
| render resolution | see “3:4 in 4K” below | --resolution landscape-4k(directly supersampled to 3840×2160) |
| pacing | fast, high information density, ~2–3 minutes | can be expansive, ~5–10 minutes |
| cover aspect ratio set (all must be produced) | 3:4 · 9:16 · 16:9 · 16:10 · 4:3 | 16:9 · 3:4 · 9:16 · 16:10 · 4:3 |
cover aspect ratio setboth formats use the same set of five, only the main cover differs — this is exactly the hardcoded part of “stop missing 4:3”.
Vertical is fixed at 3:4 (1080×1440), do not do 9:16. 9:16 (1080×1920) is too thin and tall, the mobile viewing experience is poor and information density is low; 3:4 plays normally on Douyin / Xiaohongshu / WeChat Channels and is the only choice for the vertical main canvas. 9:16 is only kept asone of the cover aspect ratios(Short thumbnails, etc.), butthe video body does not output 9:16. This is a preference the user has explicitly set, do not drift back to 9:16.
3:4 in 4K:--resolution has no 3:4 preset
hyperframes render --resolution only recognizes named presets:landscape/portrait/landscape-4k/portrait-4k/square/square-4k。there is no 3:4. So:
- 16:9 →
landscape-4k(=3840×2160) ✓ use directly, true 2× DPR supersampling. - 3:4 (vertical default) → no preset → natively render 1080×1440 + lanczos upscale to 2160×2880(reliable path):
cd build-v npx hyperframes render -c index.html --quality high --output /tmp/v-native.mp4 # 原生 1080×1440 # lanczos 放大 + loudnorm 一步到位(要重编码视频,因为变了分辨率) ffmpeg -y -i /tmp/v-native.mp4 -vf "scale=2160:2880:flags=lanczos" \ -af loudnorm=I=-14:TP=-1.5:LRA=11 -c:v libx264 -preset slow -crf 16 -pix_fmt yuv420p -c:a aac -b:a 192k \ renders/<slug>-v-4k.mp4To be honest: this is “high-quality native rendering + lanczos upscale”, not true supersampling. But
--quality high's native 1080×1440 is already very clear on its own (1080 width = full mobile vertical resolution); upscaling to 2160×2880 is mainly to resist platform recompression and make the file larger and more durable. 3:4 has no true 4K preset; this is currently the most stable path.
⚠️ Do not use the
zoom:2supersampling method (deprecated). At one point recommended adding#roottozoom:2+ doublingdata-width/heightto do “true supersampling” — testing showed it breaks all absolute elements positioned withbottom:Absolutely positioned element(footer,.wrap'sbottom, cover bottom chips all misaligned / disappeared). The early “verification” only checked “non-blank” and did not verify the bottom content, so it was a false positive.Single-file multi-scene composition especially fails. honestly follows the “native + lanczos” above.
(9:16 has a
portrait-4kpreset, butthe video body does not output 9:16, see the explanation in the format table; only use it if a 9:16 cover/special case must be produced separately.)
Project directory structure (one topic = one project directory, don't scatter into multiple sibling directories)
Iron rule: one project directory per topic, all materials go inside it. Don't scatter into <slug>-cn / <slug>-3x4 / <slug>-16x9 such parallel sibling directories (scattered, easy to miss, hard to archive — been there). The horizontal and vertical versions aretwo of the same project build-* subdirectories, sharing the upper-level audio/ assets/ fonts/。
<repo>/studio/videos/<YYYYMMDD-slug>/ # ← 一个项目,就这一个目录
├── PLAN.md / research.md # 要点核验 / 备料(财经类必有 research.md,逐项标源)
├── script.md # 脚本(画面 cue + 口播 + 章节 + 出处)
├── narration.txt # 纯口播(喂 TTS 的最终版)
├── audio/
│ ├── narration-full.mp3
│ ├── narration.srt
│ └── cue-starts.json # 逐句精确时间轴(逐句合成时产出)
├── assets/ # 素材:官方截图 / 产品图 / logo(官方来源,带出处)
├── fonts/ # 本地 woff2 一份(build-* 里软链引用,不重复拷)
├── build-h/ # 横版 16:9 合成(独立 hyperframes 项目)
├── build-v/ # 竖版 3:4 合成(独立 hyperframes 项目)
├── renders/ # 只留母版 + 响度版(中间自检渲染删掉)
│ ├── <slug>-h-4k.mp4 <slug>-h-4k-14lufs.mp4
│ └── <slug>-v.mp4 <slug>-v-14lufs.mp4
├── covers/
│ │ # 生成图,见 cover-design
│ └── cover-16x9.png cover-4x3.png cover-3x4.png cover-9x16.png # 全比例
├── publish.md # 标题候选 / 描述 / 标签 / 章节时间戳(横竖通用)
└── blog.md + blog-images/ # 配套文章(如有)
Rules:
build-h/build-veach is an independent hyperframes project(each hasindex.html+hyperframes.json+meta.json), butaudio / fonts / assets share one copy at the upper level, brought in via symlinks — ensuring both versions use the same audio timeline and don't duplicate space:cd build-v && ln -s ../fonts fonts && ln -s ../audio audio && ln -s ../assets assetsrenders/keep only the final artifacts: horizontal 4K master +-14lufs, vertical master +-14lufs;standardself-check renders are deleted once verified.- Cover PNGs go in
covers/, codex-generated images should be immediately copied from~/.codex/generated_images/into it,don't leave them outside the project / at the repo root。 - Unified naming:
<slug>-h-*(horizontal) /<slug>-v-*(vertical). - At the end use
scripts/check-deliverables.shto verify against this structure; none may be missing.
When producing only one version, create only the corresponding one
build-*; when producing only the article, omitbuild-*/renders/, keepingassets/+blog.md+blog-images/。
Workflow
Step 0 · Pick topic + format
- Get thetopicornarration script. If there's no script, first nail down the key points with the user (or use a research-type skill to find material), and write it as
narration.txt(plain text, one sentence per line, good for sentence-by-sentence cueing). - Decide
format: mobile → short; horizontal long video → long. Default is short. - For content involving the command line/architecture/processes,facts must be empirically verified(real input and output of real commands, real directory structure), don't make them up — this is the series' signature. Record the verified fact points into
PLAN.md。 - The narration script must go through the user's writing style treatment, and go through it twice (mandatory, don't skip). First determine the style source according to the resolution ladder, then run it: ① a writing style skill is installed (
personal-chinese-writing-style/personal-writing-style/ any style-type skill) → use the Skill tool to invoke it, don't hand-write style cleanup; ② no skill but there's a writing style record in memory → process according to it; ③ neither → fall back to the built-in baseline rules listed below for the first/second pass, and tell the user "no writing style skill / memory found, this pass used the built-in baseline". One pass isn't enough — a single pass usually only clears punctuation and the most glaring words, leaving voice-layer problems (the most common being personifying tools / agents as colloquial verbs of "a person doing odd jobs": quietly working, shouting once, work, growing on, tossing into…). No matter which style source is used, the division of labor for the two passes is:- First pass · punctuation + structure: curly quotes “ ”, half-width em dash
-, ASCII ellipsis......, Chinese sentences use Chinese punctuation; one sentence per line. - Second pass · voice + wording:translationese (literal translations of worth-X / can't-afford), internet slang, and the kind of anthropomorphic colloquial verbs above — replace them with neutral verbs (run / trigger / scenario / is located at / put into). Genuinely vivid colloquial language ("use your eyes to tap the screen," "leave a line for you to confirm yourself") stays.
- Save the output separately as
narration.txt(the final version fed to TTS); it's recommended to also keep a draft from before processing,diffto confirm both landed. The spoken script can be colloquial and friendly, but don't make it so anthropomorphic that it sounds like "chatting about household matters."
- First pass · punctuation + structure: curly quotes “ ”, half-width em dash
- Third pass · polyphonic character scan (mandatory before feeding TTS). After the style processing is done and before running
listenhub-tts, scan once for polyphonic characters — TTS often reads the 「调」 in 「调用」 as tiáo (it should be diào); users have actually hit this. Runlistenhub-tts'sscripts/scan-heteronyms.sh narration.txt, and for each⚠fix it by the ladder: ① expand into an unambiguous word (决定调哪个→决定调用哪个) → ② rephrase → ③ if it slips through to the final cut, then dig in and patch that single sentence. Changingnarration.txtbenefits both the audio and the subtitles. For details seelistenhub-tts's "Step 0.5 · polyphonic character scan." - Pauses / breathing rhythm (recommended for long scripts). If the content is long, don't talk from beginning to end; mark pauses at the structural joints of
narration.txt— blank line = paragraph pause (0.8s),[停 X]= explicit X seconds (0.5 after key numbers, 0.4 before transitions). The markers get stripped before being sent to TTS, real silence is inserted in the audio, the SRT shifts accordingly, and the video's breathing room follows with zero drift. For details / recommended durations seelistenhub-tts's "Step 0.7 · pause rhythm."
Start the project (the repo convention is to put the final cut in studio/videos/, see "Project directory structure" above):
cd <repo>/studio/videos && mkdir -p <slug>/{audio,assets,fonts} && cd <slug>
# 字体下到根 fonts/(Noto Sans SC + Space Grotesk + Inter + JetBrains Mono,见 producing-video)
# 每个要出的画幅建一个 build-*,各是独立 hyperframes 项目,软链共用上层音频/字体/素材:
for b in build-h build-v; do
npx hyperframes init "$b" --example blank --non-interactive
( cd "$b" && rm -rf fonts audio assets && ln -s ../fonts fonts && ln -s ../audio audio && ln -s ../assets assets )
done # 只出一版就只建对应的一个
Step 1 · Upstream: audio + subtitles (listenhub-tts)
follow the listenhub-tts process, use VerySmallWoods cloned voice(reused across the series to keep the voice consistent) to produce audio/narration-full.mp3 + audio/narration.srt。The timeline is the lifeline of the subtitles, only do text-level corrections, never touch the timeline.
Step 2 · Build the BlockFrame composition according to the SRT (producing-video core)
#rootSet the canvas according to the format (short 1080×1440 / long 1920×1080).- One continuous
<audio>clip as the clock; one full-canvas.scene.clip,per scene. Scene start time = the timestamp of its first cue.。 - Reuse
kit.css's BlockFrame components (chrome / title / hl highlight block / terminal window / card / chips / grid), and add your own widgets for this episode. - When you need AI / company official logos(terminal window signage, model comparison cards, opening badges) → go through the
brand-iconsskill to fetch official SVGs from the LobeHub CDN (find-icon.shsearch slug →fetch-icon.shdownload toassets/brand/),Don't hand-draw and don't AI-generate. Monochrome marks must be placed in white / cream small blocks (black marks on a black background are invisible). - Visual effects(spotlight / frosted glass / background blur / magnifying glass / material large text / light band sweeping text / halftone background): for the mechanics see
producing-video/references/visual-effects.md(design-neutral); for BlockFrame's skin fill values and ready-made snippets seereferences/effects-blockframe.md。 - Motion effects / transitions / imperative engine: for choreography see
producing-video/references/motion-patterns.md(15 types); if you want transitions more cinematic than a clip-path reveal (through zoom / follow-through cut / same-background hard cut) seereferences/scene-transitions.md; WebGL/Canvas/Lottie seereferences/runtime-adapters.md(proxy clock bridge) — all three are seek-safe. - Series reuse (optional): the BlockFrame series daily updates want the brand chrome / layout / motionwritten once, data swapped each episode, using composition variables (
data-composition-variables+data-var-text/src+--variables-file), seeproducing-video's "Series templates (variables)". Note the timeline is still re-sequenced per episode's SRT. - Copy all of
producing-video's gotchas: timed animations must always be attached/registered towindow.__timelines["main"]'stl(never baregsap.from/to); transitions only use the incoming scene's own clip-path reveal; accent colors act as punctuation, ink-colored text on color blocks; CJK in color blocks gets ample~0.2emvertical padding. - For long videos, recommendedsplit into directories by segment
build/seg-01..NN/, oneindex.htmlper segment paired with its own few cues (short videos can also be a single file).
Layout fill (especially important for vertical 9:16 / 3:4, don't let content crowd into the top half). defaults to .wrap top-aligned, pages with little content all crowd to the top with a big blank at the bottom — measured fill rate is often only 14–33%(i.e. six or seven tenths of the safe area is empty), looking unfinished. This is the most common layout disease of vertical short videos. The cure:
- use
.wrap.fill(three-part: top label area / middle hero / bottom landing card),justify-content:center+gap:60pxto treat the content asa single block centered. Don't usejustify-content:space-betweento force it to the edges — it leaves big gaps between blocks and looks "stretched". - Pick one hero per page to enlargeto occupy the middle: big numbers, big diagrams, big contrasts (giant version, encrypted relay, flowchart, ↓↓ big card). Vertical has ~1610px of vertical headroom, so feel free to go one size bigger on fonts/cards.
- The top area only holds a bare kicker — it will float at the top leaving a gap. Give it a lead line (
kicker + 一行 lead), and the top area has weight. - Thin pages get a bottom "why/landing" card added(the causality already present in the voiceover), completing the three parts.
- Target fill 75–85%(not 100%; leaving some symmetric breathing room is the BlockFrame flavor).
Proofreading (run after every change):
npx hyperframes lint # 0 error
npx hyperframes validate # WCAG AA 对比度
npx hyperframes inspect --samples 30 # 版面溢出;装饰出血标 data-layout-ignore
Layout fill self-check (symmetric with inspect, catches "too much whitespace"). starts a local server, uses the browser to measure the ratio of each page's content bounding box to the safe area; pages below ~50% need to go back and add a hero / bottom card:
( cd build && python3 -m http.server 8799 & ) # 起服务
# 用 Playwright/Chrome 打开 http://localhost:8799/index.html,在 console 跑:
# document.querySelectorAll('section.scene').forEach(sec=>{
# const w=sec.querySelector('.wrap'); if(!w)return;
# const k=[...w.children]; let t=1/0,b=-1/0;
# k.forEach(e=>{const r=e.getBoundingClientRect();t=Math.min(t,r.top);b=Math.max(b,r.bottom)});
# console.log(sec.id, Math.round((b-t)/1610*100)+'%'); // 1610 = 1920-150-160 安全区
# });
Step 3 · Render (branch by format)
- long(16:9):
npx hyperframes render --resolution landscape-4k --quality high --output renders/<slug>-h-4k.mp4(true 4K supersampling). - short 3:4 (vertical default): no 4K preset → native
--quality highrenders 1080×1440, then ffmpeg lanczos upscales to 2160×2880 (full command see above "3:4's 4K", outputrenders/<slug>-v-4k.mp4)。Don't usezoom:2(deprecated, see Gotcha 3). - Loudness normalization(voiceover is often on the quiet side, video stream is not re-rendered):
ffmpeg -i renders/<slug>.mp4 -af loudnorm=print_format=summary -f null - # 量 ffmpeg -y -i renders/<slug>.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11 -c:v copy -c:a aac -b:a 192k renders/<slug>-loud.mp4 - Acceptance:
ffprobeconfirm two tracks video(h264)+audio(aac), duration = audio duration; extract frames to confirm each scene lands on a cue, and no abnormally identical frame sizes across scenes (to prevent blank scenes). Also putrenders/<slug>.srt(= corrected subtitles) in place.
Step 4 · Prepare covers for all aspect ratios (Delegate cover-design)
Cover images go to cover-design management. The prompt templates, composition recipes, generation scripts, and check scripts are all there in one copy — don't start another one here.
Generate the cover with codex-cli calling gpt-image(hand-drawn editorial ink-line art style):The art style is fixed, the composition varies by theme, and the layout varies by aspect ratio.
# 见 cover-design/SKILL.md 的 Step 1–4
# 1) 按主题挑 motif(cover-design/references/prompt-templates.md)
# 2) 按比例写版式(cover-design/references/ratios.md),提示词写进文件
# 3) 逐个比例出图(脚本会定位 codex 真实落盘的图,并校验尺寸与比例)
bash <cover-design>/scripts/gen-cover.sh covers/cover-16x9.png 16:9 /tmp/p-h.txt 1600
bash <cover-design>/scripts/gen-cover.sh covers/cover-3x4.png 3:4 /tmp/p-v.txt 1000
# …其余比例同理(16:10 / 4:3 / 9:16)
# 4) 三道检查 + 逐字核对标题
bash <cover-design>/scripts/check-covers.sh covers/
This layer only guarantees two things:all five aspect ratios present(check-deliverables.sh will be verified), andseries consistency(the art style and title wording follow this series; a new episode continues it rather than starting a new one).
How to approach the design (theme → composition recipe, layout for each aspect ratio, platform safe zones, 120px readability, andafter image generation the title must be checked character by character) → read cover-design/SKILL.md。
needprecise numbers or real trademarkscovers / data charts — don't hand them to an image model — use
cover-design/scripts/render-cover.shthe HTML→PNG precise typesetting.
Step 5 · Platform copy
Write youtube.md + bilibili.md; paragraph order, platform differences, and chapter timestamps follow the skeleton in references/platform-copy.md .
Specific content — promotions, ads, personal info, related links — are placeholders, filled in by the agent based on the current situation: sometimes there are promotions, sometimes not; stay flexible. Content sources can be the current conversation context, the creator profile in the user's memory, or asking the user directly.
Step 6 · Checklist acceptance (must run at the end)
bash <本 skill>/scripts/check-deliverables.sh <slug-dir> short # 或 long;默认 full = 5 比例
# 第 3 个参数是封面档位:full(默认,5 比例)/ finance(4 比例,跳过 16:10,供 finance-stock-video 用)
Only counts as complete when all are ✓; if any are ✗, go back and fill them in.Don't auto-commit(unless the user explicitly asks).
Optional segments: cover opening + CTA end card (improves completion / conversion)
Two enhancement segments, added as needed; they noticeably improve the viewing experience and conversion.
Cover opening (cover-as-intro)
Treat the cover image as thevideo opening title card: hold for about 3 seconds (until the opening line is finished), then transition into the main video. One cover image serves two purposes — it's both the published cover and the intro.
- scene 0 = cover design(same as
cover-9x16the cover-9x16 version, full canvas),data-start=0, with the duration extending to the end of the transition (e.g., 4.2s). - Persistent header/footer/progress bar hidden during the cover phase: give them a unified
.chromeclass,tl.from(".chrome",{opacity:0,duration:0.5}, <转场时刻>)——from's immediateRender will set them transparent at time 0, keeping the cover phase clean, then they fade in with the main video during the transition. - Transition = the main video's first scene reveals itself with clip-path(
wipe("#s1", <转场时刻>)), don't wipe out the cover (this violates the producing-video transition iron rule). The cover just needs to be covered by the first scene. - Align the transition timing with the end of the opening line(the start point of the cue after the first cue); following the voice makes the transition most natural; strictly locking to 3.0s is also fine, but don't cut off the opening line.
CTA end card (like/follow/share)
After the narration finishes, add a pagesilent end card: like / follow / share, hold for about 5 seconds.
- After the last scene, add
scene(e.g.,data-start=75.5 data-duration=5.5),rootdata-durationand the progress bartl.fromTo(".pf",…,duration)both need to be extendedto the new ending, otherwise the end card gets cut off. - Content: large heading "Like it? One-click triple combo" + three action blocks (👍Like / +Follow / ↗Share). Emoji can render on the colored blocks (headless Chrome supports it), but they clash a bit with BlockFrame's flat feel; if that bothers you, use plain glyphs.
- Audio clip durationunchanged(still runs to the end of narration), the end page is silent — the standard ending for a short video.
Pitfalls after re-rendering the end page / any change (been there): rendering and
loudnormtwo stepsDon't let background tasks collide。loudnormuse-c:v copy, if it runs before the new render finishes writing and is watching the same target filename, it will take theold versionand overwrite it after normalization.Iron rule: render to a temp file → finish writing completely (probe until size is stable) → loudnorm → move back to the final name, and from thefinal fileextract frames for verification (duration correct, key pages present).
Incremental rebuild · chapterized production (change one segment and only regenerate that segment)
Motivation: a small change actually triggers three costs. After review, the voiceover script often changes (word swaps, pause adjustments, fixing proper nouns misread by TTS). In a monolithic whole-video pipeline, a small change cascades into: ① full TTS regeneration(even for one word, the entire narration reruns); ② whole-video retiming — every time TTS reruns, even if the sentences are unchanged, the timestamps drift overall (TTS is nondeterministic), so all data-start + tl timings must be realigned (manual retiming is the biggest chunk of agent token cost); ③ whole-video re-render(a dozen-plus minutes at 4K).
When the video is longer and the voiceover is expected to be tweaked repeatedly, switch to chapterized incremental mode, converging all three costs to "only touch the segment that changed." Short videos / one-shot productions don't need this setup.
Architecture in one sentence: one chapter (one [NN] subsection in the voiceover script = a natural unit) = a self-contained unit = { that segment's voiceover → that segment's audio (timed from 0) → that segment's compositing → rendered into one clip }. Each segment'sencoding parameters strictly consistent, and finally assemble-episode.sh concatenate without re-encoding, with the progress bar overlaid as the last overlay. Changing [07] only requires: regenerate [07] audio → that segment's SRT auto-retimes → re-render only [07] → re-concatenate; the other segments hit the cache and stay untouched.
Three must-hit pitfalls(full design, directory structure,manifest.json cache ledger see references/incremental-chapters.md):
- Concatenation must be without re-encoding — each segment uses exactly identical encoding parameters (read at render time from
manifest.encode), and theffmpeg -f concatdemuxer concatenates directly, in seconds, with zero quality loss. If parameters are inconsistent, all is lost. - SRT-driven timing, don't hardcode seconds — motion effects bind to "the Nth cue / anchor," and at build time the segment's SRT is read to auto-fill
tl. So when that segment's TTS changes, rerunning the timing script auto-aligns it,zero manual work, zero tokens. Even if you don't do chapterization, using this alone in a monolithic pipeline immediately eliminates retiming cost. - The progress bar is the last overlay; don't let each chapter draw its own — the progress bar is not drawn during segment rendering; after assembling, use
assemble-episode.sh'sdrawbox(width varies witht/总时长) to overlay it; decoupled from chapters, overlay only once when the total duration changes.
Final script:
scripts/assemble-episode.sh --segs segs/ --out renders/<slug>-4k.mp4 # 无重编码拼接 + 进度条 overlay
scripts/assemble-episode.sh --segs segs/ --out /tmp/body.mp4 --no-bar # 纯拼接(最快),进度条另叠
The intro (cover-as-intro) and loudnorm are still the final steps, run as usual after the assembled output. Cache check: compare segment by segment narration_hash / html_hash, and only run TTS / rendering for mismatched segments (see reference for details).
Delivery checklist (this skill's definition of "done")
-
renders/<slug>.mp4(alreadyloudnormto -14 LUFS) +renders/<slug>.srt - coverall five aspect ratios present:
cover-3x4.pngcover-9x16.pngcover-16x9.pngcover-16x10.pngcover-4x3.png(main cover per format: short=3:4 / long=16:9) -
youtube.md+bilibili.md(including chapter timestamps + promo slot) -
PLAN.md(including verified factual points) +narration.txt - voiceover script has gone through the writing style process two passes(one pass for punctuation + one pass for voice, see Step 0; style source per Step 0's resolution ladder: writing style skill → memory → built-in baseline, if a skill is available use the Skill tool to invoke it)
- voiceover script has gone through polyphone scan(
scan-heteronyms.sh,⚠lines have all been processed, see Step 0 third pass) -
check-deliverables.shall ✓ -
lint0 error ·validateall passed ·inspect0 real overflows - layout fill self-check: each page fill rate ≥ ~50% (in vertical orientation, don't let content crowd the top half and leave too much blank at the bottom, see Step 2)
- extract frames to confirm each scene matches the cue and there are no blank scenes;
ffprobeboth tracks present and durations match - if a CTA end page / cover opening was added: root duration + progress bar have been extended accordingly;from the final fileextract frames for review (to prevent rendering/loudnorm collision overwrites)
- not auto-committed
Gotchas (specific to this layer, layered on top of producing-video)
- The cover is "a set", not "a single one". delegate to
cover-designrungen-cover.sh+check-covers.shper aspect ratio to produce all ratios, thencheck-deliverables.shfinalize. This is this skill's number one reason to exist — don't only make the main cover again. - Vertical is fixed at 3:4, don't make a 9:16 video. 9:16 is too tall and narrow, poor mobile experience (user's explicit preference); the vertical main canvas is always 3:4 (1080×1440). 9:16 is kept only as one of the cover aspect ratios, the video itself is not produced in it.
- 3:4 has no 4K preset, don't use
zoom:2。 don't try--resolution 2160x2880(it will be rejected).zoom:2supersamplingis deprecated(it breaksbottom:-positioned footers/chips, false positives have been hit). Use "native 1080×1440--quality high+ ffmpeg lanczos upscaling to 2160×2880" (Step 3). For 16:9 uselandscape-4k(has a real preset). - The intermediate native render for 3:4 (
/tmp/v-native.mp4) is deleted after use. after lanczos upscaling keep onlyrenders/'s master, don't leave intermediate files in the project. - Series consistency. Voice, color scheme, fonts, header/footer, and cover layout all carry over from the series; a new episode continues rather than starting over.
- Factual verification. Command/architecture/process-type content: actually run it before putting it on screen, don't make up inputs and outputs.
- Go through the voiceover script twice
personal-chinese-writing-style, not once. A single pass lets anthropomorphic colloquial verbs in the sound layer slip through (quietly doing, shout out, work, growing on, toss into...). The first pass cleans punctuation, the second pass cleans the sound;diffconfirm the draft has landed both passes (see Step 0). - For vertical screens, don't let content crowd the top half. The default top-aligned
.wrapfill rate is only 14–33% when content is sparse, leaving a big empty area at the bottom. Use.wrap.fillthree-part structure + enlarged hero + bottom anchor card, target 75–85%; don't put a lone kicker in the top area (pair it with a lead). For layout fill self-check see Step 2. - After re-rendering changes, don't collide with loudnorm. Render to a temp file → fully finish writing → loudnorm → install back to the final name, and extract a frame from the final file to recheck. Rendering directly to the target name + parallel loudnorm will overwrite in the old version (been there).
- All 12 gotchas of producing-video take effect(especially "animations must be attached to
tl" "transitions only use clip-path reveal" which fail most often) — this skill doesn't restate them, just follow them.
Out of scope
- Cover design and rendering: is the job of
cover-design. This layer only verifies "all five ratios present". - Voiceover/subtitles(text→audio+SRT): is the job of
listenhub-tts. - Video output iron rules details(HyperFrames compositing/rendering): is the job of
producing-video, this skill copies it as-is. - Voice cloning: use a cloud TTS that supports cloning, only swap "the step that makes sound", downstream unchanged.
- Publishing and uploading: this skill only produces materials, does not upload to platforms.