Gifski: Optimized GIF Encoder
github.com
github.com
But it's such a travesty that we're in 2024 and still using GIFs in place of videos. I guess it's not necessary on the web directly anymore, but so many other platforms, like Google, support GIFs but not videos. So we're all stuck encoding into an insanely space/cpu inefficient format just to get something animating.
Ironically, GIFs are way larger than e.g. a webm video but are the only format which can be directly embedded in a presentation.
feature requests for Google:
- inline video that auto plays and loops. This is like 1 line of html. Please do this!
- let me drag a video into the slide and just upload it for me and embed it
- or just convert videos to gifs for me when I drag in!!
The contract for these could be something like "no sound, default playback in a loop, 30 seconds or less; don't render if any of these requirements aren't met" to make sure people don't abuse it for auto-playing videos.
In both cases, the result is a lack of a right-click-to-save option.
_speechless at the irony
<img src="foo.mp4">
and have it behave exactly like all other images in how CSS handles it (with auto loop, no controls). This would solve all the reasons people use GIFs in place of videos.Like for example
<img src="foo.mp4" style="width:100%">
should make the image occupy 100% of the width of the parent element and auto-scale the height according to the mp4's aspect ratio. You can't do this with the <video> element without an extremely unintuitive wrapper div.The problem with <video> of course is that there's no way to tell if it's going to play an annoying sound, take a long time to load, change the screen brightness due to HDR features, require a weird proprietary codec, need the browser to render video controls, and so on. So they end up getting blocked from autoplay by default.
I suppose APNG or WebP are ideal for this but it is poorly supported, in that very few tools let you export to APNG or WebP --- browsers actually have decent support for this [1] and Discord uses APNG for stickers.
Also, I just learnt this, gifski has integrated gifsicle for lossy gif compression.
I love using ScreenToGif to embed little videos into my email responses when I'm doing technical support. It's so much easier to show someone the process than describe everything in click by click detail.
I've been doing this for years, and people are always amazed at the videos.
The editor is minimal and has a non existent learning curve.
AFAIK screentogif even has the gifsky encoder plus a few others.
That said, I'd highly recommend Giphy Capture for recording gifs on mac. It has a very sleek/light UI.
I also like that the author spent time to add an easy way to build a static binary, which I need in order to ship gifski inside the app.
Compressing all these static binaries so I can ship them in Clop with a <100MB download was not so easy though. I’ve been using the included xz in macOS but I’m switching to lrzip [1] for better compression size and especially speed
#!/bin/sh
SCALE=500
FPS=15
SCALEPROVIDED=false
for arg in "$@"
do
case $arg in
-s=*|--size=*)
SCALE="${arg#*=}"
SCALEPROVIDED=true
shift
;;
-f=*|--fps=*)
FPS="${arg#*=}"
shift
;;
esac
done
palette="/tmp/palette.png"
# Look at the width/height of the video
width=`ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 $1`
height=`ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 $1`
# Get the max
max=$(( width > height ? width : height))
# If we havent provided a scale, and the max size is lower than the default, set it to the lower value
if [ "$SCALEPROVIDED" == true ]
then
minScale=$SCALE
else
minScale=$(( SCALE < max ? SCALE : max))
fi
# Set our flags
filters="fps=$FPS,scale=$minScale:-1:flags=lanczos"
# If there's no name provided, output the same name as the input with gif as the extension
output=$2
if [ -z "$output" ]
then
output=${1%.*}.gif
fi
# Output our gif
ffmpeg -v error -i $1 -vf "$filters,palettegen=max_colors=256" -y $palette
ffmpeg -v error -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $output
echo created $output at ${minScale}px
Example usage is something like: gif input.mov
But you can also optionally specify framerate, size, and output name: gif -s=800 -f=22 input.mov my-800px-22fps-gif.gif ffmpeg -i input.mov -f yuv4mpegpipe | gifski --width=500 -o out.gif -I googled, and saw that one technique for this is layering multiple frames with different palettes and a delay of 0.
1. It uses a unique color palette for each frame rather than using one global color palette for all frames.
2. It takes advantage of the fact that pixels from previous frames can be retained and used as part of the next frame even if the colors from those pixels are no longer part of the color palette. This is the main trick gifski uses to get more than 256 colors in a frame.
On top of that gifski uses pngquant for color quantization (converting to 256 colors) which is extremely good at its job in its own right.
This allows more reuse of the background colors, and keeps file sizes lower by avoiding adding unnecessary temporal noise. Other converters that remap and dither each frame independently will needlessly create unique noise patterns in each frame.
Gifski took 11 seconds, and GIF Brewery took 59. Both apps set to maximum quality, 50fps, no resizing. The Gifski version is a little bigger at 5.5mb vs 4.2mb but also looks a bit better.
I embed short screen recording GIFs into GitLab/GitHub several times a week as it's just a better user experience vs real video files on those platforms. I'm definitely switching tools! I wish I had known about this sooner.
I thought they were using Gifski but I'm not sure about it.
MP4 with H.264 is still king for compatibility, but WebM with VP9 isn’t as bespoke as it once was.
I usually only need GIFs for GitHub readmes. GitHub doesn't support WebM autoplay videos.
In texting, Telegram has introduced WebM stickers so there's at least a choice there.
Animated GIFs remain quite useful for non-video animations where you don't want lossy compression, and want to use other GIF features like transparency. Animated PNGs are a potential replacement, and support all of the features of GIFs along with 24-bit color, but software support remains lackluster.
That’s exactly what they’re doing. Just used the web inspector on one of their GIFs. Imgur does the same thing.
The content-type response header is image/gif.
P.S. The example gif on their site is gorgeous.
I can run:
gifski -Q50 jazz-chromecast-ultra.gif -o even-smaller.gif
and it reduces the same file by 65%. With -Q20 it can be 78% smaller: https://gif.ski/jazz-ultra-small.gifBy that measure, gif.ski is 78% better than itself!
With lossy compression it's always possible to make files smaller, and smaller, and smaller. Comparisons of lossy encoders without controlling for quality lead to such absurd results. See https://kornel.ski/en/faircomparison
I wrote about the integration here https://quickdrop.antran.app/blogs/video_to_gif_conversion
- GIF: gifski (squeeze out the best possible out of the ancient format)
- PNG: pngquant (lossy but respectful of what many PNGs are: lots of flat surfaces + limited color palette, transparency)
- JPG: jpegli (writing this post mostly to share the news about this one, it's new from libjxl learnings, approximately as good qualitatively as Guetzli, but 1000x faster -actual number, not an exaggeration-. It's amazing! https://github.com/libjxl/libjxl/tree/main/lib/jpegli )
Below's my lil' `image-opt` helper making for a nice workflow to optimize any gif/jpg/png file(s) :)
#!/usr/bin/env bash
set -euo pipefail
dbg() { if [ "${DEBUG:=false}" = 'true' ]; then echo "$@"; fi }
die() { echo "Error: $*" 1>&2; zenity --error --text="$*"; exit 1; }
if ! command -v pngquant > /dev/null; then die 'You must install pngquant'; fi
if ! command -v cjpegli > /dev/null; then die 'You must install cjpegli'; fi
if ! command -v gifsicle > /dev/null; then die 'You must install gifsicle'; fi
for filename in "${@:1}"; do
if [ -d "$filename" ]; then
echo && echo "ℹ File '$filename' is a directory. Skipping."
continue
fi
if [ ! -f "$filename" ]; then
echo && echo "ℹ File '$filename' does not exist. Skipping."
continue
fi
extension_raw="${filename##*.}"
extension="${extension_raw,,}" # Convert to lowercase
if [[ "$extension" != 'jpg' && "$extension" != 'png' && "$extension" != 'gif' ]]; then
echo && echo "ℹ Skipping file $filename due to unsupported extension: .$extension"
continue
fi
filename_noext="${filename%.*}"
filename_original="${filename_noext}_beforeOpt.${extension_raw}"
filename_final="${filename_noext}.${extension}"
dbg "filename($filename), filename_noext($filename_noext), extension($extension), filename_original($filename_original)"
echo && echo " Compressing $filename ..."
mv "$filename" "$filename_original"
if [ "$extension" == 'jpg' ]; then
cjpegli "$filename_original" "${filename_noext}_q50.${extension}" -q 50
cjpegli "$filename_original" "${filename_noext}_q60.${extension}" -q 60
cjpegli "$filename_original" "${filename_noext}_q70.${extension}" -q 70
cjpegli "$filename_original" "${filename_noext}_q80.${extension}" -q 80
cjpegli "$filename_original" "${filename_noext}_q90.${extension}" -q 90
elif [ "$extension" == 'png' ]; then
pngquant --strip --force --verbose --output "$filename_final" "$filename_original"
elif [ "$extension" == 'gif' ]; then
# https://kornel.ski/lossygif , set --lossy to 20 for light compression, 200 for heavy
gifsicle -O3 --lossy=50 --verbose --output "$filename_final" "$filename_original"
fi
gio trash "$filename_original"
done
sync