gifify.sh
#!/bin/bash

set -euo pipefail

function printHelpAndExit {
  echo 'Usage:'
  echo '  gifify [options] filename'
  echo ''
  echo 'Options: (all optional)'
  echo '  c CROP:      The x and y crops, from the top left of the image, i.e. 640:480'
  echo '  o OUTPUT:    The basename of the file to be output (default "output")'
  echo '  l LOOP:      The number of times to loop the animnation. 0 (default)'
  echo '               for infinity.'
  echo '  r FPS@SPEED: With 60@1.5, output at 60FPS at a speed of 1.5x the'
  echo '               source material. NOTE: It is best to keep FPSxSPEED'
  echo '               below approximately 60.'
  echo '  p SCALE:     Rescale the output, e.g. 320:240'
  echo '  x:           Remove the original file and resulting .gif once the script is complete'
  echo ''
  echo 'Example:'
  echo '  gifify -c 240:80 -o my-gif my-movie.mov'
  exit $1
}

crop=
output=
loop=0
fpsspeed='10@1'
scale=

OPTERR=0

while getopts 'c:o:l:p:r:s:' opt; do
  case $opt in
    c) crop=$OPTARG;;
    h) printHelpAndExit 0;;
    o) output=$OPTARG;;
    l) loop=$OPTARG;;
    r) fpsspeed=$OPTARG;;
    p) scale=$OPTARG;;
    *) printHelpAndExit 1;;
  esac
done

shift $(( OPTIND - 1 ))

filename=$1

if [ -z ${output} ]; then
  output=$filename
fi

if [ -z "$filename" ]; then printHelpAndExit 1; fi

if [ $crop ]; then
  crop="crop=${crop}:0:0"
else
  crop=
fi

if [ $scale ]; then
  scale="scale=${scale}"
else
  scale=
fi

if [ $scale ] || [ $crop ]; then
  filter="-vf $scale$crop"
else
  filter=
fi

# -delay uses time per tick (a tick defaults to 1/100 of a second), so 60fps ==
# -delay 1.666666 which is rounded to 2 because convert apparently stores this
# as an integer.
#
# To animate faster than 60fps, you must drop frames, meaning you must specify
# a lower -r. This is due to the GIF format as well as GIF renderers that cap
# frame delays < 3 to 3 or sometimes 10. Source:
# http://humpy77.deviantart.com/journal/Frame-Delay-Times-for-Animated-GIFs-214150546

fps=$(echo $fpsspeed | cut -d'@' -f1)
speed=$(echo $fpsspeed | cut -d'@' -f2)

if [ ! -z "$speed" ]; then
  speed=1
fi

delay=$(bc -l <<< "100/$fps/$speed")
temp=$(mktemp /tmp/tempfile.XXXXXXXXX)

ffmpeg -loglevel panic -i "$filename" $filter -r $fps -f image2pipe -vcodec ppm - >> $temp
cat $temp | convert +dither -layers Optimize -loop $loop -delay $delay - "${output}.gif"
echo "${output}.gif"
