Commit Graph
1015 Commits
Author SHA1 Message Date
Miguel Ángel ccee8df4e1 fix(core): revert Function constructor back to inline IIFE for composition scripts
The Function constructor (3bb0d1ef) breaks sub-composition scripts that
call document.getElementById() — the constructor creates functions with
global scope, losing access to the composition-scoped DOM proxy. Native
method calls on proxy-returned elements throw "Illegal invocation".

Reverts to the inline IIFE that preserves the closure over __hfScoped*
variables. The original motivation (handling </script> in source) is
already handled by the compiler's script bundling path.

Closes #1074
2026-05-25 15:44:35 -04:00
AnoKno 0ea8aa4ffa fix(cli): address PR #983 review feedback
- play.ts: move --remote-debugging-port parse+deps validation before any
  server setup so an invalid value exits cleanly instead of leaking a
  listening socket (the original bug — server printed 'Player running'
  and 'Press Ctrl+C to stop' before failing).
- Extract validateRemoteDebuggingPortDeps() in openBrowser.ts to keep
  preview.ts and play.ts in sync instead of copy-pasting the dep
  checks.
- Narrow parseRemoteDebuggingPort param to string | undefined; drop the
  dead null branch and the redundant String() / Number.isInteger() now
  that the regex already constrains the input.
- buildBrowserArgs: omit --remote-debugging-port when userDataDir is
  missing so a CDP endpoint cannot leak into the user's main profile
  even if a caller bypasses the CLI validation layer.
- Replace the duplicated buildBrowserArgs case with one that proves
  this defense-in-depth behaviour; add unit tests for
  validateRemoteDebuggingPortDeps.
- Drop the heavy JSDoc on parseRemoteDebuggingPort to match the file's
  surrounding style.
- Both commands: align --remote-debugging-port description (it now
  matches the actual 'requires --browser-path and --user-data-dir'
  contract) and add a CDP example to the --help output.
2026-05-25 15:38:41 -04:00
AnoKno 3902a9a82b feat(cli): add remote debugging port option
Adds a Chromium remote debugging port flag for preview and play.

The flag is only passed when launching an explicit browser/profile.

HyperFrames still does not own CDP automation.
2026-05-25 15:38:41 -04:00
Miguel Ángel 28f948aa7d fix(engine): use static import for execSync, unify VRAM cap at 16GB
- Replace require("child_process") with static import (same ESM fix
  as config.ts — require is undefined in native ESM)
- Unify cap: both VRAM probe and heuristic paths now cap at 16GB
- Add comment noting the one-time blocking execSync is cached
2026-05-25 15:37:13 -04:00
Miguel Ángel 486c204609 fix(engine): probe nvidia-smi for actual VRAM before falling back to heuristic
On NVIDIA systems, spawns nvidia-smi once (cached) to read actual GPU
memory. Uses real VRAM for the Chrome GPU budget instead of guessing
from total system RAM. Falls back to total/2 on non-NVIDIA systems or
when nvidia-smi is unavailable.

