import ffmpeg from 'fluent-ffmpeg'
interface FfmpegMetadata {
fps: number
timeBase: string
bitRate: number
duration: number
frames: number
}
const getMetadata = (videoPath: string) => new Promise<FfmpegMetadata>((resolve, reject) => {
ffmpeg.ffprobe(videoPath, (err, metadata) => {
if (err) {
reject(err)
} else {
console.log('metadata', metadata)
const frameRate = metadata.streams[0].avg_frame_rate
const [numerator, denominator] = frameRate.split('/')
const fps = parseInt(numerator, 10) / parseInt(denominator, 10)
resolve({
fps,
bitRate: parseInt(metadata.streams[0].bit_rate, 10),
timeBase: metadata.streams[0].time_base,
duration: metadata.format.duration,
frames: parseInt(metadata.streams[0].nb_frames, 10)
})
}
})
})
const createVideoFromFrames = (framesDirectory: string, outputVideoPath: string, format: string, metadata: FfmpegMetadata) => new Promise<void>((resolve, reject) => {
ffmpeg().input(`${framesDirectory}/%d.png`)
.inputFPS(metadata.fps)
.output(outputVideoPath)
.outputFPS(metadata.fps)
.outputFormat(format)
.withVideoBitrate(metadata.bitRate)
.withDuration(metadata.duration)
.outputOptions(`-time_base ${metadata.timeBase}`)
.on('error', (error) => {
console.error('[ffmpeg@createVideoFromFrames@error]', error)
reject(error)
})
.on('end', () => {
resolve()
})
.run()
})
const splitFramesFromVideo = (videoPath: string, outputDirectory: string, metadata: FfmpegMetadata) => new Promise<void>((resolve, reject) => {
ffmpeg(videoPath)
// Forces ffmpeg to output an image for each frame
.toFormat('image2')
.frames(metadata.frames)
.on('error', (error) => {
console.error('[ffmpeg@splitFramesFromVideo@error]', error)
reject(error)
})
.on('end', () => {
resolve()
})
.on('stderr', (stderrLine) => {
console.log(`[ffmpeg@splitFramesFromVideo@stderr] ${stderrLine}`)
})
.on('start', (commandLine) => {
console.log(`[ffmpeg@splitFramesFromVideo@start] ${commandLine}`)
})
.on('codecData', (codecData) => {
console.log(`[ffmpeg@splitFramesFromVideo@codecData] ${codecData}`)
})
.saveToFile(`${outputDirectory}/%d.png`)
})
export const processVideo = async (inputVideoFilePath: string, inputFramesDirectory: string, outputVideoFilePath: string, fileType: FileType) => {
const metadata = await getMetadata(inputVideoFilePath)
// Split up the video into frames.
await splitFramesFromVideo(inputVideoFilePath, inputFramesDirectory, metadata)
// Then put those frames back into a video.
let type = ''
if (fileType === FileType.MP4) {
type = 'mp4'
} else if (fileType === FileType.MOV) {
type = 'mov'
}
await createVideoFromFrames(inputFramesDirectory, outputVideoFilePath, type, metadata)
}
Expected results
I would like to be able to go from a .mp4 or .mov video, extract all frames, do some processing on them, and then write back to a video. I would expect the video to have identical metadata to the original video, i.e. the same duration, encoding, etc. I do not need to keep the audio track, just the video.
Observed results
My created video has a different duration, bit rate, and encoder, as you can see here.
The first issue seems to be that we only create 15 frames out of the input video, but in the metadata it says TODO. But I also tried with 'ffmpeg-extract-frames' and also got 15 frames:
Version information
2.1.24
6.1.1
Code to reproduce
Expected results
I would like to be able to go from a
.mp4
or.mov
video, extract all frames, do some processing on them, and then write back to a video. I would expect the video to have identical metadata to the original video, i.e. the same duration, encoding, etc. I do not need to keep the audio track, just the video.Observed results
My created video has a different duration, bit rate, and encoder, as you can see here.
Original video metadata:
Resulting video metadata:
The first issue seems to be that we only create 15 frames out of the input video, but in the metadata it says
TODO
. But I also tried with'ffmpeg-extract-frames'
and also got 15 frames:Thank you for any help!