rss logo

The ultimate guide to make your own GIF on GNU/Linux

Intro

There are already a lot of GIF available on the internet but often it's hard to get the one we're looking for, also, some are bad quality. So here a complete tutorial to see how we can make our own GIF on GNU/Linux.

Install tools

  • On Debian
root@host:~# apt install imagemagick ffmpeg

Make a GIF

Extract a video to png files

  • Frames : 10 per second
  • Scale : 320
  • Start time : 2:14
  • Duration : 6 seconds
user@host:~$ ffmpeg -ss 2:14 -t 6 -i /movies/1988.Rambo.III.MULTI.x264.1080p.mkv -vf fps=10,scale=320:-1 $filename%05d.png

Remove unwanted frames

Remove unwanted frames

Convert png files to one GIF file

user@host:~$ convert -loop 0 *.png myimage.gif

Enjoy the result

Sylvester Stallone in Rambo III movie putting his red ruban

Remove black bars

Some movies have black bars that you may want to remove.

Extract a video to png files

user@host:~$ ffmpeg -ss 8:26 -t 9 -i /movies/2000.American.Psycho.MULTI.AC3.1080p.x264.mkv -vf fps=10,scale=320:-1 $filename%05d.png

Crop files

  • Get format informations
user@host:~$ identify 00039.png
00039.png PNG 320x180 320x180+0+0 8-bit sRGB 79528B 0.000u 0:00.000
  • Where :
american psycho frame with Patrick Bateman with the pixel size of the image
  • Crop the frame :
user@host:~$ convert -crop 320x130+0+25 00039.png +repage Cropped_00039.png
  • Which gives :
american psycho frame with Patrick Bateman with the pixel size of the image
  • Crop all frames :
user@host:~$ for i in 000*; do convert -crop 320x130+0+25 "$i" +repage Cropped_"$i"; done

Make your GIF

user@host:~$ convert -loop 0 Cropped*.png myimage.gif

Enjoy the result

Christian Bale in American Psycho movie smiling

Add text to your GIF

Extract a video to png files

user@host:~$ ffmpeg -ss 19:24 -t 5 -i /movies/1998.The.Big.Lebowski.MULTI.1080p.Bluray.x264.mkv -vf fps=10,scale=320:-1 $filename%05d.png

Add text to a frame

  • -font : use convert -list font to see availaible fonts
  • -pointsize : text size
  • -annotate +0+0 : text position
user@host:~$ convert 00016.png -font Cantarell-Extra-Bold -gravity south -pointsize 30 -stroke black -fill white -strokewidth 1 -annotate +0+0 'YOU'\''RE\nAN ASSHOLE!!!' Text_00016.png
  • Which gives :
Jeff Bridges in The Big Lebowski movie, you're an asshole!

Add text to multiple frames

  • First copy all frames to new files :
user@host:~$ for i in 000*; do cp "$i" Text_"$i"; done
  • Then put text to all frames you want (example here with frames from 00016.png to 00027.png) :
user@host:~$ for i in 0001[6-9]*; do convert "$i" -font Cantarell-Extra-Bold -gravity south -pointsize 30 -stroke black -fill white -strokewidth 1 -annotate +0+0 'YOU'\''RE\nAN ASSHOLE!!!' Text_"$i"; done
user@host:~$ for i in 0002[0-7]*; do convert "$i" -font Cantarell-Extra-Bold -gravity south -pointsize 30 -stroke black -fill white -strokewidth 1 -annotate +0+0 'YOU'\''RE\nAN ASSHOLE!!!' Text_"$i"; done

Make your GIF

user@host:~$ convert -loop 0 Text_*.png myimage.gif

Enjoy the result

Jeff Bridges in The Big Lebowski movie, you're an asshole!

Impact font

If you want to add text with the classical GIF font you need to install impact font from a Windows system to your GNU/Linux system.

  • From a Windows Computer get the Impact Font (C:\Windows\Fonts\impact.ttf) :
Windows Fonts Directory Impact
  • Create a new directory for Windows fonts :
root@host:~# mkdir /usr/share/fonts/WindowsFonts
  • Copy impact.ttf to the /usr/share/fonts/WindowsFonts new folder :
root@host:~# mv impact.ttf /usr/share/fonts/WindowsFonts/
  • Now, regenerate the fontconfig cache :
user@host:~$ fc-cache --force
  • Let's do a try :
user@host:~$ ffmpeg -ss 10:48 -t 2 -i /movies/1997.Batman.and.Robin.MULTi.1080p.AC3.mkv -vf fps=10,scale=320:-1 $filename%05d.png
user@host:~$ for i in 000*; do cp "$i" Text_"$i"; done
  • Note that we now use -font Impact argument :
