scrape / lib /ffmpeg.ts
kjbytes's picture
push code
ef4c36f
Raw
History Blame Contribute Delete
2.5 kB
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<VideoMeta> {
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<void> {
await execFileAsync(getFfmpegPath(), args);
}
export async function extractFrames(
inputVideo: string,
outputPattern: string,
fps: number,
width: number,
): Promise<void> {
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<void> {
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);
}