Skip to content

Instantly share code, notes, and snippets.

@dergachev
Last active April 19, 2024 11:00
Show Gist options
  • Save dergachev/4627207 to your computer and use it in GitHub Desktop.
Save dergachev/4627207 to your computer and use it in GitHub Desktop.
OS X Screencast to animated GIF

OS X Screencast to animated GIF

This gist shows how to create a GIF screencast using only free OS X tools: QuickTime, ffmpeg, and gifsicle.

Screencapture GIF

Instructions

To capture the video (filesize: 19MB), using the free "QuickTime Player" application:

  • Open "Quicktime Player",
  • Go to File -> New Screen Recording
  • Selected screen portion by dragging a rectangle, recorded 13 second video.
  • Go to File -> Export -> As Movie
    • Saved the video in full quality with the filename in.mov

To convert in.mov into out.gif (filesize: 48KB), open Terminal to the folder with in.mov and run the following command:

ffmpeg -i in.mov -s 600x400 -pix_fmt rgb24 -r 10 -f gif - | gifsicle --optimize=3 --delay=3 > out.gif

Notes on the arguments:

  • -r 10 tells ffmpeg to reduce the frame rate from 25 fps to 10
  • -s 600x400 tells ffmpeg the max-width and max-height
  • --delay=3 tells gifsicle to delay 30ms between each gif
  • --optimize=3 requests that gifsicle use the slowest/most file-size optimization

To share the new GIF using Dropbox and Copy Public URL, run the following:

cp out.gif ~/Dropbox/Public/screenshots/Screencast-`date +"%Y.%m.%d-%H.%M"`.gif

Installation

The conversion process requires the following command-line tools:

  • ffmpeg to process the video file
  • gifsicle to create and optimize the an animated gif

If you use homebrew and homebrew-cask software packages, just type this in:

brew install ffmpeg 
brew cask install xquartz #dependency for gifsicle, only required for mountain-lion and above
open /usr/local/Cellar/x-quartz/2.7.4/XQuartz.pkg # runs the XQuartz installer (YOU NEED TO UPDATE THE PATH)
brew install gifsicle

See also

I ended up rewriting this gist's functionality into screengif, a ruby script with significant quality improvements and a few gratuitous features. Check it out at https://github.com/dergachev/screengif

Resources

Related Ideas

  • Extend https://github.com/dergachev/copy-public-url folder action for this use case
    • it would automate the conversion before copying Dropbox public URL
    • assign the folder action to ~/Dropbox/Public/Screenshots/gif
    • consider finding a way to simplify the dependency installation

GIF-Screencast-OSX performance testing

I was disappointed with the color and quality that ffmpeg's GIF conversion gives. Imagemagick's convert can also be used to do the conversion, though this has serious performance penalties.

The following details my experiments of converting a 3.8 second movie to a GIF.

FFMPEG to PNG -> CONVERT to GIF individually

  • 42 seconds in CONVERT, did not determine file size
ffmpeg -i in-trimmed.mov -r 10 -vcodec png out-static-%02d.png 
time for img in out-static*.png; do convert -verbose +dither -layers Optimize "$img" "$img.gif" ;  done

FFMPEG to PNG -> CONVERT TO GIF in bulk

ffmpeg -i in-trimmed.mov -r 10 -vcodec png out-static-%02d.png 
time convert -verbose +dither -layers Optimize -resize 600x600\> out-static*.png  GIF:- > out13.gif

FFMPEG to PNG -> CONVERT to GIF in bulk -> gifsicle

ffmpeg -i in-trimmed.mov -r 10 -vcodec png out-static-%02d.png 
time convert -verbose +dither -layers Optimize -resize 600x600\> out-static*.png  GIF:- | gifsicle --colors 128 --delay=5 --loop --optimize=3 --multifile - > out12.gif

FFMPEG to PPM -> CONVERT to GIF in bulk

ffmpeg -i in-trimmed.mov -r 10 -vcodec ppm out-static-%02d.ppm
time convert -verbose +dither -layers Optimize -resize 600x600\> out-static*.ppm  GIF:- > out14.gif

FFMPEG to PPM -> CONVERT to GIF in bulk -> gifsicle

time ffmpeg -i  in-trimmed.mov -r 10 -f image2pipe -vcodec ppm - |  time convert -verbose +dither -layers Optimize -resize 600x600\> - gif:- | gifsicle --colors 128 --delay=5 --loop --optimize=3 --multifile ->  out15.gif

FFMPEG to GIF -> gifsicle

