Files
hyperframes/packages/engine
Miguel Angel Simon Sierra da0550e386 fix(engine): prefer ffmpeg.exe over a .cmd/.bat shim on Windows PATH
When HYPERFRAMES_FFMPEG_PATH is unset, findOnPath() auto-detects
ffmpeg/ffprobe via `where` (Windows) / `which` and used the FIRST
result. On Windows `where ffmpeg` frequently lists a .cmd/.bat wrapper
(npm/scoop/winget shims) ahead of the real .exe, and Node's spawn
without shell:true cannot execute a .cmd/.bat — it throws spawn EINVAL.
So render capture/encode AND audio muxing failed with "spawn EINVAL"
even though FFmpeg was installed; the reporter's own fix was to rename
the .cmd wrappers so ffmpeg.exe resolved first.

Two independent reports of this exact EINVAL (one explicitly tracing it
to `where` returning ffmpeg.cmd before ffmpeg.exe).

Fix: extract selectBinaryFromPathResults(output, platform) — on win32
it prefers the first directly-spawnable executable (.exe/.com) among
the results, falling back to the first result only when none is listed
(so a shell-runnable .cmd-only setup isn't dropped). Non-win32 is
unchanged (first result). Scoped to selection: the .cmd-only case
(would still EINVAL on spawn) is left for a separate shell-spawn change.

Test: selectBinaryFromPathResults unit tests — win32 prefers .exe over
a leading .cmd and over a .bat, falls back to the first when no exe is
listed, returns the first on non-win32, and undefined for empty output.
Existing env-resolution tests unchanged. Engine build green.
2026-07-04 18:04:56 -07:00
..
2026-07-04 22:33:27 +00: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,
  releaseBrowser,
  createCaptureSession,
  initializeSession,
  captureFrame,
  closeCaptureSession,
} from "@hyperframes/engine";

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

// 2. Open a capture session
const session = createCaptureSession({
  browser: browser.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 releaseBrowser(browser);

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

Documentation

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