mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
* fix(engine): preserve composition root background in alpha exports initTransparentBackground() forced html, body, AND any [data-composition-id] element (the composition root, or a nested sub-composition root) to transparent for every alpha-carrying capture session. That's correct for the HDR two-pass layered DOM pass, where the HDR video itself is the backdrop — but for a plain alpha export (MOV ProRes4444, webm+alpha) of a normal composition, an author's own background painted on the root is real, intentional content, and it was being silently dropped. Add CaptureOptions.clearCompositionRootBackground, threaded through session.options into every general-session call site (default false — preserve the root's authored background). The two HDR-layered-DOM- session call sites set it to true, keeping their existing (correct) behavior. initTransparentBackground's injected stylesheet is now idempotently rewritten rather than insert-once/skip, so a caller's choice is never silently ignored by an earlier call on the same page. Add a producer test fixture + transparency-test.ts check proving a composition root's authored background survives into alpha output, plus unit tests for both flag values and the rewrite-on-repeat-call behavior directly. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * test(producer): register the new transparency fixture with the shard planner transparency-regression-root-bg is exercised by transparency-test.ts directly (asserts a real alpha channel), not by the sharded regression harness, same as its sibling transparency-regression. The shard planner hard-fails on any fixture directory it doesn't recognize as either scheduled or explicitly excluded, so register it in the excluded list with the same reason as its sibling. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * fix(engine): restore explicit transparent-background call for HDR layered sessions The previous commit removed the two HDR-layered-DOM-session call sites' own explicit initTransparentBackground() call, relying on initializeSession()'s internal call (gated behind session.options.format === "png") plus a new clearCompositionRootBackground session option. That was wrong: a plain HDR render's own DOM session captures "jpeg", not "png" — the final HDR MP4/HEVC output doesn't itself carry an alpha channel, only the layered-compositing step does. So initializeSession()'s internal call never fired at all for this common case, and the HDR video layer never got a transparent DOM backdrop to composite into. Reproduced directly against the hdr-regression fixture (part of the regression-shards CI suite): a real local render showed a severe PSNR collapse (~8-56dB) in the "Window B: z-order sandwich" segment, where a DOM element behind the HDR video, the video itself, and a translucent DOM overlay in front all need correct z-ordering — with the composition root's own background no longer cleared, it painted solid over the video slot instead of letting the video composite through. Confirmed via bisection: reverting to the prior commit's files reproduces 0 failed frames (passing); the removed-call version reproduces the exact failure; restoring the explicit calls fixes it again (100/100 checkpoints, 0 failed frames). Restore the explicit, unconditional call in both HDR call sites, right after initializeSession(). clearCompositionRootBackground still does its job for the general/plain-alpha-export path (frameCapture.ts), which is unaffected by this fix. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com> * refactor(engine): let the HDR layered pass own composition-root clearing Drop the CaptureOptions.clearCompositionRootBackground field: every HDR layered DOM session already calls initTransparentBackground right after init, so that call is the one owner. Engine paths use the default, which keeps root backgrounds. * chore(producer): drop comment additions the comment ratchet rejects --------- Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
@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
Related packages
@hyperframes/core— types, parsers, frame adapters@hyperframes/producer— high-level render pipeline built on this enginehyperframes— CLI