Files
hyperframes/packages/engine
miga-heygenandMiguel Ángel 29378b6c7f fix(engine): classify a single-keyframe video as problematic, not healthy (#3998)
* fix(engine): classify a single-keyframe video as problematic, not healthy

analyzeKeyframeIntervals short-circuited to isProblematic:false whenever
it found fewer than 2 keyframes, but exactly one keyframe is the worst
case for seek performance, not a healthy result: every seek past frame 0
must decode the entire file.

The single-keyframe branch treats the rest of the video stream's own
duration (not a container duration, which a longer audio track can
inflate) as one uninterrupted GOP. ffprobe's frame timestamps are
absolute presentation timestamps rather than relative to stream start,
so a re-muxed or trimmed clip with a nonzero start time is normalized
the same way extractFinalVideoFrameTimestamp already does for the same
quirk. The cache key now includes both values, matching that sibling
function's convention, since a stale cache entry from a different
duration/start pair would otherwise win silently.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* refactor(engine): share stream-window normalization between keyframe check and final-frame lookup

extractFinalVideoFrameTimestamp and analyzeKeyframeIntervals each
independently computed the same stream-start fallback, finite guard, and
cache-key format from the same metadata shape. Extracted resolveStreamWindow
so the two can't drift out of sync with each other.

Also: match ffprobe's real trailing-comma csv output in one keyframe mock,
add a test at the exact 2s threshold boundary, and comment the
noUncheckedIndexedAccess-only fallback at the single-keyframe branch.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* style(engine): trim long comments to one-to-three lines

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-23 02:05:51 -04:00
..
2026-09-22 16:12:35 -04: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