Files
WaterrrForeverandClaude Opus 5 c1a128a072 feat(cli): render --require-version turns a version pin into a gate
Adds `render --require-version X.Y.Z`: refuse to render unless the running
renderer is exactly that version.

## Why a pin is not enough

`npx hyperframes@X.Y.Z` stops the renderer drifting between runs, but nothing
checks the claim afterwards. A project states the version it believes it used
and no artifact can contradict it.

The provenance tag does not close the gap either. `hyperframes_version` stamped
a constant `0.0.0-dev` for twenty published versions (#4365) — measured across
710 tagged mp4s, 710 carry that value, so the tag identifies nothing. #4409
fixes the stamp, which makes drift VISIBLE once a fixed build ships. This makes
it ENFORCEABLE at the point it matters, before the encode.

## Three properties

1. **Fails closed on an unresolved version.** If the engine cannot say what it
   is, the gate cannot verify anything, and passing would hand back exactly the
   unfalsifiable claim the flag exists to remove — while looking green. It
   compares against the engine's exported `UNRESOLVED_VERSION` rather than a
   re-typed literal.
2. **Exact, never a range.** npm already gives the caller ranges. This answers
   "did the version I named run", and a caret would let the gate pass on a
   version the caller never named.
3. **Runs before the plan**, so a mismatch costs a startup rather than a full
   encode.

## Verification

`checkRequiredVersion` is a pure function so the guard can be exercised; a
guard that cannot be reached is one nobody has checked. 9 tests, extracted and
executed: exact match, whitespace, mismatch naming both versions, fail-closed
on unresolved, three exactness cases, empty value, and that the sentinel cannot
be satisfied by asking for it by name.

Two mutations, both red attributably:

- drop the fail-closed branch  -> exactly the 2 unresolved tests fail
- lenient `startsWith` match   -> exactly the 2 exactness tests fail

The second is the one worth having: a prefix match is the tempting lenient
implementation, and it silently passes `--require-version 0.8.7` against a
running 0.8.72.

I could not run the vitest suite here (no bun/node_modules on this machine), so
please let CI confirm the suite itself rather than reading my green as a test
run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 02:42:24 +08:00
..
2026-09-24 11:53:31 -04:00
2026-03-21 22:43:56 -07:00

@hyperframes/engine

Seekable web-page-to-video rendering engine built on Puppeteer and FFmpeg.

Framework-agnostic: works with GSAP, Lottie, Three.js, CSS animations, or any web content that implements the window.__hf seek protocol.

Install

npm install @hyperframes/engine

Requirements: Node.js >= 22, Chrome/Chromium (auto-downloaded by Puppeteer), FFmpeg

What it does

The engine opens your HTML composition in a headless Chrome instance, seeks frame-by-frame using Chrome's HeadlessExperimental.beginFrame API, captures screenshots, and encodes them into video with FFmpeg.

Key services

Service Description
browserManager Launches and pools headless Chrome instances (chrome-headless-shell)
frameCapture Manages capture sessions — seek, screenshot, buffer lifecycle
screenshotService BeginFrame-based capture with CDP (Chrome DevTools Protocol)
chunkEncoder FFmpeg encoding with chunked concat, GPU detection, faststart
streamingEncoder Pipe frames to FFmpeg in real time (no intermediate PNGs on disk)
audioMixer Parse <audio> elements and mix audio tracks via FFmpeg
videoFrameExtractor Extract frames from <video> elements for compositing
parallelCoordinator Split frame ranges across worker processes
fileServer Serve local HTML files to the browser via Hono

Usage

import {
  acquireBrowser,
  createCaptureSession,
  initializeSession,
  captureFrame,
  closeCaptureSession,
} from "@hyperframes/engine";

// 1. Launch browser
const browserLease = await acquireBrowser({ captureMode: "beginFrame" });

// 2. Open a capture session
const session = createCaptureSession({
  browser: browserLease.browser,
  url: "http://localhost:3000/my-composition.html",
  width: 1920,
  height: 1080,
  fps: 30,
});
await initializeSession(session);

// 3. Capture frames
for (let i = 0; i < totalFrames; i++) {
  await captureFrame(session, i, `/tmp/frames/frame-${i}.png`);
}

// 4. Clean up
await closeCaptureSession(session);
await browserLease.release();

Most users should use @hyperframes/producer or the hyperframes CLI instead of calling the engine directly.

Documentation

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