Recently I had a need to clip a compilation video into a series of short looping animations. I could have used trusty ole gif format for this, but I wanted to use the more modern WebP, sister to the popular WebM format for video.
I couldn't find any decent tools or guides to do what I needed, so I decide to whip one together. The big features that I wanted were a) ease of use, and b) able to crop black bars automatically. The compilation I was working with was a mix of both portrait and landscape so this was a must.
As usual, FFmpeg has everything necessary to handle this baked right in. First we need a basic command to actually convert a video to WebP.
$ ffmpeg -i <in_file> -loop 1 -an -vf fps=fps=20 <out_file>
I know that fps=fps=20
looks like a typo, but that is the actual syntax for this filter. A value of 20 here is a good balance between smooth playback and small filesize. So now we can convert an entire video into a looping WebP. But what if we want to clip just a specific portion?
$ ffmpeg -i <in_file> -ss <start_time> -to <end_time> -loop 1 -an -vf fps=fps=20 <out_file>
-ss
sets the start timestamp, -to
sets the end timestamp. This can be simply # of seconds like 77, or full timestamp notation "00:01:17". But this command is getting a bit bulky. So let's wrap it in a script.
#!/bin/bash
start=""
end=""
if [ ! -z "$2" ]; then
start="-ss $2"
fi
if [ ! -z "$3" ]; then
end="-to $3"
fi
ffmpeg -i "$1" $start $end -loop 1 -an -vf fps=fps=20 "$4"
Then we can use it like :
$ vid2webp infile.mp4 10 20 outfile.webp
That's a lot leaner. Using this we can quickly and easily cut clips from a video into a series of animated WebP files. That satisfies requirement a. But we can't stop there. The next requirement is to detect the black bars, for this we need the cropdetect
filter. The snippet blow seeks to the clip start time, samples 10 frames and extracts the video dimensions from the crop=
output.
crop=$(ffmpeg $start -i "$1" -vframes 10 -vf cropdetect -f null - 2>&1 | grep -m 1 -oP 'crop=\K[0-9:]+')
From that we can extract the height/width values using cut
.
width=$(echo "$crop" | cut -d':' -f1)
height=$(echo "$crop" | cut -d':' -f2)
Then apply scaling. The bc command is used here for "basic calculations" with floating point numbers, which bash can't do on it's own. Afterwards, we will generate a value to use in the scale filter, based on the larger of the x/y dimensions. The -2
value opposite this will tell FFmpeg to keep the aspect ratio but adjust the value to be divisible by 2. That way we don't get any odd numbers like 487.2 or something if the cropped dimensions aren't perfect.
scale=0.8
width=$(echo "$width * $scale" | bc)
height=$(echo "$height * $scale" | bc)
if [ "$height" -gt "$width" ]; then
scale="-2:$height"
else
scale="$width:-2"
fi
And finally add these parameters to our ffmpeg
command :
ffmpeg -i "$1" $start $end -loop 1 -an -vf crop="$crop",scale="$scale",fps=fps="$fps" "$4"
Now, when we run our script multiple times on a compilation video we always get back a perfecftly cropped output with no black bars.
$ vid2webp infile.mp4 0 10 a.webp
$ vid2webp infile.mp4 10 18 b.webp
$ vid2webp infile.mp4 18 25 c.webp
I built on this concept with few a additional features like error checking, overriding defaults with ENV vars and automatic sequential filename output. You can grab the final version from my Github Repo or install via AUR for Arch users.
Top comments (0)