SuperAPI AI Directory

producing-video

Design & creation

View repository

Updated 10-16 09:53

About this skill

Producing-Video · Audio + Subtitles → Finished Video

Turn "the user's already-recorded voiceover audio + SRT subtitles" into onevisuals follow the audioMP4. Produce the video with HyperFrames (HTML is the video).

Iron rule: the audio and SRT are the single source of truth. The visual content comes from the SRT, the visual timeline comes from the SRT's cue timestamps, and the audio is attached as a <audio> clip directly into the composition and automatically merged at render time —— there is no "render the video first, then merge the audio" step。

Division of labor (very important)

Who Does what
Upstream Write the script → record/synthesize audio → generate SRT
This skill (you) Pick frame/brand → build the HyperFrames composition per the SRT → proofread → let the user review it in preview → render the finished video

This skill's input is exactly audio + SRT, you just produce the video.Voiceover/subtitles are upstream's job: the user records it themselves and hands it to you, or when only the voiceover text is given——first use listenhub-tts skill (text → audio + subtitles) to fill in narration-full.mp3 + narration.srt, then come back here to run the main workflow. Voice cloning is a separate track (see "Out of scope").

Dependency check (pre-flight)

npx hyperframes doctor      # 需要 Node ≥ 22 · FFmpeg · Chrome

Both hyperframes / hyperframes-cli skills need to be present (writing the composition + running the CLI). If missing, have the user npx hyperframes skills install and start over.

Confirm the user gavetwo files: audio (mp3/wav/m4a) + SRT. Only audio without SRT → this skill needs the SRT to get the timeline; you can ask the user to supply the SRT (many recording tools/editing software can export it),do notby default go run Whisper transcription (when the user doesn't give an SRT it's often intentional, ask first).Only text was given (the voiceover script), and not even audio → first run the listenhub-tts skill to produce audio + raw subtitles (and correct them per its workflow), then once you have the mp3 + SRT, enter the workflow below.


Workflow

Step 1 · Pick frame / brand

The visual style = a frame.md / visual-style / an existing series brand. Three sources, choose by situation:

  1. Continue the series brand(recommended for daily updates/series). If this episode belongs to an existing series (e.g. "AI Morning Reading"), go look through that series' covers/historical videos,reuse the same set of tokens(colors, fonts, grid, header and footer), so the video and cover are of the same lineage.
  2. HyperFrames frame.md template. The user may name it directly, for example creative-mode / biennale-yellow / cobalt-grid. Take tokens:
    • curl -sSL https://www.hyperframes.dev/design/<slug>.md (the site is JS-rendered, so the body text mostly can't be fetched);
    • More reliable: on this machine in the open-design repo look for design-templates/*<slug>*/template.json, which has the precise palette / typography(hex + font names).
  3. 8 built-in visual-styles(Swiss Pulse / Velvet Standard / Shadow Cut / Maximalist, etc.). In the hyperframes skill's visual-styles.md just copy the YAML token directly.

Fonts are always local woff2(see Gotcha "Fonts"). Chinese must be paired with Noto Sans SC(400/500/700/900 depending on usage); English display is chosen by frame (Archivo Black / Manrope / Oswald…); labels and numbers commonly use JetBrains Mono. Download from fontsource CDN into the project's fonts/:

curl -sSL -o fonts/<name>.woff2 "https://cdn.jsdelivr.net/fontsource/fonts/<family>@latest/<subset>-<weight>-normal.woff2"
# 中文:subset 用 chinese-simplified;拉丁:latin

Step 2 · Start the project

cd <repo>/studio/videos                       # 仓库约定:成片放这里
npx hyperframes init <YYYYMMDD-slug> --example blank --non-interactive
cd <YYYYMMDD-slug> && mkdir -p fonts audio
cp <user-audio> audio/narration-full.mp3
cp <user-srt>   audio/narration.srt

When the directory already has source files(daily-update directories often already contain script.md / narration.txt):hyperframes init refuses non-empty directories (reports Directory already exists and is not empty). First init into a temporary directory, then cp -R __tmp/. <slug>/ merge the scaffold in, or first init then add the draft.

Step 3 · Parse SRT → cut scenes