No other headless Chrome renderer probes GPU memory — Remotion, Puppeteer,
and Playwright all ignore --force-gpu-mem-available-mb entirely.
2026-05-25 15:37:13 -04:00
Miguel Ángel 335a105ef4 fix(engine): remove 4096MB GPU budget ceiling for high-RAM systems
Scale GPU budget to half of total RAM (capped at 16GB) instead of
hardcoding 4096MB. A 32GB machine now gets 16GB GPU budget; a 64GB
machine gets 16GB (Chrome's practical limit). Low-memory tiers unchanged.
2026-05-25 15:37:13 -04:00
Miguel Ángel 0620fa908d fix(engine): use static import for os.totalmem in ESM config
Replace dynamic require("os") with static import — require is undefined
in native ESM, causing the try/catch to silently return the 16GB
fallback on every machine. The cache scaling was dead code.
2026-05-25 15:37:13 -04:00
Miguel Ángel c8ed90fa21 fix(engine): use totalmem() instead of freemem() for memory scaling
Addresses review feedback: freemem() is misleading on macOS where
aggressive file caching reports low free memory even on high-spec
machines. Switched to totalmem()-based thresholds consistent with
calculateOptimalWorkers in parallelCoordinator.ts.

Thresholds now based on total RAM:
- <4GB total: GPU=512MB, V8=256MB, cache=32/128MB
- <8GB total: GPU=1024MB, V8=512MB, cache=64/256MB
- >=8GB total: unchanged (4096MB GPU, no V8 cap, 256/1500MB cache)
2026-05-25 15:37:13 -04:00
Miguel Ángel 2015d93d2a fix(engine): scale Chrome memory budget and frame cache to available RAM
On low-memory systems (<4GB free), Chrome's --force-gpu-mem-available-mb=4096
causes the renderer to allocate more GPU texture memory than the system can
provide, leading to OOM crashes during frame capture ("Target closed").

Changes:
- Scale --force-gpu-mem-available-mb to match available system RAM
  (512MB when <2GB free, 1024MB when <4GB, 4096MB otherwise)
- Add --js-flags=--max-old-space-size=N on low-memory systems to cap
  Chrome's V8 heap (256MB when <2GB free, 512MB when <4GB)
- Scale frame data URI cache defaults: 32 entries/128MB when <2GB free,
  64 entries/256MB when <4GB, unchanged otherwise

Closes #1072
2026-05-25 15:37:13 -04:00
Miguel Ángel c46adb52e2 chore: release v0.6.44 2026-05-25 16:56:50 +00:00
Miguel Ángel 5fe62fc924 fix(studio): tighten media decode filter with error_name + sampled counter
Address review feedback:
- AND with error_name === "EncodingError" for tighter filtering
- Add sampled composition_asset_error_filtered tracking event (fires on
  1st occurrence, then every 100th) so filtered errors aren't completely
  invisible in telemetry
2026-05-25 12:55:41 -04:00
Miguel Ángel c1b26fcb32 fix(studio): guard cross-origin iframe access to prevent SecurityError crashes
Wrap all contentWindow/contentDocument access and addEventListener/removeEventListener
calls in try/catch across usePlaybackKeyboard, useAppHotkeys, and CompositionsTab.
Prevents SecurityError from propagating to the React error boundary (white screen).
Affects 1,885 crashes / 648 unique users in the last 7 days.
2026-05-25 16:49:40 +00:00
Miguel Ángel 179241e932 fix(studio): filter media decode errors from crash telemetry 2026-05-25 16:49:40 +00:00
Miguel ÁngelandClaude Sonnet 4.6 e4e2234303 chore: release v0.6.43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 13:35:55 +00:00
Miguel Ángel 8b776cb3c8 Merge pull request #1069 from func25/fix/preview-reuse-and-waapi-seek-baselines 2026-05-25 09:33:54 -04:00
func25 a0169309ae test(cli): normalize project paths in preview reuse probe tests 2026-05-25 18:08:51 +07:00
func25 e10ec358f8 fix(core): correct WAAPI rediscovery seek baselines and preview reuse invalidation 2026-05-25 17:51:47 +07:00
func25 3bb0d1efd9 fix(core): wrap bundled composition scripts as string literals 2026-05-25 15:41:13 +07:00
func25 e101b47be4 fix(core): preserve sub-composition script order in preview bundles 2026-05-25 15:27:52 +07:00
func25 bbb7a4d965 fix(core): inline local sub-composition scripts in preview bundles 2026-05-25 15:12:11 +07:00
Miguel Ángel d2b915be9e chore: release v0.6.42 2026-05-24 17:36:30 -04:00
Miguel Ángel 5a3b76a218 fix: parse scripted volume probes with html parser 2026-05-24 17:09:57 -04:00
Miguel Ángel 50c972fd50 fix: address audio volume review feedback 2026-05-24 17:02:35 -04:00
Miguel Ángel 947bf6cc78 Merge remote-tracking branch 'origin/main' into fix/audio-volume-automation
# Conflicts:
#	packages/engine/src/services/audioMixer.test.ts
2026-05-24 16:46:52 -04:00
Miguel Ángel 167a222318 fix: support animated audio volume 2026-05-24 16:39:04 -04:00
Miguel Ángel a68b4b6590 fix(cli): decode linked stylesheet paths 2026-05-24 16:38:54 -04:00
Miguel Ángel 526709cad2 fix(cli): handle encoded lint asset paths 2026-05-24 16:31:45 -04:00
Miguel Ángel 7ad10a2dff fix(engine): resolve encoded media src paths 2026-05-24 15:58:42 -04:00
Miguel Ángel 7461f1df30 chore: bump version to 0.6.41 2026-05-24 14:26:59 -04:00
Miguel Ángel 1d09e6ff36 fix(core): set __renderReady unconditionally after binding attempt
The capturedTimeline guard broke CSS/WAAPI/Lottie compositions that
have no GSAP timeline — __renderReady was never set, causing the
parity harness to timeout after 30s.

renderSeek works with or without a GSAP timeline (adapter-only
seeking), so the correct invariant is "timeline binding was
attempted" not "a timeline was found." Set __renderReady
unconditionally in all three paths, after bindRootTimelineIfAvailable
has run.
2026-05-24 13:42:30 -04:00
Miguel Ángel 3347486ae9 refactor(core): remove redundant as casts using window.d.ts declarations
window.d.ts already declares __timelines, __player, __playerReady,
and __renderReady on the global Window interface. The casts in
init.ts and init.test.ts were re-asserting the same types.

- Add __hfRuntimeTeardown to window.d.ts (used 6x in init.ts)
- Remove runtimeWindow cast variable from init.ts — use window directly
- Remove all (window as Window & { __player?: ... }).__player casts
  from init.test.ts — window.__player is already typed as PlayerAPI
- Remove all (window as Window & { __timelines?: ... }).__timelines
  casts from init.test.ts — window.__timelines is already typed
- Remove (window as Window & { __playerReady/renderReady }}) casts
  from init.ts — already declared globally
