我正在寻找一种获取gif帧数的方法。我在谷歌、StackOverflow和其他网站上搜索,但找到的都是垃圾!有人知道该怎么做吗?我只需要简单的gif帧数。
你使用的是哪种方法来加载/操作帧?你在使用PIL吗?如果没有,我建议你去看看它:Python Imaging Library,尤其是PIL gif页面。
假设你正在使用PIL读取gif文件,那么确定你所看到的帧非常简单。使用seek将转到特定帧,tell将返回你正在查看的帧。
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
否则,我认为你只能通过寻找直到出现异常(EOFError)来找到gif中的帧数。只需解析文件,GIF文件相当简单:
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
small_globe.gif
测试文件。请将您的44帧gif图像文件上传到某个地方(比如在您的问题中),然后让我知道,以便我可以下载它并查看是否能找出问题的原因。 - martineau'GIF87a'
>> b'GIF87a'
,,
>> b','
,等等。 - Benediktimport 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
gif = imageio.get_reader('cat.gif')
好的,也许9年时间有点太长了,但这是我的答案。
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()
我对目前提出的答案进行了一些计时,以下是结果:
每次循环13.2毫秒±58.3微秒(7次运行的平均值±标准偏差,每个循环100次)
每次循环115微秒±647纳秒(7次运行的平均值±标准偏差,每个循环10000次)
每次循环13.2毫秒±169微秒(7次运行的平均值±标准偏差,每个循环100次)
每次循环13.1毫秒±23.1微秒(7次运行的平均值±标准偏差,每个循环100次)
因此,尽管是纯Python,但自定义解析比使用Pillow快大约100倍...有趣。在其他解决方案中,我喜欢ImageIO,因为它很简短;但是,我是开发人员之一,所以显然有偏见。
设置代码:
# 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
自定义解析
%%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的改进(通过pillow):
%%timeit
import imageio.v3 as iio
props = iio.improps(gif_bytes, index=None)
n_frames = props.shape[0]
assert n_frames == 36
我正在编写一个基于PyAV的新插件,它比pillow更快,但仍然比纯Python方法慢。从语法上讲,它与ImageIO(通过pillow)的方法非常相似:
%%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
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)
您可以修改上述代码,以捕获每个帧中的其他数据,例如背景颜色索引、透明度或版本。每个帧上的info
字典如下所示:
{'version': b'GIF89a', 'background': 0, 'transparency': 100, 'duration': 70}