import { expect, it } from "vitest"; import { mkdtemp, readFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import * as media from "../src/image-video/media.js"; import { mediaRuntime, runMedia } from "../src/media/media-worker.js"; it("trims H3's small frame overrun to the requested duration at original speed, preserving the source and rejecting short or excessive output", async () => { const directory = await mkdtemp(join(tmpdir(), "i2v-duration-")); const { ffmpeg, ffprobe } = await mediaRuntime(); const signal = new AbortController().signal; const make = async (name: string, seconds: string) => { const path = join(directory, name); await runMedia(ffmpeg, ["-v", "error", "-f", "lavfi", "-i", "testsrc2=size=90x160:rate=24", "-f", "lavfi", "-i", "sine=frequency=440:sample_rate=32000", "-t", seconds, "-c:v", "libx264", "-preset", "ultrafast", "-c:a", "aac", "-n", path], directory, signal); return path; }; const source = await make("original.mp4", "1.125"); const original = await readFile(source); const output = await media.conformImageVideoDuration(source, directory, 1); const probe = JSON.parse(await runMedia(ffprobe, ["-v", "error", "-show_streams", "-of", "json", output], directory, signal)) as { streams: { codec_type: string; duration: string; nb_frames: string; avg_frame_rate: string }[] }; const video = probe.streams.find(s => s.codec_type === "video")!; expect(Number(video.duration)).toBe(1); expect(video.nb_frames).toBe("24"); expect(video.avg_frame_rate).toBe("24/1"); expect(Number(probe.streams.find(s => s.codec_type === "audio")!.duration)).toBeCloseTo(1, 2); expect(await readFile(source)).toEqual(original); // Actual H3 2-second response is 56 frames (17k+5), not 48 frames. const gridSource = await make("h3-2s.mp4", "2.333333"); const gridOriginal = await readFile(gridSource); const gridOutput = await media.conformImageVideoDuration(gridSource, directory, 2); const gridProbe = JSON.parse(await runMedia(ffprobe, ["-v", "error", "-select_streams", "v:0", "-show_entries", "stream=duration,nb_frames,avg_frame_rate", "-of", "json", gridOutput], directory, signal)) as { streams: { duration: string; nb_frames: string; avg_frame_rate: string }[] }; expect(gridProbe.streams[0]).toMatchObject({ duration: "2.000000", nb_frames: "48", avg_frame_rate: "24/1" }); expect(await readFile(gridSource)).toEqual(gridOriginal); await expect(media.conformImageVideoDuration(output, directory, 1)).resolves.toBe(output); await expect(media.conformImageVideoDuration(await make("short.mp4", "0.875"), directory, 1)).rejects.toBeDefined(); await expect(media.conformImageVideoDuration(await make("long.mp4", "1.5"), directory, 1)).rejects.toBeDefined(); }, 30_000);