2026-05-24 13:36:57 -04:00
Miguel Ángel e7d0b392c7 fix(core,cli): address review — guard __renderReady, drop pre-quantization, add tests
- Guard __renderReady with `if (state.capturedTimeline)` in all three
  paths (setTimeout(0) and .finally() were setting it unconditionally
  even when bindRootTimelineIfAvailable returned false)
- Remove redundant fps=30 pre-quantization in snapshot — renderSeek
  already calls quantizeTimeToFrame internally with the runtime's
  canonicalFps, so pre-quantizing was double-quantizing at a
  potentially wrong grid
- Add regression tests: __renderReady is set when timeline exists,
  stays undefined when no timeline is available
2026-05-24 13:31:36 -04:00
Miguel Ángel 16e049b320 fix(cli): address review — fps comment, fileServer cross-ref, duration note
- Add comment explaining hardcoded fps=30 (runtime's canonicalFps
  default, not exposed on PlayerAPI)
- Add cross-reference comments between init.ts and fileServer.ts
  explaining their different __renderReady timing semantics
2026-05-24 13:27:54 -04:00
Miguel Ángel e8af1e4b9d refactor(cli): clean up snapshot readiness, duration, and diagnostics
- Fix broken duration getter: use getDuration() (PlayerAPI method)
  instead of .duration (property doesn't exist, always fell through
  to the DOM attribute fallback)
- Remove redundant sub-composition wait: __renderReady already
  guarantees all timelines are bound
- Warn on readiness timeout instead of silently capturing garbage
- Warn when shader transitions don't finish pre-rendering
- Warn when no player API is available (seeks will be no-ops)
- Remove redundant node:fs re-import (already imported at top)
- Remove stale step numbering comments
- Trim verbose comments that restate the code
2026-05-24 13:20:16 -04:00
Miguel Ángel b2828e48e5 fix(core,cli): defer __renderReady until root timeline is bound
The runtime set __renderReady at the same time as __playerReady,
before the root timeline was bound. Consumers waiting for
__renderReady (the render-safe signal) could observe a player with
no captured timeline, making renderSeek a no-op.

Root cause: init.ts set both flags together, but timeline binding
happens later — synchronously via bindRootTimelineIfAvailable(),
via a deferred setTimeout(0) for bundled compositions, or
asynchronously via loadExternalCompositions().

