Initial commit
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import "server-only";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const cache = new Map<string, number>();
|
||||
|
||||
const PROBE_TIMEOUT_MS = 10_000;
|
||||
|
||||
/**
|
||||
* Probe a video file's duration in seconds via ffprobe. Cached per-path
|
||||
* for the lifetime of the process — files don't change duration on us.
|
||||
* Returns null if ffprobe fails or returns garbage.
|
||||
*
|
||||
* Caps the probe at PROBE_TIMEOUT_MS and ties to an optional AbortSignal
|
||||
* so a hung ffprobe (network mount, weird codec, dead disk) can't leave
|
||||
* the request awaiting forever or zombie the subprocess.
|
||||
*/
|
||||
export async function probeDuration(abs: string, signal?: AbortSignal): Promise<number | null> {
|
||||
const cached = cache.get(abs);
|
||||
if (cached !== undefined) return cached;
|
||||
return new Promise((resolve) => {
|
||||
const proc = spawn("ffprobe", [
|
||||
"-v", "error",
|
||||
"-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
abs,
|
||||
]);
|
||||
let out = "";
|
||||
let settled = false;
|
||||
|
||||
const settle = (n: number | null) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
if (signal && abortHandler) signal.removeEventListener("abort", abortHandler);
|
||||
if (n != null && Number.isFinite(n) && n > 0) {
|
||||
cache.set(abs, n);
|
||||
resolve(n);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
|
||||
const kill = () => {
|
||||
try { proc.kill("SIGKILL"); } catch { /* ignore */ }
|
||||
settle(null);
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(kill, PROBE_TIMEOUT_MS);
|
||||
const abortHandler = signal ? () => kill() : null;
|
||||
if (signal && abortHandler) {
|
||||
if (signal.aborted) { kill(); return; }
|
||||
signal.addEventListener("abort", abortHandler, { once: true });
|
||||
}
|
||||
|
||||
proc.stdout?.on("data", (d) => { out += d.toString(); });
|
||||
proc.on("close", () => settle(Number(out.trim())));
|
||||
proc.on("error", () => settle(null));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user