Read the full SRT (scripts/srt-cues.mjs can print each cue's start seconds + text, convenient for planning). Then:

  1. Group cues into "acts/scenes" by content. One SRT cue is usually one sentence; combine several cues about the same thing into one scene. For a 67-minute daily update, about 1216 scenes is more comfortable (average ~30s/scene, so the image won't stay still too long).
  2. Scene start time = the start timestamp of its first cue(seconds).
  3. Scene duration = next scene start − this scene start + ~0.5s(this 0.5s overlap lets the transition wipe cover the previous scene, avoiding revealing the seam). The last scene runs to the end of the audio.
  4. Sub-reveal within a scene = the start time of the corresponding sub-cue. Make titles/key points/numbers appear "at the moment they are spoken" — this is the key to the sense of sync.
  5. On-screen copy comes from the SRT, and must not clash with the voiceover. It can be refined into poster-style short phrases, but it must not say A while the screen writes B. Also check the facts (numbers, proper nouns, names) along the way — users care a lot about accuracy.

Put scene start times into a JS array const B = [...], and use it for all tweens B[i-1] + 局部偏移 to position. Later, tweaking the timeline only requires changing the array, no need to edit each tween one by one.

Step 4 · Write the composition (index.html)

  • Persistent chrome (less is more): elements that are present in every scene, such as grids/borders/progress bar/a brand pill in a corner, belong outside the scenesoutside(attach directly to #root, not a clip), and stay still throughout.Restraint is key: chrome only carries positioning and branding,don't stuff in filler text labels — text like "AI News · Objective Sharing" "Landscape · 16:9" "Portrait · 3:4", which is neither core content nor present in every frame, is noise; delete it all. Sufficient persistent chrome = a thin border + progress bar + one small brand pill, nothing else (a series/topic name in the header may be kept, or also deleted).
  • One continuous audio clip("audio is the clock"):
    <audio id="vo" src="audio/narration-full.mp3" data-start="0" data-duration="<总时长>" data-track-index="20" data-volume="1"></audio>
    
    Media elements don't need class="clip"; give it its own data-track-index。
  • One full-canvas per scene .scene.clip, each independent data-track-index(overlapping wipes need different tracks),z-index incrementing (the later one covers the earlier one).
  • Transitions use only "the incoming scene's own clip-path reveal"(see Gotcha "Transitions"):
    function wipe(sel, at){ tl.fromTo(sel,{clipPath:"inset(0 100% 0 0)"},{clipPath:"inset(0 0% 0 0)",duration:0.5,ease:"power3.inOut"}, at); }
    
  • Entrance animationUse tl.from(sel, vars, 位置秒)(must be attached to the window.__timelines registered to tl Entrance animations use tl.from(sel, vars, position-in-seconds) ( must be attached to the tl registered on window.__timelines , the third argument is absolute seconds on the tl), positioned at the corresponding cue time. Short (0.3–0.7s), staggered, varied ease. Never use bare gsap.from() / gsap.to() —— they attach to the gsap global timeline, while rendering only seeks tl , which causes some scenes (especially later ones) to be entirely blank in the seek render (see Gotcha 12)., the third argument is absolute seconds on the tl), positioned at the corresponding cue time. Short (0.3–0.7s), staggered, varying ease.Never use bare gsap.from() / gsap.to() — they attach to the gsap global timeline, while rendering only seeks tl, which will cause some scenes (especially later segments) to be completely blank in the seek render (see Gotcha 12).
  • Reuse one set of class-based CSS utility components(kicker / headline / note / stat / chip-xform / cards / numbered-list / accent-mark), shared by all 15 scenes; don't write a separate set of id styles for each scene.
  • Brand discipline: strictly follow the frame's tokens; use accent colors as "punctuation" not "fill" (dark themes: accent colors only for small, solid marks; light themes: accent colors as color blocks, with ink-colored text pressed on top of the blocks).
  • Motion choreography(15 common Motion types: transition Push/Slide, staggered entrance, state change, click ripple, scroll, navigation, loading skeleton, data bar growth/rolling text, list reorder, drag rebound, pulse guide, logo decoration, 3D flip card/parallax, particle burst, typewriter): when you need to arrange "how it moves" for a scene, see references/motion-patterns.md — each type has astandard name(use it directly when communicating with the Agent) + a design-neutral seek-safe GSAP recipe, plus choreography aids (custom cubic-bezier ease / shiftChildren whole-block retiming / timeScale write slow first then compress). Data page bar growth/number rolling (type 08, including typeNum / code table variants) is especially common.
  • Transition seams(zoom-through / follow-through cut / same-background hard cut / frame-continuous hard cut / curved-edge rising curtain): the default transition is still the incoming clip-path reveal (Gotcha 3); when you want a more cinematic transition, see references/scene-transitions.md — a still seek-safe "transition verb list", all of whichonly move the scene itself, with a "crossfade red line" and a "Z one-way law".
  • Imperative engine(WebGL shaders / Canvas particle fields / Lottie / pseudo-3D): for things GSAP tweens cannot express, see references/runtime-adapters.md — the core is theproxy clock bridge(tl on the tl ease:"none" add a {t} proxy,onUpdate draw in, never rAF), plus Lottie frame driving, WebGL→2D fallback, and CSS pseudo-3D.
  • Visual effects(local highlight/spotlight, iOS frosted glass, background blur/depth of field, local magnifier, material-filled large text, light-band text sweep, halftone canvas background): when you need to add this kind ofvisual textureto a scene, see references/visual-effects.md — inside are design-neutral mechanisms (pure CSS + GSAP + deterministic rendering precautions, with hooks left via CSS variables in the shell). All have been verified in headless Chrome to render frame by frame (including backdrop-filter)。
  • Every scene must have a "motion thesis"(motion thesis), not just an entrance: think clearly about this scene'swhat moves first, where it moves, and how the background responds, andFirst visible motion within 0.2shappens (don't let the opening frames be dead). Emphasized decorative elements (green cursor, small ball) obey the **"one-second rule"——its entire lifespan aligns witha word being spoken** (fly in → click → exit ~0.9s); lingering too long feels cheap.Multiple brands/multiple logos in the same framegive each its owndedicated color lane and ground(e.g. a color that only lives inside the terminal window is a bug once it leaves the window); accent colors are allocated per frame, better too few than too many.

Step 5 · Proofread (run after every change)

npx hyperframes lint        # 0 error 才继续(var(--x) 字体告警是误报,可忽略)
npx hyperframes validate    # WCAG AA 对比度;改掉过暗的次级灰
npx hyperframes inspect --samples 30   # 版面溢出,带时间戳;场景多就多采样

Decorative elements intentionally bleeding off-canvas → mark as data-layout-ignore. Real overflow → fix container/font size/padding.

Step 6 · Preview · Let the user look first (the manual gate before rendering)

Rendering is expensive (4K long-form takes minutes to over ten minutes),don't send a composite nobody has seen straight to render. First use HyperFrames' built-in preview studio to "play" the composite in the browser, let the user confirm with their own eyes, and only after they nod move on to Step 7 rendering.

cd studio/videos/<slug>              # 横竖分目录时进到对应的 build-h / build-v
npx hyperframes preview             # 默认 3002 端口,自动开浏览器

It starts a local studio,truly plays back along the timelineyour composite: with a playhead + ruler you can scrub freely, audio plays along (judge on the spot whether visuals and voiceover are in sync), changes to index.html hot-reload (second-level feedback, no re-render needed). This is the main force for "preview without producing a video" —— all intermediate iterations happen here,render only kept for the final output once.

  • Don't just double-click to open index.html —— that way you only see the static first frame. For the reason see Gotcha 12: all timed animations hang on window.__timelines 's tl and are driven by external seek; opening the HTML bare means nobody seeks, so wipes / entrances all stay in their initial state (scenes stacked together or blank).You must go through preview(it drives tl for you).
  • This is the manual gate: hand the preview address/frames to the user and ask them to go through it —— especially the numbers on data pages, the pacing of transition pages, and sync with the voiceover —— only after getting an explicit OK move on to Step 7. This one extra look before rendering saves "rendering, only then discovering changes are needed, and rendering all over again."
  • contact-sheet first (optional for complex pieces): before writing motion effects, first make amotion-free HTML storyboard sheet(0 GSAP: one 16:9 cell,aspect-ratio:16/9; overflow:hidden, with a note under each cell like "what moves first in this scene, where it cuts, how the background echoes it"), align keyframes with the user,only after approval write the motion effects——saves "rendering, only then discovering the composition is wrong." The storyboard reuses the same kit.css/token as the final piece, and approved cells can be moved into the composite almost verbatim.
  • agent refinement:npx hyperframes preview --context(or --selection, add --json) can output the currently selected element/context in the studio as text, and based on that help the user pinpoint exactly which part to change.
  • Cleanup:--list check the running previews,--kill-all shut them all down; when switching projects or a port is occupied add --force-new。

Step 7 · Render + acceptance

  • First run a standard version for self-check(fast), use ffmpeg to sample a few frames and verify sync: sample frames at time points where "you know what is being said at this moment," and confirm the visuals match.
    ffmpeg -y -ss <秒> -i renders/x.mp4 -frames:v 1 /tmp/f.png   # 然后看图
    
  • Blank-scene cheap self-check: Sample one frame from each scene midpoint, and first compare PNG file size / hash — an identical size appearing across scenes = a strong signal of the same blank frame (dark themes especially rely on this, don't just trust your eyes). Once hit, then look at the image specifically.
    for t in <每场中点秒>; do ffmpeg -y -ss $t -i renders/x.mp4 -frames:v 1 /tmp/f_$t.png; done; ls -la /tmp/f_*.png   # 同尺寸 = 可疑
    
  • Measure seams rather than look at them (when a transition is in doubt):onUpdate is frozen on the snapshot,Media/driver effects can only be verified as right or wrong through real rendering + ffmpeg frame extraction. To prove whether a scene change is a "clean hard cut" or a "grayish crossfade", use signalstats to measure brightness frame by frame — a hard cut is luma 191→62a one-step jump with no intermediate gray frames, while a crossfade will produce a string of darkened transition frames near the cut point (i.e. the "crossfade red line", see scene-transitions.md):
    ffmpeg -i renders/x.mp4 -vf "select='between(t,<切点-0.3>,<切点+0.3>)',signalstats,metadata=print" -f null - 2>&1 | grep YAVG
    
    When comparing revisions, force both versions to the same specs (same fps/duration/frame count) and then compare brightness frame by frame / make a contact sheet (fps=1/1.6,scale=300:169,tile=6x4), turning "does it look alike" into a numerical difference per beat → pinpoint exactly which scene needs changing.
  • Final render(master):
    npx hyperframes render --resolution landscape-4k --quality high --output renders/<slug>-4k.mp4
    
    --resolution landscape-4k is true supersampling of the same composition at 2× DPR to 3840×2160 (not upscaling); a 4K master is more resistant to platform recompression even if viewers watch at 1080p. 4K + long-form renders take a while (a few minutes to a dozen-plus minutes), and can run in the background.

    Fast capture on by default (0.7.38+): On macOS with a hardware GPU, drawElement fast capture is enabled by default, speeding up capture by about 2×, and it comes with per-frame self-validation — if a frame can't be proven correct it automatically falls back to the classic screenshot path; normally you don't need to touch it and get the speedup for free.If an abnormal frame/glitch is rendered, turn it off to reproduce:PRODUCER_EXPERIMENTAL_FAST_CAPTURE=false npx hyperframes render ...(fall back to the classic path, then investigate). To stop mid-render, use Studio's render cancel, or just kill the process.

  • Loudness check + normalization: User voiceover / TTS is often too quiet. First ffmpeg -i renders/x.mp4 -af loudnorm=print_format=summary -f null - to measure overall loudness; if below about -16 LUFS, normalize to the web standard (-14 LUFS) — Video stream -c:v copy no re-render, done in seconds:
    ffmpeg -y -i renders/x.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11 -c:v copy -c:a aac -b:a 192k renders/x-loud.mp4
    
    While you're at it, also normalize the source audio/narration-full.mp3 so future re-renders (vertical / revised versions) inherit it automatically.
  • Acceptance:ffprobe Confirm there are two tracks, video(h264) + audio(aac), and that durations match (the duration in the CLI summary line may be misreported; rely on ffprobe instead); extract frames to confirm each scene lands on its cue.

Leave the final render in studio/videos/<slug>/renders/。Do not auto-commit(unless the user explicitly asks).

Step 8 · Cover (same token set, core first)

Use platform coversa standalone static HTML(no GSAP, all elements directly visible), reusing the final render's kit.css + fonts/, screenshot to PNG (locally python3 -m http.server + Playwright,scale:'device' at 2×). Common sizes:cover-16x9(horizontal main),cover-3x4 / cover-9x16(vertical),cover-4x3(backup).

Cover discipline = less is more: one overwhelming core title + a small amount of supporting info, delete everything else.

  • Delete filler chrome: eyebrow/kicker (prefixes like "follow-up to last episode…"), secondary header description lines, footer tagline, aspect ratio labels ("horizontal · 16:9") — none of these are core, remove them all.
  • What to keep: Core title (large serif/bold) + a one-line subtitle + (optional) a small rail previewing 3–4 content points; plus one small brand pill is enough.
  • After removing the eyebrow, remember to re-center the vertical positioning of the title block (the space originally reserved for the eyebrow will make the title sit too high).

Speech rate and 1.1× speed-up (at the upstream audio stage)

Authors whose voiceover is on the slow side (common in this project) should speed it up before delivery when the audio is produced upstream:ffmpeg -af "atempo=1.1,loudnorm=I=-14:TP=-1.5:LRA=11",At the same time, scale the SRT timestamps by 1/1.1(otherwise the subtitles and the visuals will all be misaligned). Verification:ffprobe the measured duration of the final video should equal "original TTS duration ÷ 1.1", not the original duration — use this ratio to confirm the speed-up actually took effect. See listenhub-tts skill。

Series templates (variables) · one composition → swap data each episode

For daily/系列 where you wantto reuse the same composition, and only swap the title/image/accent color each episode, use HyperFrames'composition variables(HeyGen's official variables mechanism; the contract is in hyperframes-core of variables-and-media.md). This way the brand chrome, grid, and motion choreographyare written only once, and you feed in one set of data per episode.

  1. Declaration(<html> on thedeclaration array, each entry requires id/type/label/default; type supports string/number/color/boolean/enum):
    <html data-composition-variables='[
      {"id":"title","type":"string","label":"标题","default":"本期标题"},
      {"id":"accent","type":"color","label":"强调色","default":"#66d9ef"}
    ]'>
    
  2. Consumption(zero JS, three bindings, consistent between preview/render, seek-safe):
    <h1 data-var-text="title">占位标题</h1>          <!-- 换文字,保留子 clip -->
    <img data-var-src="heroImage" src="fallback.jpg"> <!-- 换 src,authored src 即 fallback -->
    <style>.kicker{ color: var(--accent); }</style>   <!-- 每个标量自动暴露成 --{id} CSS 变量 -->
    
    Only loop/derived values need to be read once:const {title,accent}=window.__hyperframes.getVariables();(unchanged during variable rendering).
  3. Per-episode override(object, keyed by id; note that it and the declaration array are two different shapes):
    npx hyperframes render --variables-file day-042.json     # 每期一份 JSON,批量出片
    npx hyperframes render --variables '{"title":"Q4 营收"}'
    
    Add --strict-variablesin CI: undeclared keys / type mismatches / enum out-of-range all causeerrors, blocking dirty data before the video is produced.

Note the timeline difference: this skill's scene timeline ismeasured per-episode from the SRT, and each episode's duration/cut points differ — so variables are best for reusingbrand tokens + chrome + layout + motion recipesthis kind ofdoes not change with durationpart (especially short videos with a fixed format); the per-episode-varyingtimeline/scene segmentationshould still be re-arranged from the SRT per the main workflow; don't expect a purely static template to absorb the timeline. For series identity at the orchestration layer, see blockframe-video。


Gotchas (blood and tears, must be followed)

These are the pitfalls we've hit; violating any one of them will produce a ruined video:

  1. Audio is the clock. Scene durations are measured from the SRT; don't set durations by feel and then force the audio in.
  2. If you have SRT, don't transcribe. The user giving you an SRT = a precise timeline for free; no Whisper needed. (If there's no SRT and you want to transcribe yourself, ask the user first.)
  3. Transitions use only the scene's own clip-path reveal。Neveruse a separate full-screen color block/curtain/knife-gate/slam board to do transitions — in the rendering engine it will "sweep in, cover the next scene, and then get stuck without leaving", turning the whole scene into a solid color/black screen. The reveal reveals the incoming scene itself.
  4. For entrances use each element's own tl.from()(attached to tl, see Gotcha 12), don't give .pad Container wrapper for the overall opacity "pushIn" — container-level opacity animation may stay at 0 in seek rendering, turning the whole scene black.
  5. Paired tweens stay independent; do not add overwrite:"auto" — it will kill the other tween in the pair (e.g. the "wipe in" is there, the "wipe out" is gone). The lint's overlapping_gsap_tweens is a harmless warning; just leave it.
  6. Fonts must be local woff2。Google Fonts <link> will be flagged by lint and is unreliable in sandbox rendering. Pair Chinese with Noto Sans SC;Chinese characters inside colored accent blocks need ample vertical padding/line-height(CJK glyphs are taller than the em box; if padding is too tight, inspect reports text_box_overflow; give ~0.2em vertical padding + line-height ~1.12).
  7. For dark themes, do not trust brightness detection. A 1×1 average brightness is always low for a dark background + sparse text, so it misjudges normal scenes as black screens.Rely on extracting frames and looking at themto confirm.
  8. False contrast warnings in light themes. validate samples at a few fixed timestampsall DOM text, including scenes that are not active at that moment. On a light background, "light-colored text" (white text, cream text) once sampled at a moment when its own scene is not active gets reported as low contrast — this is a false positive. Workaround:always use ink-colored (dark) text on accent blocks, so that when inactive, dark text on a cream background still has high contrast, with zero false positives.
  9. Determinism. Prohibit Date.now() / Math.random()(breaks reproducible rendering); when randomness is needed, use a seeded PRNG.
  10. Each scene gets an independent track-index; audio gets its own high track-index. Decorative bleed marks data-layout-ignore。
  11. Quality: native 1080p looks blurry on Retina (upscaled + H.264 4:2:0 softens colored text edges); for the master use --resolution landscape-4k --quality high。
  12. Timed animations must be attached to tl on(the most frequent cause of ruined videos). Always use tl.from / tl.to / tl.fromTo(sel, vars, 位置秒)。Never use bare gsap.from() / gsap.to() — they attach to the gsap global timeline, while HyperFrames rendering only seeks the window.__timelines that is registered to tl. As a result, the wipe (on tl) and the entrance (on the global timeline) each run separately,the clip-path of later scenes is not driven → the whole video is left with only persistent chrome and blank content. The symptom is very subtle: the first few scenes look normal, and the further in you go, the emptier it gets. Scrubbing to the later part in preview makes it obvious at a glance (Step 6), or use the "cheap blank-scene self-check" from Gotcha 7 / Step 7.
  13. data-layout-ignore elements stay static. Decorative elements marked with it (large watermark numbers, etc.) are removed by validate from the DOM context, and then applying tl.from to it reports GSAP target not found. Just let the decoration be revealed along with the scene wipe; do not tween it separately.
  14. Supplement the duration contract. HF usesscene tl 's timeline.duration()(not the host's data-duration) to decide how long this scene is displayed — on a non-root host, data-duration is stripped by the runtime.tl is shorter than its main slot → hidden early → black flash at the end of the frame. For any scene whose animation is shorter than the scene, add an empty tween at the end to fill it out:tl.to({}, { duration: 本场时长 }, 0);. (In the studio console, enumerate iw.__timelines each duration, compare against the main data-duration, mark those below.)
  15. Media (video/image) iron rules。① <video>/<img class="clip"> must be attached to the master index.html, and putting them into a sub-composition template willrender as black(transforming master-level media is fine). ② Videomust have id(without an id it freezes/goes black, lint media_missing_id). ③ The frameworkforces clip opacity:1— if you want to fade in/out, fade theouter wrapper, don't fade the <video> itself. ④ Videoa real 3D/perspective ancestor will kill it。⑤ HF will not hold the last frame:data-duration exceeds the source length → empty window; to freeze the last frame,bake it into the file ffmpeg -vf "tpad=stop_mode=clone:stop_duration=0.7", or use a poster/screenshot <img> at the video window boundary, use tl.set to switch and fill the window. ⑥ For a base video shared by multiple scenes,frame-continuous hard cutsuse data-media-start(see scene-transitions.md D)。
  16. immediateRender contention. By default, immediateRender 's fromTo/from will, atbuild time(functional measurement set() before it runs) apply the from state — if an entrance tween and a "measurement positioning" (getBoundingClientRect, including ancestor transform) share an element/ancestor, the positioning willovershoot(gotcha: the card landed 1250px off-screen). Add immediateRender:falseto this kind of entrance tween. Continuous dot rings/word relays also rely on it to prevent all from states from grabbing the paint at t=0.
  17. Multi-composition orchestration (advanced). When there are many scenes (10+) or you want to preview each scene in isolation, you can split each scene into compositions/*.html sub-compositions, master index.html usinga single zero-tween gsap.timeline({paused:true}) as a pure clock, positioning only at the seams. At this point, two extra iron rules: ① A sub-composition script must include a fallback when getting root —const R=(document.currentScript&&document.currentScript.closest('#root'))||document.querySelector('#root'); if(!R||!window.gsap) return;(at runtime, after the sub-composition isinlinedinto index, currentScript is outside root,closest will be null → the script bails → previewing alone works, but entering index leaves the whole scene frozen). ② Every sub-composition must follow Gotcha 14 for padding duration.This skill's single-file index (scenes are inlined .scene.clip) is still the default, and multi-composition is just an optional structure for long/complex videos.

SRT → scene timeline (recipe)

场景[i].start    = cue[第一条].start                 (秒)
场景[i].duration = 场景[i+1].start − 场景[i].start + 0.5   (末场到音频末尾)
场景内某元素入场 = 它对应 cue 的 start
音频 clip        = data-start=0, data-duration=总时长
root data-duration = 音频末句之后留 ~3s 收尾

In JS:

const B = [0, 38.63, 53.96, /* ...每场 start... */];   // 从 SRT 量
const at = (i, off) => B[i-1] + off;                   // i 是 1-based 场号
function wipe(sel, i){ tl.fromTo(sel, {clipPath:"inset(0 100% 0 0)"}, {clipPath:"inset(0 0% 0 0)", duration:0.5, ease:"power3.inOut"}, B[i-1]); }

