Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions packages/producer/scripts/validate-perf-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Validation driver for the 5-PR perf stack.
*
* Runs a producer test fixture through `executeRenderJob` and prints the
* resulting `RenderPerfSummary` as JSON. Designed to be called multiple
* times with different env flags so we can compare HWaccel on/off, cache
* miss vs hit, etc., without bisecting git history.
*
* Usage:
* tsx scripts/validate-perf-stack.ts <fixture-name> [--label <tag>]
*
* Honors: HYPERFRAMES_EXTRACT_CACHE_DIR, PRODUCER_HWACCEL_SDR_DECODE,
* PRODUCER_HWACCEL_MIN_DURATION_SECONDS — same env surface as a real
* producer render.
*/

import { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createRenderJob, executeRenderJob } from "../src/services/renderOrchestrator.js";

const __dirname = fileURLToPath(new URL(".", import.meta.url));

async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("usage: validate-perf-stack.ts <fixture-name> [--label <tag>]");
process.exit(1);
}
const fixtureName = args[0]!;
const labelIdx = args.indexOf("--label");
const label = labelIdx >= 0 ? args[labelIdx + 1] : undefined;

const fixtureRoot = resolve(__dirname, "..", "tests", fixtureName);
const srcDir = join(fixtureRoot, "src");
if (!existsSync(srcDir)) {
console.error(`Fixture not found: ${srcDir}`);
process.exit(1);
}

const metaPath = join(fixtureRoot, "meta.json");
const meta = existsSync(metaPath)
? JSON.parse(await Bun.file(metaPath).text())
: { renderConfig: { fps: 30 } };

const tempRoot = mkdtempSync(join(tmpdir(), "hf-perf-validate-"));
const tempSrc = join(tempRoot, "src");
cpSync(srcDir, tempSrc, { recursive: true });

const outDir = join(tempRoot, "out");
mkdirSync(outDir, { recursive: true });
const outputFormat = meta.renderConfig?.format ?? "mp4";
const outputPath = join(outDir, `output.${outputFormat}`);

const job = createRenderJob({
fps: meta.renderConfig?.fps ?? 30,
quality: "high",
format: outputFormat,
workers: meta.renderConfig?.workers ?? 1,
useGpu: false,
debug: false,
hdr: meta.renderConfig?.hdr ?? false,
});

const started = Date.now();
try {
await executeRenderJob(job, tempSrc, outputPath);
} catch (err) {
console.error(
JSON.stringify({
event: "render_failed",
fixture: fixtureName,
label,
error: err instanceof Error ? err.message : String(err),
elapsedMs: Date.now() - started,
}),
);
rmSync(tempRoot, { recursive: true, force: true });
process.exit(2);
}

const summary = job.perfSummary;
console.log(
JSON.stringify(
{
event: "render_complete",
fixture: fixtureName,
label,
elapsedMs: Date.now() - started,
envToggles: {
HYPERFRAMES_EXTRACT_CACHE_DIR: process.env.HYPERFRAMES_EXTRACT_CACHE_DIR ?? null,
PRODUCER_HWACCEL_SDR_DECODE: process.env.PRODUCER_HWACCEL_SDR_DECODE ?? null,
PRODUCER_HWACCEL_MIN_DURATION_SECONDS:
process.env.PRODUCER_HWACCEL_MIN_DURATION_SECONDS ?? null,
},
perfSummary: summary,
},
null,
2,
),
);

rmSync(tempRoot, { recursive: true, force: true });
}

main().catch((err) => {
console.error(err);
process.exit(3);
});
21 changes: 10 additions & 11 deletions packages/producer/src/services/renderOrchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1111,17 +1111,16 @@ export async function executeRenderJob(
extractionResult = await extractAllVideoFrames(
composition.videos,
projectDir,
{
fps: job.config.fps,
outputDir: join(workDir, "video-frames"),
// WebP gives smaller files than JPEG at equivalent visual quality
// AND handles alpha natively — used by the producer as the single
// extraction format so we don't have a jpg/png split. HDR pixel
// paths still produce PNG on their own out-of-band codepath (see
// the HDR pre-extract block below); this setting only affects
// SDR frames that flow through the `<img>` injector.
format: "webp",
},
// Default format is "jpg" (set by extractAllVideoFrames). WebP is
// available as an opt-in via `ExtractionOptions.format: "webp"` and
// handles alpha natively, but on typical content libwebp encode is
// 16-20× slower than libjpeg-turbo — a hot-path regression that the
// architecture review's validation plan caught against these
// fixtures. WebP stays in the extractor/injector/cache for callers
// who want it (e.g. future alpha-input paths), but the producer's
// default stays JPEG until an AVIF/hardware-WebP encoder erases
// the cost gap.
{ fps: job.config.fps, outputDir: join(workDir, "video-frames") },
abortSignal,
// Forward extractCacheDir (when configured) so repeat renders of
// the same source+window+fps+format pair skip Phase 3 entirely.
Expand Down
Loading