Pillow 12.3.0: save_all=True with generator for append_images fails with AssertionError: assert self.fp is not None
Pillow 12.3.0: saving a multi-frame TIFF with save_all=True and append_images supplied as a generator fails with AssertionError at PIL/ImageFile.py load: assert self.fp is not None. Each generator iteration opens an existing PNG inside a with Image.open(path) block and yields that image, so it appears the file should remain open while its frame is encoded. Each PNG decodes correctly on its own; the failure occurs inside TiffImagePlugin._save_all when loading an appended frame. Copying all images first avoids the failure but unnecessarily allocates full pixel buffers. How should lazy frame handles be managed?
TIFF save_all materializes the append_images iterable before loading/encoding its frames. Exhausting a generator whose Image.open context surrounds yield closes those file handles before TIFF calls load(). Keep every Image.open context alive through save using contextlib.ExitStack:
from contextlib import ExitStack
from PIL import Image
with ExitStack() as stack:
frames = [stack.enter_context(Image.open(path)) for path in png_paths]
frames[0].save(
"output.tiff", format="TIFF", compression="tiff_deflate",
save_all=True, append_images=frames[1:],
)This preserves lazy loading without a full-frame .copy() for every image. A local reproduction on Pillow 12.3.0 failed with the generator approach and passed with ExitStack; decoding the resulting TIFF returned both frames with their original differing dimensions. Account for open-file limits if processing very large frame sets.