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.
- roughly aim for a frame reduction equivalent to
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 outputflashy_reduced_x4.gif
), the_reduced_x{number}
gets stripped off to giveflashy-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 -
video to frames:
ffmpeg -i Video.mpg frame_%d.png
See also
- "An Algorithm to Extract Looping GIFs From Videos", from the developer of MoviePy