← Home

Convert videos to GIFs with ffmpeg

2024-03-15

Shell script to convert videos to high-quality GIFs with optional boomerang loop effect. Uses ffmpeg’s palettegen for optimal colors.

#!/bin/sh

# We divide by this SCALE value (higher is smaller output, lower is better quality)
SCALE_DEFAULT="2.5"
SCALE="$SCALE_DEFAULT"
FPS_DEFAULT="15"
FPS="$FPS_DEFAULT"

# Parse arguments
while [ $# -gt 0 ]; do
    case "$1" in
        -s)
            SCALE="$2"
            shift 2
            ;;
        -f)
            FPS="$2"
            shift 2
            ;;
        -l)
            LOOP_FLAG=1
            shift
            ;;
        *)
            VIDEO="$1"
            shift
            ;;
    esac
done

if [ -z "$VIDEO" ]; then
    echo "Usage: $0 [-s scale] [-f fps] [-l] <video>"
    echo "Default scale: $SCALE_DEFAULT"
    echo "Default fps: $FPS_DEFAULT"
    exit 1
fi

TMPDIR=${TMPDIR:-/tmp}
TMP_PREFIX="giffify_$(basename "$VIDEO" | sed 's/[^a-zA-Z0-9_-]/_/g')_$$"

make_gif() {
    echo "Making gif from $1"
    ffmpeg -v quiet -i "$1" -vf "fps=${FPS},scale=iw/${SCALE}:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" "$OUTPUT"
}

reverse_and_trim() {
    echo "Reversing and trimming $1"
    ffmpeg -v quiet -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -i "$1" \
        -vf "reverse" -c:v h264 -an "$TMPDIR/${TMP_PREFIX}_reversed_full.mp4"

    ffmpeg -v quiet -ss 0.5 -i "$TMPDIR/${TMP_PREFIX}_reversed_full.mp4" -c copy "$TMPDIR/${TMP_PREFIX}_r.mp4"
    rm -f "$TMPDIR/${TMP_PREFIX}_reversed_full.mp4"
}

make_gif_reversed() {
    echo "Making reversed gif from ${1%.*}.mp4"
    ffmpeg -v quiet -i "$TMPDIR/${TMP_PREFIX}_r.mp4" -vf "fps=${FPS},scale=iw/${SCALE}:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" "$TMPDIR/${TMP_PREFIX}_r.gif"
}

combine_gifs() {
    echo "Combining gifs for $1"
    ffmpeg -v quiet -i "$TMPDIR/${TMP_PREFIX}.gif" -i "$TMPDIR/${TMP_PREFIX}_r.gif" \
        -filter_complex "[0:v][1:v]concat=n=2:v=1:a=0,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
        "loop_${1%.*}.gif"
}

if [ "$LOOP_FLAG" = "1" ]; then
    OUTPUT="$TMPDIR/${TMP_PREFIX}.gif"
    (make_gif "$VIDEO") &
    (reverse_and_trim "$VIDEO") &
    wait

    make_gif_reversed "$VIDEO"
    combine_gifs "$VIDEO"

    rm -f "$TMPDIR/${TMP_PREFIX}.gif" "$TMPDIR/${TMP_PREFIX}_r.mp4" "$TMPDIR/${TMP_PREFIX}_r.gif"
else
    OUTPUT="${VIDEO%.*}.gif"
    echo "Outputting to $OUTPUT"
    make_gif "$VIDEO"
fi

Usage: