import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, utimesSync, writeFileSync, } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { spawnSync } from "node:child_process"; import { parseVideoElements, parseImageElements, extractAllVideoFrames, extractVideoFramesRange, createFrameLookupTable, resolveProjectRelativeSrc, resolveFrameFormat, codecMayHaveAlpha, decoderForCodec, getFrameAtTime, analyzeClipMediaFit, type VideoElement, type ExtractedFrames, type ExtractionResult, } from "./videoFrameExtractor.js"; import { extractVideoMetadata, type VideoMetadata } from "../utils/ffprobe.js"; import { runFfmpeg } from "../utils/runFfmpeg.js"; import { COMPLETE_SENTINEL, GC_MARKER, SCHEMA_PREFIX } from "./extractionCache.js"; // ffmpeg is not preinstalled on GitHub's ubuntu-24.04 runners. The producer // regression test at packages/producer/tests/vfr-screen-recording/ runs inside // Dockerfile.test (which does include ffmpeg) and is the primary CI signal // for this bug. Locally and in any CI job with ffmpeg on PATH, the tests // below run too — they exercise the extractor in isolation against a // synthesized VFR fixture. const HAS_FFMPEG = spawnSync("ffmpeg", ["-version"]).status === 0; // Codec-based alpha defaulting replaces tag-based detection (the // alpha_mode/ALPHA_MODE case bug — see ffprobe.test.ts for the regression // pin on that). The extractor uses these helpers for two decisions: // 1. whether to force the alpha-aware decoder (libvpx-vp9 for VP9, libvpx // for VP8) // 2. whether to default the cached frame format to PNG (with alpha) vs JPG // The "default to capable" trade is small file-size growth on opaque VP9 // content for correctness on alpha-having content even when the sidecar tag // is missing or muxed with the wrong case. describe("codec alpha capability", () => { it("flags VP9, VP8, and ProRes as alpha-capable", () => { expect(codecMayHaveAlpha("vp9")).toBe(true); expect(codecMayHaveAlpha("VP9")).toBe(true); expect(codecMayHaveAlpha("vp8")).toBe(true); expect(codecMayHaveAlpha("prores")).toBe(true); }); it("does not flag h264 / h265 / mpeg4 (no alpha in their bitstreams)", () => { expect(codecMayHaveAlpha("h264")).toBe(false); expect(codecMayHaveAlpha("h265")).toBe(false); expect(codecMayHaveAlpha("hevc")).toBe(false); expect(codecMayHaveAlpha("mpeg4")).toBe(false); }); it("treats undefined / empty input as non-alpha", () => { expect(codecMayHaveAlpha(undefined)).toBe(false); expect(codecMayHaveAlpha("")).toBe(false); }); it("returns the alpha-aware decoder name for VP9 and VP8", () => { expect(decoderForCodec("vp9")).toBe("libvpx-vp9"); expect(decoderForCodec("VP9")).toBe("libvpx-vp9"); expect(decoderForCodec("vp8")).toBe("libvpx"); }); }); describe("resolveFrameFormat", () => { function metadata(overrides: Partial = {}): VideoMetadata { return { durationSeconds: 1, width: 320, height: 180, fps: 30, hasAudio: false, videoCodec: "h264", colorSpace: { colorTransfer: "bt709", colorPrimaries: "bt709", colorSpace: "bt709", }, isVFR: false, hasAlpha: false, ...overrides, }; } it("keeps opaque non-alpha sources on jpg by default", () => { expect(resolveFrameFormat(metadata(), undefined)).toBe("jpg"); expect(resolveFrameFormat(metadata(), "auto")).toBe("jpg"); }); it("honors explicit png for opaque videos", () => { expect(resolveFrameFormat(metadata(), "png")).toBe("png"); }); it("honors explicit jpg for opaque videos", () => { expect(resolveFrameFormat(metadata(), "jpg")).toBe("jpg"); }); it("forces png when alpha is present or the codec can carry alpha", () => { expect(resolveFrameFormat(metadata({ hasAlpha: true }), "jpg")).toBe("png"); expect(resolveFrameFormat(metadata({ videoCodec: "vp9" }), "jpg")).toBe("png"); }); }); // Regression: a long-standing footgun where `