Any way to automatically compress a .gif file?

I have a 2.0 mb gif I want to make smaller, preferably by dropping some frames.

Is there a command line utility or a program that will do it for me automatically?

Thanks.


Solution 1:

ImageOptim uses Gifsicle for its Gif compression. As far as I know, it works quite well (I don't generally use Gif these days). From their site:

Optimize your animations! This stores only the changed portion of each frame, and can radically shrink your GIFs. You can also use transparency to make them even smaller. Gifsicle’s optimizer is pretty powerful, and usually reduces animations to within a couple bytes of the best commercial optimizers.

Solution 2:

You can use the GimpInstall gimp to remove some frames from the GIF as well as re-optimizing the file by saving it back to a GIF again.

Solution 3:

I use ezgif.com/optimize, as GIMP and other tools aren't good at this job. The difference is that ezgif uses a gifsicle modification (with a lossy flag), not available on the apt version. See release 1.82.1 here (replace /usr/bin/gifsicle with the appropriate one for your system (the binary ending with -static).

My notes on a full gif optimisation workflow are here.

Edit: current script as kept in use will be updated on GitHub here

I won't describe how I do it all here, the general idea is a few bash functions/aliases:

function gifopt() {
    # args: $input_file ($loss_level)
    if [ -z "$2" ]
    then
        # use default of 30
        local loss_level=30
    elif [[ "$2" =~ ^[0-9]+$ ]] && [ "$2" -ge 30 -a "$2" -le 200 ]
    then
        local loss_level=$2
    else
        echo "${2:-"Loss level parameter must be an integer from 30-200"}" 1>&2
        exit 1
    fi
    local inputgif="${1?'Missing input file parameter'}"
    local gifname="$(basename $inputgif .gif)"
    local basegifname=$(echo "$gifname" | sed 's/_reduced_x[0-9]//g')
    local outputgif="$basegifname-opt.gif"
    gifsicle -O3 --lossy="$loss_level" -o "$outputgif" "$inputgif";
    local oldfilesize=$(du -h $inputgif | cut -f1)
    local newfilesize=$(du -h $outputgif | cut -f1)
    echo "File reduced from $oldfilesize to $newfilesize as $outputgif"
}

function gifopt() {
    # args: $input_file ($loss_level)
    if [ -z "$2" ]
    then
        # use default of 30
        loss_level=30
    elif [[ "$2" =~ ^[0-9]+$ ]] && [ "$2" -ge 30 -a "$2" -le 200 ]
    then
        loss_level=$2
    else
        echo "${2:-"Loss level parameter must be an integer from 30-200"}" 1>&2
        exit 1
    fi
    local inputgif="${1?'Missing input file parameter'}"
    local gifname="$(basename $inputgif .gif)"
    local basegifname=$(echo "$gifname" | sed 's/_reduced_x[0-9]//g')
    local outputgif="$basegifname-opt.gif"
    gifsicle -O3 --lossy="$loss_level" -o "$outputgif" "$inputgif";
    local oldfilesize=$(du -h $inputgif | cut -f1)
    local newfilesize=$(du -h $outputgif | cut -f1)
    echo "File reduced from $oldfilesize to $newfilesize as $outputgif"
}

function gifspeedchange() {
  # args: $gif_path $frame_delay (1 = 0.1s)
  local orig_gif="${1?'Missing GIF filename parameter'}"
  local frame_delay=${2?'Missing frame delay parameter'}
  gifsicle --batch --delay $frame_delay $orig_gif
  local newframerate=$(echo "$frame_delay*10" | bc)
  echo "new GIF frame rate: $newframerate ms"
}

The --lossy flag takes an integer from 30 to 200, so for best quality:

gifsicle -O3 --lossy=30 -o output.gif input.gif

With the above function gifopt you could simplify matters, since it defaults to 30

gifopt input.gif

...the output would be automatically named input-opt.gif. You could change the function to use the --batch flag for in-place editing but I wouldn't recommend overwriting your starting material.

That function also works to remove any _reduced_x suffix added by the frame count reduction function, i.e. you could have an intermediate file input_reduced_x2.gif (a step that would ~halve file size by dropping every other frame).

If you did do that the speed may change, fixed with gifspeedchange input-opt.gif 5 for example, to use a 50ms frame delay.