High quality GIF from video

October 13, 2018
(shell, ffmpeg, utils)

When it comes to converting video to GIF, one usually gets a huge file and a questionable quality. Most of the guides suggest to use FFmpeg to do the conversion, but usually, they don’t bother with the quality of the result. As it turns out, folks from FFmpeg made some huge steps in improving the GIF output.

As you probably know, GIF is limited to a palette of 256 colors. And by default, FFmpeg just uses a generic palette that tries to cover the whole color space in order to support the largest variety of content:

The idea is to generate a custom palette and use it during conversion.

Since I am not good at remembering this kind of commands (and I hate to cycle the history of commands), I’ve created a simple script for doing exactly this. It’s called gifify and you can find it in my .environment repository.

Strong points of this script:

  1. You can pass scaling to ffmpeg (usually I don’t want to mess with the actual size). Default value is 1.

  2. You can pass FPS to ffmpeg. Default value is 24.

  3. The output file is defined automatically (input filename extension is changed to gif), but you can specify it manually.

  4. Allows using default palette (custom one won’t be generated).

  5. Allows compressing resulting GIF using gifsicle. By default, result is not compressed.

  6. You can pass any additional arguments to ffmpeg (like time).

My concern usually is about quality, not about the physical size if the output. But you have several options here:

  1. The lesser FPS is, the lesser output is. In most cases, 15 FPS is good enough.
  2. Use scaling. When it comes to screen video, you rarely need the original size, scale it a bit.
  3. In case you are good with the default palette, just use it.
  4. Use compression provided by gifsicle.

Palettes: default vs custom

Here are several examples, were the difference between default and custom palette is visible. In the first example, the difference is more obvious than in others, but still.

Installation

First, you need to install dependencies: ffmpeg and gifsicle. On macOS, you can install them using brew.

Then you need to grab the latest version of gifify.

Note that gifify should be in the folder that is visible from your PATH. I usually put gifify in $HOME/.local/bin.

Source code

#!/usr/bin/env bash

set -e

POSITIONAL=()
SCALE=1
FPS=24
PALETTE="custom"
COMPRESS=0

while [[ $# -gt 0 ]]
do
  key="$1"

  case $key in
    -i|--input)
      INPUT="$2"
      shift # past argument
      shift # past value
      ;;
    -o|--output)
      OUTPUT="$2"
      shift # past argument
      shift # past value
      ;;
    -s|--scale)
      SCALE="$2"
      shift # past argument
      shift # past value
      ;;
    --fps)
      FPS="$2"
      shift # past argument
      shift # past value
      ;;
    --default-palette)
      PALETTE="default"
      shift # past argument
      ;;
    --compress)
      COMPRESS=1
      shift # past argument
      ;;
    *)    # unknown option
      POSITIONAL+=("$1") # save it in an array for later
      shift # past argument
      ;;
  esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

if [[ -z $OUTPUT ]]; then
  OUTPUT="${INPUT%.*}.gif"
fi

if [[ $COMPRESS == "1" ]]; then
  OUTPUT_TEMP="tmp_$OUTPUT"
else
  OUTPUT_TEMP="$OUTPUT"
fi

PALETTE_FILE="${INPUT%.*}.png"

echo "input    = ${INPUT}"
echo "output   = ${OUTPUT}"
echo "scale    = ${SCALE}"
echo "fps      = ${FPS}"
echo "palette  = ${PALETTE}"
echo "compress = ${COMPRESS}"
echo "args     = ${POSITIONAL[*]}"
echo

function cleanup () {
  rm -f "$PALETTE_FILE"
}

trap cleanup INT TERM EXIT

filters="fps=${FPS},scale=iw*${SCALE}:ih*${SCALE}:flags=lanczos"

case $PALETTE in
  custom)
    ffmpeg ${POSITIONAL[*]} \
           -i "$INPUT" \
           -vf "$filters,palettegen" \
           "$PALETTE_FILE"

    ffmpeg ${POSITIONAL[*]} \
           -i "$INPUT" \
           -i "$PALETTE_FILE" \
           -filter_complex "$filters [x]; [x][1:v] paletteuse" \
           "$OUTPUT_TEMP"
    ;;

  default)
    ffmpeg ${POSITIONAL[*]} \
           -i "$INPUT" \
           -filter_complex "$filters" \
           "$OUTPUT_TEMP"
    ;;
esac

if [[ $COMPRESS == "1" ]]; then
  gifsicle --optimize=3 --delay=3 "$OUTPUT_TEMP" -o "$OUTPUT"
fi

More quality

In this post, I covered only quality improvements from using a custom palette. But actually, there are other ways to tweak you GIF when using FFmpeg. You can find out more in High quality GIF with FFmpeg.