Stitching videos from frames with ffmpeg (and audio/video editing tricks)
ffmpeg
is awesome. In case you haven't heard, ffmpeg
is a command-line tool for editing and converting audio and video files. It's taken me a while to warm up to it (the command-line syntax is pretty complicated), but since I've found myself returning to it again and again I thought I'd blog about it so you can use it too (and also for my own reference :P).
I have 2 common tasks I perform with ffmpeg
:
- Converting audio/video files to a more efficient format
- Stitching PNG images into a video file
I've found that most tasks will fall into 1 of the 2 boxes. Sometimes I want to do something more complicated, but I'll usually just look that up and combine the flags I find there with the ones I usually use for one of the 2 above tasks.
Stitching PNG images into a video
I don't often render an animation, but when I do I always render to individual images - that way if it crashes half way through I can more easily restart where I left off (and also divide the workload more easily across multiple machines if I need it done quickly).
Individual image files are all very well, but they have 2 problems:
- They take up lots of space
- You can't easily watch them like a video
Solving #1 is relatively easy with optipng
(sudo apt install optipng
), for example:
find . -iname "*.png" -print0 | xargs -0 -P4 -n1 optipng -preserve
I cooked that one up a while ago and I've had it saved in my favourites for ages. It finds all png images in the current directory (recursively) and optimises them with optipng
.
To resolve #2, we can use ffmpeg
as I mentioned earlier in this post. Here's one I use:
ffmpeg -r 24 -i path/to/frame_%04d.png -c:v libvpx-vp9 -b:v 2000k -crf 31 path/to/output.webm
A few points on this one:
-r 24
: This sets the frames per second.-i path/to/frame_%04d.png
: The path to the png images to stitch.%04d
refers to 4 successive digits in a row that count up from 1 - e.g.0001
,0002
, etc.%d
would mean1
,2
, ...., and%02d
for example would mean01
,02
,03
....-c:v libvpx-vp9
: The codec. We're exporting to webm, and vp9 is the latest available codec for webm video files.-b:v 2000k
: The bitrate. Higher values mean more bits will be used per second of video to encode detail. Raise to increase quality.-crf 31
: The encoding quality. Should be anumber betwee 0 and 63 - higher means lower quality and a lower filesize.
Read more about encoding VP9 webm videos here: Encode/VP9- ffmpeg
Converting audio / video files
I've talked about downmuxing audio before, so in this post I'm going to be focusing on video files instead. The above command for encoding to webm can be used with minimal adjustment to transcode videos from 1 format to another:
ffmpeg -hide_banner -i "path/to/input.avi" -c:v libvpx-vp9 -c:a libopus -crf 31 -b:v 0 "path/to/output/webm";
In this one, we handle the audio as well as the video all in 1 go, encoding the audio to use the opus codec, and the video as before. This is particularly useful if you've got a bunch of old video files generated by an old camera. I've found that often old cameras like to save videos as raw uncompressed AVI files, and that I can reclaim a sigificant amount of disk space if I transcode them to something more efficient.
Naturally, I cooked up a one-liner that finds all relevant video files recursively in a given directory and transcodes them to 1 standard efficient format:
find . -iname "*.AVI" -print0 | nice -n20 xargs --verbose -0 -n1 -I{} sh -c 'old="{}"; new="${old%.*}.webm"; ffmpeg -hide_banner -i "${old}" -c:v libvpx-vp9 -c:a libopus -crf 30 -b:v 0 "${new}" && rm "${old}"';
Let's break this down. First, let's look at the commands in play:
find
: Locates the target files to transcodenice
: Runs the following command in the backgroundxargs
: Runs the following command over and over again for every line of inputsh
: A shell, to execute multiple command in sequenceffmpeg
: Does the transcodingrm
: Deletes the old file if transcoding completes successfully
The old="{}"; new="${old%.*}.webm";
bit is a bit of gymnastics to pull in the path to the target file (the -I {}
tells xargs
where to substitute it in) and determine the new filepath by replacing the file extension with .webm
.
I've found that this does the job quite nicely, and I can set it running and go off to do something else while it happily sits there and transcodes all my old videos, reclaiming lots of disk space in the process.
Found this useful? Got a cool command for processing audio/video/image files in bulk? Comment below!