import { execFile } from "child_process"; import { promisify } from "util"; import ffmpegStatic from "ffmpeg-static"; import ffprobeStatic from "ffprobe-static"; import type { VideoMeta } from "@/types"; const execFileAsync = promisify(execFile); export function getFfmpegPath(): string { if (!ffmpegStatic) throw new Error("ffmpeg-static not available"); return ffmpegStatic; } export function getFfprobePath(): string { return ffprobeStatic.path; } function parseFps(rate: string): number { if (!rate || rate === "0/0") return 30; const [num, den] = rate.split("/").map(Number); if (!den) return 30; return Math.round((num / den) * 100) / 100; } export async function extractVideoMeta(videoFile: string): Promise { const ffprobe = getFfprobePath(); const { stdout } = await execFileAsync(ffprobe, [ "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,r_frame_rate,duration", "-show_entries", "format=duration", "-of", "json", videoFile, ]); const data = JSON.parse(stdout); const stream = data.streams?.[0] ?? {}; const formatDuration = Number(data.format?.duration ?? 0); const streamDuration = Number(stream.duration ?? 0); const duration = streamDuration || formatDuration || 0; return { width: Number(stream.width ?? 1920), height: Number(stream.height ?? 1080), duration, fps: parseFps(stream.r_frame_rate ?? "30/1"), }; } export async function runFfmpeg(args: string[]): Promise { await execFileAsync(getFfmpegPath(), args); } export async function extractFrames( inputVideo: string, outputPattern: string, fps: number, width: number, ): Promise { await runFfmpeg([ "-i", inputVideo, "-vf", `fps=${fps},scale=${width}:-1:flags=lanczos`, "-q:v", "2", "-y", outputPattern, ]); } export async function encodeVideo( inputVideo: string, framePattern: string, outputVideo: string, fps: number, audioFile?: string, ): Promise { const args = [ "-framerate", String(fps), "-i", framePattern, ]; if (audioFile) { args.push("-i", audioFile, "-map", "0:v", "-map", "1:a"); } else { args.push("-i", inputVideo, "-map", "0:v", "-map", "1:a?"); } args.push( "-c:v", "libx264", "-preset", "fast", "-crf", "23", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", "-shortest", "-y", outputVideo, ); await runFfmpeg(args); }