fix(studio): preserve alpha proxy playback (#2625)

* fix(studio): preserve alpha proxy playback

* test(cli): pin VP8 alpha proxy pre-resolution

* fix(studio): emit textarea field-sizing CSS

* test(studio): verify textarea CSS generation
This commit is contained in:
Miguel Ángel
2026-07-17 21:56:27 -04:00
committed by GitHub
parent bcdb90a45f
commit e64a22893a
15 changed files with 125 additions and 88 deletions
@@ -215,7 +215,7 @@ describe("decideMediaProxyEligibility", () => {
});
});
it("does not recycle an alpha VP9 source into the same proxy codec", () => {
it("proxies an alpha VP9 source to the Chromium-compatible VP8 variant", () => {
expect(
decideMediaProxyEligibility({
codecName: "vp9",
@@ -223,19 +223,19 @@ describe("decideMediaProxyEligibility", () => {
representativeMime: 'video/webm; codecs="vp09.00.10.08"',
hasAlpha: true,
}),
).toEqual({ eligible: false, reason: "proxy_target_codec" });
).toEqual({ eligible: true });
});
});
describe("proxyVariantFor", () => {
it("uses VP9 for alpha and H.264 otherwise", () => {
it("uses VP8 for alpha and H.264 otherwise", () => {
const facts = {
codecName: "prores",
browserHostile: true,
representativeMime: null,
hasAlpha: true,
};
expect(proxyVariantFor(facts)).toBe("vp9");
expect(proxyVariantFor(facts)).toBe("vp8");
expect(proxyVariantFor({ ...facts, hasAlpha: false })).toBe("h264");
});
});
@@ -248,7 +248,7 @@ describe("resolveProxyVariantRequest", () => {
representativeMime: null,
hasAlpha: true,
};
expect(resolveProxyVariantRequest("auto", facts)).toBe("vp9");
expect(resolveProxyVariantRequest("auto", facts)).toBe("vp8");
expect(resolveProxyVariantRequest("h264", facts)).toBeNull();
});
});
@@ -23,7 +23,7 @@ export interface AssetCodecFacts {
* the runtime always proxies rather than probing `canPlayType`). */
representativeMime: string | null;
/** Source carries an alpha channel (ffprobe pix_fmt). Alpha sources use a
* VP9/WebM proxy so their transparency is preserved. */
* VP8/WebM proxy so their transparency is preserved across Chromium builds. */
hasAlpha: boolean;
}
@@ -47,7 +47,7 @@ export const BROWSER_HOSTILE_CODECS: Record<string, string | null> = {
vp9: 'video/webm; codecs="vp09.00.10.08"',
};
export type ProxyVariant = "h264" | "vp9";
export type ProxyVariant = "h264" | "vp8";
export type ProxyVariantRequest = ProxyVariant | "auto";
export const PROXY_VARIANT_CONFIG: Record<
@@ -55,7 +55,7 @@ export const PROXY_VARIANT_CONFIG: Record<
{ extension: ".mp4" | ".webm"; contentType: "video/mp4" | "video/webm" }
> = {
h264: { extension: ".mp4", contentType: "video/mp4" },
vp9: { extension: ".webm", contentType: "video/webm" },
vp8: { extension: ".webm", contentType: "video/webm" },
};
export function isProxyVariant(value: string): value is ProxyVariant {
@@ -67,7 +67,7 @@ export function isProxyVariantRequest(value: string): value is ProxyVariantReque
}
export function proxyVariantFor(facts: AssetCodecFacts): ProxyVariant {
return facts.hasAlpha ? "vp9" : "h264";
return facts.hasAlpha ? "vp8" : "h264";
}
export function resolveProxyVariantRequest(
@@ -78,10 +78,7 @@ export function resolveProxyVariantRequest(
return request === "auto" || request === expected ? expected : null;
}
export type MediaProxyIneligibilityReason =
| "browser_safe_codec"
| "proxy_target_codec"
| "unknown_codec";
export type MediaProxyIneligibilityReason = "browser_safe_codec" | "unknown_codec";
export type MediaProxyEligibility =
| { eligible: true }
@@ -91,9 +88,6 @@ export type MediaProxyEligibility =
export function decideMediaProxyEligibility(facts: AssetCodecFacts | null): MediaProxyEligibility {
if (!facts) return { eligible: false, reason: "unknown_codec" };
if (!facts.browserHostile) return { eligible: false, reason: "browser_safe_codec" };
if (facts.hasAlpha && facts.codecName === "vp9") {
return { eligible: false, reason: "proxy_target_codec" };
}
return { eligible: true };
}
@@ -73,7 +73,7 @@ function injectScriptTagIntoHead(html: string, scriptTag: string): string {
* responses). No second concurrency limiter here — the transcoder's own
* global bound throttles both pre-warm and element-triggered calls.
* Pre-warm failures are swallowed; an actual `?hf-proxy=` request surfaces
* them as a 502. Alpha-bearing entries pre-warm their VP9/WebM variant.
* them as a 502. Alpha-bearing entries pre-warm their VP8/WebM variant.
*
* The single shared implementation for every auto-proxy surface — the studio
* preview route (via `injectMediaCodecMap` below) and the CLI's composition /
@@ -157,7 +157,7 @@ describe("resolveProxy", () => {
expect(cacheDirEntries).toEqual([expectedCachePath.split("/").at(-1)]);
});
it("uses VP9 alpha-safe args and a distinct WebM cache path", async () => {
it("uses Chromium-compatible VP8 alpha args and a distinct WebM cache path", async () => {
const { spawn, calls } = createSpawnSpy();
const { resolveProxy, getProxyCachePath } = await loadModule(spawn, FFMPEG_PATH);
const projectDir = tmpProject();
@@ -165,15 +165,16 @@ describe("resolveProxy", () => {
writeFileSync(sourcePath, "source-bytes");
const h264Path = getProxyCachePath(projectDir, sourcePath, "h264");
const vp9Path = getProxyCachePath(projectDir, sourcePath, "vp9");
const resultPromise = resolveProxy(projectDir, sourcePath, "vp9");
const vp8Path = getProxyCachePath(projectDir, sourcePath, "vp8");
const resultPromise = resolveProxy(projectDir, sourcePath, "vp8");
await flush();
expect(vp9Path).not.toBe(h264Path);
expect(vp9Path).toMatch(/\.webm$/);
expect(vp8Path).not.toBe(h264Path);
expect(vp8Path).toMatch(/\.webm$/);
expect(h264Path).toMatch(/\.mp4$/);
const args = calls[0]!.args;
expect(args).toContain("libvpx-vp9");
expect(args).toContain("libvpx");
expect(args).not.toContain("libvpx-vp9");
expect(args).toContain("yuva420p");
expect(args).toContain("libopus");
expect(args[args.indexOf("-b:v") + 1]).toBe("0");
@@ -182,14 +183,14 @@ describe("resolveProxy", () => {
expect(args[args.indexOf("-auto-alt-ref") + 1]).toBe("0");
expect(args[args.indexOf("-metadata:s:v:0") + 1]).toBe("alpha_mode=1");
expect(args[args.indexOf("-ac") + 1]).toBe("2");
expect(args).toContain("-row-mt");
expect(args).not.toContain("-row-mt");
expect(args).toContain("-cpu-used");
expect(args).not.toContain("-movflags");
expect(args).not.toContain("+faststart");
expect(args[args.indexOf("-vf") + 1]).toContain("format=yuva420p");
succeed(calls[0]!, "fake-vp9-bytes");
await expect(resultPromise).resolves.toBe(vp9Path);
succeed(calls[0]!, "fake-vp8-bytes");
await expect(resultPromise).resolves.toBe(vp8Path);
});
it("returns without spawning on a cache hit", async () => {
@@ -236,21 +237,21 @@ describe("resolveProxy", () => {
await result;
});
it("preserves alpha on HDR-tagged VP9 proxies by bypassing the opaque tonemap chain", async () => {
it("preserves alpha on HDR-tagged VP8 proxies by bypassing the opaque tonemap chain", async () => {
const { spawn, calls } = createSpawnSpy();
const { resolveProxy } = await loadModule(spawn, FFMPEG_PATH, true);
const projectDir = tmpProject();
const sourcePath = join(projectDir, "hdr-alpha.mov");
writeFileSync(sourcePath, "source-bytes");
const result = resolveProxy(projectDir, sourcePath, "vp9");
const result = resolveProxy(projectDir, sourcePath, "vp8");
await flush();
expect(calls).toHaveLength(1);
const filter = calls[0]!.args[calls[0]!.args.indexOf("-vf") + 1];
expect(filter).toContain("format=yuva420p");
expect(filter).not.toContain("tonemap=");
succeed(calls[0]!, "fake-vp9-alpha-bytes");
succeed(calls[0]!, "fake-vp8-alpha-bytes");
await result;
});
@@ -32,7 +32,7 @@ import { PROXY_VARIANT_CONFIG, type ProxyVariant } from "./mediaCodecMap.js";
* entry still lands for the next request.
*/
export const PROXY_PARAMS_VERSION = "v3";
export const PROXY_PARAMS_VERSION = "v4";
const CACHE_DIR_NAME = ".transcode-cache";
@@ -314,13 +314,13 @@ async function runFfmpeg(
if (!ffmpegPath) {
throw new FfmpegUnavailableError();
}
// The HDR tonemap filters discard alpha. VP9 is the alpha-preserving proxy
// The HDR tonemap filters discard alpha. VP8 is the alpha-preserving proxy
// variant, so retain its source color values instead of making it opaque.
if (metadata.color.isHdr && variant !== "vp9") await ensureHdrFilters(ffmpegPath);
if (metadata.color.isHdr && variant !== "vp8") await ensureHdrFilters(ffmpegPath);
const evenScale = "scale=trunc(iw/2)*2:trunc(ih/2)*2";
const pixelFormat = variant === "vp9" ? "yuva420p" : "yuv420p";
const pixelFormat = variant === "vp8" ? "yuva420p" : "yuv420p";
const videoFilter =
metadata.color.isHdr && variant !== "vp9"
metadata.color.isHdr && variant !== "vp8"
? [
"zscale=t=linear:npl=100",
"tonemap=hable:desat=0",
@@ -354,9 +354,9 @@ async function runFfmpeg(
"-movflags",
"+faststart",
];
const vp9Args = [
const vp8Args = [
"-c:v",
"libvpx-vp9",
"libvpx",
"-b:v",
"0",
"-crf",
@@ -371,8 +371,6 @@ async function runFfmpeg(
"bt709",
"-color_trc",
"bt709",
"-row-mt",
"1",
"-cpu-used",
"4",
"-auto-alt-ref",
@@ -384,7 +382,7 @@ async function runFfmpeg(
"-c:a",
"libopus",
];
const args = [...commonArgs, ...(variant === "vp9" ? vp9Args : h264Args), outputPath];
const args = [...commonArgs, ...(variant === "vp8" ? vp8Args : h264Args), outputPath];
// Hard ceiling so a hung ffmpeg can never permanently occupy one of the
// global transcode slots: the child is killed and the slot released via