I have a folder full of videos that I want to convert to an animated gifs. ffmpeg/avconv does a bad job of doing it directly, so I instead convert the video to a gif by first outputting each frame as a png and then converting back to gif using imagemagick. The problem is that this results in a large gif in terms of file size. To solve this I want to "drop" every second or nth frame from the gif, either by skipping every image file when converting to a gif or by removing frames from a gif. How can I do this on Ubuntu (13.04) using imagemagick or some other command-line utility?
5 Answers
Here's a simpler solution using gifsicle than JohnB's script:
gifsicle -U input.gif `seq -f "#%g" 0 2 99` -O2 -o output.gif
This command should work in most Unix shells; I've tested it in bash. Replace input.gif
and output.gif
with the input and output file names, and 99
with the number of frames in your animation. (It's OK to use a larger number, but gifsicle will complain about it.)
Some notes:
The
-U
switch will merge frames in the input animation with the preceding ones, so that each frames stands alone and doesn't depend in any others. You really want to do this before doing pretty much anything with animations, otherwise you're likely to get messy results. (If your input animation is already unoptimized, gifsicle may print a warning about it, but this is also completely harmless.)Conversely, the
-O2
switch re-optimizes the output animation to minimize the file size. With JohnB's sample animation, it shrinks the output size down by 27%.The
seq
command just outputs a sequence of numbers from 0 to 99, counting up in steps of 2. The-f "#%g"
makes it print a#
before each number, which makes gifsicle understand it as a frame selection instead of a file name. The backticks (`
) around the seq command cause its output to be included as parameters in the gifsicle command line.
If you don't want the gif to speed up, you can use gifsicle -I input.gif
to get the current frame delay, multiply it by 2 and use gifsicle -d ${delay} ...
.
-
1Nicely done! If you really wanted, you could use the grep from my script to accurately give the number of frames (and make one behemoth of a command). Or perhaps there is a simpler way to return the number of frames in an animated GIF?– JohnBSep 3, 2013 at 17:33
-
3I ended up making a behemoth of a command, @JohnB -
gifsicle input.gif `seq -f "#%g" 0 2 $(identify input.gif | tail -1 | cut -d "[" -f2 - | cut -d "]" -f1 -)` --unoptimize -O2 -o output.gif
Jan 22, 2014 at 5:18 -
On Windows 7 x64 with version 1.71, I got:
useless unoptimization-related input option
. So I did it in two steps (from within Git Bash): 1.gifsicle -U -o unoptimized.gif input.gif
2.gifsicle unoptimized.gif `seq -f "#%g" 0 2 99` -O2 -o output.gif
– fekleeDec 1, 2014 at 21:47
Using a bash script
To do this from the command line, you could use a utility called Gifsicle. There is no built in method to delete every other frame, so you'll need to get your hands dirty with some scripting.
Here is a quick script I made to do just a single GIF:
#!/bin/bash
# This script will take an animated GIF and delete every other frame
# Accepts two parameters: input file and output file
# Usage: ./<scriptfilename> input.gif output.gif
# Make a copy of the file
cp $1 $2
# Get the number of frames
numframes=`gifsicle $1 -I | grep -P "\d+ images" --only-matching | grep -P "\d+" --only-matching`
# Deletion
let i=0
while [[ $i -lt $numframes ]]; do
rem=$(( $i % 2 ))
if [ $rem -eq 0 ]
then
gifsicle $2 --delete "#"$(($i/2)) -o $2
fi
let i=i+1
done
I tested it out with a simple countdown GIF:
And here is the result after running it through the script:
This script is of course not bulletproof, but it should lead you in the right direction.
-
Note that your deletion loop can be simplified to just
let i=0; while [[ $i -lt $(($numframes / 2)) ]]; do gifsicle $2 --delete "#$i" -o $2; let i=i+1; done
Sep 3, 2013 at 17:00 -
1In fact, you don't need a loop at all:
gifsicle "$1" --unoptimize $(seq -f "#%g" 0 2 $numframes) -O2 -o "$2"
will do it in one call. Sep 3, 2013 at 17:12 -
I actually don't know how to run this script from the command line. I tried to save it as gifdrop.sh file and run it as instructed in the usage (./gifdrop.sh in.gif out.gif) on a gif called in.gif and it said unknown command gifdrop– mheaversMar 29, 2015 at 19:25
-
-
1Works better on macOS: github.com/colindean/hejmo/blob/master/scripts/… - my version that uses perl instead of grep so that it works on macOS which doesn't have GNU grep installed by default. Also, handles spaces in the filenames. Apr 25, 2019 at 17:13
NOTE: This answer was posted before there was a command line/open source requirement, but I'm leaving it up as it may help someone else in the future
Using Photoshop
This is not an open source or command line solution, but you can do this with Photoshop:
File ▸ Import ▸ Video Frames to Layers...
"Limit To Every __ Frames" will do the trick for you
I think I need new more sophisticated explanation (frame delay is also important thing that should be considered too)... For this new process you'll need to download JREPL.BAT from this link : http://www.dostips.com/forum/viewtopic.php?t=6044
Firstly copy captured gif into input.gif file then start command interpreter and type :
gifsicle input.gif -I "#-1" >input.txt
look into input.txt and see what is the length of animation - how many frames it contains... also look into delay of frame and if its 0.07s that means that you should put option -d14 (7msec*2) into this line in process.cmd : gifsicle -b -U -d14 input.gif , after edit save process.cmd
then edit test.bat and change value of variable 166 from this line to match to number of frames of animation : for /L %%i IN (1,2,166) DO echo "#%%i">> input.bat , after edit save test.bat...
then start process.cmd and croped animation with every odd frame will be processed into file input.gif
P.S. The advantage of this method is also that you have full control of what do you want to delete from gif (every second frame (1,2,166), or every third (1,3,166)), and so on just change middle number in batch line, but be aware that if you specify (1,3,166), you should accordingly change -d14 (delay option) from process.cmd to reflect change (7msec*3/2 = 10), so instead of (7msec*2/1 = 14), option for delay should be -d10...
Other files (for example of removing every second frame) : test.bat :
@echo off
echo gifsicle -b input.gif --delete>> input.bat
for /L %%i IN (1,2,166) DO echo "#%%i">> input.bat
echo --done>> input.bat
call jrepl.bat "\n" "" /x /m /f input.bat /o -
process.cmd :
gifsicle -b -U -d14 input.gif
call "test.bat"
call "input.bat"
gifsicle -b -O3 input.gif
erase "input.bat"
And helper readme file input.txt :
* input.gif 166 images
logical screen 1366x768
global color table [256]
background 15
loop forever
+ image #165 1x1 at 1365,767 transparent 15
disposal asis delay 0.07s
Here is my solution using batch script processing...
Firstly copy captured-original animated gif into input.gif file then start command interpreter and type :
gifsicle input.gif -I "#-1" >input.txt
look into input.txt and see what is the length of animation - how many frames it contains...
input.txt* input.gif 166 images
logical screen 1366x768
global color table [256]
background 15
loop forever
+ image #165 1x1 at 1365,767 transparent 15
disposal asis delay 0.07s
then edit-create test.bat and change value of variable len=specify_length_number_from_input.txt and save test.bat...
test.bat@echo off
set /A len=166
set /A i=1
set /A ii=0
:loop
if %i%==%len% goto :eof
if %ii%==0 (set /A ii=1) else (set /A ii=0)
set /A iii=%ii%*%i%
if %i%==%iii% echo gifsicle -b input.gif --delete "#1" --done
set /A i=%i%+1
goto :loop
then finally start process1.bat and process2.bat and croped animation with every odd frame will be processed into file input.gif
process1.batgifsicle -b -U input.gif
test.bat > input.bat
process2.bat
call "input.bat"
gifsicle -b -O2 input.gif
erase "input.bat"