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?

2 Answers 2

8

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.
    – MBR
    Commented Sep 3, 2013 at 13:39
2

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)
    – Scott
    Commented Nov 4, 2017 at 2:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .