I like to capture my Xbox One gaming moments to share with friends and have been working on a shell script to quickly convert videos to animated GIFs, I've put together this fairly simple little shell script that takes an input video and uses ffmpeg to convert to an animated GIF.
The GIF file format has a colour limitation of 256 colours, so optimisation needs to be done to ensure a good quality output. The script utilises ffmpegs palettegen options to generate a palette of colours to use in the second pass using the bayer dither option. This is what the palette PNG file looks like (upscaled from 16x16):
The resulting animated GIF turns out quite nice, this was captured from my Xbox One in the Battlefield 1 beta, the file is 480x260 and 4.3MB at 15 frames per second:
$ wget https://gist.githubusercontent.com/mplinuxgeek/dcbc3a4d0f51f2b445608e3da832ebb5/raw/vid2gif.sh
$ chmod +x vid2gif.sh
To use this script just execute it with the filename of a video appended, the script defaults to a width of 480 pixels and frame rate of 15fps:
$ ./vid2gif.sh video.mp4
To change the width and fps of the gif add -w and -f arguments to the script:
$ ./vid2gif.sh -w 320 -f 10 video.mp4
UPDATE: I've recently done some testing with different bayer dithering levels, results are below, I've also added a dithering argument to the script.
To specify a different dithering level use the -d argument:
$ ./vid2gif.sh -w 320 -f 10 -d 5 video.mp4
Dither | Size | Notes |
---|---|---|
0 | 3.91MB | Largest file, worst image quality, |
1 | 3.33MB | Noticeable vertical lines |
2 | 3.19MB | Vertical lines still visible but better than 1 |
3 | 3.05MB | Vertical lines gone, very good image quality |
4 | 2.90MB | Hard to pick a difference from 3 |
5 | 2.82MB | Hard to notice a difference from 4 but some colour banding evident |

Bonus: if you want to be able to run the script from anywhere without specifying the full path or ./ simply copy it to /usr/local/bin:
$ sudo cp vid2gif.sh /usr/local/bin/
The script can now be run from any directory:
$ vid2gif.sh
Script:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# rev 4 - changes suggested by KownBash https://www.reddit.com/r/bash/comments/5cxfqw/i_wrote_a_simple_video_to_gif_bash_script_thought/da19gjz/ | |
# rev 5 - made the script more verbose | |
# rev 6 - fixed an issue with a variable not displaying correctly | |
# rev 7 - added option to change dither level | |
# rev 8 - rewrote usage function | |
# Usage function, displays valid arguments | |
usage() { | |
echo "Usage: $(basename ${0}) [arguments] inputfile" 1>&2 | |
echo " -f fps, defaults to 15" 1>&2 | |
echo " -w width, defaults to 480" 1>&2 | |
echo " -d dither level, value between 0 and 5, defaults to 5" 1>&2 | |
echo " 0 is no dithering and large file" 1>&2 | |
echo " 5 is maximum dithering and smaller file" 1>&2 | |
echo -e "\nExample: $(basename ${0}) -w 320 -f 10 -d 1" 1>&2 | |
exit 1 | |
} | |
# Default variables | |
fps=15 | |
width=480 | |
dither=5 | |
# getopts to process the command line arguments | |
while getopts ":f:w:d:" opt; do | |
case "${opt}" in | |
f) fps=${OPTARG};; | |
w) width=${OPTARG};; | |
d) dither=${OPTARG};; | |
*) usage;; | |
esac | |
done | |
# shift out the arguments already processed with getopts | |
shift "$((OPTIND - 1))" | |
if (( $# == 0 )); then | |
printf >&2 'Missing input file\n' | |
usage >&2 | |
fi | |
# set input variable to the first option after the arguments | |
input="$1" | |
# Extract filename from input file without the extension | |
filename=$(basename "${input}") | |
#extension="${filename##*.}" | |
filename="${filename%.*}.gif" | |
# Debug display to show what the script is using as inputs | |
echo "Input: ${1}" | |
echo "Output: ${filename}" | |
echo "FPS: ${fps}" | |
echo "Width: ${width}" | |
echo "Dither Level: ${dither}" | |
# temporary file to store the first pass pallete | |
palette="/tmp/palette.png" | |
# options to pass to ffmpeg | |
filters="fps=${fps},scale=${width}:-1:flags=lanczos" | |
# ffmpeg first pass | |
echo -ne "\nffmpeg 1st pass... " | |
ffmpeg -v warning -i "${input}" -vf "${filters},palettegen=stats_mode=diff" -y "${palette}" && echo "done" | |
# ffmpeg second pass | |
echo -ne "ffmpeg 2nd pass... " | |
ffmpeg -v warning -i "${input}" -i "${palette}" -lavfi "${filters} [x]; [x][1:v] paletteuse=dither=bayer:bayer_scale=${dither}" -y "${filename}" && echo "done" | |
# display output file size | |
filesize=$(du -h "${filename}" | cut -f1) | |
echo -e "\nOutput File Name: ${filename}" | |
echo "Output File Size: ${filesize}" |
http://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html
Just a warning, keep the input videos short, GIFs can get very large very quickly.
No comments:
Post a Comment