Files
hyperframes/packages/cli
ukimsanov a3e7c6176f fix(cli): inject real video frames in snapshot to match render
The snapshot command previously just called `tl.seek(t)` + `page.screenshot`
and trusted Chrome to advance `<video>`-element decoders. Chrome headless
silently ignores `video.currentTime = X` writes — the setter is accepted
but the decoder never moves. Result: every snapshot of a composition that
uses body-level `<video data-start>` elements renders the same frame
regardless of the requested timestamp (the z-topmost video's first-frame
paints through, because all clips share `position: absolute; inset: 0`
and visibility:hidden doesn't always prevent the GPU surface from
contributing to the composite).

The render pipeline has already solved this: for each body-level video it
extracts the needed frame via FFmpeg and overlays it as an <img> sibling
via `injectVideoFramesBatch` (packages/engine/src/services/screenshot
Service.ts). This commit ports that same primitive into `snapshot`:

1. Added `extractVideoFrameToBuffer(videoPath, t)` — one FFmpeg spawn per
   active video, `-ss` keyframe seek (~100-200 ms), writes a temp PNG.
2. After the existing seek + settle, enumerate `<video data-start>`
   elements that are active at the target time, resolve each one's
   `currentSrc` URL back to a filesystem path under `projectDir`, extract
   the frame, and call `injectVideoFramesBatch`.
3. Then screenshot — as before.

Non-breaking: when no body-level `<video data-start>` elements exist (every
other project in the repo — basecamp, linear, stripe, github component), the
new block short-circuits on `active.length === 0` and behaves identically
to the pre-fix path. Verified against three no-video projects: bit-identical
snapshot output, no latency regression.

Measured on macOS M2 (4 frames, cold):
  launch-video-2 (11 timed videos): 6.48s → 6.16s   (-5%)
  basecamp-tour  (no timed videos): 5.67s → 4.87s   (-14%)

Proof: Pre-fix MD5 at t=12.5, 16.0, 20.5, 32.5 — all 4 identical (wrong frame)
  Post-fix MD5 at same timestamps  — all 4 distinct, match ffmpeg-from-render
Made-with: Cursor
2026-04-20 09:36:33 -04:00
..
2026-04-19 23:03:07 -04:00
2026-03-21 22:43:56 -07:00

hyperframes

CLI for creating, previewing, and rendering HTML video compositions.

Install

npm install -g hyperframes

Or use directly with npx:

npx hyperframes <command>

Requirements: Node.js >= 22, FFmpeg

Commands

init

Scaffold a new Hyperframes project from a template:

npx hyperframes init my-video
cd my-video

preview

Start the live preview studio in your browser:

npx hyperframes preview
# Studio running at http://localhost:3002

npx hyperframes preview --port 4567

render

Render a composition to MP4:

npx hyperframes render ./my-composition.html -o output.mp4

lint

Validate your Hyperframes HTML:

npx hyperframes lint ./my-composition
npx hyperframes lint ./my-composition --json      # JSON output for CI/tooling
npx hyperframes lint ./my-composition --verbose   # Include info-level findings

By default only errors and warnings are shown. Use --verbose to also display informational findings (e.g., external script dependency notices). Use --json for machine-readable output with errorCount, warningCount, infoCount, and a findings array.

compositions

List compositions found in the current project:

npx hyperframes compositions

benchmark

Run rendering benchmarks:

npx hyperframes benchmark ./my-composition.html

doctor

Check your environment for required dependencies (Chrome, FFmpeg, Node.js):

npx hyperframes doctor

browser

Manage the bundled Chrome/Chromium installation:

npx hyperframes browser

info

Print version and environment info:

npx hyperframes info

docs

Open the documentation in your browser:

npx hyperframes docs

upgrade

Check for updates and show upgrade instructions:

npx hyperframes upgrade
npx hyperframes upgrade --check --json  # machine-readable for agents

Documentation

Full documentation: hyperframes.heygen.com/packages/cli