user@host:~$ for i in 0000[2-7]*; do convert "$i" -font Impact -gravity south -pointsize "55" -stroke black -fill white -strokewidth 1 -annotate +0+0 'FREEZE...'  Text_"$i"; done
user@host:~$ for i in 0001[0-4]*; do convert "$i" -font Impact -gravity south -pointsize "55" -stroke black -fill white -strokewidth 1 -annotate +0+0 'YOU'\''RE MAD!'  Text_"$i"; done
user@host:~$ convert -loop 0 Text_000* myimage.gif

Enjoy the result

Batman : Freeze, you're mad!

Play with text

Text motion

We can simulate text motion

Extract a video to png files

user@host:~$ ffmpeg -ss 16:43 -t 1 -i /movies/1993.groundhog.day.MULTI.1080p.bluray.x264.mkv -vf fps=10,scale=320:-1 $filename%05d.png

Add text

  • $a : text position
  • a=-250 : text start position
  • ((a+=60)) : text speed
user@host:~$ a=-250;for i in 000*; do convert "$i" -font Cantarell-Extra-Bold -gravity south -pointsize 35 -stroke black -fill white -strokewidth 1 -annotate -"$a"+0 'I'\''M THINKING...' Texted_${i}; ((a+=60)); echo "$a"; done
  • Same stuff but easier to read :
a=-250
for i in 000*
do 
	convert "$i" -font Cantarell-Extra-Bold -gravity south -pointsize 35 -stroke black -fill white -strokewidth 1 -annotate -"$a"+0 'I'\''M THINKING...' Texted_${i}
	a=$((a+60))
	echo "$a"
done

Make your GIF

user@host:~$ convert -loop 0 Texted_*.png myimage.gif

Enjoy the result

Bill Murray in Groundhog Day movie, I'm thinking

Text shaking

We can simulate text shaking

Extract a video to png files

user@host:~$ ffmpeg -ss 45:02 -t 2 -i /movies/1995.ace.ventura.when.nature.calls.MULTI.1080p.x264.ac3.mkv -vf fps=10,scale=320:-1 $filename%05d.png

Add text

  • $posX : text horizontal position
  • $posY : text vertical position
user@host:~$ for i in 000*; do posX=$RANDOM;let 'posX %= 5'; posY=$RANDOM;let 'posY %= 15'; convert "$i" -font Impact -gravity south -pointsize 40 -stroke black -fill white -strokewidth 1 -annotate +"$posX"+"$posY" 'WARRRMMM!' Texted_${i}; done
  • Same stuff but easier to read :
for i in 000*
do
	posX=$RANDOM
	let 'posX %= 5'
	posY=$RANDOM;
	let 'posY %= 15'
	convert "$i" -font Impact -gravity south -pointsize 40 -stroke black -fill white -strokewidth 1 -annotate +"$posX"+"$posY" 'WARRRMMM!' Texted_${i}
done

Make your GIF

user@host:~$ convert -loop 0 Texted_*.png myimage.gif

Enjoy the result

Ace Ventura, Jim Carrey, Warm!!

Text zooming

zoom on text

Extract a video to png files

user@host:~$ ffmpeg -ss 4:09 -t 2 -i /movies/1991.Armour.of.God.2.1080p.x264.mkv -vf fps=10,scale=320:-1 $filename%05d.png

Add text

  • a=20 : text start size
  • ((a+=3)) : text growing speed
user@host:~$ a=20; for i in 000*; do convert "$i" -font Impact -gravity south -pointsize "$a" -stroke black -fill white -strokewidth 1 -annotate +0+0 'OK!' Texted_${i}; ((a+=3)); done
  • Same stuff but easier to read :
a=20
for i in 000*
do
	convert "$i" -font Impact -gravity south -pointsize "$a" -stroke black -fill white -strokewidth 1 -annotate +0+0 'OK!' Texted_${i}
	((a+=3))
done

Make your GIF

user@host:~$ convert -loop 0 Texted_*.png myimage.gif

Enjoy the result

Armour of God II, Jackie Chan, OK!

Simulate Zoom

Intro

Imagemagick tools allow a lot of things as to simulate a zoom. Let's try it.

Extract a video to png files

user@host:~$ ffmpeg -ss 1:00:20 -t 2 -i /movies/1994.Street.Fighter.720P.x264.AC3.mkv -vf fps=10,scale=320:-1 $filename%05d.png

Add Text

  • Go here for details.
user@host:~$ for i in 000*; do cp "$i" Text_"$i"; done
user@host:~$ for i in 0000[7-9]*; do convert "$i" -font Impact -gravity south -pointsize "35" -stroke black -fill white -strokewidth 1 -annotate +0+0 'OF COURSE!'  Text_"$i"; done
user@host:~$ for i in 0001[0-4]*; do convert "$i" -font Impact -gravity south -pointsize "35" -stroke black -fill white -strokewidth 1 -annotate +0+0 'OF COURSE!'  Text_"$i"; done

