mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 10:14:30 +00:00
* feat(producer): enable webm in distributed mode via concat-copy PR 8.2 of the WebM distributed-rendering plan (v1.5 backlog #1; see DISTRIBUTED-RENDERING-PLAN.md §7.2). Wires libvpx-vp9 webm through the distributed pipeline now that PR 8.1 proved concat-copy works. Architectural decision: Path A (concat-copy) — based on PR 8.1's smoke test result (9/9 tests pass for both yuv420p and yuva420p VP9 streams). The simpler architecture wins; no re-encode in assemble, no encode- parallelism loss. Changes: - plan.ts: - DistributedRenderConfig.format and PlanResult.format now include "webm" — type-level acceptance matches the runtime gate. - rejectUnsupportedDistributedFormat() no longer trips on webm. HDR mp4 remains the only refused configuration. - resolveEncoderTriple() returns libvpx-vp9-software + yuva420p + preset="good" for format="webm". yuva420p preserves alpha — the format's main reason for existing for web delivery. - codec= remains rejected for non-mp4 formats (mov is always ProRes 4444; webm is always libvpx-vp9). The error message lists all four distributed-supported formats. - FormatNotSupportedInDistributedError docstring updated to reflect the new reality (only HDR is unsupported). - freezePlan.ts: LockedRenderConfig.encoder gains "libvpx-vp9-software". Mirrors libx265-software / prores-software / png-sequence in shape; the chunk worker reads this discriminant to decide encode args. - renderChunk.ts: drops the now-incorrect cast that excluded webm from buildSyntheticRenderJob's format input; tightens the preset-format cast to include webm. - assemble.ts: docstring + comment updates. The mp4/mov concat-copy path is format-agnostic — webm uses the exact same code (applyFaststart is a no-op for webm via the existing chunkEncoder.ts gate; muxVideoWithAudio already routes webm to libopus audio). - planFormatBanlist.test.ts: webm-rejection tests removed; replaced with "accepts webm" tests + a HDR+webm combo test that verifies HDR is the trip regardless of format. - plan.test.ts: new describe block pins the webm wiring contract: format="webm" produces an encoder=libvpx-vp9-software / pixelFormat=yuva420p planDir with closedGop=true and gopSize=chunkSize. - webm-concat-copy.test.ts (smoke): extended with a yuva420p variant that proves the alpha pixel format the distributed pipeline actually emits also round-trips through concat-copy. 9/9 tests pass locally. §8 format support matrix in DISTRIBUTED-RENDERING-PLAN.md is intentionally left unchanged at this PR — it flips to ✓ in PR 8.4 once the end-to-end fixture (PR 8.3) is green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(producer): include webm in plan-time needsAlpha + strengthen alpha smoke PR review feedback from Miguel and Vai on #951 caught a real bug: `plan.ts`'s `needsAlpha` disjunction excluded `"webm"`, so the plan stage froze `forceScreenshot: false` into the `LockedRenderConfig` even though distributed webm uses `yuva420p`. Every chunk worker captured opaque RGB via BeginFrame (which doesn't preserve alpha on Linux headless-shell), and libvpx-vp9 encoded uniformly-opaque alpha that the encoder then dropped — producing un-keyable webm. Two changes: 1. **plan.ts**: include `"webm"` in `needsAlpha`. Matches the in-process renderer's logic at `renderOrchestrator.ts:1469` (`const needsAlpha = isWebm || isMov || isPngSequence`); the two sites must stay in sync since the distributed pipeline's PSNR regression compares against the in-process baseline. 2. **Smoke test (yuva420p describe)**: source frames now use a real alpha gradient (`geq=a='X*255/W'` on top of `testsrc2`) instead of `testsrc2 + format=rgba` which was uniformly opaque. The decode- pix_fmt assertion is dropped (ffprobe reports `yuv420p` for VP9-with-alpha because the alpha lives in a Matroska `BlockAdditional` sidecar) and replaced with two stronger checks: - `TAG:ALPHA_MODE=1` is present on the stream — proves the encoder was actually configured for alpha - alpha plane variance after `-c:v libvpx-vp9 -i ... -pix_fmt rgba -vf extractplanes=a,signalstats` — proves the alpha sub-stream round-trips through concat-copy with spatially-varying content, not uniform/dropped alpha - decode-test gate is now exit-code-only (was `exitCode || stderr` which would flake on chatty ffmpeg `-v error` builds emitting non-fatal DTS/container notes) These checks would have caught the `needsAlpha` bug before review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aws-lambda): widen narrow format types to include webm CI on PR #951 was failing at typecheck/build because the producer's `DistributedRenderConfig.format` widened to include webm in this PR but the aws-lambda package's narrow `"mp4" | "mov" | "png-sequence"` type literals in `events.ts`, `handler.ts`, and `validateConfig.ts` hadn't kept up. `renderToLambda.ts:87` passed `config.format` (now including webm) into a parameter typed against the narrow union, producing TS2345. This widening originally landed in PR #952 (test fixture PR) but needs to be atomic with the producer's widening here to keep each PR independently typecheck-clean. Also refactor `formatExtension` from a switch dispatch to a `Record<DistributedFormat, string>` lookup. Adding the webm case tipped the switch's CRAP to the 30.0 fallow threshold; the lookup table drops cyclomatic from 5 to 1 with the same compile-time exhaustiveness guarantee (TS errors on missing entries when `DistributedFormat` adds a new format). The runtime `_exhaustive: never` throw was only protecting against a string slipping past TS; `validateConfig.ts`'s `ALLOWED_FORMATS` already gates untrusted input at the SDK boundary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(producer): add webm-vp9 distributed regression fixture PR 8.3 of the WebM distributed-rendering plan (v1.5 backlog #1; see DISTRIBUTED-RENDERING-PLAN.md §7.2). End-to-end regression coverage for the webm distributed path PRs 8.1 and 8.2 wired up. Adds packages/producer/tests/distributed/webm-vp9/ matching the mp4-h264-sdr fixture pattern: a 2-second composition (60 frames @ 30fps) with text, a crossfade across the frame-30 chunk seam, and a continuous icon rotation — exercises chunk-boundary continuity for both display contents and VP9 closed-GOP alpha encoding. `chunkSize: 15` produces 4 chunks so 3 seams are tested, and the crossfade straddles the middle seam to surface alpha-plane discontinuities introduced by alt-ref drift. Baseline regenerated inside Dockerfile.test via `bun run --cwd packages/producer docker:test:update webm-vp9`. Runs in: - in-process mode: byte-identical match against baseline ✓ - distributed-simulated mode: PSNR 56.88-63.49 dB across 100 checkpoints, well above the 30 dB threshold ✓ Wiring updates required to let webm flow through the harness: - regression-harness-distributed.ts: - checkDistributedSupport() no longer rejects webm. HDR mp4 + NTSC fps + non-{24,30,60} fps remain rejected. - RunDistributedSimulatedInput.format widened to include webm. - Docstring + comments updated. - regression-harness-distributed.test.ts: webm-rejection test replaced with "accepts format=webm" test. - regression-harness.ts: the now-incorrect format cast at the distributed-input call site is dropped; comment about why webm was excluded is replaced with "webm is now distributed-supported". - regression-harness-lambda-local-types.ts: RunLambdaLocalInput.format widened to include webm so lambda-local mode can also exercise webm fixtures end-to-end. - aws-lambda webm support (Path A through the Lambda handler): - formatExtension.ts: DistributedFormat gains "webm" → ".webm" case. - events.ts: RenderChunkEvent / AssembleEvent / PlanLambdaResult Format widened to include webm. - sdk/validateConfig.ts: ALLOWED_FORMATS gains "webm". - handler.ts: downloadChunkObjects format param widened. The Lambda handler delegates to the producer's assemble() primitive which PR 8.2 already taught to handle webm (concat-copy + applyFaststart no-op + muxVideoWithAudio with libopus); no Lambda-side rendering changes are needed beyond the type/validation surfaces above. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(aws-lambda): drop stale webm rejection from validateConfig docblock PR #952 review nit (Miguel): the validateConfig.ts file-header comment still claimed the SDK rejects webm, but the runtime check no longer does (ALLOWED_FORMATS now includes 'webm'). Update the docblock to reflect that only force-hdr remains an SDK-side rejection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(regression): add webm-vp9 to shard-3 + refactor formatExtension Three follow-ups bundled together (Vai's review feedback on PR #952 plus the fallow audit finding that surfaced when the webm case was added): 1. **Wire webm-vp9 into CI regression.** The fixture was added in this PR but never appeared in any `.github/workflows/regression.yml` shard's args allowlist, so the regression harness's positional-args gate skipped it in CI. Append `webm-vp9` to shard-3 (which already carries `mp4-h264-sdr` + `webm-transparency`) so the fixture runs. 2. **Fix stale "four hard gates" prose in checkDistributedSupport docstring.** Earlier in the stack I removed the webm bullet but didn't update the count. Two gates remain (fps + hdr). 3. **Refactor `formatExtension` from switch to lookup table.** Adding the webm case made the switch dispatch's CRAP score hit 30.0 (cyclomatic = 5, plus the function's small body). Replaced with a `Record<DistributedFormat, string>` lookup, which: - drops cyclomatic from 5 → 1, - keeps exhaustiveness enforcement at compile time (TS errors if a new format gets added to `DistributedFormat` without a matching key in the Record literal), - drops the runtime `_exhaustive: never` throw, which was only guarding against an arbitrary string slipping past TS — a caller-side concern, not this function's job. The function now reads as a table lookup, which matches what it actually does, and the fallow audit now reports zero new complexity findings (down from 1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
158 lines
6.4 KiB
TypeScript
158 lines
6.4 KiB
TypeScript
// Pure-function tests for the harness mode dispatch logic. End-to-end
|
|
// PSNR contract lives in `Dockerfile.test` runs of the regression harness.
|
|
|
|
import { describe, expect, it } from "bun:test";
|
|
import {
|
|
checkDistributedSupport,
|
|
DISTRIBUTED_SIMULATED_MIN_PSNR_DB,
|
|
parseHarnessModeFlag,
|
|
resolveMinPsnrForMode,
|
|
} from "./regression-harness-distributed.js";
|
|
|
|
describe("parseHarnessModeFlag()", () => {
|
|
it("parses --mode=in-process", () => {
|
|
expect(parseHarnessModeFlag("--mode=in-process")).toBe("in-process");
|
|
});
|
|
|
|
it("parses --mode=distributed-simulated", () => {
|
|
expect(parseHarnessModeFlag("--mode=distributed-simulated")).toBe("distributed-simulated");
|
|
});
|
|
|
|
it("parses --mode=lambda-local", () => {
|
|
expect(parseHarnessModeFlag("--mode=lambda-local")).toBe("lambda-local");
|
|
});
|
|
|
|
it("returns null for tokens that aren't --mode", () => {
|
|
expect(parseHarnessModeFlag("--update")).toBeNull();
|
|
expect(parseHarnessModeFlag("font-variant-numeric")).toBeNull();
|
|
expect(parseHarnessModeFlag("--exclude-tags")).toBeNull();
|
|
});
|
|
|
|
it("throws on a known prefix with a bad value", () => {
|
|
expect(() => parseHarnessModeFlag("--mode=foo")).toThrow(/--mode must be/);
|
|
expect(() => parseHarnessModeFlag("--mode=")).toThrow(/--mode must be/);
|
|
});
|
|
|
|
it("error message lists all three accepted modes", () => {
|
|
expect(() => parseHarnessModeFlag("--mode=foo")).toThrow(/lambda-local/);
|
|
expect(() => parseHarnessModeFlag("--mode=foo")).toThrow(/distributed-simulated/);
|
|
});
|
|
});
|
|
|
|
describe("checkDistributedSupport()", () => {
|
|
it("accepts mp4 SDR at 24 / 30 / 60 fps", () => {
|
|
for (const fpsNum of [24, 30, 60]) {
|
|
const result = checkDistributedSupport({ fps: { num: fpsNum, den: 1 } });
|
|
expect(result.supported).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("accepts explicit format=mp4", () => {
|
|
const result = checkDistributedSupport({ fps: { num: 30, den: 1 }, format: "mp4" });
|
|
expect(result.supported).toBe(true);
|
|
});
|
|
|
|
it("rejects fps with non-1 denominator (NTSC)", () => {
|
|
const result = checkDistributedSupport({ fps: { num: 30000, den: 1001 } });
|
|
expect(result.supported).toBe(false);
|
|
if (!result.supported) {
|
|
expect(result.reason).toMatch(/non-integer fps/);
|
|
}
|
|
});
|
|
|
|
it("rejects fps outside the {24,30,60} set", () => {
|
|
for (const fpsNum of [12, 25, 48, 50, 120]) {
|
|
const result = checkDistributedSupport({ fps: { num: fpsNum, den: 1 } });
|
|
expect(result.supported).toBe(false);
|
|
if (!result.supported) {
|
|
expect(result.reason).toMatch(/not in \{24, 30, 60\}/);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("accepts format=webm (distributed-supported via closed-GOP concat-copy)", () => {
|
|
const result = checkDistributedSupport({ fps: { num: 30, den: 1 }, format: "webm" });
|
|
expect(result.supported).toBe(true);
|
|
});
|
|
|
|
it("rejects hdr=true", () => {
|
|
const result = checkDistributedSupport({ fps: { num: 30, den: 1 }, hdr: true });
|
|
expect(result.supported).toBe(false);
|
|
if (!result.supported) {
|
|
expect(result.reason).toMatch(/hdr/);
|
|
}
|
|
});
|
|
|
|
it("accepts hdr=false (or unset)", () => {
|
|
expect(checkDistributedSupport({ fps: { num: 30, den: 1 }, hdr: false }).supported).toBe(true);
|
|
expect(checkDistributedSupport({ fps: { num: 30, den: 1 } }).supported).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("resolveMinPsnrForMode()", () => {
|
|
it("in-process mode uses the fixture's own threshold verbatim", () => {
|
|
expect(resolveMinPsnrForMode("in-process", 30)).toBe(30);
|
|
expect(resolveMinPsnrForMode("in-process", 50)).toBe(50);
|
|
expect(resolveMinPsnrForMode("in-process", 60)).toBe(60);
|
|
});
|
|
|
|
it("distributed-simulated uses the fixture's own minPsnr when above the absolute floor", () => {
|
|
// Fixtures with minPsnr >= the absolute floor (catastrophic-failure
|
|
// guard) use their authored threshold unchanged. Distributed must pass
|
|
// the same quality bar the in-process renderer passes against the same
|
|
// baseline — no extra tightening, since baseline drift is shared across
|
|
// modes.
|
|
expect(resolveMinPsnrForMode("distributed-simulated", 30)).toBe(30);
|
|
expect(resolveMinPsnrForMode("distributed-simulated", 50)).toBe(50);
|
|
expect(resolveMinPsnrForMode("distributed-simulated", 80)).toBe(80);
|
|
});
|
|
|
|
it("distributed-simulated raises pathologically-low thresholds to the absolute floor", () => {
|
|
// A fixture authored with minPsnr=0 (or very low) wouldn't catch a
|
|
// distributed-mode renderer producing fully-black output. The absolute
|
|
// floor exists to catch that pathology.
|
|
expect(resolveMinPsnrForMode("distributed-simulated", 0)).toBe(
|
|
DISTRIBUTED_SIMULATED_MIN_PSNR_DB,
|
|
);
|
|
expect(resolveMinPsnrForMode("distributed-simulated", 5)).toBe(
|
|
DISTRIBUTED_SIMULATED_MIN_PSNR_DB,
|
|
);
|
|
});
|
|
|
|
it("lambda-local mirrors distributed-simulated's pathology floor", () => {
|
|
// Both non-in-process modes run through the same producer primitives,
|
|
// so they share the same pathology threshold.
|
|
expect(resolveMinPsnrForMode("lambda-local", 30)).toBe(30);
|
|
expect(resolveMinPsnrForMode("lambda-local", 0)).toBe(DISTRIBUTED_SIMULATED_MIN_PSNR_DB);
|
|
});
|
|
|
|
it("every committed fixture authors a minPsnr above the absolute floor", async () => {
|
|
// The pathology floor only fires for a fixture whose authored minPsnr
|
|
// is below it — by design that should be no committed fixture. If
|
|
// someone lands a permissive fixture (minPsnr: 5), distributed mode
|
|
// will silently use 10 dB instead, which is the right behavior but
|
|
// worth flagging so reviewers ask "is this fixture really meant to
|
|
// accept near-black output?". This test prevents accidental misuse
|
|
// by failing loudly when a fixture drops below the floor.
|
|
const { readdirSync, readFileSync, statSync } = await import("node:fs");
|
|
const { join: pathJoin } = await import("node:path");
|
|
const testsDir = pathJoin(import.meta.dir, "..", "tests");
|
|
const offenders: Array<{ fixture: string; minPsnr: number }> = [];
|
|
for (const entry of readdirSync(testsDir)) {
|
|
const metaPath = pathJoin(testsDir, entry, "meta.json");
|
|
let stat;
|
|
try {
|
|
stat = statSync(metaPath);
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (!stat.isFile()) continue;
|
|
const meta = JSON.parse(readFileSync(metaPath, "utf-8")) as { minPsnr?: unknown };
|
|
if (typeof meta.minPsnr === "number" && meta.minPsnr < DISTRIBUTED_SIMULATED_MIN_PSNR_DB) {
|
|
offenders.push({ fixture: entry, minPsnr: meta.minPsnr });
|
|
}
|
|
}
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
});
|