| import { execFile, type ExecFileOptions } from "node:child_process"; |
| import { promisify } from "node:util"; |
| import { |
| MEDIA_FFMPEG_MAX_BUFFER_BYTES, |
| MEDIA_FFMPEG_TIMEOUT_MS, |
| MEDIA_FFPROBE_TIMEOUT_MS, |
| } from "./ffmpeg-limits.js"; |
|
|
| const execFileAsync = promisify(execFile); |
|
|
| export type MediaExecOptions = { |
| timeoutMs?: number; |
| maxBufferBytes?: number; |
| }; |
|
|
| function resolveExecOptions( |
| defaultTimeoutMs: number, |
| options: MediaExecOptions | undefined, |
| ): ExecFileOptions { |
| return { |
| timeout: options?.timeoutMs ?? defaultTimeoutMs, |
| maxBuffer: options?.maxBufferBytes ?? MEDIA_FFMPEG_MAX_BUFFER_BYTES, |
| }; |
| } |
|
|
| export async function runFfprobe(args: string[], options?: MediaExecOptions): Promise<string> { |
| const { stdout } = await execFileAsync( |
| "ffprobe", |
| args, |
| resolveExecOptions(MEDIA_FFPROBE_TIMEOUT_MS, options), |
| ); |
| return stdout.toString(); |
| } |
|
|
| export async function runFfmpeg(args: string[], options?: MediaExecOptions): Promise<string> { |
| const { stdout } = await execFileAsync( |
| "ffmpeg", |
| args, |
| resolveExecOptions(MEDIA_FFMPEG_TIMEOUT_MS, options), |
| ); |
| return stdout.toString(); |
| } |
|
|
| export function parseFfprobeCsvFields(stdout: string, maxFields: number): string[] { |
| return stdout |
| .trim() |
| .toLowerCase() |
| .split(/[,\r\n]+/, maxFields) |
| .map((field) => field.trim()); |
| } |
|
|
| export function parseFfprobeCodecAndSampleRate(stdout: string): { |
| codec: string | null; |
| sampleRateHz: number | null; |
| } { |
| const [codecRaw, sampleRateRaw] = parseFfprobeCsvFields(stdout, 2); |
| const codec = codecRaw ? codecRaw : null; |
| const sampleRate = sampleRateRaw ? Number.parseInt(sampleRateRaw, 10) : Number.NaN; |
| return { |
| codec, |
| sampleRateHz: Number.isFinite(sampleRate) ? sampleRate : null, |
| }; |
| } |
|
|