mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
* fix(sdk): moveElement survives GSAP animation per-axis via runtime delta translate A committed moveElement wrote data-x/data-y but nothing rendered them: hosts shimmed CSS translate, which GSAP folds into the cached transform at first parse and then discards on the animated axis at every seek — dragging an animated element kept only the un-animated axis. Spike-proven on GSAP 3.15: a translate set AFTER GSAP's first parse is never read, folded, or cleared across seeks and composes natively with the animated transform. So: - moveElement captures the pre-edit baseline once (data-hf-edit-base-x/y) - the runtime (new core runtime/positionEdits.ts, applied at timeline bind — after GSAP parse) renders translate = (data-x − base), a pure delta that composes with GSAP tweens, tl.set positions, and CSS alike - applyDraft now drives the drag preview through the same translate channel (the --hf-studio-dx/dy vars had no consumer outside authored Studio bridges), and commitPreview mirrors the committed move onto the live element so it holds without an srcdoc reload Acceptance: packages/engine/scripts/test-runtime-position-edits-browser.ts (real Chrome + GSAP + runtime IIFE, no Studio shell) — X-animated, Y-animated, and static elements hold both edited axes across the full seek range. New subpath export @hyperframes/core/runtime/position-edits. Known limitation (documented): a tween created lazily at runtime that first-parses a marked element after apply folds the edit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sdk): harden position-edit rendering and the drag draft channel Fixes six issues from adversarial review of the moveElement stack: - Runtime: apply position edits at init as well as at timeline bind, so committed moves render in compositions with no usable GSAP timeline (CSS/WAAPI-animated or fully static) — previously the apply was unreachable outside the boundDuration > 0 bind branch and the edit silently vanished from reloads and renders. - Runtime: guard bind-path re-apply against post-fold double-apply — if the previously written translate was consumed externally (a lazily created tween folding it into GSAP's cached transform), skip instead of re-setting it on top ({force} escape hatch for editor commits). - Adapter: stop writing the --hf-studio-dx/dy custom properties during drags — compositions with the documented var-consuming drag-bridge CSS moved by twice the pointer delta (var transform + new inline translate). The inline translate is now the only draft channel; deltas accumulate in adapter fields. Docs updated to match. - Adapter: switching applyDraft to a new id reverts the abandoned element's draft translate instead of leaving it displaced with no op. - Adapter: cancelPreview restores the raw inline translate (removing it when there was none), so a stylesheet-authored translate is never promoted to a permanent inline style. - Adapter: commitPreview reverts the draft and clears state when dispatch throws, instead of leaving the element shifted by an uncommitted draft. Cleanups: reuse readCurrentTranslate from the core module (was a verbatim copy), drop the dead __hfApplyPositionEdits window hook. Browser acceptance test now also covers the GSAP-free composition path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): prime GSAP transform cache before position-edit apply; add fold-loss telemetry Addresses PR #1875 review feedback (Rames, Miga): - Prime the element's GSAP transform parse (gsap.getProperty) before the first translate apply — positioned tl.set()s and tweens that first RENDER after the apply now reuse the cache instead of folding the edit. This closes the lazy-first-parse fold-loss for any page where GSAP is loaded at apply time; the residual limitation is GSAP itself loading after the apply. Proven by the extended browser acceptance test. - Emit position_edit_fold_skipped analytics at the fold-guard skip site so the residual degradation is observable instead of silent. - Browser acceptance test: add a both-axis-animated element (the shape that originated the per-axis loss) and a positioned tl.set() element, asserted across the full seek range. - Simplify the num() null guard (review nit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.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,
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
Related packages
@hyperframes/core— types, parsers, frame adapters@hyperframes/producer— high-level render pipeline built on this enginehyperframes— CLI