mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-08-31 02:41:44 +00:00
main
19
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bf630bfe1e |
fix(cli): always check GitHub skills on init while skills.sh syncs (#1768)
* fix(cli): always check GitHub skills on init while skills.sh syncs The "don't pass --skip-skills" guidance lives in SKILL.md, which ships through the laggy skills.sh registry and can't be relied on to reach the agent — so an agent that improvises `--skip-skills` silently dodges the GitHub skills freshness pull. Put the guarantee in the CLI instead (the one channel that updates promptly via `npx hyperframes@latest`): - Neuter the `--skip-skills` FLAG so it no longer skips the check; gate skipping on the HYPERFRAMES_SKIP_SKILLS=1 env var instead (the agent/user CLI path never sets it). Print a one-line notice when the ignored flag is passed. - Wire the env escape hatch into the init test helper (one place) and the CI smoke-test / windows-canary steps so they stay offline and fast. - Update the skill docs that previously told agents `--skip-skills` opts out. Temporary measure while skills.sh catches up — revert init.ts's `skipSkills` to `args["skip-skills"] === true` once it does (noted inline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): build @hyperframes/lint before core in Test and Studio jobs The lint extraction (#1756) made @hyperframes/lint a runtime dependency of core — core's compiled compiler/staticGuard.js imports it via the package's "node" export condition (./dist/index.js). But the Test and Studio-load-smoke jobs pre-build only @hyperframes/{parsers,studio-server} before packages/core, so loading core's dist at test / dev-server time fails with: ERR_MODULE_NOT_FOUND: Cannot find module .../@hyperframes/lint/dist/index.js imported from .../packages/core/dist/compiler/staticGuard.js Build the canonical pre-core set @hyperframes/{parsers,lint,studio-server} (the glob the root build script uses) in both jobs so it can't drift again. The SDK job is left as-is — it builds parsers+core only and passes. Reproduced locally: removing packages/lint/dist reproduces the exact ERR_MODULE_NOT_FOUND; building lint resolves it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): address PR #1768 review — stale comment + harden offline init - Update the stale interactive-path comment that still said "Opt out with --skip-skills"; the flag is neutered, opt-out is HYPERFRAMES_SKIP_SKILLS=1. - Wrap installAllSkills in ensureSkillsCurrent with try/catch. installAllSkills is already non-strict (swallows its own failures), but since --skip-skills no longer escapes this path, every init — including offline ones that fall through to "install anyway" — runs it. The guard guarantees a skills-install failure only warns and proceeds, never breaks init. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
25420bf4cf |
ci: skip ffmpeg-static CDN download on ubuntu; retry Windows FFmpeg install (#1275)
* ci: skip ffmpeg-static CDN download on ubuntu; retry Windows FFmpeg install ubuntu-24.04 runners ship /usr/bin/ffmpeg. Set FFMPEG_BIN so ffmpeg-static's postinstall script skips its GitHub-release binary download, preventing bun install failures when that CDN is unavailable. For Windows: increase BtbN/FFmpeg-Builds download max-attempts 3→8 with longer backoff (30×attempt s) and set FFMPEG_BIN after install so bun install also skips ffmpeg-static's download in both render and test jobs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: fix FFMPEG_BIN approach — use writable copy via composite action /usr/bin/ffmpeg is not writable by the runner user. When bun runs ffmpeg-static's postinstall script in a context where process.exit(0) is intercepted, the skip-if-exists check has no effect and the download proceeds to the destination path. Pointing FFMPEG_BIN at a system path (/usr/bin/ffmpeg) therefore causes EACCES even when the CDN returns 200. Replace the top-level env var with a prepare-ffmpeg-bin composite action that copies the system ffmpeg to $RUNNER_TEMP (writable). Call it before every bun install step in the CI workflow. Whether the postinstall script skips or overwrites, the write target is now writable and the job succeeds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: skip apt fallback in prepare-ffmpeg-bin; use stub when ffmpeg absent ubuntu-24.04 GHA runners do not have ffmpeg pre-installed. The apt-get fallback triggered the install of ffmpeg and its dependencies, but the Azure apt mirror returned 404 for libcaca0, aborting the composite action. ffmpeg-static's postinstall only needs a regular file to exist at FFMPEG_BIN in order to reach the statSync check and call process.exit(0) — it does not need a real executable. Write a minimal shell stub when 'which ffmpeg' returns empty. Jobs that require an actual ffmpeg binary (cli-smoke-required) install it via apt before calling this action, so 'which ffmpeg' returns the real path and the copy branch runs instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1f37920fe1 |
fix(cli): re-validate SSRF denylist on redirects + harden isPrivateUrl (#1212)
## Summary - Adds `safeFetch`, a redirect-aware wrapper around `fetch` that re-runs the SSRF denylist on every hop before following a redirect. - Routes `fetchBuffer` and the Lottie media fetch through `safeFetch` so redirect chains can't bounce through a public URL to reach an internal or cloud-metadata host. - Hardens `isPrivateUrl` to also block `0.0.0.0` / `0.0.0.0/8`, IPv6 loopback (`::1`), IPv4-mapped (`::ffff:…`), unique-local (`fc00::/7`), and link-local (`fe80::/10`) ranges. ## Security **F-002 MED** — `fetchBuffer` followed redirects without re-checking the denylist on the destination. A `30x` redirect from an allowlisted public URL to `169.254.169.254` or an internal host would succeed, leaking the response to the caller (e.g. captured page assets written to local disk). **F-003 MED** — `isPrivateUrl` did not cover `0.0.0.0` (maps to localhost on most OSes), IPv6 loopback, or IPv6 private ranges. An asset URL using those addresses would bypass the denylist. Alternate IPv4 encodings (decimal/octal/hex) are already normalized to dotted-quad by WHATWG URL parsing and remain blocked. ## Test plan - [x] Unit tests cover redirect-chain blocking (redirect to metadata IP rejected) - [x] Unit tests cover new `isPrivateUrl` address forms (`0.0.0.0`, `::1`, `fc00::1`, `fe80::1`, `::ffff:192.168.1.1`) - [x] Existing fetch and asset-download tests pass |
||
|
|
07bcb4f73b |
fix(cli): stop dropping CI/agent telemetry, suppress HeyGen CI at workflow level
The CI=true early-exit in shouldTrack() was hiding most modern usage (coding agents in Codespaces, CI pipelines, agent sandboxes). Remove it. Each event still carries is_ci/is_docker/is_tty from system.ts, so CI vs laptop traffic can be separated in PostHog without being dropped at ingestion. HeyGen's own CI is suppressed via HYPERFRAMES_NO_TELEMETRY=1 added to each workflow that exercises the CLI. |
||
|
|
c50f59a53b |
feat(lambda): add Lambda handler, ZIP bundling, and BeginFrame probe (#878)
* feat(lambda): add Lambda handler, ZIP bundling, and BeginFrame probe
Phase 6 of the distributed rendering plan: AWS Lambda turnkey adoption
(see DISTRIBUTED-RENDERING-PLAN.md §11 Phase 6 + §15).
This PR adds the new packages/aws-lambda/ workspace package that wraps
the OSS plan/renderChunk/assemble primitives in an AWS Lambda handler,
plus a build pipeline that bundles the handler + Chromium runtime +
ffmpeg into a deployable ZIP.
Architecture: ZIP deploy (not Docker image), Chrome via @sparticuz/chromium
with chrome-headless-shell fallback, dispatch on event.Action ∈ {plan,
renderChunk, assemble}.
The load-bearing concern — does @sparticuz/chromium's chrome-headless-shell
build honour CDP HeadlessExperimental.beginFrame? — is pinned by the new
scripts/probe-beginframe.ts regression guard. Probe boots the runtime
inside public.ecr.aws/lambda/nodejs:22, navigates to a static page, and
asserts beginFrame returns a PNG buffer. Verified locally + inside the
Docker container; both pass with hasDamage=true.
Sizes (sparticuz source): unzipped 157 MiB, zipped 99 MiB. Well under
the 240 MiB / 150 MiB in-house gates and the Lambda 250 MiB hard ceiling.
This is part of a stack of 8 PRs (3 in Phase 6a, 5 in Phase 6b); this is
PR 6.1.
* fix(lambda): address PR 878 review feedback
- Verify event.PlanHash against the untarred plan.json at the handler
boundary before invoking the producer primitive. Throws typed
PLAN_HASH_MISMATCH on divergence so Step Functions routes it as
non-retryable; previously the field was schema bloat the handler
ignored, leaving enforcement entirely inside the producer.
- Standardize on MiB throughout build-zip.ts, verify-zip-size.ts, and
the README. Lambda's hard ceiling is 250 MiB (AWS docs label "250 MB"
but use binary mebibytes); previously mixed units made the 248 MiB
budget look like a ~5 MB margin instead of the 2 MiB it actually is.
- stageChromeHeadlessShell now picks Chrome versions via numeric semver
comparison instead of lexicographic sort+reverse — the latter would
silently pick "99.x" over "131.x" once Chrome cached three-digit
majors that aren't width-aligned.
- Drop _setSparticuzChromiumForTests from the public index barrel.
Test-only DI seam imported directly from ./chromium.js in tests.
- Replace require("node:fs") inside walkSize() with the top-level fs
imports — file is ESM and the same module is already imported.
* docs(lambda): drop internal plan-doc refs from package README
* ci(windows): fix bun filter UNION bug excluding producer from Windows tests
`bun run --filter "!a" --filter "!b" test` composes as a UNION (any
package matching either negation runs), not an intersection. Effect:
@hyperframes/producer was still being tested on Windows even though
it's explicitly excluded — its regression harness (Docker + LFS golden
mp4 baselines) is Linux-only and was driving the 32min timeout.
Enumerate the packages we DO want to test instead.
|
||
|
|
00984133fc |
ci(preflight): extract preflight steps into a composite action
Same 5-step preflight body (setup-bun, setup-node, cache, install, lint, format:check) was duplicated across 5 workflows. Move it to .github/actions/preflight/action.yml so future tweaks (adding typecheck, swapping the cache key, etc.) are a single-file change. Net diff: +33 / -65. Addresses the "shared preflight" follow-up Vai called out on #877. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e29d7db331 |
ci(preflight): cache ~/.bun/install/cache keyed on bun.lock
Each of the 5 preflight gates was doing a cold bun install, costing ~30-60s of redundant install time per PR. Cache the install dir keyed on bun.lock so subsequent preflights (and reruns) hit warm. Addresses Vai's review on #877. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
18e49cb69c |
ci: fast-fail regression matrix + preflight gate before expensive jobs
Don't burn 60+ runner-minutes on regression shards, perf shards, preview-parity, Windows renders, or catalog-preview renders when the PR is already failing lint or format. - regression: matrix fail-fast: false → true (first failing shard cancels the rest), plus a new preflight (lint + format:check) job gating regression-shards. - player-perf: matrix fail-fast → true, plus preflight gate. - preview-regression, windows-render, catalog-previews: preflight gate added; heavy jobs now needs: [..., preflight]. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8348e19fd9 |
fix(ci): switch Windows install to hoisted linker; narrow FormData iter
Pushing further to actually get Windows render verification green, not just work around it. ## What's wrong on Windows Bun 1.3's default `isolated` linker creates nested workspace junctions under `packages/*/node_modules/` on Windows GHA runners. Those junctions don't materialize reliably — Node's `realpathSync` returns `EPERM` on stat, and ESM resolution returns `ERR_MODULE_NOT_FOUND`. Every Windows build since PR #748 has tripped this in one of three places: - `packages/producer/build.mjs` importing `esbuild` - `packages/producer/scripts/generate-font-data.ts` reading `@fontsource/*` - `packages/producer` running `tsc` to emit `.d.ts`s Long-running bun bugs: oven-sh/bun#23615, #18354, #10146. ## Fix **1. `--linker=hoisted` for the Windows install step** (workflow change, Windows only). Hoisted layout puts deps as real directories at the workspace root + workspace package node_modules. No junctions, no Windows-specific path quirks. Linux CI keeps the default isolated linker; the lockfile is linker-agnostic so `--frozen-lockfile` is still valid. **2. Source-level FormData narrowing in `packages/core/src/studio-api/routes/files.ts`** (needed because the hoisted layout exposes a `@types/node@25` typecheck issue that the isolated layout hides). With v25 + an `onmessage` global in scope, the ambient `FormData.entries()` infers `[string, string]` instead of `[string, File | string]`, so the `value instanceof File` check breaks at `TS2358`. Cast the iterator to a `[string, FileLike | string]` shape and narrow via `typeof value === "string"`. Identical runtime behavior; works under both v24 (isolated layout, what Linux CI sees) and v25 (hoisted, what Windows CI sees with this change). ## Verification - `bun install --frozen-lockfile` (isolated, default): full build green - `bun install --frozen-lockfile --linker=hoisted`: full build green, core typecheck passes, `@hyperframes/core` 853 tests pass - Format/lint clean on both layouts |
||
|
|
91bdffffe6 |
fix(ci): scope LOC check to studio, split useTimelinePlayer + hyperframes-player under 500 LOC (#750)
* refactor: split useTimelinePlayer.ts and hyperframes-player.ts into focused modules (<500 LOC each) * fix(ci): scope 500 LOC check to packages/studio, add allowlist for grandfathered files * feat(cli): Linux ARM64 support — auto-install Chromium on DGX Spark / GB10 / Jetson Chrome Headless Shell has no Linux ARM64 binary. On arm64 Linux: - Detects the platform automatically - Tries to auto-install system Chromium via apt-get (works on Ubuntu/Debian ARM) - Falls back to clear manual instructions with exact commands - 'hyperframes browser ensure' guides through the setup interactively - After setup, all render commands work without any flags * fix(ci): disable Windows Defender real-time monitoring to prevent EPERM builds Path exclusions are insufficient — Defender re-scans new files created during bun install before the exclusion takes effect. Disable real-time monitoring for the entire job duration instead (standard CI practice). * refactor(studio): split all files >500 LOC + extract useToast, delete allowlist All 11 large files split into focused modules under 500 LOC. App.tsx extracted toast logic into useToast hook (493 LOC now). .filesize-allowlist deleted — no longer needed. * fix: remove unused imports from split files, extract useToast from App.tsx App.tsx: 504 → 493 lines (toast logic extracted to useToast hook) timelineDOM.ts: remove unused imports from re-export pattern MotionPanel.tsx: remove unused clampStudioCustomEasePoints import studioMotionOps.ts: remove unused StudioGsapMotionDirection import * fix(ci): use Set-MpPreference to fully disable Windows Defender (both jobs) * fix(producer): use node --experimental-strip-types instead of tsx for build:fonts Eliminates the tsx binary dependency that Windows Defender locks during bun install, causing EPERM errors. Node 22.6+ strips TypeScript types natively with no external binary. * chore: remove .filesize-allowlist — App.tsx is now 493 lines (<500) * fix(ci): disable Windows Defender before checkout to prevent all EPERM races * fix(producer): skip build:fonts if fontData.generated.ts already exists The generated file is tracked in git, so CI doesn't need to regenerate it. This avoids @fontsource/inter node_modules access on Windows which triggers EPERM from Defender scanning during bun install. |
||
|
|
8986ab2739 | fix(ci): harden GitHub Actions workflows against supply chain attacks (#740) | ||
|
|
6a59ef6106 |
fix: skip metadata waits for injected video frames (#575)
## Problem Closes #574. On Windows with cached headless-shell Chrome, a composition that reuses the same video file in three timeline clips can fail before frame capture starts: ```html <video id="video1" src="1.mp4" data-start="0" muted data-duration="4" data-track-index="0" data-media-start="0"></video> <video id="video2" src="1.mp4" data-start="4" muted data-duration="4" data-track-index="0" data-media-start="4"></video> <video id="video3" src="1.mp4" data-start="8" muted data-duration="4" data-track-index="0" data-media-start="8"></video> ``` The reported render reaches video frame extraction, then dies at frame-capture initialization with: ```text [FrameCapture] video metadata not ready after 45000ms. Video elements must load metadata before capture starts. ``` The important detail is that by this stage HyperFrames has already extracted video pixels through FFmpeg. Native Chromium video metadata is only being waited on for DOM layout stability, not because Chromium is the source of rendered pixels. ## Root Cause The render pipeline has two separate media responsibilities: - FFmpeg extracts video frames and audio from declared media. - Chromium owns DOM layout and capture, while injected FFmpeg frames supply the video pixels before each captured frame. Before this PR, every capture session still waited for every DOM `<video>` to reach `readyState >= 1` unless the element was a native HDR exception. That made native browser media metadata a hard render prerequisite even when the browser would not decode or provide the final video pixels. That is why the issue fails at `25% Starting frame capture`: FFmpeg extraction has already succeeded, but capture initialization blocks on repeated native `<video src="1.mp4">` metadata loading in cached Windows headless-shell Chrome. There was a second constraint: the readiness wait also prevents first-frame layout bugs. If a skipped `<video>` has no native metadata, Chromium can use the default `300x150` intrinsic video size, which breaks layouts such as `width: 100%; height: auto` before the first injected frame. The fix therefore must not simply skip all video readiness waits; it must provide dimensions for any skipped videos. ## What This Fixes - Treats videos with successfully extracted FFmpeg frames and usable dimensions as out-of-band rendered video sources. - Skips native browser metadata readiness waits for those extracted videos because Chromium is not responsible for their pixels. - Passes FFmpeg-probed dimensions into capture as `videoMetadataHints`. - Applies those hints before the readiness wait in both screenshot and BeginFrame initialization paths. - Sets missing `width` / `height` attributes and an explicit `aspect-ratio` only when the element does not already provide one, preserving author styles where present. - Keeps native HDR video IDs in the skip list, preserving the existing HEVC/HDR behavior where Chrome may not decode the source but FFmpeg/native HDR compositing can still render it. - Uses one `buildCaptureOptions()` helper so calibration, HDR DOM capture, streaming capture, parallel capture, and sequential capture receive the same skip IDs and metadata hints. - Adds tests for the skip-list and metadata-hint contract. - Adds a Windows CI regression that reproduces the issue shape after the canary render warms the cached-browser path. ## Reviewer Map Primary files: - `packages/producer/src/services/renderOrchestrator.ts` - `collectVideoReadinessSkipIds()` includes native HDR IDs plus extracted videos that have finite positive FFmpeg dimensions. - `collectVideoMetadataHints()` converts extracted FFmpeg metadata into capture hints. - `buildCaptureOptions()` threads `skipReadinessVideoIds` and `videoMetadataHints` into every capture path. - `packages/engine/src/services/frameCapture.ts` - `applyVideoMetadataHints()` runs in the page before video readiness polling. - Both screenshot and BeginFrame initialization call it before checking non-skipped videos for `readyState >= 1`. - `packages/engine/src/types.ts` - Adds `CaptureVideoMetadataHint` and documents that readiness skips should be paired with metadata hints when layout may depend on intrinsic dimensions. - `packages/producer/src/services/renderOrchestrator.test.ts` - Covers that extracted videos with dimensions are skipped, invalid dimensions are not, native HDR IDs are preserved, and hints are stable/sorted. - `.github/workflows/windows-render.yml` - Adds the issue #574 Windows regression with the exact three-clip markup and a generated deterministic `1.mp4`. ## Why This Is Safe The skip is intentionally gated: - A standard video is skipped only after `extractAllVideoFrames()` succeeded for that video and returned usable dimensions. - Videos with invalid dimensions are not skipped, so the old browser readiness guard still applies. - DOM videos are still present for layout and element bounds; only the native metadata wait is skipped for sources whose pixels come from FFmpeg injection. - Metadata hints are applied conservatively: existing `width`, `height`, and explicit `aspect-ratio` are not overwritten. - Non-extracted videos, images, fonts, page readiness, and `window.__hf` readiness keep the existing waits. - The fix is not limited to the sequential path from the issue; it is threaded through calibration, HDR DOM capture, streaming encode, parallel capture, and sequential capture. A first local revision skipped readiness too broadly and caused `overlay-montage-prod` first-frame layout shrinkage. The current version fixes that by pairing skips with FFmpeg metadata hints; `overlay-montage-prod` now passes and is listed in verification below. ## Verification ### Root-Cause Reproduction Before Fix The reporter did not attach the actual `1.mp4`, so the regression uses the exact issue markup and a deterministic generated 12s H.264 file named `1.mp4`. I reproduced the failure in GitHub Actions by running this branch's new Windows workflow against unpatched `main`: ```bash gh workflow run windows-render.yml --repo heygen-com/hyperframes --ref fix/reused-video-metadata -f ref=main ``` That means the workflow contains the new issue #574 regression, but the code under test is `main` without this fix. Baseline failure: - Run: https://github.com/heygen-com/hyperframes/actions/runs/25174603730 - Failed job: https://github.com/heygen-com/hyperframes/actions/runs/25174603730/job/73803179086 - Checkout proof: `ref: main`, `origin/main`, commit `8662598a3ac64018a2999d189ffb369e6d46b53a`. - Failure proof: `Browser: cache`, `staticDuration:12`, `videoCount:3`, then `25% Starting frame capture` -> `[FrameCapture] video metadata not ready after 15000ms`. This is the same failure class as the issue, on Windows, in cached-browser mode, before the fix. ### Fixed Windows Regression The same regression passes on this PR branch: - Run: https://github.com/heygen-com/hyperframes/actions/runs/25175048215 - Passing job: https://github.com/heygen-com/hyperframes/actions/runs/25175048215/job/73804781017 - Checkout proof: PR merge contains `79d6b41c9f2ba137cbfb9301678e0815b16c4f5a` merged into `8662598a3ac64018a2999d189ffb369e6d46b53a`. - Passing proof: `Browser: cache`, `staticDuration:12`, `videoCount:3`, `25% Starting frame capture`, captures `360/360` frames, renders `issue-574.mp4`, and `ffprobe` verifies `1920x1080 @ 30/1, 12s`. ### Local Checks - `bun run build:hyperframes-runtime` - `bunx vitest run packages/producer/src/services/renderOrchestrator.test.ts` - `bun run --filter @hyperframes/producer typecheck` - `bun run --filter @hyperframes/engine typecheck` - `bunx oxlint packages/engine/src/services/frameCapture.ts packages/engine/src/types.ts packages/engine/src/index.ts packages/producer/src/services/renderOrchestrator.ts packages/producer/src/services/renderOrchestrator.test.ts` - `bunx oxfmt --check .github/workflows/windows-render.yml packages/engine/src/services/frameCapture.ts packages/engine/src/types.ts packages/engine/src/index.ts packages/producer/src/services/renderOrchestrator.ts packages/producer/src/services/renderOrchestrator.test.ts` - `git diff --check` - Lefthook pre-commit: lint, format, typecheck where applicable - Lefthook commit-msg: commitlint ### Local Render Checks - Created `/tmp/hf-issue-574-repro` with the issue shape: three clips using the same `1.mp4`, `data-media-start=0/4/8`, 12s total. - `PRODUCER_PLAYER_READY_TIMEOUT_MS=5000 bun packages/cli/src/cli.ts render /tmp/hf-issue-574-repro --workers 1 --quality draft --fps 30 --output /tmp/hf-issue-574-h264-fixed-v2.mp4` -> completed. - Created `/tmp/hf-issue-574-prores` with the same three-clip shape using one FFmpeg-readable ProRes `.mov`, which exercises the browser-metadata failure class because Chromium should not be needed to decode the source. - `PRODUCER_PLAYER_READY_TIMEOUT_MS=3000 bun packages/cli/src/cli.ts render /tmp/hf-issue-574-prores --workers 1 --quality draft --fps 30 --output /tmp/hf-issue-574-prores-fixed-v2.mp4` -> completed. - `bun run --filter @hyperframes/producer test --sequential --keep-temp overlay-montage-prod` -> passed; this guards against skipped metadata shrinking `height:auto` video layout before the first injected frame. - `ffmpeg -v error -i /tmp/hf-issue-574-prores-fixed-v2.mp4 -f null -` - `ffmpeg -v error -i /tmp/hf-issue-574-h264-fixed-v2.mp4 -f null -` - `ffprobe -v error -show_entries format=duration:stream=codec_name,width,height,r_frame_rate -of json /tmp/hf-issue-574-h264-fixed-v2.mp4` -> H.264, 320x180, 30fps, 12.0s. ### Current PR Checks - Windows render verification: pass on https://github.com/heygen-com/hyperframes/actions/runs/25175048215. - Windows tests: pass on https://github.com/heygen-com/hyperframes/actions/runs/25175048215. - Main CI build/lint/typecheck/test/smoke jobs: pass on https://github.com/heygen-com/hyperframes/actions/runs/25175048175. - Regression shards observed passing include HDR, render-compat, styles A-G, and `overlay-montage-prod`. At the time this body was updated, the `fast` regression shard was still in progress in run https://github.com/heygen-com/hyperframes/actions/runs/25174515546. ### Browser Verification - Used `agent-browser` to open `file:///tmp/hf-issue-574-h264-fixed-v2.mp4` and verify the rendered output displays in Chromium. - Screenshot: `.debug/issue-574/h264-output-page.png` - Agent-browser recording: `.debug/issue-574/h264-output-playback.webm` ## Notes / Caveats - The reporter's exact `1.mp4` was not attached to #574. The committed Windows regression uses a generated deterministic H.264 file with the same filename and exact markup from the issue. - The exact H.264 issue shape did not reproduce the timeout on this macOS/system-Chrome machine before the fix; it rendered successfully locally. The GitHub Actions baseline above reproduces it on Windows/cache without the fix. - The Windows fixture intentionally runs after the existing canary render so the browser path is `Browser: cache`, matching the reporter's environment. - The generated fixture emits sparse-keyframe warnings. Those warnings are expected and are not the failure being fixed; the baseline failure occurs before any frame capture because native browser video metadata never becomes ready. - Browser proof artifacts are local-only under `.debug/issue-574/` and intentionally not committed. |
||
|
|
745b877796 | chore(deps): pin dependencies | ||
|
|
267ffd3fca |
fix(engine,producer): preserve template-wrapped sub-composition media offsets (#476)
## Problem Template-wrapped sub-compositions could still lose correct parent timing during render in more than one place. In the validated repros, a host sub-composition starting after the intro (and in one follow-up repro, starting at `20s` after earlier compositions) contained scene-local media inside it. On the broken paths: - template-wrapped media could be missed during compile and scheduled at raw scene-local time - already-correct first-pass offsets could be clobbered during `recompileWithResolutions()` - even after those two fixes, the browser-metadata reconcile step in `executeRenderJob()` could still overwrite a compiled global `end` with a scene-local `data-end` from the inlined DOM, clipping the tail off late-start sub-composition media ## What this fixes ### Template-wrapped media discovery - `parseVideoElements`, `parseImageElements`, and `parseAudioElements` now unwrap a single top-level `<template>` wrapper before scraping media - the unwrap helper is DOM-based, not regex-based, so it avoids the CodeQL backtracking warning and only unwraps the exact single-wrapper shape we want - multiple sibling templates or other top-level content are left untouched instead of being rewritten heuristically ### Offset preservation after duration resolution - `recompileWithResolutions()` now preserves the first-pass sub-composition media arrays when the already-inlined HTML no longer contains `[data-composition-src]` hosts - that prevents correctly offset media metadata from being overwritten by scene-local media parsed from the merged DOM ### Browser metadata reconciliation in the compiled time origin - browser-discovered media can still report scene-local `data-start` / `data-end` from the merged DOM after inlining - the producer now reprojects browser `end` values into the compiled element's time origin before reconciling them back into `composition.videos` / `composition.audios` - this prevents late-start sub-composition media from getting truncated back to a scene-local end during the probe phase ### Regression coverage - adds focused engine tests for the template unwrap helper - adds producer regression coverage for both the initial compile path and the post-inline `recompileWithResolutions()` path - adds producer regression coverage for late-start host compositions (`t≈20`) with scene-local media inside them - adds producer unit coverage for the browser-end reprojection helper used by the reconcile path ## Root cause There were three distinct renderer failures behind the bug: ### 1. Template contents were invisible to the media scrapers `parseSubCompositions()` reads raw sub-composition HTML and applies the host offset to discovered media. But the engine media helpers were querying the parsed document directly, and linkedom follows browser semantics here: top-level `<template>` contents live in a `DocumentFragment`, so `querySelectorAll()` never saw those `<video>` / `<audio>` / `<img>` nodes. That meant template-wrapped sub-compositions could silently produce zero discovered media during the first pass. ### 2. The duration-resolution recompile could clobber already-correct offsets After the browser resolves composition durations, `recompileWithResolutions()` reparses the already-inlined HTML. By that point the original `[data-composition-src]` hosts are gone, so `parseSubCompositions()` legitimately returns no nested media. The old code still rebuilt the deduped media arrays from the merged DOM, which let scene-local media parsed from the inlined HTML overwrite the correctly offset first-pass metadata. ### 3. The browser probe reconcile path mixed two timing coordinate systems `discoverMediaFromBrowser()` reads `data-start` / `data-end` directly from the live DOM after sub-compositions are already inlined. For nested media, those attributes can still be scene-local even though the compiled metadata has already been offset into the parent host timeline. The old reconcile path compared those values directly and overwrote `existing.end` whenever the numbers differed. For a late-start sub-composition, that could replace a correct global end like `25.5` with a scene-local end like `5.5`, cutting the clip off during render. ## Verification ### Local checks - `bun test packages/engine/src/utils/htmlTemplate.test.ts` - `bun test packages/producer/src/services/htmlCompiler.test.ts` - `bunx vitest run packages/producer/src/services/renderOrchestrator.test.ts` - `bun run --filter @hyperframes/engine test` - `bun run --filter @hyperframes/engine typecheck` - `bun run --filter @hyperframes/producer typecheck` - `bunx oxlint packages/engine/src/utils/htmlTemplate.ts packages/engine/src/utils/htmlTemplate.test.ts packages/producer/src/services/renderOrchestrator.ts packages/producer/src/services/renderOrchestrator.test.ts packages/producer/src/services/htmlCompiler.test.ts` - `bunx oxfmt --check packages/engine/src/utils/htmlTemplate.ts packages/engine/src/utils/htmlTemplate.test.ts` - `bun run build:producer` ### Render / browser verification Verified against two local repros: 1. **Early offset repro** - host starts at `2s` - child media is scene-local `0-4s` - compiled render summary keeps the child video/audio at `start: 2` - browser verification via `agent-browser` confirmed the `2.2s` frame still shows the child clip active in the host timeline 2. **Late offset repro** - earlier compositions run first, then the target host starts at `20s` - child media starts scene-local at `1.5s` and should remain visible through `24.5s` - compiled render summary keeps the child video/audio at `start: 21.5`, `end: 25.5` - browser verification via `agent-browser` confirmed the `24.5s` frame still shows the late clip visible, which is the exact tail-clipping case the old reconcile path could break ## Notes - the `/tmp/hf-pr475-repro` and `/tmp/hf-pr476-late-offset-repro` projects plus their browser-proof artifacts are verification-only and are not part of this PR - this PR stays narrowly scoped to sub-composition media timing across compile, recompile, and browser probe reconciliation; it does not broaden into general sub-composition HTML normalization beyond the single-wrapper case |
||
|
|
3089c8ee3a |
build(lfs): track tests/*/src/*.png via Git LFS (#376)
## Summary Track `tests/*/src/*.png` via Git LFS to mirror the existing policy for golden videos and `.mp4` fixtures. ## Why `Chunk 11C` of `plans/hdr-followups.md`. Without this rule, regression suites that grow PNG fixtures over time would bloat the working-tree history and slow shallow clones. ## What changed - `.gitattributes`: add `tests/*/src/*.png` to the LFS-tracked patterns. - Migrates the six existing PNG fixtures (1.6 MB combined: `hdr-photo-pq.png` plus `heygen-promo-preview-assets/` screenshots) onto LFS in the same commit so the rule applies retroactively. ## Test plan - [x] `git lfs ls-files` includes the HDR PNG fixtures after commit. - [x] Working tree size for these files goes from 1.6 MB to 6 × ~130 B LFS pointers. ## Stack Chunk 11C of `plans/hdr-followups.md`. Independent of all code changes. |
||
|
|
2f58e9d188 |
ci(windows-render): bypass Chocolatey, fetch ffmpeg from BtbN/GitHub (#436)
## What Replace the `choco install ffmpeg` step in `windows-render.yml` with a direct download of the upstream Windows GPL build from [`BtbN/FFmpeg-Builds`](https://github.com/BtbN/FFmpeg-Builds/releases/latest) on GitHub Releases. ## Why The `Render on windows-latest` canary started failing on every PR with: ``` [NuGet] Response status code does not indicate success: 504 (Gateway Timeout). [NuGet] Response status code does not indicate success: 503 (Service Unavailable). ``` The Chocolatey community feed (`community.chocolatey.org/api/v2/package/ffmpeg/8.1.0`) is degraded for the `ffmpeg` package right now. The earlier 3-attempt retry I added wasn't enough — every attempt across multiple runs failed with 503/504, so retrying does nothing. The Chocolatey path is also a bit indirect for what this job actually validates. The real point of the canary is the [PR #336](https://github.com/heygen-com/hyperframes/pull/336) fix where `findFFmpeg()` / `where ffmpeg` discovery has to work on a fresh Windows runner. As long as `ffmpeg.exe` ends up on `PATH`, the underlying thing under test (the harness can find ffmpeg, capture frames, mux to MP4) is exercised exactly the same. BtbN/FFmpeg-Builds is the canonical upstream nightly Windows GPL build (Chocolatey itself rebundles essentially the same artifact), so this is closer to the source, not further from it. ## How - Download `ffmpeg-master-latest-win64-gpl.zip` from the BtbN release with `Invoke-WebRequest` (3-attempt retry with backoff). - Extract to `$env:RUNNER_TEMP/ffmpeg` and locate `ffmpeg.exe` recursively. - Add the bin directory to `$env:GITHUB_PATH` so all subsequent steps in the job (the Bun-driven harness, `findFFmpeg()`, etc.) see ffmpeg on `PATH` exactly the same way as before. - Print `ffmpeg -version` as a sanity check. ## Test plan - [ ] CI: `Render on windows-latest` job goes green on this PR. - [ ] Subsequent PRs no longer get blocked on `choco install ffmpeg` 503s. |
||
|
|
113f9eafd5 |
ci: subscribe to edited PR events so workflows re-fire after Graphite restacks (#429)
## What Brief description of the change. ## Why Why is this change needed? ## How How was this implemented? Any notable design decisions? ## Test plan How was this tested? - [ ] Unit tests added/updated - [ ] Manual testing performed - [ ] Documentation updated (if applicable) |
||
|
|
2e8e579df2 |
ci: skip PR runs when targeting a non-main base branch (#426)
* ci: skip PR runs when targeting a non-main base branch Adds `branches: [main]` to the `pull_request:` trigger of each workflow that runs on PRs (CI, regression, Windows render verification, Docs, Catalog Previews). PRs whose base is something other than main — typical for stacked PRs — no longer trigger these workflows. On a 5-PR Graphite stack this turns 5× CI runs into 1× (when the tip of the stack reaches main). When a child PR is rebased/promoted so its base becomes main, CI fires as normal. publish.yml and the default CodeQL setup are untouched: publish already filters to main, and CodeQL is default-setup (org UI, not a repo YAML). * chore(ci): fix oxfmt formatting on renovate.json Same drive-by as #423. Renovate's config-migration PR #422 landed unformatted (Renovate bot skips lefthook), so every PR branched from current main fails `bun run format:check`. Whichever of #423 / #426 merges first cleans it up. |
||
|
|
03c2158e0f |
ci: verify on windows-latest + fix cross-platform build bugs it surfaced (#342)
* fix(cli): make build copy cross-platform and deterministic
* fix(core): keep rewritten asset URLs POSIX on Windows
* ci(windows): add render verification workflow
* ci(windows): load canary gsap from cdn
* build: use dependency-aware workspace ordering
* Revert "build: use dependency-aware workspace ordering"
This reverts commit
|