HyperFrames: Render MP4/WebM Videos from HTML Compositions
Render MP4/WebM videos from HTML compositions.
Written by Neura Market from the official Hermes Agent documentation for Hyperframes. Commands, paths, and version numbers are reproduced from the source unchanged.
Read the official documentationHyperFrames turns HTML into video. You write a composition as an HTML file with data-* attributes for timing, a GSAP timeline for animation, and CSS for appearance. The engine captures the page frame-by-frame and encodes to MP4/WebM with FFmpeg. Reach for this when a user asks for a rendered video from text, a script, or a website, especially for motion graphics, talking-head with captions, product tours, social overlays, shader transitions, or anything driven by real video/audio media.
What it does
HyperFrames treats HTML as the source of truth for video. Instead of keyframing in a traditional editor, you build a web page and let the engine record it. The result is deterministic: the same HTML and timeline produce the same frames every time, which makes it suitable for automated pipelines and repeatable renders.
The skill is a complement to manim-video. Use manim-video for mathematical or geometric explainers, like equations or 3B1B-style animations. Use hyperframes for everything else that is motion-graphics driven: animated title cards, lower thirds, typographic intros, captioned narration, audio-reactive visuals, scene transitions, social overlays, and website-to-video captures.
Before you start
This is an optional skill, installed on demand. It is authored by heygen-com, version 1.0.0, licensed under Apache-2.0, and supports Linux, macOS, and Windows. The skill path is optional-skills/creative/hyperframes.
You need Node.js >= 22 and FFmpeg installed. The setup script verifies both and prints fix instructions if they are missing. It also installs the hyperframes CLI globally and pre-caches chrome-headless-shell via Puppeteer, which is required for best-quality rendering through Chrome's HeadlessExperimental.beginFrame capture path. Finally, it runs npx hyperframes doctor and reports the result.
If setup fails, the troubleshooting reference has guidance.
Quick Reference
The core commands are:
npx hyperframes init my-video # scaffold a project
cd my-video
npx hyperframes lint # validate before preview/render
npx hyperframes preview # live-reload preview (long-lived server, port 3002)
npx hyperframes render --output final.mp4 # render to MP4
npx hyperframes doctor # diagnose environment issues
preview is a long-lived Next.js server that holds Chrome render workers open. Always stop it when done. A forgotten preview keeps idle chrome-headless-shell workers alive that, on GPU-less hosts (WSL, containers, CI), spin a CPU core each indefinitely via software WebGL (swiftshader).
Render flags: --quality draft|standard|high · --fps 24|30|60 · --format mp4|webm · --docker (reproducible) · --strict.
Full CLI reference is in references/cli.md.
Setup (one-time)
Run the setup script:
bash "$(dirname "$(find ~/.hermes/skills -path '*/hyperframes/SKILL.md' 2>/dev/null | head -1)")/scripts/setup.sh"
The script:
- Verifies Node.js >= 22 and FFmpeg are installed (prints fix instructions if not).
- Installs the
hyperframesCLI globally (npm install -g hyperframes@>=0.4.2). - Pre-caches
chrome-headless-shellvia Puppeteer, required for best-quality rendering via Chrome'sHeadlessExperimental.beginFramecapture path. - Runs
npx hyperframes doctorand reports the result.
See references/troubleshooting.md if setup fails.
Procedure
1. Plan before writing HTML
Before touching code, articulate at a high level:
- What, narrative arc, key moments, emotional beats
- Structure, compositions, tracks (video/audio/overlays), durations
- Visual identity, colors, fonts, motion character (explosive / cinematic / fluid / technical)
- Hero frame, for each scene, the moment when the most elements are simultaneously visible. This is the static layout you'll build first.
Visual Identity Gate (HARD-GATE). Before writing ANY composition HTML, a visual identity must be defined. Do NOT write compositions with default or generic colors (#333, #3b82f6, Roboto are tells that this step was skipped). Check in order:
-
DESIGN.mdat project root? → Use its exact colors, fonts, motion rules, and "What NOT to Do" constraints. -
User named a style (e.g. "Swiss Pulse", "dark and techy", "luxury brand")? → Generate a minimal
DESIGN.mdwith## Style Prompt,## Colors(3-5 hex with roles),## Typography(1-2 families),## What NOT to Do(3-5 anti-patterns). -
None of the above? → Ask 3 questions before writing any HTML:
- Mood? (explosive / cinematic / fluid / technical / chaotic / warm)
- Light or dark canvas?
- Any brand colors, fonts, or visual references?
Then generate a DESIGN.md from the answers. Every composition must trace its palette and typography back to DESIGN.md or explicit user direction.
2. Scaffold
npx hyperframes init my-video --non-interactive
Templates: blank, warm-grain, play-mode, swiss-grid, vignelli, decision-tree, kinetic-type, product-promo, nyt-graph. Pass --example to pick one, --video clip.mp4 or --audio track.mp3 to seed with media.
3. Layout before animation
Write the static HTML+CSS for the hero frame first, no GSAP yet. The .scene-content container must fill the scene (width:100%; height:100%; padding:Npx) with display:flex + gap. Use padding to push content inward, never position: absolute; top: Npx on a content container (content overflows when taller than the remaining space).
Only after the hero frame looks right, add gsap.from() entrances (animate to the CSS position) and gsap.to() exits (animate from it).
See references/composition.md for the full data-attribute schema and composition rules.
4. Animate with GSAP
Every composition must:
- Register its timeline:
window.__timelines[""] = tl - Start paused:
gsap.timeline({ paused: true }), the player controls playback - Use finite
repeatvalues (norepeat: -1, breaks the capture engine). Calculate:repeat: Math.ceil(duration / cycleDuration) - 1. - Be deterministic, no
Math.random(),Date.now(), or wall-clock logic. Use a seeded PRNG if you need pseudo-randomness. - Build synchronously, no
async/await,setTimeout, or Promises around timeline construction.
See references/gsap.md for the core GSAP API (tweens, eases, stagger, timelines).
5. Transitions between scenes
Multi-scene compositions require transitions. Rules:
- Always use a transition between scenes, no jump cuts.
- Always use entrance animations on every scene element (
gsap.from(...)). - Never use exit animations except on the final scene, the transition IS the exit.
- The final scene may fade out.
Use npx hyperframes add to install shader transitions (flash-through-white, liquid-wipe, etc.). Full list: npx hyperframes add --list.
6. Audio, captions, TTS, audio-reactive, highlighting
- Audio: always a separate `` element (video is
muted playsinline). - TTS:
npx hyperframes tts "Script text" --voice af_nova --output narration.wav. List voices with--list. Voice ID first letter encodes language (a/b=English,e=Spanish,f=French,j=Japanese,z=Mandarin, etc.), the CLI auto-infers the phonemizer locale; pass--langonly to override. Non-English phonemization requiresespeak-nginstalled system-wide. - Captions:
npx hyperframes transcribe narration.wav→ word-level transcript. Pick style from the transcript tone (hype / corporate / tutorial / storytelling / social, see the table inreferences/features.md). Language rule: never use.enwhisper models unless the audio is confirmed English,.entranslates non-English audio instead of transcribing it. Every caption group MUST have a hardtl.set(el, { opacity: 0, visibility: "hidden" }, group.end)kill after its exit tween, otherwise groups leak visible into later ones. - Audio-reactive visuals: pre-extract audio bands (bass / mid / treble) and sample per-frame inside the timeline with a
forloop oftl.call(draw, [], f / fps), a single long tween does NOT react to audio. Map bass →scale(pulse), treble →textShadow/boxShadow(glow), overall amplitude →opacity/y/backgroundColor. Avoid equalizer-bar clichés, let content guide the visual, audio drive its behavior. - Marker-style highlighting: highlight, circle, burst, scribble, sketchout effects for text emphasis are deterministic CSS+GSAP, see
references/features.md#marker-highlighting. Fully seekable, no animated SVG filters. - Scene transitions: every multi-scene composition MUST use transitions (no jump cuts). Pick from CSS primitives (push slide, blur crossfade, zoom through, staggered blocks) or shader transitions (
flash-through-white,liquid-wipe,cross-warp-morph,chromatic-split, etc.) vianpx hyperframes add. Mood and energy tables live inreferences/features.md#transitions. Do not mix CSS and shader transitions in the same composition.
7. Lint, validate, inspect, preview, render
npx hyperframes lint # catches missing data-composition-id, overlapping tracks, unregistered timelines
npx hyperframes validate # WCAG contrast audit at 5 timestamps
npx hyperframes inspect # visual layout audit — overflow, off-frame elements, occluded text
npx hyperframes preview # live browser preview
npx hyperframes render --quality draft --output draft.mp4 # fast iteration
npx hyperframes render --quality high --output final.mp4 # final delivery
hyperframes validate samples background pixels behind every text element and warns on contrast ratios below 4.5:1 (or 3:1 for large text). hyperframes inspect is the layout-side companion, runs the page at multiple timestamps and flags issues that a static lint can't see (a caption that wraps past the safe area only at 4.5s, a card that overflows when its title is the longest variant, an element that ends up behind a transition shader). Run inspect especially on compositions with speech bubbles, cards, captions, or tight typography.
8. Website-to-video (if the user gives a URL)
Use the 7-step capture-to-video workflow in references/website-to-video.md: capture → DESIGN.md → SCRIPT.md → storyboard → composition → render → deliver.
Cleanup
render is one-shot (workers exit when it finishes). preview is not, it runs a background Next.js server that keeps Chrome workers resident until you stop it. Never leave one running: on GPU-less hosts each idle worker's swiftshader process pegs a CPU core, and a preview left open for days stacks up multiple.
Stop a preview when the user is done reviewing (or before starting a new one):
pkill -f "hyperframes.*preview" # the Studio server (frees port 3002)
pkill -f chrome-headless-shell # its render workers; only safe if nothing else uses them
If unsure whether other tools use chrome-headless-shell, check first: pgrep -af chrome-headless-shell. Recover a wedged host (many idle workers spinning CPU) the same way, see references/troubleshooting.md#runaway-cpu-from-leftover-preview-workers.
Pitfalls
- Leaving
previewrunning, it's a long-lived server holding Chrome workers; on WSL/containers/CI those idle workers spin a CPU core each (software WebGL). Stop it when done, see Cleanup. HeadlessExperimental.beginFrame' wasn't found, Chromium 147+ removed this protocol. Ensure you're onhyperframes@>=0.4.2(auto-detects and falls back to screenshot mode). Escape hatch:export PRODUCER_FORCE_SCREENSHOT=true. See hyperframes#294 andreferences/troubleshooting.md.- System Chrome (not
chrome-headless-shell), renders hang for 120s then timeout. Runnpx puppeteer browsers install chrome-headless-shell(setup.sh does this).hyperframes doctorreports which binary will be used. repeat: -1anywhere, breaks the capture engine. Always compute a finite repeat count.gsap.set()on clip elements that enter later, the element doesn't exist at page load. Usetl.set(selector, vars, timePosition)inside the timeline instead, at or after the clip'sdata-start.inside content text, forced breaks don't know the rendered font width, so natural wrap +double-breaks. Usemax-widthto let text wrap. Exception: short display titles where each word is deliberately on its own line.- Animating
visibilityordisplay, GSAP can't tween these. UseautoAlpha(handles both visibility and opacity). - Calling
video.play()oraudio.play(), the framework owns playback. Never call these yourself. - Building timelines async, the capture engine reads
window.__timelinessynchronously after page load. Never wrap timeline construction inasync,setTimeout, or a Promise. - **Standalone
index.htmlwrapped in**, hides all content from the browser. Only **sub-compositions** loaded via `data-composition-src` use. - Using video for audio, always muted
+ separate.
Verification
Before and after rendering:
- Lint + validate + inspect pass:
npx hyperframes lint --strict && npx hyperframes validate && npx hyperframes inspect(lint catches structural issues, validate catches contrast, inspect catches visual layout / overflow issues, see troubleshooting.md if warnings appear). - Animation choreography, for new compositions or significant animation changes, run the animation map.
npx hyperframes initcopies the skill scripts into the project, so the path is project-local:
node skills/hyperframes/scripts/animation-map.mjs <composition-dir> \
--out <composition-dir>/.hyperframes/anim-map
Outputs a single animation-map.json with per-tween summaries, ASCII Gantt timeline, stagger detection, dead zones (>1s with no animation), element lifecycles, and flags (offscreen, collision, invisible, paced-fast <0.2s, paced-slow >2s). Scan summaries and flags, fix or justify each. Skip on small edits.
3. File exists + non-zero: ls -lh final.mp4.
4. Duration matches data-duration: ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 final.mp4.
5. Visual check: extract a mid-composition frame: ffmpeg -i final.mp4 -ss 00:00:05 -vframes 1 preview.png.
6. Audio present if expected: ffprobe -v error -show_streams -select_streams a -of default=nw=1:nk=1 final.mp4 | head -1.
If hyperframes render fails, run npx hyperframes doctor and attach its output when reporting.
When not to use it
Do not use this skill for pure math or equation animation, which is better served by manim-video. Do not use it for image generation or memes, which belong to meme-generation or image models. Do not use it for live video conferencing or streaming, since it renders to a file rather than a live feed.
Limits and gotchas
The biggest operational gotcha is the long-lived preview server. It is easy to forget and it burns CPU on GPU-less hosts. The other common failure is using system Chrome instead of chrome-headless-shell, which causes renders to hang for 120 seconds before timing out. The repeat: -1 prohibition is absolute; the capture engine cannot handle infinite loops. Similarly, any async timeline construction breaks the synchronous read of window.__timelines. Forced breaks ( ) in text cause double-wrapping because the engine does not know the rendered font width. And never call video.play() or audio.play() yourself; the framework owns playback.
What pairs with this
HyperFrames is designed to complement manim-video for math and geometric explainers, and meme-generation for image-based memes. For a full workflow, the references cover composition rules, CLI commands, GSAP API, features like captions and transitions, a website-to-video pipeline, and troubleshooting.