Zoom

user@host:~$ a=110; b=10; for i in $(seq 20 30); do convert Text_00018.png -resize "$a"% Text_000"$i"_.png; convert -gravity center -crop 320x133+$b+0 +repage Text_000"$i"_.png Text_000"$i".png ;((a+=30)); ((b+=25)); done; rm *_.png
  • Same stuff but easier to read :
    • Text_00018.png : Frame that will be zoomed in
    • a=110 : first Zoom in value
    • b=10 : first abscisse offset to the right value
    • $(seq 20 30) : number of frames to zoom (Text_00020.png to Text_00030.png)
    • ((a+=30)) : zoom increment for each zoomed frame
    • ((b+=25)) : right offset increment for each zoomed frame
    • rm *_.png : delete temporary files
a=110
b=10
for i in $(seq 20 30); do 
	convert convert Text_00018.png -resize "$a"% Text_000"$i"_.png
	convert -gravity center -crop 320x133+$b+0 +repage Text_000"$i"_.png Text_000"$i".png
	((a+=30))
	((b+=25))
done
rm *_.png

Make your GIF

user@host:~$ convert -loop 0 Text_*.png myimage.gif

Enjoy the result

Street Fighter the movie Mr Bison Raul Julia Of Course!

Reduce GIF size

Depending on where you want to publish your GIF, size could matter. For example if I want to share GIF via MMS and keep source quality, size should be lower than 600KB.

Let's see differents way to reduce our GIF.

With normal parameters

user@host:~$ ffmpeg -ss 17:05 -t 8 -i /movies/1994.Pulp.Fiction.MULTI.mkv -vf fps=10,scale=320:-1 $filename%05d.png
user@host:~$ convert -loop 0 00* myimage.gif
user@host:~$ du -sh myimage.gif
2,3M    myimage.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda

Use palettegen

user@host:~$ ffmpeg -y -ss 17:05 -t 8 -i /movies/1994.Pulp.Fiction.MULTI.mkv -vf fps=10,scale=320:-1:flags=lanczos,palettegen palette.png
user@host:~$ ffmpeg -y -ss 17:05 -t 8 -i /movies/1994.Pulp.Fiction.MULTI.mkv -i palette.png -filter_complex "fps=10,scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif
user@host:~$ du -sh output.gif
1,7M    output.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda

Compress with fuzz parameter

It will reduce GIF size and output quality. Good compress rate results with limited motions.

user@host:~$ convert myimage.gif -fuzz 2% -layers Optimize result.gif; du -sh result.gif
user@host:~$ du -sh result.gif
1,5M    result.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda
user@host:~$ convert myimage.gif -fuzz 5% -layers Optimize result.gif
user@host:~$ du -sh result.gif
720K    result.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda

Fuzz + palettegen

user@host:~$ ffmpeg -y -ss 17:05 -t 8 -i /movies/1994.Pulp.Fiction.MULTI.mkv -vf fps=10,scale=320:-1:flags=lanczos,palettegen palette.png
user@host:~$ ffmpeg -y -ss 17:05 -t 8 -i /movies/1994.Pulp.Fiction.MULTI.mkv -i palette.png -filter_complex "fps=10,scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse" out%05d.png
user@host:~$ convert -loop 0 out000* myimage.gif; du -sh myimage.gif
user@host:~$ convert myimage.gif -fuzz 2% -layers Optimize result.gif; du -sh result.gif
1,1M    result.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda
user@host:~$ convert myimage.gif -fuzz 5% -layers Optimize result.gif; du -sh result.gif
536K    result.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda

Reducing scale

We could also scale down our GIF

  • Reduce scale to 240 :
user@host:~$ for i in 00*; do convert "$i" -resize 240x Scalled_$i; done
user@host:~$ convert -loop 0 Scalled_000* myimage.gif; du -sh myimage.gif
1,3M	myimage.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda

Drop frames + Fuzz

user@host:~$ ffmpeg -y -ss 17:05 -t 8 -i /movies/1994.Pulp.Fiction.MULTI.mkv -vf fps=10,scale=320:-1:flags=lanczos,palettegen palette.png
user@host:~$ ffmpeg -y -ss 17:05 -t 8 -i /movies/1994.Pulp.Fiction.MULTI.mkv -i palette.png -filter_complex "fps=10,scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse" out%05d.png
user@host:~$ convert -delay 22 -loop 0 $(ls out000* | grep -E "*[02468].png") myimage.gif; du -sh myimage.gif
1,1M	myimage.gif
user@host:~$ convert myimage.gif -fuzz 5% -layers Optimize result.gif; du -sh result.gif
340K	result.gif
Samuel L. Jackson in Pulp Fiction movie, drinking soda
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Contact :

contact mail address