fix(studio-server): keep transparency in proxies of VP8/VP9 alpha webm by miguel-heygen · Pull Request #6025 · heygen-com/hyperframes · GitHub
Skip to content

fix(studio-server): keep transparency in proxies of VP8/VP9 alpha webm - #6025

Merged
miguel-heygen merged 2 commits into
mainfrom
fix/studio-proxy-alpha-mode
Oct 21, 2026
Merged

miguel-heygen merged 2 commits into
mainfrom
fix/studio-proxy-alpha-mode

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

What

A transparent webm, such as the output of hyperframes remove-background, lost its transparency in the Studio preview proxy. hyperframes check, Studio and publish all made an opaque H.264 copy of it.

Why

There were two bugs on the same path.

  1. Wrong variant. VP8/VP9 keep their alpha in a side channel, so ffprobe reports pix_fmt=yuv420p plus an ALPHA_MODE=1 stream tag. Studio-server decided "has alpha" from pix_fmt alone, and it never asked ffprobe for the tag. proxyVariantFor therefore picked h264. The engine already read the tag; the two copies of the rule had drifted (the studio-server regex also missed ayuv*).
  2. Wrong decoder. Even on the vp8 variant, the transcode read the source with ffmpeg's native VP9 decoder, which drops the side channel. format=yuva420p then filled in a fully opaque alpha plane.

Change

  • New @hyperframes/parsers/media-alpha is the one owner of pixelFormatHasAlpha, videoStreamHasAlpha (pix_fmt or a case-insensitive alpha_mode=1 tag) and alphaDecoderFor (libvpx-vp9 / libvpx).
  • Studio-server's probe now requests stream_tags=alpha_mode, sets MediaMetadata.hasAlpha, and the codec map uses it. Its own pix_fmt regex is deleted.
  • The proxy transcode passes -c:v libvpx-vp9|libvpx before -i only when it builds the vp8 (alpha) variant of a VP8/VP9 source.
  • The engine imports the shared rule. Its regex copy is deleted, and decoderForCodec delegates to the shared decoder choice. Engine behavior is unchanged.

Opaque media is untouched. Its probe answer, variant, ffmpeg args and cache key are all the same as before. No proxy cache version bump is needed: before this change, no VP8/VP9 source could reach the vp8 variant, so no stale alpha proxies exist.

Evidence

The same project and the same half-transparent VP9 clip (libvpx-vp9, yuva420p, alpha_mode=1, mean alpha 127.5), run through hyperframes check with ffmpeg 8.1.1:

.transcode-cache entry decoded mean alpha
main h264 / yuv420p (.mp4) none (opaque)
this branch vp8 / yuv420p / alpha_mode=1 (.webm) 127.5

Tests

  • parsers/src/mediaAlpha.test.ts: the merged pix_fmt lists from both old tests, the tag in both casings, and the decoder choice.
  • studio-server/src/helpers/mediaCodecMap.test.ts: ffprobe output for a real VP9 alpha encode now gives hasAlpha and the vp8 variant.
  • studio-server/src/helpers/proxyTranscoder.test.ts: the spawn args put -c:v libvpx-vp9 before -i for vp8 and leave h264 alone. An end-to-end test with real ffmpeg covers probe, variant and transcode, and checks that the proxy's mean alpha stays at about 127.5. It is skipped when ffmpeg lacks libvpx.
  • Mutation check: removing the tag read fails the codec-map and end-to-end tests. Removing the decoder arg fails the spawn-args and end-to-end tests.

Package size

