File size: 2,503 Bytes
ef4c36f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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);
}