This repository has no description
1

Configure Feed

Select the types of activity you want to include in your feed.

at main 1.5 kB View raw
1// Frame extraction via ffmpeg. Operates on a local video FILE — it decodes frames, 2// it does not "play" anything. (mp4/mov in → png out.) 3 4import { execFile } from "node:child_process"; 5import { mkdir } from "node:fs/promises"; 6import { resolve } from "node:path"; 7import { promisify } from "node:util"; 8 9const exec = promisify(execFile); 10 11/** "1:23" | "0:09" | "83" -> seconds. */ 12export function toSeconds(at: string): number { 13 const parts = at.split(":").map(Number); 14 if (parts.some((n) => Number.isNaN(n))) return 0; 15 return parts.reduce((acc, n) => acc * 60 + n, 0); 16} 17 18function hhmmss(sec: number): string { 19 const h = Math.floor(sec / 3600); 20 const m = Math.floor((sec % 3600) / 60); 21 const s = Math.floor(sec % 60); 22 return [h, m, s].map((n) => String(n).padStart(2, "0")).join(":"); 23} 24 25/** Cut a single frame at `at` from `videoPath` into outDir; returns the png path. */ 26export async function extractFrameAt(videoPath: string, at: string, outDir: string): Promise<string> { 27 await mkdir(outDir, { recursive: true }); 28 const sec = toSeconds(at); 29 const out = resolve(outDir, `frame_${String(sec).padStart(4, "0")}s.png`); 30 // -ss before -i = fast, frame-accurate seek; one frame; scale down for a ≤1MB blob. 31 await exec("ffmpeg", [ 32 "-y", 33 "-ss", 34 hhmmss(sec), 35 "-i", 36 videoPath, 37 "-frames:v", 38 "1", 39 "-vf", 40 "scale='min(1600,iw)':-2", 41 out, 42 "-loglevel", 43 "error", 44 ]); 45 return out; 46}