SuperAPI AI Directory

producing-video

Design & creation

View repository

Updated 10-16 02:31

About this skill

Producing-Video · Audio + Subtitles → Finished Video

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

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

Division of labor (very important)

Who What they do
User Write the script → record/synthesize audio → generate SRT → hand audio.mp3 + audio.srt to you
This skill (you) Choose frame/brand → build the HyperFrames composition according to the SRT → proofread → render the finished video

Userdo notexpect you to generate the voiceover (that's upstream). If the user doesn't have audio yet and is asking "how to do the voiceover," that's not this skill — TTS / voice cloning is a separate track (see "Out of scope" below).

Dependency check (pre-flight)

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

Both the hyperframes / hyperframes-cli skills are required (authoring the composition + running the CLI). If they're missing, have the user npx hyperframes skills install and start over.

Confirm the user gavetwo files: audio (mp3/wav/m4a) + SRT. If only audio is given without an SRT → this skill needs the SRT for the timeline; you can ask the user to supply an SRT (many recording tools/editing software can export one).do notdefault to running Whisper transcription (a user not providing an SRT is often intentional; ask first).


Workflow

Step 1 · Choose frame / brand

The visuals' style = a frame.md / visual-style / an existing series brand. Three sources, choose according to the situation:

  1. Continue the series brand(recommended for daily updates/series). If this episode belongs to an existing series (such as "AI Morning Read"), go look through that series' covers/past finished videos,reuse the same set of tokens(colors, fonts, grid, header and footer) so the video and the cover carry the same lineage.
  2. HyperFrames frame.md template. The user might 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 you probably won't get the body text);
    • more reliable: on this machine, in the open-design repo, find design-templates/*<slug>*/template.json, which contains the exact 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 , copy the YAML tokens 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 per frame (Archivo Black / Manrope / Oswald…); label numbers commonly use JetBrains Mono. Download from the 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

Step 3 · Parse SRT → split into scenes

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

  1. Group cues into "acts/scenes" by content. A single SRT cue is usually one sentence; combine several cues that talk about the same thing into one scene. For a 67-minute daily update, about 1216 scenes is more comfortable (average ~30s/scene, so the visuals don't stay still for 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 and avoid revealing the seams). 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 a sense of synchronization.
  5. On-screen copy comes from the SRT, and it must not conflict with the voiceover. It can be refined into poster-style short phrases, but it cannot say A while the visuals show B. Also double-check facts (numbers, proper nouns, names) — users care a lot about accuracy.

Put the scene start times into a JS array const B = [...], and position all tweens with B[i-1] + 局部偏移 . When fine-tuning the timeline later, only change the array, not each tween individually.

Step 4 · Write the composition (index.html)

  • Persistent chrome: elements that are present in every scene, such as header/footer/grid/border, go outside the sceneoutside(attach directly to #root, not a clip), and stay still for the whole piece — creating a "program" feel.
  • 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 do not need class="clip"; give it its own data-track-index。
  • Each scene gets one full-canvas .scene.clip, each with its own independent data-track-index(overlapping wipes need different tracks),z-index increments (later ones cover earlier ones).
  • Transitions only use "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 animationsuse gsap.from(), positioned at the corresponding cue time. Short (0.3–0.7s), staggered, varied ease.
  • Reuse one set of class-based CSS utilities(kicker / headline / note / stat / chip-xform / cards / numbered-list / accent-mark), shared by 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 text in ink color pressed on top of the blocks).

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 bleed off-canvas → mark data-layout-ignore. Real overflow → change the container/font size/padding.

Step 6 · Render + acceptance

  • First run a standard self-check pass(fast), use ffmpeg to sample a few frames and verify synchronization: sample frames at time points where "you know what is being talked about at this moment" and confirm the visuals match.
    ffmpeg -y -ss <秒> -i renders/x.mp4 -frames:v 1 /tmp/f.png   # 然后看图
    
  • 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); even if viewers watch at 1080p, a 4K master holds up better against platform recompression. 4K + long-form rendering takes a while (a few minutes to a dozen-plus minutes), and can run in the background.
  • Acceptance:ffprobe Confirm there are two tracks, video (h264) + audio (aac), and that the durations match; extract frames to confirm each scene lands on its cue.

Leave the final cut in studio/videos/<slug>/renders/。Do not auto-submit(unless the user explicitly asks for it).


Gotchas (hard-won lessons, must follow)

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; do not set durations by feel and then force-fit the audio.
  2. If there's an SRT, don't transcribe. If the user gave an SRT = you get a precise timeline for free, no Whisper needed. (If there's no SRT and you want to transcribe yourself, ask the user first.)
  3. For transitions, only use the scene's own clip-path reveal。Neveruse a separate full-screen color block/curtain/guillotine/slam board for transitions — in the rendering engine it will "sweep in, cover the next scene, then get stuck and not leave," turning the whole scene into a solid color/black screen. The reveal reveals the incoming scene itself.
  4. Do not wrap the .pad container in an overall-opacity "pushIn" wrapper. Container-level opacity animation may stay at 0 in seek rendering, turning the whole scene black. Use each element's own gsap.from()。
  5. Do not add overwrite:"auto"to paired tweens. It will kill the other tween in the pair (e.g., the "sweep in" is there but the "sweep out" is gone). The lint overlapping_gsap_tweens is a harmless warning; better to leave it.
  6. Fonts must be local woff2。Google Fonts <link> will be flagged by lint and is unreliable in sandbox rendering. For Chinese, use Noto Sans SC;Chinese characters in colored accent blocks need ample vertical padding/line-height(CJK glyphs are taller than the em box; if padding is too tight, inspect will report text_box_overflow; give ~0.2em vertical padding + line-height ~1.12).
  7. For dark themes, don't trust brightness detection. The 1×1 average brightness is always low for a dark background + sparse text, and will misjudge a normal scene as a black screen.Rely on extracting frames and looking at the imagesto confirm.
  8. False contrast positives on 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) will be reported as low contrast once it is sampled at a time when its own scene is not active — 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 is still high contrast, with zero false positives.
  9. Determinism. Prohibit Date.now() / Math.random()(breaks reproducible rendering); for randomness 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. Image quality: native 1080p looks soft on Retina (upscaled + H.264 4:2:0 softens colored text edges); for the master use --resolution landscape-4k --quality high。

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

  • Generate voiceover / voice cloning: this skill only consumes audio provided by the user. If you want it to "sound like me," use cloud TTS (MiniMax / ElevenLabs, supports Chinese + cloning); cloning only replaces the "speaking step," and the downstream timeline/mounting/rendering stays the same. HyperFrames' built-in Kokoro TTS cannot do Chinese(CLI passes zh, espeak needs cmn, and poor quality); local temporary solution for macOS say -v Tingting。
  • slide deck: what is wanted is a horizontal page-turning deck rather than a video → use a slides-type skill.

Output checklist

  • lint 0 error · validate all passed · inspect 0 issue
  • extract frames to confirm each scene lands on its cue (especially data pages/turning-point pages)
  • ffprobe: two tracks, video + audio, duration = audio duration
  • master uses 4K high (unless the user says otherwise)
  • the finished video is in studio/videos/<slug>/renders/, not automatically submitted