out of range

  • Voiceover / subtitles (upstream): turning spoken text into audio + SRT is the job of the listenhub-tts skill (ListenHub natively /v1/speech outputs audio + built-in subtitles, with cloud ASR as fallback). This skill only consumes audio + SRT.
  • Voice cloning / "sounds like me": upstream TTS uses a generic voice. To clone your own voice, use a cloud TTS that supports cloning (MiniMax / ElevenLabs); cloning only changes "the step that produces the sound", and the downstream timeline/mounting/rendering stays the same. HyperFrames' bundled Kokoro TTS can't do Chinese(CLI passes zh, espeak requires cmn, and the quality is poor); a temporary local option on macOS say -v Tingting。
  • slide deck: what's wanted is a horizontal page-turning deck, not a video → use a slides-type skill.

Output checklist

  • lint 0 error · validate all pass · inspect 0 issue
  • already in hyperframes preview played, the user has watched and OK'd, then proceed to render (don't skip this human gate)
  • all timed animations are attached to tl (no bare gsap.from/gsap.to, see Gotcha 12); short scenes have had their duration padded to fill the main slot (Gotcha 14, no black flash at frame end)
  • if video/images are used: media is attached to master, video has id, the faded element is the wrapper not <video>(Gotcha 15)
  • each scene has a "motion thesis", the first movement is within 0.2s; scene changes/imperative engines follow scene-transitions.md / runtime-adapters.md and are still seek-safe
  • extract frames to confirm each scene lands on its cue (especially data pages/turning-point pages); no anomalously identical frame sizes across scenes (no blank scenes)
  • ffprobe: video + audio two tracks, duration = audio duration (the CLI summary line duration may misreport)
  • loudness has been checked: anything below -16 LUFS has been loudnorm to -14 (-c:v copy no re-render)
  • master uses 4K high (unless the user says otherwise)
  • chrome is minimal: no filled header/footer labels (things like "AI News · Objective Sharing", "Landscape · 16:9"), only border + progress bar + small brand pill
  • cover core first: no eyebrow / header secondary line / footer tagline / aspect ratio label, highlight the main title
  • if the author speaks slowly: audio has been sped up 1.1× and the SRT has been scaled by 1/1.1 (final duration = original TTS ÷ 1.1)
  • final video is in studio/videos/<slug>/renders/, not auto-submitted