Commit Graph
749 Commits
Author SHA1 Message Date
Via e5c4e1970c test(producer): reproducer fixture for both-escape-hatches-fail case
Field signal ts=1784039841 (win32/x64, CLI 0.7.57): shifted-DOM-image-
layer bug at frame 120 reproduces with BOTH PRODUCER_FORCE_SCREENSHOT=true
AND HF_DE_PARALLEL_ROUTER=false set. First case where both known escape
hatches fail simultaneously. Standalone 1920x1080 GSAP paused timeline,
absolute PNG layers, 4 independent scenes.

Ships a skipped regression fixture to codify the shape. NOT a fix — no
root cause identified. The composition is preserved so a future
diagnostic pass has a real, checked-in repro and a proposed fix can be
validated against the same shape the field reported.

Skip mechanism (belt-and-suspenders):
  1. meta.json tagged `field-signal-reproducer` and `known-broken`;
     producer/package.json test:regression* scripts add the tag to
     --exclude-tags alongside the existing `transparency` skip.
  2. Not registered in any .github/workflows/regression.yml shard's
     args, so the CI regression sweep won't pick it up either.

Un-skip when a fix lands: drop the tags from meta.json AND add the
fixture id to a shard's args in the workflow. See src/README.md for
the field-signal envelope and diagnostic starting points.

Stack: PR #8 of 9 (base via/gpu-parity-gate).

Signed-off-by: Via
2026-07-15 23:35:58 +00:00
Via 971bcf39ae feat(producer): calibration-aware heartbeat + worker-death terminal-error contract
Field signals ts=1784019503 (heartbeat reports 0 frames during 64s
browser calibration — reads as broken but is healthy) and ts=1784042064
(1292s Windows render hard-exited during video frame extraction with
no final error string — silent worker crash).

Add calibrating/capturing state to heartbeat labels; surface synthetic
terminal error on unexpected worker exit when no explicit error was
emitted.

Stack: PR #6 of 9 (base via/overlay-count-lint).
Signed-off-by: Via <vance@heygen.com>
2026-07-15 23:14:49 +00:00
Via 69b393865a feat(producer): parity telemetry gate for per-clip frame-count invariant
Field signals ts=1784139267 (win32 15-injection blank later clips,
injection-count-scaled) + ts=1784144554 (darwin 147-authored-clip
visibility gap, authored-clip-count-scaled). check/snapshot passes,
final MP4 has blank/missing clips. Extends #2474's videoCount:0 probe
with per-clip captured-vs-expected frame ratio + threshold fail-loud
gate at render finalization.

Stack: PR #4 of 9 (base via/darwin-goto-nav-timeout-hint).
Signed-off-by: Via <via@heygen.com>
2026-07-15 22:46:45 +00:00
Via 58cff5f6d5 feat(engine): surface escape hatches in page.goto Nav timeout errors
Field signal ts=1784146416 (darwin/arm64, CLI 0.7.58, 7/10): host
page.goto hit Navigation timeout of 60000ms twice on a CSS 3D + audio
composition; Docker rendered the same composition successfully.
Puppeteer's stock "Navigation timeout of 60000 ms exceeded" text names
none of HyperFrames' existing escape hatches, so the reporter had no
signal that the failure had knobs.

Wraps main-render Puppeteer `page.goto` errors matching
/Navigation timeout|net::ERR_TIMED_OUT/i with an augmented message that
names:

- The effective timeout currently applied (`cfg.pageNavigationTimeout`).
- Raise-the-timeout: `PRODUCER_PAGE_NAVIGATION_TIMEOUT_MS` env,
  `--browser-timeout` CLI flag (seconds).
- Browser-binary escape hatch: `HYPERFRAMES_BROWSER_PATH` env.
- Field-signal shape: darwin/arm64 + CSS 3D + audio compound Docker
  hint — gated on all three inputs being explicitly true; falls back
  to generic hints when any input is unknown.

