diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index c9bb92a0f..1b1c2e34e 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -732,19 +732,21 @@ def test_save_all_progress() -> None: out = BytesIO() progress = [] - def callback(state) -> None: - if state["image_filename"]: - state["image_filename"] = os.path.basename(state["image_filename"]) + def callback(state: Image.Progress) -> None: + if state.image_filename: + state = state._replace( + image_filename=os.path.basename(state.image_filename) + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "PNG", save_all=True, progress=callback) assert progress == [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 1, - } + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=1, + ) ] out = BytesIO() @@ -759,21 +761,21 @@ def test_save_all_progress() -> None: expected = [] for i in range(2): expected.append( - { - "image_index": i, - "image_filename": "single_frame.png", - "completed_frames": i + 1, - "total_frames": 7, - } + Image.Progress( + image_index=i, + image_filename="single_frame.png", + completed_frames=i + 1, + total_frames=7, + ) ) for i in range(5): expected.append( - { - "image_index": 2, - "image_filename": "delay.png", - "completed_frames": i + 3, - "total_frames": 7, - } + Image.Progress( + image_index=2, + image_filename="delay.png", + completed_frames=i + 3, + total_frames=7, + ) ) assert progress == expected diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py index 170097bff..54b9cca22 100644 --- a/Tests/test_file_avif.py +++ b/Tests/test_file_avif.py @@ -220,19 +220,21 @@ class TestFileAvif: out = BytesIO() progress = [] - def callback(state) -> None: - if state["image_filename"]: - state["image_filename"] = os.path.basename(state["image_filename"]) + def callback(state: Image.Progress) -> None: + if state.image_filename: + state = state._replace( + image_filename=os.path.basename(state.image_filename) + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "AVIF", save_all=True, progress=callback) assert progress == [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 1, - } + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=1, + ) ] out = BytesIO() @@ -245,20 +247,20 @@ class TestFileAvif: expected = [] for i in range(5): expected.append( - { - "image_index": 0, - "image_filename": "star.avifs", - "completed_frames": i + 1, - "total_frames": 6, - } + Image.Progress( + image_index=0, + image_filename="star.avifs", + completed_frames=i + 1, + total_frames=6, + ) ) expected.append( - { - "image_index": 1, - "image_filename": None, - "completed_frames": 6, - "total_frames": 6, - } + Image.Progress( + image_index=1, + image_filename=None, + completed_frames=6, + total_frames=6, + ) ) assert progress == expected diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 6bbff3af8..ff602aa59 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -323,19 +323,21 @@ def test_save_all_progress() -> None: out = BytesIO() progress = [] - def callback(state) -> None: - if state["image_filename"]: - state["image_filename"] = os.path.basename(state["image_filename"]) + def callback(state: Image.Progress) -> None: + if state.image_filename: + state = state._replace( + image_filename=os.path.basename(state.image_filename) + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "GIF", save_all=True, progress=callback) assert progress == [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 1, - } + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=1, + ) ] out = BytesIO() @@ -345,22 +347,22 @@ def test_save_all_progress() -> None: im = Image.new("RGB", im2.size) im.save(out, "GIF", save_all=True, append_images=[im2], progress=callback) - expected: list[dict[str, int | str | None]] = [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 32, - } + expected: list[Image.Progress] = [ + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=32, + ) ] for i in range(31): expected.append( - { - "image_index": 1, - "image_filename": "chi.gif", - "completed_frames": i + 2, - "total_frames": 32, - } + Image.Progress( + image_index=1, + image_filename="chi.gif", + completed_frames=i + 2, + total_frames=32, + ) ) assert progress == expected diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 2619dac6f..541ae3eac 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -323,21 +323,23 @@ def test_save_all_progress() -> None: out = BytesIO() progress = [] - def callback(state) -> None: - if state["image_filename"]: - state["image_filename"] = ( - state["image_filename"].replace("\\", "/").split("Tests/images/")[-1] + def callback(state: Image.Progress) -> None: + if state.image_filename: + state = state._replace( + image_filename=state.image_filename.replace("\\", "/").split( + "Tests/images/" + )[-1] ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "MPO", save_all=True, progress=callback) assert progress == [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 1, - } + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=1, + ) ] out = BytesIO() @@ -351,12 +353,12 @@ def test_save_all_progress() -> None: for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]): for j in range(2): expected.append( - { - "image_index": i, - "image_filename": filename, - "completed_frames": i * 2 + j + 1, - "total_frames": 4, - } + Image.Progress( + image_index=i, + image_filename=filename, + completed_frames=i * 2 + j + 1, + total_frames=4, + ) ) assert progress == expected diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index a1e7afb3c..65aa13c03 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -183,19 +183,21 @@ def test_save_all_progress() -> None: out = BytesIO() progress = [] - def callback(state) -> None: - if state["image_filename"]: - state["image_filename"] = os.path.basename(state["image_filename"]) + def callback(state: Image.Progress) -> None: + if state.image_filename: + state = state._replace( + image_filename=os.path.basename(state.image_filename) + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "PDF", save_all=True, progress=callback) assert progress == [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 1, - } + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=1, + ) ] out = BytesIO() @@ -209,12 +211,12 @@ def test_save_all_progress() -> None: for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]): for j in range(2): expected.append( - { - "image_index": i, - "image_filename": filename, - "completed_frames": i * 2 + j + 1, - "total_frames": 4, - } + Image.Progress( + image_index=i, + image_filename=filename, + completed_frames=i * 2 + j + 1, + total_frames=4, + ) ) assert progress == expected diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index aad199212..5c9cddaa2 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -789,19 +789,21 @@ class TestFileTiff: out = BytesIO() progress = [] - def callback(state) -> None: - if state["image_filename"]: - state["image_filename"] = os.path.basename(state["image_filename"]) + def callback(state: Image.Progress) -> None: + if state.image_filename: + state = state._replace( + image_filename=os.path.basename(state.image_filename) + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "TIFF", save_all=True, progress=callback) assert progress == [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 1, - } + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=1, + ) ] out = BytesIO() @@ -814,21 +816,21 @@ class TestFileTiff: ) expected = [ - { - "image_index": 0, - "image_filename": "hopper.tif", - "completed_frames": 1, - "total_frames": 4, - } + Image.Progress( + image_index=0, + image_filename="hopper.tif", + completed_frames=1, + total_frames=4, + ) ] for i in range(3): expected.append( - { - "image_index": 1, - "image_filename": "multipage.tiff", - "completed_frames": i + 2, - "total_frames": 4, - } + Image.Progress( + image_index=1, + image_filename="multipage.tiff", + completed_frames=i + 2, + total_frames=4, + ) ) assert progress == expected diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 8437a6f94..ed560b6ee 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -132,19 +132,21 @@ class TestFileWebp: out = BytesIO() progress = [] - def callback(state) -> None: - if state["image_filename"]: - state["image_filename"] = os.path.basename(state["image_filename"]) + def callback(state: Image.Progress) -> None: + if state.image_filename: + state = state._replace( + image_filename=os.path.basename(state.image_filename) + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "WEBP", save_all=True, progress=callback) assert progress == [ - { - "image_index": 0, - "image_filename": None, - "completed_frames": 1, - "total_frames": 1, - } + Image.Progress( + image_index=0, + image_filename=None, + completed_frames=1, + total_frames=1, + ) ] out = BytesIO() @@ -157,20 +159,20 @@ class TestFileWebp: expected = [] for i in range(42): expected.append( - { - "image_index": 0, - "image_filename": "iss634.webp", - "completed_frames": i + 1, - "total_frames": 43, - } + Image.Progress( + image_index=0, + image_filename="iss634.webp", + completed_frames=i + 1, + total_frames=43, + ) ) expected.append( - { - "image_index": 1, - "image_filename": None, - "completed_frames": 43, - "total_frames": 43, - } + Image.Progress( + image_index=1, + image_filename=None, + completed_frames=43, + total_frames=43, + ) ) assert progress == expected diff --git a/src/PIL/Image.py b/src/PIL/Image.py index aaaf230a4..4293dc7e5 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -40,7 +40,7 @@ import tempfile import warnings from collections.abc import MutableMapping from enum import IntEnum -from typing import IO, Protocol, cast +from typing import IO, NamedTuple, Protocol, cast # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -80,6 +80,13 @@ class DecompressionBombError(Exception): pass +class Progress(NamedTuple): + image_index: int + image_filename: str | None + completed_frames: int + total_frames: int + + WARN_POSSIBLE_FORMATS: bool = False # Limit to around a quarter gigabyte for a 24-bit (3 bpp) image @@ -2720,7 +2727,7 @@ class Image: def _save_all_progress( self, - progress, + progress: Callable[[Progress], None] | None, im: Image | None = None, im_index: int = 0, completed: int = 1, @@ -2730,12 +2737,12 @@ class Image: return progress( - { - "image_index": im_index, - "image_filename": getattr(im or self, "filename", None), - "completed_frames": completed, - "total_frames": total, - } + Progress( + image_index=im_index, + image_filename=getattr(im or self, "filename", None), + completed_frames=completed, + total_frames=total, + ) ) def seek(self, frame: int) -> None: