Files
hyperframes/packages/cli
DanteandMiguel Angel Simon Sierra 0d3d423200 fix(cli): select containing source frames in snapshots (#4809)
* fix(cli): select containing source frames in snapshots

* fix(cli): find the containing frame from packet timestamps, not a full decode

Probing decoded frames and selecting by index decoded the source from its start, so a sample 290 s into a 1080p clip took about 35 s and hit the 30 s extraction bound. Packet timestamps need only a demux, and an accurate -ss to the midpoint before the containing frame decodes from the nearest keyframe.

* fix(cli): end snapshot's ffprobe options with -- before the input

* fix(cli): seek snapshot frames from their keyframe and skip unreadable videos

Fragmented MP4 seeks by dts and MPEG-TS seeks imprecisely, so an input seek to
the frame midpoint returned the next frame before each keyframe (fMP4) or a
corrupt or missing frame (TS). Seek to the keyframe that starts the frame's
decode instead and pick the frame by decoded time with a select filter.

A failed or unparsable ffprobe now returns null, so one unreadable video is
blanked with a warning instead of aborting the whole snapshot.

* fix(cli): keep open-GOP leading frames and extract without ffprobe

The keyframe walk went by decode order, so an open-GOP leading frame seeked to
the keyframe that follows it, the decoder dropped it and the next frame was
returned. Only keyframes at or before the frame in presentation order count.

Without ffprobe, extraction falls back to a plain seek instead of failing the
snapshot, as it worked before frame selection needed ffprobe.

* fix(cli): measure snapshot frame times from the exact video start

ffprobe prints format.start_time rounded to microseconds. When it rounds down,
t=0 found no frame and every exact frame time picked the previous frame (24 fps
fragmented MP4 with B-frames, many MPEG-TS files). Subtract the stream's
integer start_pts in time-base ticks when the video sets the format start.

* fix(cli): probe snapshot packets to an absolute end past edit-list preroll

A stream-copy trim keeps preroll packets before the first visible frame. The packet
probe read a window relative to the first packet, so it stopped before the requested
time and picked an earlier frame. Probe the stream start first, then up to start + time + 1 s.

* fix(cli): keep snapshot ffprobe argv a literal ending in -- before the input

The producer ffprobe argv contract reads each argv literal and requires -- right before the input;
the probe helper spliced flags in from its callers, so the contract saw argv without it.

* fix(cli): select snapshot frames on the video stream clock when audio leads

When the video starts after the container (TS with audio, late video), FFmpeg puts zero at
the first video frame after a seek to 0, so the threshold picked the next frame, and times
before the first frame gave none. Select on raw stream time (-copyts) and hold the first frame.

* fix(cli): measure snapshot frame selection from the first decoded frame

-copyts kept raw time in the filter but changed which frames survive the seek trim, so a
file whose timestamps start below zero froze on a late frame. Seek to the keyframe display
time and select relative to start_t; the no-ffprobe path keeps the plain seek.

* fix(cli): anchor snapshot frame selection on the keyframe a seek lands on

MPEG-TS seeks can land a whole keyframe group late, and an open-GOP keyframe is not a clean
start, so a predicted start_t picked later frames. Read the landing keyframe with a stream
copy, seek exactly to it so its leading frames are trimmed, and select relative to it.

---------

Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
2026-10-02 22:04:32 +00:00
..
2026-10-02 12:14:00 -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: http://localhost:3002/#project/my-video
# Server: http://localhost:3002

npx hyperframes preview --port 4567

In an interactive terminal, the preview stays attached until you press Ctrl+C. In a non-interactive shell such as a coding-agent session, the same command starts a managed preview that survives after the command returns. Use --background or --foreground to choose explicitly, and manage persistent previews with --status, --stop, --list, and --kill-all. Add --json to managed lifecycle commands for machine-readable output. --foreground --json prints the ready-session envelope once, then remains attached until stopped.

normalize-audio

Measure two local authored audio clips with integrated LUFS and match the target to the unchanged reference. The command is a dry run unless --write is passed:

npx hyperframes normalize-audio --reference target-audio --target user-audio
npx hyperframes normalize-audio --reference target-audio --target user-audio --write

It updates only the target element's data-volume and refuses unsafe boosts that exceed Studio's +12 dB ceiling or would clip.

render

Render a composition to MP4. Run from the project directory; the positional argument is the project directory (not a file), so render the project's index.html directly, or point at a specific composition file with -c:

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

Set HYPERFRAMES_RENDER_DETACHED=1 when starting a render with nohup or disown so it keeps running after the invoking shell exits.

publish

Upload a project directory and get a hosted URL that keeps working after the CLI exits. Published projects are private by default:

npx hyperframes publish
npx hyperframes publish ./my-video
npx hyperframes publish --public
npx hyperframes publish --yes

Signed-out publishing returns an authentication-required claim URL; opening it lets someone sign in and claim the project. Sign in first with npx hyperframes auth login to publish an owned project you can update. Use --public to make the claimed project visible to anyone. --yes only skips the confirmation prompt and does not change visibility.

Signed-in publishers can use --update <url-or-id> to target an existing project or --space <space-id> to publish into a shared team space. If the requested project is missing or inaccessible, publishing can create a new project instead; check the printed URL and status.

See the publish reference for all options, including video proxy settings.

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