46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
}
|