What did you do?
Loaded an animated webp to display it as an animation
What did you expect to happen?
'duration' data should be populated within the image on load, and refreshed when seeking to another file. This is what happens with an animated gif.
What actually happened?
'duration' data in the info struct is not populated until the current frame is consumed by calling convert('RGB'). Other methods can probably trigger this; I didn't investigate into what exactly loads info. For animated gifs and APNG, the info struct is populated immediately on seek().
What are your OS, Python and Pillow versions?
- OS: Ubuntu 24.04.4 LTS (also tested on Windows 10)
- Python: Python 3.12.3
- Pillow: 12.3.0
--------------------------------------------------------------------
Pillow 12.1.1
Python 3.12.3 (main, Jun 19 2026, 12:46:00) [GCC 13.3.0]
--------------------------------------------------------------------
Python executable is /home/qaz/python-venv/bin/python3
Environment Python files loaded from /home/qaz/python-venv
System Python files loaded from /usr
--------------------------------------------------------------------
Python Pillow modules loaded from /home/qaz/python-venv/lib/python3.12/site-packages/PIL
Binary Pillow modules loaded from /home/qaz/python-venv/lib/python3.12/site-packages/PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 12.1.1
*** TKINTER support not installed
--- FREETYPE2 support ok, loaded 2.14.1
--- LITTLECMS2 support ok, loaded 2.17
--- WEBP support ok, loaded 1.6.0
--- AVIF support ok, loaded 1.3.0
--- JPEG support ok, compiled for libjpeg-turbo 3.1.3
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.4
--- ZLIB (PNG/ZIP) support ok, loaded 1.3, compiled for zlib-ng 2.3.2
--- LIBTIFF support ok, loaded 4.7.1
--- RAQM (Bidirectional Text) support ok, loaded 0.10.3, fribidi 1.0.13, harfbuzz 12.3.0
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------
import os, sys
import io
from PIL import Image
filenames = ['clock.gif', 'clock.webp']
for fname in filenames:
print("Open:", fname)
img = Image.open(fname)
is_animated = getattr(img, "is_animated", False)
count = img.n_frames
delays = [None] * count
for i in range(count):
img.seek(i)
print(i, "img info is", img.info) # --for the gif, the img.info includes 'duration'
delays[i] = img.info.get('duration', '-')
frame = img.convert('RGB')
print(i, "img info is now", img.info) # --This loads the first frame for WEBP
print(f'Animated? {is_animated}. Frames: {count}. Delays[0]: {delays[0]}. Delays[1]: {delays[1]}. Delays[5]: {delays[5]}')
print("Finished with", fname, "\n")
img.close()
'clock.gif' and 'clock.webp' are just animated images I generated for testing.
https://github.com/user-attachments/assets/38429bab-ceeb-40aa-8026-35a68b79dd2a"
https://github.com/user-attachments/assets/1bf95ff8-3865-4292-959a-7ffa6d0963e0"
Sample relevant output:
Gif:
Open: clock.gif
0 img info is {'version': b'GIF89a', 'background': 0, 'loop': 0, 'duration': 1000, 'extension': (b'NETSCAPE2.0', 795)}
0 img info is now {'version': b'GIF89a', 'background': 0, 'loop': 0, 'duration': 1000, 'extension': (b'NETSCAPE2.0', 795)}
1 img info is {'version': b'GIF89a', 'background': 0, 'loop': 0, 'duration': 1000}
1 img info is now {'version': b'GIF89a', 'background': 0, 'loop': 0, 'duration': 1000}
...
5 img info is {'version': b'GIF89a', 'background': 0, 'loop': 0, 'duration': 5000}
5 img info is now {'version': b'GIF89a', 'background': 0, 'loop': 0, 'duration': 5000}
Webp:
Open: clock.webp
0 img info is {'loop': 0, 'background': (0, 0, 0, 0)}
0 img info is now {'loop': 0, 'background': (0, 0, 0, 0), 'timestamp': 0, 'duration': 1000}
1 img info is {'loop': 0, 'background': (0, 0, 0, 0), 'timestamp': 0, 'duration': 1000}
1 img info is now {'loop': 0, 'background': (0, 0, 0, 0), 'timestamp': 1000, 'duration': 1000}
...
5 img info is {'loop': 0, 'background': (0, 0, 0, 0), 'timestamp': 4000, 'duration': 1000}
5 img info is now {'loop': 0, 'background': (0, 0, 0, 0), 'timestamp': 5000, 'duration': 5000}
For the GIF, the duration is immediately available after seeking. For the webp, seeking does not update/populate duration, so it is showing the previous frame's data (or nothing at all for the first frame) until img.convert('RGB') is called.
What did you do?
Loaded an animated webp to display it as an animation
What did you expect to happen?
'duration' data should be populated within the image on load, and refreshed when seeking to another file. This is what happens with an animated gif.
What actually happened?
'duration' data in the info struct is not populated until the current frame is consumed by calling
convert('RGB'). Other methods can probably trigger this; I didn't investigate into what exactly loads info. For animated gifs and APNG, the info struct is populated immediately on seek().What are your OS, Python and Pillow versions?
'clock.gif' and 'clock.webp' are just animated images I generated for testing.
https://github.com/user-attachments/assets/38429bab-ceeb-40aa-8026-35a68b79dd2a"
https://github.com/user-attachments/assets/1bf95ff8-3865-4292-959a-7ffa6d0963e0"
Sample relevant output:
Gif:
Webp:
For the GIF, the duration is immediately available after seeking. For the webp, seeking does not update/populate duration, so it is showing the previous frame's data (or nothing at all for the first frame) until
img.convert('RGB')is called.