Fix in init.ts:
- Remove __renderReady from the __playerReady assignment
- Set it after bindRootTimelineIfAvailable() when timeline is found
- Set it in the setTimeout(0) deferred path
- Set it in the external compositions .finally() path

Fix in snapshot.ts:
- Wait for __renderReady (truthful signal) not __timelines
- Use renderSeek() with frame quantization, not seek()
- Tick the GSAP ticker after seeking
- Await document.fonts.ready before capturing

Closes #1047
2026-05-24 13:13:28 -04:00
Miguel Ángel b8d521edfb Merge pull request #1045 from heygen-com/feat/vfx-liquid-glass-v2
feat(registry): Apple Liquid Glass components — iOS 26 + macOS Tahoe
2026-05-24 12:33:37 -04:00
Miguel Ángel 7eb92badb2 Merge pull request #1058 from kiyeonjeon21/fix/aws-lambda-build-zip-linux-ffmpeg
fix(aws-lambda): fail build-zip when ffmpeg-static binary isn't Linux x86-64
2026-05-24 12:30:00 -04:00
Miguel Ángel c9d5dc5ff6 Merge pull request #1056 from kiyeonjeon21/fix/aws-lambda-handle-plan-chromium-init
fix(aws-lambda): resolve Chromium for handlePlan before invoking probe
2026-05-24 12:28:36 -04:00
Miguel Ángel 7397702f3a Merge pull request #1060 from func25/f/preserve-static
fix(core): stop binding class values in composition scoped window
2026-05-24 12:27:16 -04:00
func25 d3127a7507 fix(core): preserve static methods on classes exposed through scoped window 2026-05-24 22:50:40 +07:00
Kiyeon Jeon bc20b87b9a fix(aws-lambda): resolve Chromium for handlePlan before invoking probe
The producer's probe stage launches Chromium when Plan has to resolve
browser-only data: root duration unknown, unresolved sub-compositions, or
data-hf-auto-start media. Only handleRenderChunk was setting
PRODUCER_HEADLESS_SHELL_PATH, so a cold Plan invocation launched
puppeteer-core with no executablePath and failed before writing plan.tar.gz.

Mirror the renderChunk env-var guard inside handlePlan so the bundled
Sparticuz binary gets resolved on the first Plan invocation and reused on
warm starts. The skipChromeResolution dep stays honored for SAM-local RIE
smokes.

A warm Lambda environment can mask this only if it previously served a
renderChunk from another execution and left the env var sticky. Within a
single Step Functions execution, Plan still runs before RenderChunks.

A new dispatch test exercises the guard path by pre-seeding
PRODUCER_HEADLESS_SHELL_PATH and asserting Plan does not overwrite it.
2026-05-24 16:22:31 +09:00
Kiyeon Jeon 9b34114dd1 fix(aws-lambda): fail build-zip when ffmpeg-static binary is not Linux x86-64
`ffmpeg-static` materializes a single platform-selected binary at install
time. By default that follows the install host, so a default macOS/arm64
or linux/arm64 install can stage a binary that Lambda's x86-64 runtime
cannot execute.

Verify the ELF header (ELFCLASS64, EM_X86_64) before copying the binary
into `bin/ffmpeg`. When the check fails, surface the canonical workarounds:
run the build inside a linux/amd64 container, or pre-install with
`npm_config_platform=linux npm_config_arch=x64`.

In the distributed pipeline this mismatch can fail as early as Plan's
`ffmpeg -version` probe after browser probing and before chunks are
scheduled. Later encode/assemble spawns would fail for the same reason.

ffprobe already goes through the platform-segmented
`ffprobe-static/bin/linux/x64` path, so that side doesn't need the same
check.
2026-05-24 16:21:01 +09:00
Miguel Ángel d97935b336 fix(engine): scope WebGPU flag to hardware mode 2026-05-24 01:53:24 -04:00
James 90bf485db8 fix(distributed): reject cfr:true with h265 codec (per review)
The cfr re-encode pass hardcodes `-c:v libx264`. Pairing it with
`codec: "h265"` would silently transcode the h265 chunks to h264.
Detect the encoder discriminant in `meta/encoder.json` and throw a
typed error parallel to the existing non-mp4 format guard, so callers
surface the conflict instead of producing a wrong-codec deliverable.