Mirrors #2443's HYPERFRAMES_BROWSER_PATH surfacing pattern (which
covered download-time failures) at the runtime `page.goto` layer.
Non-matching errors flow through unchanged. Original error preserved
via `err.cause`.

Wired into `renderOrchestrator.executeRenderJob`'s top-level catch,
composed after `augmentProtocolTimeoutError` so the two augmenters
never both fire on the same error (mutually exclusive regexes).
Current wire-up passes no `hasCss3D` / `hasAudio` context — no
compile-time CSS-3D signal is threaded through the render pipeline,
and `hasAudio` is block-scoped inside the try. Per the helper's
fallback docs, unknown flags route to the generic env + browser-path
hints. A future compile-time CSS-3D scan can thread both flags to
enable the full compound Docker hint without touching this helper's
signature.

Stack: PR #3 of 9 (base via/win32-streaming-encode-autodisable).

Signed-off-by: Via <vance@heygen.com>
2026-07-15 22:33:37 +00:00
Via 6944a1c2d0 feat(engine): surface protocolTimeout env + flag in Puppeteer timeout errors
Field signal ts=1784047847 (darwin/arm64, 8GB M1, 9 videos + 22 images):
reporter hit Runtime.callFunctionOn timeout and switched to FFmpeg
because the error didn't surface HyperFrames' existing knobs
(PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS env, --protocol-timeout CLI).

Wraps main-render Puppeteer errors matching /Runtime\.callFunctionOn
timed out|Target closed|protocolTimeout/i with an augmented message that
names the effective timeout, the env var, the CLI flag, and the
field-signal shape. Non-matching errors pass through unchanged
(returned as the same instance). Original error preserved via err.cause.

Also adds a dedicated --protocol-timeout row to the CLI docs Flags table
so PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS is discoverable via search.