ffmpeg -i in-trimmed.mov -vf "scale=min(iw\,600):-1" -pix_fmt rgb24 -r 10 -f gif - | gifsicle --optimize=3 --delay=7 --colors 128 > out16.gif

Notes

  • Omitting resizing down to 600x600 before converting to GIF dramatically slows down CONVERT.
  • PPM is the only image format that is compatible with FFMPEG piping directly to CONVERT
    • it has the same performance and compression characteristics as outputting to PNG
    • it avoids creating and cleaning up temporary image files
    • otherwise the temporary files would need to be sorted by numeric order before globbing

Resources

@sergey-alekseev
Copy link

Gifify is faster and easier to install and worked much better for me.

@claudiopro
Copy link

Just did this today on Sierra and the commands to install XQuartz on Mac OS X now are:

brew cask install xquartz
open /usr/local/Caskroom/xquartz/2.7.11/XQuartz.pkg

@jonathan-lg
Copy link

jonathan-lg commented Dec 1, 2017

This works like a charm with the change needed for xquartz.

I skipped the following: open /usr/local/Caskroom/xquartz/2.7.11/XQuartz.pkg

It appears homebrew now does that for you:

==> Installing Cask xquartz
==> Running installer for xquartz; your password may be necessary.

Thanks!!

@motatoes
Copy link

Thanks! I needed this for a Medium post ;) But I noticed that the speed is doubled; is that due to the 10fps option ?

@robfraz
Copy link

robfraz commented Aug 9, 2018

Great instructions - many thanks!

@betandr
Copy link

betandr commented Mar 5, 2019

Is it brew cask install xquartz rather than x-quartz?

@thiagodiogo
Copy link

Great tips! Thanks!

@agustin107
Copy link

Useful! Thanks!

@vinnyA3
Copy link

vinnyA3 commented Jun 3, 2019

@betandr yeah, gist should be updated

@dergachev
Copy link
Author

@vinnyA3 updated :)

@andrewar-sportsbet
Copy link

I did not need to install xQuartz, just a straight forward
brew install gifsicle
did the job

@mrk-han
Copy link

mrk-han commented Aug 23, 2019

Made a small bash script to save to ~/Downloads/ and prompts for path to .mov and .gif name.

To use: option + right click on .mov file to get the PATH. Then Paste that path into the script when it asks for a .mov

Then, Enter a name for your .gif file. The script will append .gif to the name given.

#!/usr/bin/env bash

if ! [ -x "$(command -v ffmpeg)" ]; then
  echo 'Error: ffmpeg is not installed. please install with (brew install ffmpeg)' >&2
  exit 1
fi

if ! [ -x "$(command -v gifsicle)" ]; then
  echo 'Error: gifsicle is not installed. please install with (brew install gifsicle)' >&2
  exit 1
fi


echo -n "enter absolute path to .mov: "
read -r MOVNAME

echo -n "enter name for your gif (the script will add the extension for you): "
read -r GIFNAME

ffmpeg -i "$MOVNAME" -pix_fmt rgb24 -r 10 -f gif - | gifsicle --optimize=3 --delay=3 > ~/Downloads/"$GIFNAME".gif

echo -e "\nSaved ${GIFNAME} to downloads"

Changelog:

  • Removed -s 600x400 to not distort when taking videos of emulators/simulators or things with different dimensions.
  • Added check for gifsicle and ffmpeg

EDIT Also, to confirm what @andrewar-sportsbet said, I also did not need to install xQuartz. I just did brew install ffmpeg and brew install gifsicle. Also, edited script to check for these.

@Floriferous
Copy link

I had to use --delay=10 to get the proper frame rate combo and speed. According to gifsicle's manual:

--delay time: Set the delay between frames to time in hundredths of a second.

So if you change the framerate to 10fps, you want 10 hundredths of a second between each frame :)

@ursetto
Copy link

ursetto commented Dec 14, 2019

Be aware that brew install ffmpeg brings in a million dependencies, including for some bizarre reason, gnutls and unbound (a validating, recursive, caching DNS resolver):

  • aom, libpng, freetype, fontconfig, frei0r, gettext, libidn2, libtasn1, p11-kit, libevent, unbound, gnutls, lame, fribidi, glib, pixman, cairo, graphite2, harfbuzz, libass, libbluray, libsoxr, libvidstab, libogg, libvpx, opencore-amr, jpeg, libtiff, little-cms2, openjpeg, opus, rtmpdump, flac, libsamplerate, rubberband, sdl2, snappy, giflib, webp, leptonica, tesseract, theora, x264, x265 and xvid