— Rames Jusso
2026-05-24 00:24:30 -04:00
James 71d1889da6 feat(distributed): add optional cfr flag for exact constant frame rate
Distributed-render output today uses -c:v copy through concat → mux →
faststart, which means PTS timestamps from each chunk pass through
unchanged. Container r_frame_rate is exact (#1040 + this PR's parent),
but stream-level avg_frame_rate stays PTS-derived and can land on
fractional rationals like 27648000/921677 over a 60s render. Same for
sub-ms duration drift.

This is the achievable bar within -c copy stream-copy concat. For most
consumers (browser playback, YouTube, etc.) the difference is invisible.
For downstream tools that strict-check avg_frame_rate or
ms-precision duration (broadcast workflows, frame-accurate compositors,
some third-party transcoders), it matters.

Adds an opt-in cfr config flag (default false). When true, the
assemble step's final pass re-encodes with -fps_mode cfr -r <fps>
instead of -c copy, producing exact CFR output. Trade-off: ~2-5x the
stitch time for a 60s 1080p clip; second-generation H.264 quality loss
is negligible at -crf 18 but is non-zero.
2026-05-24 00:24:30 -04:00
James 49281a5c17 fix(distributed): apply -r <fps> to single-chunk pass-through path
The v0.6.39 fix added -r <fps> to the multi-chunk concat ffmpeg
invocation but didn't reach the single-chunk pass-through path,
which is taken when totalFrames * fpsDen / fpsNum fits in one
chunk. Result: 1-chunk renders shipped with fractional
r_frame_rate (e.g. 359/12) while multi-chunk renders shipped
with exact 30/1.

Single-chunk path now goes through the same -r <fps> + -c copy
ffmpeg invocation as the concat path, ensuring uniform exact
r_frame_rate metadata across all chunk-count configurations.

Adds a regression test exercising the 1-chunk path and asserting
r_frame_rate === "<fpsNum>/<fpsDen>" exact.
2026-05-24 03:15:56 +00:00
Miguel Ángel 1891578dd6 feat(engine): add --enable-unsafe-webgpu flag for WebGPU glass rendering
Adds WebGPU support to the Chrome launch args alongside the existing
CanvasDrawElement flag. Use PRODUCER_HEADLESS_SHELL_PATH to point to
Brave for full WebGPU + drawElementImage support.

Also fixes flicker in liquid glass blocks by removing onpaint/requestPaint
callbacks that conflicted with GSAP's deterministic onUpdate rendering.

Adds macos-tahoe-liquid-glass block (WIP).
2026-05-23 16:23:32 -04:00
Miguel Ángel 61c7e4ab74 fix(producer): address review feedback on htmlInCanvas worker clamp
- Always log when html-in-canvas pins to 1 worker, not just on
  explicit --workers override (Vance, James)
- Assert recommendScreenshot is true for htmlInCanvas detection —
  pins the load-bearing coupling with the screenshot paint-force
  protocol (Vance, Magi)
- Skip capture calibration for htmlInCanvas auto-sized renders to
  avoid wasting 600ms–3s on an estimate that gets thrown away (Vance)
- Add TODO documenting Chrome's two root-cause mechanisms: paint
  cache race and SwiftShader contention (James, Magi)
2026-05-23 16:08:27 -04:00
Miguel Ángel 497f220c5f fix(producer): force single worker for html-in-canvas compositions
Chrome's drawElementImage API does not support concurrent usage across
multiple browser instances — running >1 capture worker causes flickering
artifacts. Detect the layoutsubtree canvas attribute during compilation
and unconditionally pin workers to 1, overriding both auto-sizing and
explicit --workers flags.
2026-05-23 15:48:02 -04:00
Miguel Ángel 47d57bff10 chore: bump version to 0.6.40 2026-05-23 15:01:38 -04:00