feat(engine): preserve alpha channel on <video> frame extraction#357
feat(engine): preserve alpha channel on <video> frame extraction#357leopeak wants to merge 1 commit intoheygen-com:mainfrom
Conversation
Source videos with transparency (WebM yuva420p, PNG-in-AVI rgba, ProRes 4444, HEVC yuva444p, etc.) used to lose their alpha channel during the ffmpeg pre-extract step because the extractor defaulted to JPEG and did not request `-pix_fmt rgba` even when PNG was forced. Downstream the <img>-data-URI injector and Chrome compositor already handle alpha correctly, so fixing the extract stage alone unlocks transparency for every output container, MP4 included. Adds a `data-alpha="true"` opt-in on <video> (with ffprobe-based auto-detect as fallback), routes it through the extractor to force `format=rgba` + `-pix_fmt rgba` + PNG output for tagged clips only, and skips the `video.readyState` gate for alpha videos whose source codec Chrome cannot decode natively (e.g. PNG-in-AVI). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrusso1020
left a comment
There was a problem hiding this comment.
Thanks for the contribution! Nice, well-scoped feature and a genuinely excellent PR description — the problem analysis and the "fix only the extract stage, downstream already handles alpha" framing made this very easy to review.
Looks good
- VideoToolbox hwaccel skipped for alpha (
videoFrameExtractor.ts:161) — non-obvious catch, VT does strip alpha on decode. - Belt-and-braces RGBA:
vf format=rgba+-pix_fmt rgbaon the PNG encoder. Either alone can misbehave on some ffmpeg builds; doing both is the right call. - Auto-detect via ffprobe
pix_fmtas default, explicitdata-alpha="true"opt-in for misreported sources — right layering, and thepixelFormatHasAlpha()pixel-format coverage looks correct (yuva*, rgba/argb/bgra/abgr, rgba64*, ya8/ya16*). - Opaque videos keep the JPEG fast path — zero regression for existing compositions.
- Lint + engine tests pass on the branch (287/287).
Needs fixing
1. Conflicts with skipReadinessVideoIds — please consolidate mechanisms
Since this branch was cut, main landed #346 which adds skipReadinessVideoIds on CaptureOptions (packages/engine/src/types.ts:108). The producer populates it with native-HDR video IDs and frameCapture.ts:260 / :339 filter those out of the readyState>=1 gate. This PR solves the same problem — "skip the video readiness gate because frames come from ffmpeg out-of-band" — with a different mechanism (DOM data-alpha="true" filter), and they conflict on both gate sites.
Suggested path: drop the frameCapture.ts changes entirely and instead populate skipReadinessVideoIds from the producer when a video has hasAlpha: true, next to the existing nativeHdrVideoIds plumbing (renderOrchestrator.ts:1001,1184,1245). One mechanism, one code path; both cases ("native HDR" and "alpha") become instances of the same pattern.
2. ffprobe.ts merge must preserve main's stillImageMeta fallback
Main added a PNG cICP chunk fallback: colorSpace: ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null. The PR region reverts to the old hasColorInfo check. The rebase needs to keep main's stillImage fallback and add pixelFormat + hasAlpha alongside it — naively picking the PR side breaks HDR PNG detection.
3. Please drop IMPLEMENTATION_PLAN.md and PR_DRAFT.md from the repo root
These read like author scratch files. Repo root hasn't carried planning docs before; they'll rot and confuse future contributors. Either delete, or move under notes/ (already gitignored). The .gitignore entry for /test-alpha/ is fine to keep.
Minor / style (non-blocking)
extractVideoFramesRangenow takes 8 positional args with the newoverridesparam. FoldhasAlphaintooptionsinstead.videoFrameExtractor.test.ts:25weakenstoEqual→toMatchObject, which silently allows extra fields. Prefer keepingtoEqualwith explicithasAlpha: undefined.pixelFormat: ""as absent-marker is inconsistent withcolorSpace: VideoColorSpace | null.string | nullwould be more in line with the rest of the file.- No automated end-to-end test asserting alpha survives through ffmpeg (the new tests cover parser only). A small fixture-based test reading one non-255 alpha byte from the output would lock in the behavior — low priority, but nice to have for a feature this "silent" in its failure mode.
Thanks again — once (1)–(3) are sorted, this is a solid addition.
|
Thanks for the thoughtful review — the feedback on consolidating around Planned updates (likely in the next few days):
Will ping once the updates are pushed. Thanks again for the detailed look! |
Alpha-channel preservation for
<video>extractionProblem
When a HyperFrames composition contains a
<video>with an alpha channel —WebM VP8/VP9
yuva420p, HEVCyuva444p, PNG-in-AVIrgba, QuickTime ProRes4444, etc. — the alpha is silently dropped before the frame ever reaches
Chrome. The offending step is the ffmpeg pre-extract:
--format webm/--format mov),the command omits
-pix_fmt rgba, so ffmpeg may downconvert yuva → yuv andwrite an opaque RGB PNG.
Downstream the compositor works correctly —
<img>data URIs preserve alpha,Chrome's compositor handles blending — so a one-line extract fix unlocks
transparency for every output container, including opaque MP4.
Approach
Added opt-in + auto-detect alpha preservation at the extraction stage. No
new CLI flag, no changes to the renderer / encoder.
data-alphaattribute on<video>elements:data-alpha="true"— force RGBA PNG extraction.data-alpha="false"— force opaque (overrides auto-detect)."auto"— auto-detect from ffprobe'spix_fmt.pixelFormatHasAlpha()util. Flags theyuva-family, rgba/argb/bgra/abgr, rgba64, and ya8/ya16 gray-alpha formats.
format=rgba+-pix_fmt rgbaforalpha-bearing videos, with PNG forced. Other videos stay on JPEG at full
speed — zero regression for existing compositions.
frameCapture.initializeSessionno longerblocks on
video.readyState >= 1fordata-alpha="true"elements, sinceChrome often can't decode the source codec (e.g. PNG-in-AVI) and the
renderer swaps the
<video>for an<img>before capture anyway.Files changed
packages/engine/src/utils/ffprobe.tspixelFormat+hasAlphatoVideoMetadata; newpixelFormatHasAlpha()util.packages/engine/src/services/videoFrameExtractor.tsdata-alphaintoVideoElement.hasAlpha; force PNG+rgba extraction when alpha is in play; threadhasAlphathroughextractAllVideoFrames.packages/engine/src/services/videoFrameExtractor.test.tsdata-alphaparsing.packages/engine/src/services/frameCapture.tsdata-alpha="true"elements in both screenshot and BeginFrame paths.packages/engine/src/index.tspixelFormatHasAlpha.packages/producer/src/services/htmlCompiler.tshasAlphathrough browser-discovered media metadata.packages/producer/src/services/renderOrchestrator.tshasAlphainto newVideoElemententries discovered via DOM probe.Usage
For well-tagged sources (e.g. WebM with
yuva420pcorrectly advertised) thedata-alpha="true"attribute is optional — ffprobe auto-detection will setthe flag at extraction time.
Test results
bun run testfor@hyperframes/engine— all 4parseVideoElementstests pass, including 2 new ones for
data-alpha="true"/"false".(
data-alpha="true") over a red div →output.mp4shows the red backgroundthrough every transparent pixel of the lion. No black box, no fringe.
(
vsl-hf-reproduction/index.html) with 13 alpha-tagged lion overlayscomposited on top of ~13 opaque background clips — rendered end-to-end in
13m4s to a 79.6 MB MP4. Alpha preserved across all lion shots; no regression
on the opaque background videos.
Non-goals
--format webm/--format movtransparent-output path.Those already worked; this fix is about alpha on the input side.
data-has-audio,data-media-start, etc.videos that opt in or auto-detect as alpha-bearing.