89 lines
2.7 KiB
TypeScript
89 lines
2.7 KiB
TypeScript
import { spawn } from "node:child_process";
|
||
import { mkdir, mkdtemp, rm, stat } from "node:fs/promises";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
import ffmpegInstaller from "@ffmpeg-installer/ffmpeg";
|
||
|
||
/** Path of the on-the-fly fixture video. Cached per Vitest worker. */
|
||
let fixturePath: string | undefined;
|
||
let fixtureDir: string | undefined;
|
||
|
||
/**
|
||
* Synthesise a 5-second 320×240 colour-gradient MP4 once per worker and return
|
||
* its absolute path. Subsequent calls return the cached path. Uses ffmpeg's
|
||
* `lavfi` virtual source so no checked-in binary is required.
|
||
*/
|
||
export async function getFixtureVideo(): Promise<string> {
|
||
if (fixturePath !== undefined) {
|
||
return fixturePath;
|
||
}
|
||
const dir = await mkdtemp(join(tmpdir(), "vt-fixture-"));
|
||
const out = join(dir, "fixture.mp4");
|
||
await new Promise<void>((resolve, reject) => {
|
||
const child = spawn(
|
||
ffmpegInstaller.path,
|
||
[
|
||
"-y",
|
||
"-f",
|
||
"lavfi",
|
||
"-i",
|
||
"testsrc=duration=5:size=320x240:rate=10",
|
||
"-pix_fmt",
|
||
"yuv420p",
|
||
"-c:v",
|
||
"libx264",
|
||
"-preset",
|
||
"ultrafast",
|
||
"-tune",
|
||
"zerolatency",
|
||
out,
|
||
],
|
||
{ stdio: "pipe" }
|
||
);
|
||
let stderr = "";
|
||
child.stderr.on("data", (chunk) => {
|
||
stderr += String(chunk);
|
||
});
|
||
child.once("error", reject);
|
||
child.once("exit", (code) => {
|
||
if (code === 0) {
|
||
resolve();
|
||
} else {
|
||
reject(new Error(`ffmpeg fixture build failed: ${stderr}`));
|
||
}
|
||
});
|
||
});
|
||
const fileStat = await stat(out);
|
||
if (fileStat.size === 0) {
|
||
throw new Error("fixture video has zero bytes");
|
||
}
|
||
fixtureDir = dir;
|
||
fixturePath = out;
|
||
return out;
|
||
}
|
||
|
||
/** Create a unique tmp output directory for one test. */
|
||
export async function makeOutputDir(label: string): Promise<string> {
|
||
const dir = await mkdtemp(join(tmpdir(), `vt-${label}-`));
|
||
await mkdir(dir, { recursive: true });
|
||
return dir;
|
||
}
|
||
|
||
/** Best-effort cleanup; safe to ignore failures (Windows / locked files). */
|
||
export async function safeRm(path: string): Promise<void> {
|
||
try {
|
||
await rm(path, { recursive: true, force: true });
|
||
} catch {
|
||
// ignored
|
||
}
|
||
}
|
||
|
||
/** Clear the cached fixture (used by setup teardown). */
|
||
export async function clearFixtureCache(): Promise<void> {
|
||
if (fixtureDir !== undefined) {
|
||
await safeRm(fixtureDir);
|
||
}
|
||
fixturePath = undefined;
|
||
fixtureDir = undefined;
|
||
}
|