13

I am looking some kind method to get gif frames number. I am looking on Google, StackOverflow and any other sites and I find only rubbish! Someone know how to do it? I need only simple number of gif frames.

OMGtechy
  • 7,935
  • 8
  • 48
  • 83
Kula
  • 155
  • 1
  • 1
  • 3

7 Answers7

23

Which method are you using to load/manipulate the frame? Are you using PIL? If not, I suggest checking it out: Python Imaging Library and specifically the PIL gif page.

Now, assuming you are using PIL to read in the gif, it's a pretty simple matter to determine which frame you are looking at. seek will go to a specific frame and tell will return which frame you are looking at.

from PIL import Image
im = Image.open("animation.gif")

# To iterate through the entire gif
try:
    while 1:
        im.seek(im.tell()+1)
        # do something to im
except EOFError:
    pass # end of sequence

Otherwise, I believe you can only find the number of frames in the gif by seeking until an exception (EOFError) is raised.

Chris W.
  • 37,583
  • 36
  • 99
  • 136
Derek Springer
  • 2,666
  • 1
  • 14
  • 12
  • 2
    I had to change "import Image" into "from PIL import Image" to make it work (I'm guessing I have a recent Pillow version installed) – sellibitze Feb 04 '17 at 10:54
  • See my [answer](https://stackoverflow.com/questions/7503567/python-how-i-can-get-gif-frames/60121528#60121528) below if you need to load an image from a byte array. – asm Feb 07 '20 at 21:50
  • 1
    I just want to add that you should be able to get the number of frames using im.n_frames (so the above code could just simply use a "for loop" instead). – Abang F. Apr 06 '20 at 08:34
  • Warning: if two adjacent frames are identical, this method will yield only one frame! – Daniel Chin Apr 30 '22 at 15:16
13

Just parse the file, gifs are pretty simple:

class GIFError(Exception): pass

def get_gif_num_frames(filename):
    frames = 0
    with open(filename, 'rb') as f:
        if f.read(6) not in ('GIF87a', 'GIF89a'):
            raise GIFError('not a valid GIF file')
        f.seek(4, 1)
        def skip_color_table(flags):
            if flags & 0x80: f.seek(3 << ((flags & 7) + 1), 1)
        flags = ord(f.read(1))
        f.seek(2, 1)
        skip_color_table(flags)
        while True:
            block = f.read(1)
            if block == ';': break
            if block == '!': f.seek(1, 1)
            elif block == ',':
                frames += 1
                f.seek(8, 1)
                skip_color_table(ord(f.read(1)))
                f.seek(1, 1)
            else: raise GIFError('unknown block type')
            while True:
                l = ord(f.read(1))
                if not l: break
                f.seek(l, 1)
    return frames
adw
  • 4,901
  • 1
  • 25
  • 18
  • I'm assuming this requires PIL, I tried using this on a 44 frame `gif` file, and it returned 0 on me... (I counted the frames using Preview, but I need some code to get me the info on over 2500 images)... – Zizouz212 Feb 15 '15 at 19:19
  • 1
    @Zizouz212: This worked for me with the `small_globe.gif` test file I used in [my answer](http://stackoverflow.com/a/28549748/355230) to another question of yours. Please upload your 44 frame gif image file somewhere (like in one of your questions), and then let me know so I can download it and see if I can figure out what's causing the problem. – martineau Feb 19 '15 at 16:11
  • In Python 3+ you have to change the strings to byte strings or the comparisons will return `False`. E.g. `'GIF87a'` >> `b'GIF87a'`, `','` >> `b','`, etc. – Benedikt Jan 21 '20 at 12:35
6

I was faced with the same problem recently and found the documentation on GIFs particularly lacking. Here's my solution using imageio's get_reader to read the bytes of an image (useful if you just fetched the image via HTTP, for example) which conveniently stores frames in numpy matrices:

import imageio
gif = imageio.get_reader(image_bytes, '.gif')

# Here's the number you're looking for
number_of_frames = len(gif)

for frame in gif:
  # each frame is a numpy matrix

If you just need to open a file, use:

gif = imageio.get_reader('cat.gif')
asm
  • 718
  • 7
  • 9
2

If you are using PIL (Python Imaging Library) you can use the n_frames attribute of an image object.

See this answer.

nik7
  • 806
  • 3
  • 12
  • 20
mofoe
  • 3,634
  • 1
  • 16
  • 16
1

Ok, 9 years maybe are a little too much time, but here is my answer

import tkinter as tk
from PIL import Image  

    

def number_of_frames(gif):
    "Prints and returns the number of frames of the gif"
    print(gif.n_frames)
    return gif.n_frames


def update(ind):
    global root, label

    frame = frames[ind]
    ind += 1
    if ind == frameCnt:
        ind = 0
    label.configure(image=frame)
    root.after(100, update, ind)


file = Image.open("001.gif")
frameCnt = number_of_frames(file)
root = tk.Tk()
frames = [tk.PhotoImage( file='001.gif', format = f'gif -index {i}')
            for i in range(frameCnt)]
label = tk.Label(root)
label.pack()
root.after(0, update, 0)
root.mainloop()
PythonProgrammi
  • 22,305
  • 3
  • 41
  • 34
1

I did some timings on the currently proposed answers, which might be of interest:

  • Pillow seek: 13.2 ms ± 58.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  • Custom parsing: 115 µs ± 647 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
  • Pillow n_frames: 13.2 ms ± 169 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  • ImageIO improps (via pillow): 13.1 ms ± 23.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

So despite being pure python, custom parsing is about 100x faster than using pillow ... interesting. Among the other solutions, I like the ImageIO one, because it is short; however, I'm one of the devs there so I am obviously biased.


Setup code:

# get a test GIF (as in-memory stream to avoid measuring file IO)
import imageio.v3 as iio
import io

gif_array = iio.imread("imageio:newtonscradle.gif", index=None)
test_file = io.BytesIO()
iio.imwrite(test_file, gif_array, format="GIF")

# ImageIO is more strict with file handling and would close test_file
# It does handle byte strings natively though, so we can pass that for timings
gif_bytes = iio.imwrite("<bytes>", gif_array, format="GIF")

Pillow seek:

%%timeit

from PIL import Image

test_file.seek(0) # reset file

n_frames = 1  # opening returns the first frame
with Image.open(test_file) as file:
    # To iterate through the entire gif
    try:
        while 1:
            file.seek(file.tell()+1)
            n_frames += 1
            # do something to im
    except EOFError:
        pass # end of sequence

assert n_frames == 36

Custom Parsing

%%timeit

class GIFError(Exception): pass

def get_gif_num_frames(filename):
    frames = 0
    with io.BytesIO(filename) as f:  # I modified this line to side-step measuring IO
        if f.read(6) not in (b'GIF87a', b'GIF89a'): # I added b to mark these as byte strings
            raise GIFError('not a valid GIF file')
        f.seek(4, 1)
        def skip_color_table(flags):
            if flags & 0x80: f.seek(3 << ((flags & 7) + 1), 1)
        flags = ord(f.read(1))
        f.seek(2, 1)
        skip_color_table(flags)
        while True:
            block = f.read(1)
            if block == b';': break  # I also added a b'' here
            if block == b'!': f.seek(1, 1) # I also added a b'' here 
            elif block == b',':  # I also added a b'' here
                frames += 1
                f.seek(8, 1)
                skip_color_table(ord(f.read(1)))
                f.seek(1, 1)
            else: raise GIFError('unknown block type')
            while True:
                l = ord(f.read(1))
                if not l: break
                f.seek(l, 1)
    return frames

n_frames = get_gif_num_frames(gif_bytes)
assert n_frames == 36

Pillow n_frames:

%%timeit

from PIL import Image

test_file.seek(0) # reset file

with Image.open(test_file) as file:
    # To iterate through the entire gif
    n_frames = file.n_frames

assert n_frames == 36

ImageIO's improps (via pillow):

%%timeit

import imageio.v3 as iio

props = iio.improps(gif_bytes, index=None)
n_frames = props.shape[0]
assert n_frames == 36

There is also a new PyAV based plugin I'm writing which is faster than pillow, but still slower than the pure-python approach. Syntax-wise it's pretty similar to the ImageIO (via pillow) approach:

%%timeit
# IIO improps (via PyAV)
# 507 µs ± 17.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
# Note: at the time of this writing this approach is still a PR.
#       it needs documentation and test coverage before being merged.

import imageio.v3 as iio

props = iio.improps(gif_bytes, index=None, plugin="pyav")
n_frames = props.shape[0]
assert n_frames == 36
FirefoxMetzger
  • 2,880
  • 1
  • 18
  • 32
0

Here's some code that will get you a list with the duration value for each frame in the GIF:

from PIL import Image
gif_image = Image.open("animation.gif")
metadata = []

for i in range(gif_image.n_frames):
    gif_image.seek(i)
    duration = gif_image.info.get("duration", 0)
    metadata.append(duration)

You can modify the above code to also capture other data from each frame such as background color index, transparency, or version. The info dictionary on each frame looks like this:

{'version': b'GIF89a', 'background': 0, 'transparency': 100, 'duration': 70}
kmans
  • 61
  • 1
  • 1