@hyperframes/parsers was already at its packed budget on main (190591 B). The new media-alpha subpath adds 676 B of JS and 803 B of declarations, which brings the packed size to 190958 B in CI. The budget moves to 191000 B. Nothing else in the tarball changed.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact e884fb52d25f6bc8aea9985ad87b7a575d19465c. No blocker found.

  • packages/parsers/src/mediaAlpha.ts:1–27 preserves the engine alpha-format predicate and decoder mapping, and recognizes alpha_mode=1 in any tag-name casing. Studio requests the tag in its existing probe rather than adding a subprocess.
  • packages/studio-server/src/helpers/proxyTranscoder.ts:448–451 places the correct VP9/VP8 decoder before input only for the alpha variant. That variant remains VP8/WebM with yuva420p and alpha metadata; opaque media retains the H.264 recipe.
  • Independent consumer/cache review traced check/publish baking, preview routes, runtime selection and ETags: choosing vp8 bypasses old h264 cache entries; variant validation and cache identity remain consistent.

Executed the current-source metadata → codec-map → resolveProxy chain in an isolated harness with real FFmpeg 8.0.1 and generated half-transparent VP9 and VP8 clips. Both produce WebM proxies with mean alpha exactly 127.5; opaque VP9 chooses H.264/MP4. Cache reuse also passes. Removing the input decoder makes the alpha assertion fail at 255; removing the requested alpha tag makes detection fail. The harness transpiles current TS and isolates unrelated HDR helpers, HTML collection, safe-path integration, binary discovery and cache maintenance; these are not full CLI/Studio or committed Vitest-suite runs. Read the added real VP9 test and shared helper cases; the committed real test explicitly skips without libvpx.

Missing-library behavior: no decoder fallback exists. A build lacking the required libvpx support fails visibly through ProxyTranscodeError (preview error / failed publish proxy), rather than silently generating an opaque replacement. The alpha output encoder already requires libvpx. I did not execute a minimal FFmpeg build with those codecs absent. Engine delegation is behavior-equivalent; parser source/runtime/types/build export entries agree.

Verdict: APPROVE
Reasoning: The shared rule repairs variant selection, and real VP8/VP9 transcodes preserve alpha with the correct input decoder; opaque-media and cache contracts remain intact in the inspected paths.

— hf-review

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

A webm whose alpha lives in the VP8/VP9 side channel (alpha_mode=1,
pix_fmt yuv420p) was proxied to opaque H.264, and the VP8 proxy path
decoded it with ffmpeg's native decoder, which drops alpha. The alpha
rule and the alpha-reading decoder now live once in
@hyperframes/parsers/media-alpha, used by engine and studio-server.
parsers already sat at its packed budget on main; the shared alpha rule
adds 676 B of JS and 803 B of declarations (190958 B packed in CI).
@miguel-heygen
miguel-heygen force-pushed the fix/studio-proxy-alpha-mode branch from d1795b4 to cf3b539 Compare October 21, 2026 13:28

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact cf3b53993ec50c57d7622795c5910af275c60b5a against previously approved e884fb52d25f6bc8aea9985ad87b7a575d19465c.

All 14 alpha code/test/package-export files are byte-identical to that reviewed revision. The additional own-diff budget change raises the parsers packed allowance from 190591 to 191000 bytes; inherited main changes do not alter the inspected alpha pipeline.

Parent rebuilt the harness from current TypeScript and reran real FFmpeg 8.0.1: VP8 and VP9 alpha inputs both yield WebM proxies with mean alpha 127.5, cache reuse passes, and opaque VP9 still chooses H.264/MP4. A fresh independent rebase audit agreed. Prior scope and missing-libvpx behavior remain: unsupported builds fail visibly, with no alpha-stripping fallback. Harness isolates unrelated HDR/HTML/path/binary/cache-maintenance integrations; no full Vitest, Studio/CLI, Windows-host or codec-absent build run.

Verdict: APPROVE
Reasoning: The rebase preserves the reviewed alpha behavior and current-source real transcodes still retain transparency without changing opaque-media output.

— hf-review

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit d67e81c Oct 21, 2026
74 checks passed
@miguel-heygen
miguel-heygen deleted the fix/studio-proxy-alpha-mode branch October 21, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants