UNPKG

@calipsa/video-utils

Version:
96 lines (78 loc) 2.47 kB
import ffmpeg, { FfprobeData, FfmpegCommandOptions, FfmpegCommand, } from 'fluent-ffmpeg' import { StreamWithFrames, } from '@calipsa/stream-utils' import getFrameAt from '../getFrameAt' import getFrameIndices from './getFrameIndices' interface Options { path: string, numFrames: number, videoMetadata: FfprobeData, ffmpegCommandOptions?: FfmpegCommandOptions, configureFfmpeg?: (ffmpegCommand: FfmpegCommand) => void, } export default async ({ path, numFrames, videoMetadata, ffmpegCommandOptions, configureFfmpeg, }: Options) => { const videoStreamMetaData = videoMetadata.streams.find(item => item.codec_type === 'video') if (!videoStreamMetaData) { throw new Error('No video streams found') } const { nb_frames: numInputFramesStr, start_time: startTime = 0, duration: durationStr, } = videoStreamMetaData const duration = durationStr === undefined ? undefined : +durationStr if (!duration || !Number.isFinite(duration)) { throw new TypeError(`ffprobe returned invalid duration: ${durationStr}`) } if (numFrames === 1) { const frame = await getFrameAt({ path, time: startTime + duration / 2, ffmpegCommandOptions, configureFfmpeg, }) return [frame] } const numInputFrames = numInputFramesStr === undefined ? undefined : +numInputFramesStr if (!numInputFrames || !Number.isFinite(numInputFrames)) { throw new TypeError(`ffprobe returned invalid nb_frames: ${numInputFramesStr}`) } const frameIndices = getFrameIndices(numInputFrames, numFrames) const filterSelect = frameIndices.map(i => `eq(n\\,${i})`).join('+') const outStream = new StreamWithFrames() const vcodec = 'mjpeg' return new Promise<Buffer[]>((resolve, reject) => { const proc = ffmpeg(path, ffmpegCommandOptions) .output(outStream, { end: true, }) .noAudio() // .setStartTime(startTime) .format('image2pipe') // -f .videoCodec(vcodec) // -vcodec .size('640x480') // .fpsOutput(numFrames / duration) // .videoFilter(`fps=${numFrames / duration}`) // .outputOption('-filter:v', `fps=${numFrames / duration}`) .videoFilter(`select='${filterSelect}'`) .outputOption('-vsync', 'vfr') // make the above work .outputOption('-q:v', '2') .on('error', reject) .on('end', () => { resolve(outStream.buffers) }) configureFfmpeg?.(proc) proc.run() }) }