refactor(media): trim unused ffmpeg wrapper options

Binary paths, the ffmpeg version, the probe's container name and a
configurable stall timeout had no caller; detection is cached per process,
so every helper looks the binaries up itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
This commit is contained in:
Claude 2026-10-03 13:51:44 +00:00
parent 44702e3770
commit f14d91d24a
No known key found for this signature in database
3 changed files with 6 additions and 15 deletions

View File

@ -60,7 +60,6 @@ describe("probe parsing", () => {
);
expect(probe).toEqual({
durationMs: 12_345,
formatName: "mov,mp4,m4a,3gp,3g2,mj2",
video: {
codec: "h264",
width: 1080,

View File

@ -13,12 +13,10 @@ import { delimiter, join } from "node:path";
export interface FfmpegInfo {
ffmpegPath: string;
ffprobePath: string;
version: string;
}
export interface MediaProbe {
durationMs: number;
formatName: string;
/** Width and height as displayed: swapped when the stream is rotated by 90 or 270 degrees. */
video?: {
codec: string;
@ -50,8 +48,6 @@ export interface RunFfmpegOptions {
maxOutputBytes?: number;
/** Files removed when the run fails or is aborted. */
outputs?: readonly string[];
/** Kills ffmpeg when it reports no progress for this long. */
stallTimeoutMs?: number;
}
export class FfmpegError extends Error {
@ -65,7 +61,8 @@ export class FfmpegError extends Error {
}
}
const DEFAULT_STALL_TIMEOUT_MS = 2 * 60_000;
/** ffmpeg reports progress twice a second while it works; silence this long means a hang. */
const STALL_TIMEOUT_MS = 2 * 60_000;
const PROBE_TIMEOUT_MS = 60_000;
const STDERR_TAIL_BYTES = 8 * 1024;
const KILL_GRACE_MS = 5_000;
@ -101,9 +98,7 @@ async function locateFfmpeg(): Promise<FfmpegInfo | null> {
capture(ffmpegPath, ["-hide_banner", "-version"], { timeoutMs: 10_000 }),
capture(ffprobePath, ["-hide_banner", "-version"], { timeoutMs: 10_000 }),
]);
if (ffmpeg.code !== 0 || ffprobe.code !== 0) return null;
const version = /version (\S+)/.exec(ffmpeg.stdout)?.[1] ?? "unknown";
return { ffmpegPath, ffprobePath, version };
return ffmpeg.code === 0 && ffprobe.code === 0 ? { ffmpegPath, ffprobePath } : null;
} catch {
return null;
}
@ -129,7 +124,7 @@ async function findExecutable(name: string): Promise<string | null> {
return null;
}
export async function requireFfmpeg(): Promise<FfmpegInfo> {
async function requireFfmpeg(): Promise<FfmpegInfo> {
const info = await detectFfmpeg();
if (!info) throw new Error("ffmpeg and ffprobe are not installed on this server.");
return info;
@ -223,7 +218,7 @@ interface ProbeStream {
/** Parses `ffprobe -print_format json -show_format -show_streams` output. */
export function parseProbeOutput(json: string): MediaProbe {
const data = JSON.parse(json) as {
format?: { duration?: string; format_name?: string };
format?: { duration?: string };
streams?: ProbeStream[];
};
const streams = data.streams ?? [];
@ -237,7 +232,6 @@ export function parseProbeOutput(json: string): MediaProbe {
Math.max(0, ...streams.map((stream) => positive(Number(stream.duration)) ?? 0));
return {
durationMs: Math.round(seconds * 1000),
formatName: data.format?.format_name ?? "",
...(video?.width && video.height
? { video: probeVideo(video, video.width, video.height) }
: {}),
@ -385,12 +379,11 @@ function spawnFfmpeg(
setTimeout(() => finish(error), KILL_GRACE_MS).unref?.();
};
const abort = () => kill(signal?.reason ?? new Error("aborted"));
const stallTimeoutMs = options.stallTimeoutMs ?? DEFAULT_STALL_TIMEOUT_MS;
const armStallTimer = () => {
clearTimeout(stallTimer);
stallTimer = setTimeout(
() => kill(new FfmpegError("ffmpeg stopped making progress.", null, tail(stderr))),
stallTimeoutMs,
STALL_TIMEOUT_MS,
);
stallTimer.unref?.();
};

View File

@ -13,7 +13,6 @@ function probe(
const { durationMs = 10_000, hasAudio = true, ...video } = overrides;
return {
durationMs,
formatName: "mov,mp4,m4a,3gp,3g2,mj2",
video: {
codec: "h264",
width: 1920,