Commit Graph
1004 Commits
Author SHA1 Message Date
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
Miguel Ángel 3983d4e7bf fix(core): set explicit dimensions on data-hf-inner-root wrapper
The prepareFlattenedInnerRoot function creates a wrapper div when
inlining sub-compositions. This wrapper had no width/height, which
broke CSS height:100% chains — any sub-composition using percentage
heights with flexbox centering would collapse to 0px and render
content at the top instead of centered.

Read data-width/data-height from the inner root and set matching
pixel dimensions on the wrapper's inline style. Applied in both the
compiler (server-side bundling) and the runtime (browser-side
composition loader).

Adds a producer regression test with a centered card sub-composition
that fails without this fix.
2026-05-23 14:53:16 -04:00
Miguel Ángel 377f081941 fix(studio): guard import.meta.env access for non-Vite bundlers
import.meta.env is undefined in Next.js Turbopack/Webpack, causing
"Cannot read properties of undefined" when the studio telemetry client
loads. Wrap accesses in try-catch so they gracefully fall back.

Also hardcode the PostHog API key and host — they're public write-only
values with no reason to be overridable via env.
2026-05-23 13:59:08 -04:00
James 179b09ec9d chore: release v0.6.39 2026-05-23 17:30:05 +00:00
James a4c4b2ff03 fix(distributed): enforce exact framerate at concat + mux boundaries
When the distributed render path stitches chunks with `-c copy`,
ffmpeg averages the container framerate from PTS rather than
carrying the source's exact rational rate, producing values like
`360000/12001` instead of `30/1` and ~5ms duration drift over
60s.

This is a known ffmpeg behavior at the concat-demuxer-copy
boundary. The industry-standard fix is `-r <fps>` as an input
flag on the concat step plus an output flag on the subsequent
mux step — both with `-c copy` retained, no re-encode required.

Three sites updated:
- `assemble.ts` concat step: `-r <fps>` input flag.
- `chunkEncoder.muxVideoWithAudio`: `-r <fps>` output flag.
- `chunkEncoder.applyFaststart`: same, threaded from caller.

Adds `r_frame_rate` + duration-equivalence assertions to
`assemble.test.ts` to close the regression hole.
2026-05-23 01:12:19 -04:00
Miguel Ángel 3560678bb2 chore: bump version to 0.6.38 2026-05-23 00:13:50 -04:00
Miguel Ángel 26e8ef596b Merge pull request #1039 from heygen-com/fix/orphaned-child-processes
fix: clean up orphaned Chrome/ffmpeg on preview exit
2026-05-23 06:12:32 +02:00
Miguel Ángel 7e4ce96ba8 fix: SIGKILL escalation in killProcessTree + unit tests
Remaining review follow-ups:

- killProcessTree now escalates to SIGKILL after 500ms if SIGTERM
  doesn't kill the process (same pattern as killTrackedProcesses).
  Covers orphan cleanup and dev/local mode tree kill.

- Added unit tests for both new modules:
  - processTracker.test.ts (6 tests): track/remove on exit/error,
    kill running processes, SIGKILL escalation for SIGTERM-resistant
    processes, idempotency.
  - orphanCleanup.test.ts (5 tests): tree kill with children,
    SIGKILL escalation, non-existent PID handling, orphan detection
    returns 0 when clean.
2026-05-23 00:10:30 -04:00
Miguel Ángel 84edce908a fix: address code review feedback on process cleanup
- Blocker: arm 3s force-exit timer BEFORE awaiting cleanup, not
  inside .finally(). Prevents hang if drainBrowserPool() blocks on
  dead Chrome.
- Reorder cleanup: killTrackedProcesses() (sync, fast) runs first,
  then async browser drain. Ffmpeg dies immediately instead of
  surviving if the hard timer fires early.
- SIGKILL escalation: processTracker now SIGTERMs all tracked
  processes, then SIGKILLs survivors after 500ms grace period.
- Scope pgrep to current user (pgrep -u $(id -u)) so orphan
  detection doesn't touch other users' Chrome on shared machines.
- Add process.on('exit') handler for crash paths (unhandled
  exceptions/rejections that bypass signal handlers).
- Document Windows no-op behavior on killProcessTree handlers.
2026-05-22 23:53:46 -04:00
Miguel Ángel e87f5bb769 fix(engine): widen VFR test frame count tolerance for cross-platform FFmpeg
FFmpeg's VFR-to-CFR normalization produces slightly different frame
counts across versions due to timestamp rounding in the fps filter.
The ±1 tolerance was too tight for Linux FFmpeg builds. Widen to ±3
frames — still catches the 25% shortfall regression these tests
guard against.
2026-05-22 23:42:49 -04:00
Miguel Ángel a54953b936 fix: clean up orphaned Chrome and ffmpeg processes on preview exit
The preview command's shutdown handler only closed the HTTP server,
leaving Chrome (browser pool) and ffmpeg processes alive. This caused
silent resource leaks — orphaned processes consuming CPU and RAM with
no parent.

Root cause: preview.ts never called drainBrowserPool() or killed
tracked ffmpeg processes. The thumbnail browser in studioServer.ts
registered its own competing signal handlers that raced with
preview's shutdown.

Fix:
- Add a central process tracker (processTracker.ts) that registers
  every spawned ffmpeg across engine and producer packages
- Centralize thumbnail browser cleanup via exported
  closeThumbnailBrowser() instead of scattered signal handlers
- Wire preview shutdown to call closeThumbnailBrowser(),
  drainBrowserPool(), and killTrackedProcesses() before closing the
  HTTP server (embedded mode)
- Add killProcessTree() for dev/local modes where Chrome runs in a
  child process tree
- Add startup orphan detection that finds and kills orphaned
  chrome-headless-shell/Puppeteer Chrome processes (PPID=1) from
  previously crashed sessions

Closes #1038
2026-05-22 23:31:00 -04:00
Miguel Ángel ea4d920589 refactor(studio): split oversized files and raise line limit to 600
Split PlayerControls.tsx into focused sub-components (SeekBar,
WorkAreaOverlay, MuteButton, LoopButton, FullscreenButton,
ShortcutsPanel, SpeedMenu) and extracted seek bar drag/progress
tracking into useSeekBarDrag hook.

Split manualEditsDom.ts patch-builder functions into
manualEditsDomPatches.ts with data-driven helpers to reduce
duplication and complexity.

Extracted per-type reapply helpers from reapplyPositionEditsAfterSeek
and factored out identity-matrix check from
stripGsapTranslateFromTransform.

Raised file-size limit from 500 to 600 lines, removed
.filesize-allowlist.
2026-05-22 22:57:12 -04:00