Remove nth frames of a gif (remove a frame every n frames)

5,605

Solution 1

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

Solution 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
Share:
5,605

Related videos on Youtube

Vittorio Romeo
Author by

Vittorio Romeo

Updated on September 18, 2022

Comments

  • Vittorio Romeo
    Vittorio Romeo almost 2 years

    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?

  • Vittorio Romeo
    Vittorio Romeo almost 11 years
    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?
  • Scott
    Scott over 6 years
    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)