Signed-off-by: Via <noreply@heygen.com>
2026-07-15 22:11:44 +00:00
Miguel Ángel 35e623b4f3 fix(cli): align video output boundaries (#2490)
* fix(cli): align video output boundaries

* test(cli): honor CI ffmpeg fixture path

* test(cli): decouple duration precedence from ffmpeg

* test(cli): pin half-open video boundaries

* test(producer): refresh style 7 boundary golden
2026-07-15 16:07:44 -04:00
Miguel Ángel 850f57ea04 fix(render): ignore favicon probe noise (#2487) 2026-07-15 15:26:25 -04:00
Vance Ingalls ff3b1541e5 chore: release v0.7.59 2026-07-15 11:57:49 -07:00
Miguel Ángel 882c203241 fix(render): avoid Windows output path limit for work dirs (#2479) 2026-07-15 10:30:00 -04:00
WaterrrForever 7d21cc9b8a fix(skills,cli): close four reproduced contract gaps from the CLI feedback digest (#2476)
* fix(cli): invalidate the skills nudge cache after a successful install/update/check

The passive "N skills out of date or missing" nudge reads a 24h config
cache that only the background check (on non-skills commands) ever wrote.
The skills commands themselves are excluded from the nudge pipeline, so a
successful `skills update`/install/check never refreshed or dropped the
cached verdict — the pre-install count kept printing on every other
command for up to 24h.

Reconcile commands now drop the cached verdict (counts + timestamp) so
the next command's background check re-runs for real. The offline
presence-only path deliberately keeps the cache: that run learned nothing
about freshness.

* fix(skills): win32-safe npx spawns in media-use + accurate whisper wording

The Whisper transcribe fallback and the Kokoro local-TTS delegation both
spawned a bare "npx" via execFileSync — on Windows npx is npx.cmd, which
spawn cannot exec, so both paths died with `spawnSync npx ENOENT`. Route
them through the skill's existing resolveSpawnCommand (node + npx-cli.js
on win32, no shell:true), same as the audio engine's TTS spawns.

Also corrects the "bundled with the hyperframes CLI" claim about
whisper.cpp: it is resolved from PATH / installed via Homebrew / built
from source with git+cmake on first use, and models download from
HuggingFace — nothing whisper is shipped in the package.

* feat(skills): canonical fully-silent marker + auth status exit-code docs

product-launch's Step 3.1 gate said "or the project is marked silent"
but nothing defined how to mark one, and audio.mjs unconditionally
retrieved BGM. Define the canonical marker — `music: none` in the
storyboard's top YAML block, plus no SCRIPT.md — and honor it:
audio generate produces nothing (removing stale audio_meta.json, since
absence is what assemble treats as silent), and `music: none` with
narration keeps TTS while turning BGM off.

Also documents the `auth status` exit-code contract (exit 1 while
signed out is the normal offline state, not a failure) in the
product-launch Step 0 note and the CLI skill's cloud reference.

* fix(skills): transient-init retry for standalone animation-map and contrast-report

The standalone helpers called initializeSession exactly once, so a valid
modular project — whose sub-composition timelines register asynchronously
— could hit the readiness deadline and die with the transient
"zero duration / Runtime ready: false" diagnostic the render pipeline
retries (probeStage). Add initializeSessionWithRetry to the shared
package-loader (both byte-identical copies): close the crashed session
and retry once with a fresh browser, gated by the engine's canonical
isTransientBrowserError — now re-exported from @hyperframes/producer,
with a frozen fallback pattern list for older published packages. The
"Runtime ready: true" fast-fail (a genuine authoring bug) still fails
without a retry.

* feat(skills): extend the fully-silent marker to faceless-explainer and pr-to-video

Both workflows reuse product-launch's audio model — their Step 3.1 gates
carried the same undefined "marked silent" phrase, and their (intentionally
identical) audio.mjs copies had the same unconditional BGM retrieve. Port
the `music: none` marker handling into both copies, define the marker in
their SKILL.md Step 3.1 and story-design references, and turn the
copies' "intentionally identical" header claim into a byte-identity pin
test so the next fix can't silently miss one of them.

* test(cli): reset the prune mock explicitly instead of relying on restoreAllMocks

The converge test's toHaveBeenCalledTimes(1) held only because vitest 3's
vi.restoreAllMocks() clears vi.fn() call state; vitest 4 restores spies
only, so the count would accumulate across tests and fail. Reset
pruneOrphanedLockEntries in beforeEach like the other manifest mocks —
passes under both vitest 3.2.4 (pinned) and vitest 4.

* test(skills): close review findings — package-loader pin, whisper win32 parity, quoted-none

Review follow-ups on #2476:

- package-loader.mjs byte-identity pin (the elevated concern): the two
  copies now carry initializeSessionWithRetry + FALLBACK_TRANSIENT_PATTERNS,
  exactly the shared-logic shape a future fix could land in one copy and
  miss in the other — same enforcement as the audio.mjs pin.
- whisper win32 call-site parity: runWhisper's npx resolution lifted into
  lib/npx-sync.mjs (resolveNpxInvocation, injectable params matching the
  localTtsGenerate idiom) with the same three-branch coverage as the
  Kokoro site — plus the hard-fail contract (throws actionably, since the
  whisper fallback has no next provider to fall through to).
- quoted music: "none" pin: the vendored storyboard parser strips matching
  quotes at parse time (stripQuotes), so the silent marker already accepts
  the quoted spelling — pinned so that stays true.
2026-07-15 22:22:16 +08:00
Miguel Ángel 1895286189 fix(render): normalize local AAC duration before mux (#2472)
* fix(render): normalize local AAC duration before mux

* style(render): apply repository formatter

* fix(render): count AAC packets for duration normalization

Older FFmpeg versions estimate raw ADTS duration from bitrate and can undercount variable-bitrate audio, causing the normalizer to append a false silence tail. Derive the mixed AAC duration from packet count and sample rate instead.

* fix(render): isolate normalized audio temp path

* fix(engine): centralize AAC packet duration

* test(producer): refresh AAC duration golden
2026-07-15 10:07:07 -04:00
Miguel Ángel 47e4b18f99 fix(render): discover runtime-inserted media before extraction (#2474)
* fix(render): discover runtime-inserted media before extraction

* fix(render): detect runtime Audio constructors

* test(render): cover runtime audio insertion paths
2026-07-15 08:13:17 -04:00
Vance Ingalls 36075c6797 fix(engine,producer): classify blank vs psnr from a structural field, not the error message
Deepwork's request-changes on #2411 (twice): deFallbackReason's blank/psnr
split still ran /blank/i.test(err.message) even after this PR's stated goal
of moving off message-text parsing — a reworded message, a translated
string, or a differently-shaped error crossing a module boundary could
silently relabel a blank failure as psnr (or vice versa), corrupting the
soak's telemetry taxonomy.

DrawElementVerificationDetails now carries a required `kind: "blank" | "psnr"`
field, set at all three real throw sites in captureStreamingStage.ts. The
orchestrator derives deFallbackReason from getDrawElementVerificationDetails's
kind instead of regexing the message. Making `kind` a required constructor
argument means any future throw site that omits it fails to compile, closing
the gap for good rather than just at today's three call sites.

New tests in frameCapture.test.ts prove message-independence directly: kind
survives a reworded message that says neither "blank" nor "psnr", and stays
correctly "psnr" even when the message adversarially contains the substring
"blank" — the exact scenario a regex-based classifier would get wrong.
2026-07-14 23:29:49 -07:00
Vance Ingalls af923d947c fix(producer,cli): round the fallback dB consistently, thread the verify threshold through
Review feedback on #2411 (Rames): the crash-survival RenderCaptureObservability
mirror passed deFallbackFailedDb raw/unrounded while the render_complete
perfSummary path rounded to 1 decimal — the same underlying PSNR could ship two
different values to PostHog depending on which event fired. Extracted the
existing inline round/clamp expression (previously duplicated for verifyMinDb
and fallbackFailedDb) into a shared roundDb helper, applied once at the single
point deFallbackFailedDb is derived from the thrown error so both downstream
consumers agree.

Also threads verifyThresholdDb (captured on the error but never propagated,
per the nit) through DrawElementPerfInput/RenderCaptureObservability/render.ts/
telemetry as de_fallback_threshold_db on both events — the HF_DE_VERIFY_MIN_DB
value the failing dB breached, letting ops read "28.4dB failed a 32dB
threshold" directly instead of cross-referencing config.
2026-07-14 23:29:49 -07:00
Vance Ingalls a5cbb78ff9 feat(engine,producer,cli): capture failing dB/frame index on drawElement verify fallback
de_fallback_reason only told you the fallback happened (blank/psnr/oom/
capture_error), not the failing PSNR or frame index — that data existed as
text inside the thrown error's message and was discarded on the way to
telemetry. DrawElementVerificationError now carries structured
frameIndex/failedDb/verifyThresholdDb; the orchestrator reads them via the
new getDrawElementVerificationDetails helper instead of regexing message
text, and both telemetry surfaces (the render_complete perfSummary path and
the crash-survival RenderCaptureObservability mirror) emit
de_fallback_failed_db / de_fallback_frame_index.

Needed to distinguish "32dB vs the 32dB threshold, tune it" from "12dB real
corruption, investigate" during the parallel-router soak — currently that
distinction is invisible.
2026-07-14 23:29:49 -07:00
Miguel Ángel 6813eaeece fix(producer): fail renders when audio mixing fails (#2429) 2026-07-14 23:27:20 -04:00
Miguel Ángel 7382fabab9 fix(core): consolidate external asset and dependency preservation (#2410)
## Summary

- preserve external SVG fragment references during bundling
- preserve external module scripts and serve `.mjs` with a JavaScript MIME type
- retain template-head stylesheets when mounting sub-compositions
- add regression coverage across compiler runtime and file-server paths

Consolidates and replaces #2390, #2297, and #2375.

## Verification

- core compiler/runtime tests: 89 passed
- producer file-server tests: 48 passed
- core, producer, engine, and CLI typechecks passed
- `git diff --check`
2026-07-14 21:55:51 -04:00
Miguel Ángel 37e1b26434 fix(fonts): consolidate composition-aware compilation (#2406)
* fix(fonts): subset Google Fonts to composition text

* fix(producer): skip slow TTC recompression

* fix(fonts): include decoded composition text in subsets
2026-07-14 20:12:30 -04:00
Miguel Ángel 0b3dfb3f84 fix(render): consolidate duration and timing correctness (#2405)
* fix(producer): pass variables to duration probe

* fix(producer): tolerate rounded frame-boundary durations

* fix(cli): resolve relative data-start references in composition duration

`compositions --json` computed each timed child's start with a bare
parseFloat(data-start ?? "0") in parseCompositions (host duration) and
parseSubComposition (sub-comp duration). A relative reference like
data-start="s1" ("start when clip s1 ends") is not numeric, so parseFloat
returned NaN and that clip's contribution to the max-end was silently
dropped — a host with two 3s clips (2nd data-start="s1") reported duration 3
instead of 6, breaking compositions/inspect/snapshot for composition-clip
relative timing.

Resolve relative references the same way the extractor does (parseStartExpression
from @hyperframes/core + a findReferenceTargetEl/resolveReferencedStart port,
since the engine's referenceResolver isn't a public export across the package
boundary). Verified: host duration now 6; 3 tests pass.
(Implemented via Codex; verified independently.)
2026-07-14 17:12:36 -04:00
Miguel Ángel 4b0b89e8b1 chore: release v0.7.58 (#2446) 2026-07-14 16:15:59 -04:00
Miguel Ángel d2c8c2d808 fix(producer): probe variable-bound media sources (#2444) 2026-07-14 15:42:00 -04:00
James Russo fb0d823500 fix(render): publish artifacts atomically (#2154) 2026-07-14 12:54:13 -04:00
Miguel Ángel 90be05019b chore: release v0.7.57 (#2393) 2026-07-14 00:29:29 -04:00
Miguel Ángel 3f6ea0a1ab perf(producer): cache local font compression (#2397) 2026-07-14 00:26:37 -04:00
Vance Ingalls 58cfa0c655 Merge pull request #2359 from heygen-com/via/issue-3-software-gpu-screenshot
fix(engine): software-GPU browsers imply screenshot capture
2026-07-13 20:37:12 -07:00
Vance Ingalls f44bc3a525 fix(engine,producer): drive forceScreenshot from one authoritative local
Miguel R4 blocker on #2359: my R3 fix at renderOrchestrator only updated
the observability copy, leaving the authoritative captureForceScreenshot
local at compileResult.forceScreenshot (false for auto→software). The
frameCapture side clamped its own local and correctly routed screenshot,
but downstream orchestrator code overwrote observability back to
beginframe from the still-false local at two sites:

  - Parallel-stream label at renderOrchestrator.ts:2293 mis-labelled the
    stream as 'beginframe' when actual capture was 'screenshot'.
  - capture_strategy telemetry at renderOrchestrator.ts:2440-2450
    overwrote the earlier observability correction, so the final
    captureMode observation flipped back to 'beginframe' while the
    engine actually captured screenshot.

Fix: extract the clamp into a caller-facing helper
applyConcreteGpuScreenshotClamp(current, resolvedGpuMode, cfg) that
returns the (possibly-promoted) new boolean. Callers assign it back to
their authoritative local, so routing + telemetry + strategy code read
one value.

Changes:

  - packages/engine/src/config.ts: new exported
    applyConcreteGpuScreenshotClamp; delegates to
    shouldClampToScreenshotForConcreteGpu but computes the caller's
    final value, not just the clamp decision. Reads the programmatic
    opt-out from cfg.forceScreenshotExplicitlyOptedOut. Idempotent on
    already-true input.
  - packages/engine/src/index.ts: export the new helper.
  - packages/engine/src/services/frameCapture.ts: replace the inline
    OR expression with applyConcreteGpuScreenshotClamp.
  - packages/producer/src/services/renderOrchestrator.ts: assign result
    into the AUTHORITATIVE captureForceScreenshot local (was updating
    only observability). Downstream parallel-stream label at :2293 and
    capture_strategy telemetry at :2440-2450 now read the corrected
    value.

Tests: 6 new caller-level cases for applyConcreteGpuScreenshotClamp
covering the exact matrix Miguel called out:

  - resolved software + default false → promotes to true (screenshot)
  - resolved software + programmatic opt-out → stays false (BeginFrame)
  - resolved hardware + default false → stays false
  - resolved software + already-true → stays true (idempotent)
  - resolved software + env PRODUCER_FORCE_SCREENSHOT=false → stays false
  - resolved software + undefined cfg → promotes to true (frameCapture path)

Local: 67/67 engine config tests pass (was 61). oxfmt clean.
2026-07-14 03:17:29 +00:00
Vance Ingalls 72daac2a1d fix(engine): carry programmatic forceScreenshot opt-out to concrete-resolved site
Miguel R3 blocker on #2359: the runtime helper only checked the env opt-out
(PRODUCER_FORCE_SCREENSHOT=false), silently defeating the documented
programmatic escape hatch (overrides.forceScreenshot === false) on the
browserGpuMode:'auto' → software probe path. At the concrete-resolution
site the boolean forceScreenshot === false is ambiguous between default
and explicit opt-out — resolveConfig sees the provenance but the runtime
helper does not.

Fix: persist provenance on the resolved config.

- New INTERNAL EngineConfig field forceScreenshotExplicitlyOptedOut, set
  by resolveConfig when EITHER env or programmatic explicit-false is
  present. Purpose-documented in the type as 'not intended to be set by
  callers'.
- shouldClampToScreenshotForConcreteGpu gains an opts.programmaticOptOut
  parameter; returns false early when set. Env stays as the third arg
  (backward compatibility with existing tests).
- frameCapture.ts and renderOrchestrator.ts pass
  config.forceScreenshotExplicitlyOptedOut through at both call sites, so
  the auto→software probe path preserves the same escape hatches as
  literal browserGpuMode:'software'.

New tests: 5 additional cases across the helper (programmatic opt-out
alone; programmatic beats missing env) and resolveConfig provenance
(programmatic sets flag; env sets flag; neither leaves it undefined).
Local: 61/61 engine config tests pass (was 56).
2026-07-14 03:06:34 +00:00
Vance Ingalls 2e44602f99 fix(engine,producer): apply software-GPU screenshot invariant at concrete-resolved point
Addresses Miguel's R1 blockers:

1. `browserGpuMode: "auto"` that runtime-probes to software slipped past the
   `resolveConfig` clamp — that clamp only sees the pre-resolve string. Add
   `shouldClampToScreenshotForConcreteGpu(resolvedGpuMode, currentForceScreenshot, env)`
   in `packages/engine/src/config.ts` and apply it at BOTH concrete-resolution
   sites:
   - `packages/engine/src/services/frameCapture.ts`: downgrades `preMode`
     from "beginframe" to "screenshot" when resolved GPU is software (respects
     `PRODUCER_FORCE_SCREENSHOT=false` env opt-out), fixing the routing.
   - `packages/producer/src/services/renderOrchestrator.ts`: updates
     `captureObservability.forceScreenshot` (and thus `captureMode`) at the
     same call site, fixing the observability truth on the auto → software
     case.

2. New unit tests in `config.test.ts`:
   - Documents the auto-branch gap (resolveConfig leaves auto as
     forceScreenshot=false — the runtime companion closes it).
   - 5 branch tests on `shouldClampToScreenshotForConcreteGpu` covering
     software / hardware / already-forced / env-opt-out / non-"false" env
     values.
   Full suite: 56/56 pass.

Scope narrowing on Blocker 2: the distributed rendering path at
`packages/producer/src/services/distributed/plan.ts:753-754` and
`renderChunk.ts:462-466` explicitly hardcodes `browserGpuMode:"software",
forceScreenshot:false` post-resolveConfig and stays outside this PR's
invariant boundary. `compileStage` may still flip it to true for alpha
formats, but generic MP4 distributed renders on SwiftShader hosts remain
BeginFrame. That's a separate architectural cleanup (needs its own
behavior-change trace); the PR body now scopes the invariant to the
in-process CLI/orchestrator path.
2026-07-13 23:13:30 +00:00
James Russo cb69d3fe00 fix(render): surface structured outcomes (#2153) 2026-07-13 19:07:39 -04:00
Miguel Ángel 5ff4ba13f4 fix(render): retry explicit parallel capture timeouts (#2331) 2026-07-13 16:51:58 -04:00
James cc3ca2f9f7 fix(core): preserve runtime transport contract 2026-07-13 14:08:52 -04:00
Miguel Ángel 94c2e0f6eb chore: release v0.7.56 2026-07-13 07:04:42 +00:00
Miguel Ángel 9940503102 fix(producer): dedupe local font embedding by resolved path (#2317) 2026-07-13 02:01:28 -04:00
Miguel Ángel 9c0c1f99d4 fix(render): stream long low-memory captures (#2245) 2026-07-12 22:17:44 -04:00
Miguel Ángel 995885484f fix(render): reject incomplete captured frames (#2293) 2026-07-12 22:17:41 -04:00
Vance Ingalls c830aa83c5 chore: release v0.7.55 2026-07-12 11:52:53 -07:00
Vance IngallsandClaude Opus 4.8 de95daa096 fix(producer): stop clipping the bottom edge of software-GPU video renders
resolveVideoCaptureBeyondViewport gated Chrome's beyond-viewport screenshot
path to hardware-GPU captures, to skip the full-surface software
re-rasterization tax. But without beyond-viewport, the viewport-bound
capture clips the bottom edge of any frame containing a native video
surface (the same #1094 tall-portrait guard the alpha capture paths already
hardcode) — leaving ~87 bottom rows black.

This hit two cohorts: software-GPU macOS/Linux hosts, and — worse — EVERY
distributed chunk render, which hardcodes browserGpuMode "software", so the
whole distributed fleet shipped video renders with a black bottom band.
Reporter confirmed forcing resolveVideoCaptureBeyondViewport=true fixes it.

Correct output wins over the software perf optimization: enable
beyond-viewport for any render with a native video surface, regardless of
GPU mode. Drops the now-vestigial browserGpuMode parameter (and its type)
and updates both call sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:28:09 -07:00
Vance IngallsandClaude Opus 4.8 07fb35c375 fix(producer): fall back fast when the parallel DE streaming capture stalls
The DE parallel router auto-enables the interleaved parallel-streaming
capture for the >=24GB macOS trial cohort. If a worker wedges mid-capture
(a hung seek/screenshot at an early frame) the render made no frame
progress yet sat until the per-frame CDP protocolTimeout (~5 min) fired
before the pinned self-verify fallback could run — a silent multi-minute
hang shipped to real users (report: stuck at frame 2/2031 for 6+ min,
no fallback, until HF_DE_PARALLEL_ROUTER=false).

Add a no-frame-progress watchdog to the parallel branch of
runCaptureStreamingStage. It ticks off executeParallelCapture's progress
callback; if no NEW frame lands within HF_DE_PARALLEL_STALL_MS (default
60s, well under protocolTimeout and >> the 15-32ms/frame budget), it
aborts the reorder buffer (unsticking peer workers parked in waitForFrame
so the pool doesn't deadlock) and aborts the pool via a SEPARATE
controller linked to the parent abort. Because the parent abortSignal
stays un-aborted, the orchestrator reads the failure as a generic
capture_error (not a cancellation) and re-renders on the pinned screenshot
path — the same fallback a verify failure already uses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:35:21 -07:00
James 0af07a07c5 fix(core): enforce strict runtime safety 2026-07-11 21:31:30 -07:00
Vance Ingalls ae4946e624 chore: release v0.7.54 2026-07-11 17:15:24 -07:00
Vance Ingalls 1a9c1b66b0 Merge pull request #2264 from heygen-com/07-11-fix-cli-localize-remote-fonts-in-audit-capture
fix(cli): localize remote fonts in snapshot/check capture to match render
2026-07-11 17:05:35 -07:00
Vance Ingalls ad0d2393fe fix(cli): localize remote fonts in snapshot/check capture to match render 2026-07-11 16:14:36 -07:00
vanceingalls 591235f05c feat(producer): emit eligible_off cohort-sizing signal when parallel-stream router is off 2026-07-11 22:38:04 +00:00
vanceingalls 6d5a7711f3 feat(producer): route eligible parallel screenshot/beginframe renders to streaming 2026-07-11 22:34:46 +00:00
vanceingalls 2416e9bcd5 feat(producer): routing predicate for non-drawelement parallel streaming 2026-07-11 22:34:13 +00:00
Miguel Ángel 9d91c2a23e fix(render): scale timeout for long video encodes (#2244) 2026-07-11 18:32:48 -04:00
Miguel Ángel 6b7431bf45 fix(producer): render -c <scene> uses the scene's own duration, not the project's (#2087)
* fix(producer): render -c <scene> uses the scene's own duration, not the project's

When rendering a single sub-composition standalone (`hyperframes render -c
compositions/scene.html`), the producer extracts the scene's mount from
index.html and wraps it in a shallow clone of the master root. That clone
kept the master's `data-duration`, so the standalone composition advertised
the whole project's length instead of the scene's own: a 2s scene rendered
for the full 12s project, and a master that derives its length from sibling
mounts (now removed) produced "Composition has zero duration".

Re-point the extracted wrapper's `data-duration` at the scene's own, read
from the scene file's `<template>` root (the source of truth for that scene),
with a fallback to the mount's `data-duration`. Full-project renders are
unaffected — they never take the extraction branch.

Verified end-to-end via the pre-capture duration gate: a 2s scene now resolves
to 2s and a 10s scene to 10s (both were 12s), while the full index render stays
at 12s. Adds unit coverage for both the scene-file and mount-fallback paths.

* test(producer): distinguish scene and mount durations
2026-07-11 18:32:12 -04:00
Vance Ingalls fecd7dc1d3 Merge pull request #2248 from heygen-com/bf-reuse-telemetry
feat(producer): surface beginframe no-damage reuse counters in perf summary and telemetry
2026-07-11 15:31:58 -07:00
Vance Ingalls c20e20235b refactor(producer): stop coupling the hdr disk-headroom gate's control flow to error-string matching
Addresses non-blocking review feedback on #2256: the previous shape used
err.message.includes(...) inside a catch to distinguish a real headroom
violation from a statfsSync failure — a future message tweak would silently
fail open (statfs-unsupported and headroom-violation would take the same
code path). Now statfsSync failure returns early (skip the gate) and the
headroom check/throw happens in plain control flow outside any try/catch.
2026-07-11 14:14:09 -07:00
Vance Ingalls 9a1afce43c fix(producer): disk-headroom gate before hdr raw frame pre-extraction 2026-07-11 14:14:09 -07:00