Gif terminal manipulation - lmmx/devnotes GitHub Wiki

Update: current script in use is kept on GitHub here


Old way to cut out every other frame (requires you manually find frame count):

gifsicle input_file.gif `seq -f "#%g" 0 2 273` --unoptimize -O2 -o output_file.gif

Alternative, easier to put in bashrc (via), e.g. to halve frames (not necessarily half filesize):

gif_framecount_reducer input.gif 2

appends _reduced_x2 to filename

function gifopt() {
	# args: $input_file ($loss_level)
	if [ -z "$2" ]
	then
		# use default of 30
		local loss_level=30
	elif [ "$2" =~ ^[0-9]+$ ](/lmmx/devnotes/wiki/-"$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"
}

Replace /usr/bin/gifsicle with new release (static binary) via current GitHub release: 1.82.1

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

Unlike the hosted service at ezgif.com/optimize (which is great!), can use any value for the --lossy flag.

function gifopt() {
	# args: $input_file ($loss_level)
	if [ -z "$2" ]
	then
		# use default of 30
		loss_level=30
	elif [ "$2" =~ ^[0-9]+$ ](/lmmx/devnotes/wiki/-"$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"
}

You can then chain them together, first making a suitable frame reduction towards file size (roughly half frames gives half file size), followed by as heavy a lossy setting as needed to get to whatever max. file size you're aiming at.

I've added one more function to quickly modify the output gif (in-place) if its speed isn't quite right after going through the frame reduction.

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 workflow

Tip: the optimisation will work best on animations with consistently unchanging regions - i.e. it seeks to make these transparent across frames.

An example workflow then might look like this:

  • run gifcast, selecting a video playing on the screen
  • manually modify the output gif (crop, cut off surplus end frames, "merge down" starting frames appropriately) in Photoshop/GIMP etc.
  • quickly check the filesizes (I use the alias below to get a neat head of recently modified files with their sizes), get an idea of frame rate
    • roughly aim for a frame reduction equivalent to current file size / 2 x final file size
    • if you have a 30MB GIF to start with, aiming for 3 MB (e.g. for Twitter), reduce maybe 4x, towards ~7MB. Optimisation at a light (--lossy=30) default setting will likely reach 3MB - the output will let you know the file size it reaches.
alias recentsizes='du -h $(recent)'
alias recent='ls -ht | head'
  • gif_framecount_reducer flashy.gif 4; gifopt flashy_r*4.gif 60 - run at a setting of 60 on the result of a framecount reduction step (on the output flashy_reduced_x4.gif), the _reduced_x{number} gets stripped off to give flashy-opt.gif

  • In the upload preview (or open directly in a browser) if the speed isn't right, change it (almost instant) with e.g gifspeedchange flashy-opt.gif 4 to use 40ms frame delay for all frames

  • related: ffcast/xrectsel GIF screencast function

  • video to frames: ffmpeg -i Video.mpg frame_%d.png

See also