FFmpeg Tips and Tricks

Video stabilisation

FFmpeg can use the vid.stab library to stabilize (i.e. remove shakiness) video. Some guides say it needs to be installed explicitly (vid.stab on Arch Linux, libvidstab* on Debian) but on both my systems it already came as a dependency of ffmpeg.

It requires a 2-pass process.

ffmpeg -i "$vid" -vf vidstabdetect -f null -
ffmpeg -i "$vid" -vf vidstabtransform "${vid%.*}.stab.${vid##*.}"

The first pass will create a transforms.trf file in the current directory. It happened at ~0.4x speed on an old thinkpad, heating up all 4 cores.
The second pass (at ~0.3x speed) uses it.
In other words, it will take roughly 5-6x the length of the video (MP4@1920x1080) to stabilise it.

At first I wasn't happy with the results so I increased shakiness for the first pass:

ffmpeg -i "$vid" -vf 'vidstabdetect=shakiness=10:accuracy=15' -f null -

In the second pass I set smoothing=0 which assumes that the camera is static (which is the case in my example video):

ffmpeg -i "$vid" -vf 'vidstabtransform=smoothing=0' "${vid%.*}.stab.${vid##*.}"

Here's a before/after side-by-side comparison:

ffmpeg -i "$vid" -i "${vid%.*}.stab.${vid##*.}" -filter_complex \
"[0:v]crop=iw/2:ih:0:0[left]; [1:v]crop=iw/2:ih:ow:0[right]; [left][right]hstack" \

Create a looping GIF from a (very short) video

You might want to reduce resolution and framerate drastically:

ffmpeg -i input.mp4 -vf "fps=10,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif

This produces a large file roughly the size of the original (in my case MP4@1920x1080). If you don't mind dithering artifacts, this will result in a file 1/3 the size:

ffmpeg -i input.mp4 -vf "fps=10,scale=640:-1" -loop 0 output.gif

I noticed that the GIF becomes a lot smaller if I first scale the video, then convert it to GIF:

ffmpeg -i input.mp4 -vf "scale=640:-1" input_scaled.mp4
ffmpeg -i input_scaled.mp4 -vf "fps=10" -loop 0 output.gif

Result (video from previous chapter).

Cut a video without re-encoding...

...at keyframes only (forsaking time accuracy), and without messing up timestamps:

ffmpeg -noaccurate_seek -ss [START_TIME] -i [INPUT] -t [DURATION] -c copy -avoid_negative_ts make_zero [OUTPUT]

As always with ffmpeg, the order of the options is relevant. Either -ss [START_TIME] or -t [DURATION] can be omitted.

Create a slideshow from a bunch of images

This also adds nice transitions and the end result loops perfectly.

#!/bin/bash # Creates a loopable slideshow movie from a bunch of # PNGs of identical resolution & dimensions in the PWD # For example: # for i in *.jpg; do convert "$i" -resize 1000x1000^ -gravity Center -extent 1000x1000 "${i%.*}.png"; done # -d == debug or dry run, just show the ffmpeg command [[ "$1" == '-d' ]] && nl="\n" || nl='' shift # not currently required out=video.mp4 # !!!! vcodec x264 is hardcoded dur=2 # crossfade duration in s t=5 # time per slide in s xf=smoothleft # type of crossfade, see trac.ffmpeg.org/wiki/Xfade tr="xfade=transition=${xf}:duration=${dur}:offset=" ff="ffmpeg -hide_banner" cmd=( $ff $nl ) pngs=( *.png ) t=$((t+dur)) # building a list of inputs. First image = last image, and lasts only half the time, to makeit gor round smoothly cmd=( ${cmd[@]} -loop 1 -t $((t/2+t%2)) -i "${pngs[0]}" $nl ) for ((c=1;c<${#pngs[@]};c++)); do cmd=( ${cmd[@]} -loop 1 -t $t -i "${pngs[c]}" $nl ); done cmd=( ${cmd[@]} -loop 1 -t $((t/2)) -i "${pngs[0]}" $nl ) cmd=( ${cmd[@]} -filter_complex $nl ) # building the super complex Xfade filter command; see $tr above off=$((t/2+t%2-dur)) # off=offset: grows steadily, but this first one is shorter fc="[0][1]$tr${off}[f0]; $nl" for ((i=0;i<c-2;i++)); do off=$((t+off-dur)); fc="$fc [f$i][$((i+2))]$tr${off}[f$((i+1))]; $nl"; done off=$((t+off-dur)); fc="$fc [f$i][$((i+2))]$tr${off}; $nl" # lastly cmd=( "${cmd[@]}" "$fc" -movflags +faststart -r 25 -pix_fmt yuv420p -vcodec libx264 "$out" ) [[ "$nl" == "\n" ]] && echo -e "${cmd[@]}" || "${cmd[@]}" # also see: # bannerbear.com/blog/how-to-create-a-slideshow-from-images-with-ffmpeg/

Encode to 8 bit unsigned PCM WAV Mono

Typically used for game sounds. I had difficulties using the aformat filter, but this syntax worked:

ffmpeg -i speeddown.wav -af "areverse" -ar 22050 -acodec pcm_u8 -ac 1 speedup.wav

It reverses an existing audio snippet.
It would be nice if ffmpeg had an option to transcode a file, but use the same format as the input.

"Fix bad video"

I'm posting this reluctantly because I cannot well explain the problem: sometimes a "bad" video file will play OK, but searching or skipping is impossible. In this case it can help to convert it to an uncompressed raw format like ffvhuff:

ffmpeg -i bad_video.ext -c:v ffvhuff -c:a copy good_video.mkv

I chose MKV because not all containers support this uncompressed format.

This will take orders of magnitude more space then compressed video, but now you can convert it back to something compressed, and it won't be "broken" anymore.

I did not convert the audio here, and it should have no effect on "searchability".

In any case, the ffmpeg wiki has this to say about audio formats, and this SO Q&A has a few more examples.


I could not find out if it's possible to make ffmpeg read some configuration options. It respects a number of envrionment variables, but again, I could not find documentation for this anywhere.

Anyhow, one thing I really do not need is ffmpeg's huge "banner" listing compile options, so I'll at least add an alias to my shell's rc:

alias ffmpeg='ffmpeg -hide_banner'

Online documentation