Files
podcastdistributiona/lib/ai/ffmpeg.ts
T

46 lines
1.4 KiB
TypeScript
Raw Normal View History

import { spawn } from "node:child_process";
const FFMPEG = process.env.FFMPEG_PATH ?? "ffmpeg";
const FFPROBE = process.env.FFPROBE_PATH ?? "ffprobe";
/** Run ffmpeg with the given args; rejects with the tail of stderr on non-zero exit. */
export function runFfmpeg(args: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const proc = spawn(FFMPEG, args, { stdio: ["ignore", "ignore", "pipe"] });
let stderr = "";
proc.stderr.on("data", (d) => {
stderr += d.toString();
});
proc.on("error", (err) =>
reject(new Error(`Failed to spawn ffmpeg (${FFMPEG}): ${err.message}`))
);
proc.on("close", (code) =>
code === 0 ? resolve() : reject(new Error(`ffmpeg exited ${code}: ${stderr.slice(-600)}`))
);
});
}
/** Probe an audio file's duration in whole seconds, or null if ffprobe is unavailable. */
export function ffprobeDuration(file: string): Promise<number | null> {
return new Promise((resolve) => {
const proc = spawn(FFPROBE, [
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1",
file,
]);
let out = "";
proc.stdout.on("data", (d) => {
out += d.toString();
});
proc.on("error", () => resolve(null));
proc.on("close", () => {
const n = parseFloat(out.trim());
resolve(Number.isFinite(n) ? Math.round(n) : null);
});
});
}