9

I've recorded a .gif of the screen with ffmpeg. I've used gifsicle and imagemagick to compress it a bit, but it's still to big. My intent is making it small by removing, say, a frame every 2 frames, so that the total count of frames will be halved.

I couldn't find a way to do it, neither with gifsicle nor with imagemagick. man pages didn't help.

How can I remove a frame from a .gif animation every n frames?

3 Answers 3

9

There is probably a better way to do it, but here is what I would do

First, split your animation in frames

convert animation.gif +adjoin temp_%02d.gif 

Then, select one over n frames with a small for-loop in which you loop over all the frames, you check if it is divisible by 2 and if so you copy it in a new temporary file.

j=0; for i in $(ls temp_*gif); do if [ $(( $j%2 )) -eq 0 ]; then cp $i sel_`printf %02d $j`.gif; fi; j=$(echo "$j+1" | bc); done 

If you prefer to keep all the non-divisible numbers (and so if you want to delete rather than to keep every nth frame), replace -eq by -ne.

And once you done it, create your new animation from the selected frames

convert -delay 20 $( ls sel_*) new_animation.gif 

You can make a small script convert.sh easily, which would be something like that

#!/bin/bash animtoconvert=$1 nframe=$2 fps=$3 # Split in frames convert $animtoconvert +adjoin temp_%02d.gif # select the frames for the new animation j=0 for i in $(ls temp_*gif); do if [ $(( $j%${nframe} )) -eq 0 ]; then cp $i sel_`printf %02d $j`.gif; fi; j=$(echo "$j+1" | bc); done # Create the new animation & clean up everything convert -delay $fps $( ls sel_*) new_animation.gif rm temp_* sel_* 

And then just call, for example

$ convert.sh youranimation.gif 2 20 
2
  • Thanks for the reply. The script is very nice, but unfortunately no matter what the my .gifs cannot be read by imagemagick as they are corrupted! They work fine in a browser, but imagemagick complains. I've tried recording directly to a .gif and recording to a .mkv and converting afterwards. Still corrupted - is there any way I can fix this "corruption" the gif has? Commented Sep 3, 2013 at 12:57
  • If your file is corrupted, you can get informations using identify; it will (hopefully) tell you which frames are corrupted. Then, for example, you could do something like that if it is the 42th frame that is corrupted: convert 'animation.gif[0-41]' anim_nocorrupt_1.gif and convert 'animation.gif[42-99]' anim_nocorrupt_2.gif and then merge the produced files. Commented Sep 3, 2013 at 13:39
3

There is an alternate version of MBR's answer, as a bash function:

gif_framecount_reducer () { # args: $gif_path $frames_reduction_factor local orig_gif="${1?'Missing GIF filename parameter'}" local reduction_factor=${2?'Missing reduction factor parameter'} # Extracting the delays between each frames local orig_delay=$(gifsicle -I "$orig_gif" | sed -ne 's/.*delay \([0-9.]\+\)s/\1/p' | uniq) # Ensuring this delay is constant [ $(echo "$orig_delay" | wc -l) -ne 1 ] \ && echo "Input GIF doesn't have a fixed framerate" >&2 \ && return 1 # Computing the current and new FPS local new_fps=$(echo "(1/$orig_delay)/$reduction_factor" | bc) # Exploding the animation into individual images in /var/tmp local tmp_frames_prefix="/var/tmp/${orig_gif%.*}_" convert "$orig_gif" -coalesce +adjoin "$tmp_frames_prefix%05d.gif" local frames_count=$(ls "$tmp_frames_prefix"*.gif | wc -l) # Creating a symlink for one frame every $reduction_factor local sel_frames_prefix="/var/tmp/sel_${orig_gif%.*}_" for i in $(seq 0 $reduction_factor $((frames_count-1))); do local suffix=$(printf "%05d.gif" $i) ln -s "$tmp_frames_prefix$suffix" "$sel_frames_prefix$suffix" done # Assembling the new animated GIF from the selected frames convert -delay $new_fps "$sel_frames_prefix"*.gif "${orig_gif%.*}_reduced_x${reduction_factor}.gif" # Cleaning up rm "$tmp_frames_prefix"*.gif "$sel_frames_prefix"*.gif } 

Usage:

gif_framecount_reducer file.gif 2 # reduce its frames count by 2 
1
  • I was able to use this answer, except I had to modify the line that gets the original delay value ($orig_delay), as the animated gifs I was working with, had a 0.05 s delay between MOST frames, but 0.50s delay between one of the frames. I decided to only retain the most common delay value, and I did so by using the line: local orig_delay=$(gifsicle -I "$orig_gif" | sed -ne 's/.*delay \([0-9.]\+\)s/\1/p' | sort | uniq -d | head -n 1) Commented Nov 4, 2017 at 2:55
2

The best way to do it would be with ffmpeg, preferably when you initially create the gif rather than after for the sake of preserving quality.

There is almost no loss of quality thanks to the palettegen/paletteuse filter used (and the trick of setting it to not use a dither pattern), however it can lead to some color information getting lost, but it's better than the alternative which will always apply heavy dither patterns through sws_dither which is extremely damaging for the quality.

As an example here is a 24fps gif exactly 1 second long (so 24 frames). 24 frames 1 second

ffmpeg -i in.gif -vf "fps=12,split[s0][s1];[s0]palettegen=max_colors=256:reserve_transparent=0:stats_mode=diff[p];[s1][p]paletteuse=dither=none" out.gif 

12 frames 1 second

And now it is still exactly 1 second long, but only 12 frames.

ffmpeg -r 48 -i in.gif -vf "fps=24,split[s0][s1];[s0]palettegen=max_colors=256:reserve_transparent=0:stats_mode=diff[p];[s1][p]paletteuse=dither=none" out.gif 

12 frames 0.5 seconds

And this time it's only half a second long, with 12 frames.

For better context of the question.

  • Every other frame from a 24fps gif: fps=24/2
  • Every third frame from a 24fps gif: fps=24/1.5
  • Every fourth frame from a 24fps gif: fps=24/1.33333333333
  • Every fifth frame from a 24fps gif: fps=24/1.25

What you do is divide the source framerate by the n for every nth frame you wish to remove. So 24/3=8.

Then you subtract that value from the source framerate. 24-8=16

So fps=16 would remove every 3rd frame. There's probably a better way to calculate this but i'm not very much of a math guy so this is what I've got.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.