That said, this worked really well. I used --delay=10 too.

@ThomasKientz
Copy link

You can find xquartz here if you installed it with homebrew v2.2+,

/usr/local/Caskroom/xquartz/2.7.11/XQuartz.pkg

@fernandodev
Copy link

❤️ It works like a charm!

@rattrayalex-stripe
Copy link

rattrayalex-stripe commented Mar 19, 2020

I got Incompatible pixel format 'rgb24' for codec 'gif', auto-selecting format 'rgb8' despite installing gifsicle, upgrading ffmpeg, etc.

Here is my version for slightly higher-quality output (my use-case is short screencasts of UI interactions for sharing on Github PR's). Note it is slower.

mov2gif() {
  out="$(echo $1 | sed 's/\.mov$/\.gif/')"
  max_width="650"
  frames_per_second="20"
  ffmpeg -i $1 -vf "scale=min(iw\,$max_width):-1" -r "$frames_per_second" -sws_flags lanczos -f image2pipe -vcodec ppm - \
    | convert -delay 5 -layers Optimize -loop 0 - "$out" &&
  echo "$(tput setaf 2)output file: $out$(tput sgr 0)" &&
  open -a Google\ Chrome $out
}

@kwv
Copy link

kwv commented Mar 30, 2020

I got Incompatible pixel format 'rgb24' for codec 'gif', auto-selecting format 'rgb8' despite installing gifsicle, upgrading ffmpeg, etc.

Here is my version for slightly higher-quality output (my use-case is short screencasts of UI interactions for sharing on Github PR's). Note it is slower.

mov2gif() {
  out="$(echo $1 | sed 's/\.mov$/\.gif/')"
  max_width="650"
  frames_per_second="20"
  ffmpeg -i $1 -vf "scale=min(iw\,$max_width):-1" -r "$frames_per_second" -sws_flags lanczos -f image2pipe -vcodec ppm - \
    | convert -delay 5 -layers Optimize -loop 0 - "$out" &&
  echo "$(tput setaf 2)output file: $out$(tput sgr 0)" &&
  open -a Google\ Chrome $out
}

for future readers - convert is from imagemagick (installed via brew install imagemagick)

@felipebn
Copy link

To avoid installing the dependencies if you don't need them, use docker:

docker run -v $PWD:/tmp jrottenberg/ffmpeg \
        -stats  \
        -i /tmp/file.mov \
        -vf "scale=min(iw\,650):-1" -r 20 \
        -f gif - > file.gif

The file must be in the same directory as you run, if not you need to provide the right volume configuration (see docker volume documentation).

@crazymanish
Copy link

For macOS, we have built one small app: https://github.com/joshdholtz/crunchygif

@mattneub
Copy link

What @mrk-han said. I didn't need to do anything other than brew install ffmpeg and brew install gifsicle and away we go with the one-liner, which you can easily tweak to fix the size and the output delay framerate.

@ugultopu
Copy link

ugultopu commented Dec 2, 2020

ffmpeg -i in.mov -r 10 -f gif - | gifsicle --optimize=3 > out.gif was enough for me. Also you need to run brew install ffmpeg gifsicle before running the command.

@tapadipti
Copy link

ffmpeg -i in.mov -r 10 -f gif - | gifsicle --optimize=3 > out.gif worked well. Thanks @ugultopu

@piglovesx
Copy link

cool

@yuis-ice
Copy link

yuis-ice commented Feb 2, 2022

Thanks dude.

@maheshmnj
Copy link

Awesome!
few things that I could collect to use ffmpeg from terminal

  • brew install gifsicle
  • brew install ffmpeg I already had it, you can verify by typing ffmpeg on terminal (install only if you see command not found)
  • It works fine with mp4 and mov format

@dergachev
Copy link
Author

The dropbox link at the top of this gist died long ago. (Thanks Dropbox for removing features).
Here's a hack to bring it back: uploading image as a comment to this gist, then linking the resulting URL from the markdown in the gist itself. :)

OBDHSF-KJDFKJS-screencapture

@SudKul
Copy link

SudKul commented May 9, 2022

brew cask install xquartz has been updated to brew install xquartz --cask

@newbie-lad
Copy link

Thanks, Dropbox!

brew cask install xquartz has been updated to brew install xquartz --cask
Should be updated to something else ;-)

@eddieajau
Copy link

Gifski ftw!

  • Record with Quicktime.
  • Drag-and-drop the recording into Gifski.
  • Tweak your options.
  • Generate gif.
  • Tweak again.
  • Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment