From 6850465d54ce0fb321fc5c1def722aa30f25b1be Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 2 Oct 2023 15:44:43 +1100 Subject: [PATCH 1/5] Added progress callback when save_all is used --- Tests/test_file_apng.py | 32 ++++++++++++++++++++++++++++++++ Tests/test_file_gif.py | 23 +++++++++++++++++++++++ Tests/test_file_mpo.py | 25 +++++++++++++++++++++++++ Tests/test_file_pdf.py | 31 ++++++++++++++++++++++++++++--- Tests/test_file_tiff.py | 28 +++++++++++++++++++++++++++- Tests/test_file_webp.py | 30 +++++++++++++++++++++++++++--- src/PIL/GifImagePlugin.py | 16 ++++++++++++++-- src/PIL/MpoImagePlugin.py | 15 +++++++++++++-- src/PIL/PdfImagePlugin.py | 5 +++++ src/PIL/PngImagePlugin.py | 23 +++++++++++++++++------ src/PIL/TiffImagePlugin.py | 18 ++++++++++++++++-- src/PIL/WebPImagePlugin.py | 5 +++++ 12 files changed, 232 insertions(+), 19 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 8cb9a814ea5..d30df9dc7cb 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -1,3 +1,5 @@ +from io import BytesIO + import pytest from PIL import Image, ImageSequence, PngImagePlugin @@ -663,6 +665,36 @@ def test_apng_save_blend(tmp_path): assert im.getpixel((0, 0)) == (0, 255, 0, 255) +def test_save_all_progress(): + out = BytesIO() + progress = [] + + def callback(filename, frame_number, n_frames): + progress.append((filename, frame_number, n_frames)) + + Image.new("RGB", (1, 1)).save(out, "PNG", save_all=True, progress=callback) + assert progress == [(None, 1, 1)] + + out = BytesIO() + progress = [] + + with Image.open("Tests/images/apng/single_frame.png") as im: + with Image.open("Tests/images/apng/delay.png") as im2: + im.save( + out, "PNG", save_all=True, append_images=[im, im2], progress=callback + ) + + assert progress == [ + ("Tests/images/apng/single_frame.png", 1, 7), + ("Tests/images/apng/single_frame.png", 2, 7), + ("Tests/images/apng/delay.png", 3, 7), + ("Tests/images/apng/delay.png", 4, 7), + ("Tests/images/apng/delay.png", 5, 7), + ("Tests/images/apng/delay.png", 6, 7), + ("Tests/images/apng/delay.png", 7, 7), + ] + + def test_seek_after_close(): im = Image.open("Tests/images/apng/delay.png") im.seek(1) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index d571692b144..9b2280dacb3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -265,6 +265,29 @@ def test_roundtrip_save_all_1(tmp_path): assert reloaded.getpixel((0, 0)) == 255 +def test_save_all_progress(): + out = BytesIO() + progress = [] + + def callback(filename, frame_number, n_frames): + progress.append((filename, frame_number, n_frames)) + + Image.new("RGB", (1, 1)).save(out, "GIF", save_all=True, progress=callback) + assert progress == [(None, 1, 1)] + + out = BytesIO() + progress = [] + + with Image.open("Tests/images/hopper.gif") as im: + with Image.open("Tests/images/chi.gif") as im2: + im.save(out, "GIF", save_all=True, append_images=[im2], progress=callback) + + expected = [("Tests/images/hopper.gif", 1, 32)] + for i in range(31): + expected.append(("Tests/images/chi.gif", i + 2, 32)) + assert progress == expected + + @pytest.mark.parametrize( "path, mode", ( diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 2e921e46701..2497fe0af94 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -278,3 +278,28 @@ def test_save_all(): # Test that a single frame image will not be saved as an MPO jpg = roundtrip(im, save_all=True) assert "mp" not in jpg.info + + +def test_save_all_progress(): + out = BytesIO() + progress = [] + + def callback(filename, frame_number, n_frames): + progress.append((filename, frame_number, n_frames)) + + Image.new("RGB", (1, 1)).save(out, "MPO", save_all=True, progress=callback) + assert progress == [(None, 1, 1)] + + out = BytesIO() + progress = [] + + with Image.open("Tests/images/sugarshack.mpo") as im: + with Image.open("Tests/images/frozenpond.mpo") as im2: + im.save(out, "MPO", save_all=True, append_images=[im2], progress=callback) + + assert progress == [ + ("Tests/images/sugarshack.mpo", 1, 4), + ("Tests/images/sugarshack.mpo", 2, 4), + ("Tests/images/frozenpond.mpo", 3, 4), + ("Tests/images/frozenpond.mpo", 4, 4), + ] diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index ffc392d6b2b..96ef787fd10 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -1,8 +1,8 @@ -import io import os import os.path import tempfile import time +from io import BytesIO import pytest @@ -169,6 +169,31 @@ def im_generator(ims): assert os.path.getsize(outfile) > 0 +def test_save_all_progress(): + out = BytesIO() + progress = [] + + def callback(filename, frame_number, n_frames): + progress.append((filename, frame_number, n_frames)) + + Image.new("RGB", (1, 1)).save(out, "PDF", save_all=True, progress=callback) + assert progress == [(None, 1, 1)] + + out = BytesIO() + progress = [] + + with Image.open("Tests/images/sugarshack.mpo") as im: + with Image.open("Tests/images/frozenpond.mpo") as im2: + im.save(out, "PDF", save_all=True, append_images=[im2], progress=callback) + + assert progress == [ + ("Tests/images/sugarshack.mpo", 1, 4), + ("Tests/images/sugarshack.mpo", 2, 4), + ("Tests/images/frozenpond.mpo", 3, 4), + ("Tests/images/frozenpond.mpo", 4, 4), + ] + + def test_multiframe_normal_save(tmp_path): # Test saving a multiframe image without save_all with Image.open("Tests/images/dispose_bgnd.gif") as im: @@ -323,12 +348,12 @@ def test_pdf_info(tmp_path): def test_pdf_append_to_bytesio(): im = hopper("RGB") - f = io.BytesIO() + f = BytesIO() im.save(f, format="PDF") initial_size = len(f.getvalue()) assert initial_size > 0 im = hopper("P") - f = io.BytesIO(f.getvalue()) + f = BytesIO(f.getvalue()) im.save(f, format="PDF", append=True) assert len(f.getvalue()) > initial_size diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f13436ce868..1e8fa944e16 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -658,7 +658,7 @@ def test_palette(self, mode, tmp_path): with Image.open(outfile) as reloaded: assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) - def test_tiff_save_all(self): + def test_save_all(self): mp = BytesIO() with Image.open("Tests/images/multipage.tiff") as im: im.save(mp, format="tiff", save_all=True) @@ -688,6 +688,32 @@ def im_generator(ims): with Image.open(mp) as reread: assert reread.n_frames == 3 + def test_save_all_progress(self): + out = BytesIO() + progress = [] + + def callback(filename, frame_number, n_frames): + progress.append((filename, frame_number, n_frames)) + + Image.new("RGB", (1, 1)).save(out, "TIFF", save_all=True, progress=callback) + assert progress == [(None, 1, 1)] + + out = BytesIO() + progress = [] + + with Image.open("Tests/images/hopper.tif") as im: + with Image.open("Tests/images/multipage.tiff") as im2: + im.save( + out, "TIFF", save_all=True, append_images=[im2], progress=callback + ) + + assert progress == [ + ("Tests/images/hopper.tif", 1, 4), + ("Tests/images/multipage.tiff", 2, 4), + ("Tests/images/multipage.tiff", 3, 4), + ("Tests/images/multipage.tiff", 4, 4), + ] + def test_saving_icc_profile(self, tmp_path): # Tests saving TIFF with icc_profile set. # At the time of writing this will only work for non-compressed tiffs diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 30938e971dd..1bfc9488b52 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,7 +1,7 @@ -import io import re import sys import warnings +from io import BytesIO import pytest @@ -102,10 +102,10 @@ def test_write_rgb(self, tmp_path): def test_write_method(self, tmp_path): self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6}) - buffer_no_args = io.BytesIO() + buffer_no_args = BytesIO() hopper().save(buffer_no_args, format="WEBP") - buffer_method = io.BytesIO() + buffer_method = BytesIO() hopper().save(buffer_method, format="WEBP", method=6) assert buffer_no_args.getbuffer() != buffer_method.getbuffer() @@ -122,6 +122,30 @@ def test_save_all(self, tmp_path): reloaded.seek(1) assert_image_similar(im2, reloaded, 1) + @skip_unless_feature("webp_anim") + def test_save_all_progress(self): + out = BytesIO() + progress = [] + + def callback(filename, frame_number, n_frames): + progress.append((filename, frame_number, n_frames)) + + Image.new("RGB", (1, 1)).save(out, "WEBP", save_all=True, progress=callback) + assert progress == [(None, 1, 1)] + + out = BytesIO() + progress = [] + + with Image.open("Tests/images/iss634.webp") as im: + im2 = Image.new("RGB", im.size) + im.save(out, "WEBP", save_all=True, append_images=[im2], progress=callback) + + expected = [] + for i in range(42): + expected.append(("Tests/images/iss634.webp", i + 1, 43)) + expected.append((None, 43, 43)) + assert progress == expected + def test_icc_profile(self, tmp_path): self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None}) if _webp.HAVE_WEBPANIM: diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 92074b0d49e..d17d36319b2 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -24,7 +24,6 @@ # See the README file for information on usage and redistribution. # -import itertools import math import os import subprocess @@ -578,10 +577,17 @@ def _write_multiple_frames(im, fp, palette): duration = im.encoderinfo.get("duration") disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) + progress = im.encoderinfo.get("progress") + imSequences = [im] + list(im.encoderinfo.get("append_images", [])) + if progress: + n_frames = 0 + for imSequence in imSequences: + n_frames += getattr(imSequence, "n_frames", 1) + im_frames = [] frame_count = 0 background_im = None - for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): + for imSequence in imSequences: for im_frame in ImageSequence.Iterator(imSequence): # a copy is required here since seek can still mutate the image im_frame = _normalize_mode(im_frame.copy()) @@ -611,6 +617,10 @@ def _write_multiple_frames(im, fp, palette): # This frame is identical to the previous frame if encoderinfo.get("duration"): previous["encoderinfo"]["duration"] += encoderinfo["duration"] + if progress: + progress( + getattr(imSequence, "filename", None), frame_count, n_frames + ) continue if encoderinfo.get("disposal") == 2: if background_im is None: @@ -624,6 +634,8 @@ def _write_multiple_frames(im, fp, palette): else: bbox = None im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + if progress: + progress(getattr(imSequence, "filename", None), frame_count, n_frames) if len(im_frames) > 1: for frame_data in im_frames: diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index f9261c77d68..de9e6b74ea0 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -18,7 +18,6 @@ # See the README file for information on usage and redistribution. # -import itertools import os import struct @@ -42,6 +41,7 @@ def _save(im, fp, filename): def _save_all(im, fp, filename): + progress = im.encoderinfo.get("progress") append_images = im.encoderinfo.get("append_images", []) if not append_images: try: @@ -50,11 +50,19 @@ def _save_all(im, fp, filename): animated = False if not animated: _save(im, fp, filename) + if progress: + progress(getattr(im, "filename", None), 1, 1) return mpf_offset = 28 offsets = [] - for imSequence in itertools.chain([im], append_images): + imSequences = [im] + list(append_images) + if progress: + frame_number = 0 + n_frames = 0 + for imSequence in imSequences: + n_frames += getattr(imSequence, "n_frames", 1) + for imSequence in imSequences: for im_frame in ImageSequence.Iterator(imSequence): if not offsets: # APP2 marker @@ -73,6 +81,9 @@ def _save_all(im, fp, filename): else: im_frame.save(fp, "JPEG") offsets.append(fp.tell() - offsets[-1]) + if progress: + frame_number += 1 + progress(getattr(imSequence, "filename", None), frame_number, n_frames) ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd[0xB000] = b"0100" diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 09fc0c7e6ce..5e230e7f407 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -246,6 +246,7 @@ def _save(im, fp, filename, save_all=False): # catalog and list of pages existing_pdf.write_catalog() + progress = im.encoderinfo.get("progress") page_number = 0 for im_sequence in ims: im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence] @@ -281,6 +282,10 @@ def _save(im, fp, filename, save_all=False): existing_pdf.write_obj(contents_refs[page_number], stream=page_contents) page_number += 1 + if progress: + progress( + getattr(im_sequence, "filename", None), page_number, number_of_pages + ) # # trailer diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 2ed182d32de..1dec51f5a7c 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1091,16 +1091,21 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE)) + progress = im.encoderinfo.get("progress") - if default_image: - chain = itertools.chain(append_images) - else: - chain = itertools.chain([im], append_images) + imSequences = [] + if not default_image: + imSequences.append(im) + imSequences += append_images + if progress: + n_frames = 0 + for imSequence in imSequences: + n_frames += getattr(imSequence, "n_frames", 1) im_frames = [] frame_count = 0 - for im_seq in chain: - for im_frame in ImageSequence.Iterator(im_seq): + for imSequence in imSequences: + for im_frame in ImageSequence.Iterator(imSequence): if im_frame.mode == rawmode: im_frame = im_frame.copy() else: @@ -1149,12 +1154,18 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) previous["encoderinfo"]["duration"] += encoderinfo.get( "duration", duration ) + if progress: + progress( + getattr(imSequence, "filename", None), frame_count, n_frames + ) continue else: bbox = None if "duration" not in encoderinfo: encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + if progress: + progress(getattr(imSequence, "filename", None), frame_count, n_frames) # animation control chunk( diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 96de03a3e17..d6e718ee99e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -2117,14 +2117,24 @@ def fixOffsets(self, count, isShort=False, isLong=False): def _save_all(im, fp, filename): encoderinfo = im.encoderinfo.copy() encoderconfig = im.encoderconfig + progress = encoderinfo.get("progress") append_images = list(encoderinfo.get("append_images", [])) if not hasattr(im, "n_frames") and not append_images: - return _save(im, fp, filename) + _save(im, fp, filename) + if progress: + progress(getattr(im, "filename", None), 1, 1) + return cur_idx = im.tell() + imSequences = [im] + append_images + if progress: + frame_number = 0 + n_frames = 0 + for ims in imSequences: + n_frames += getattr(ims, "n_frames", 1) try: with AppendingTiffWriter(fp) as tf: - for ims in [im] + append_images: + for ims in imSequences: ims.encoderinfo = encoderinfo ims.encoderconfig = encoderconfig if not hasattr(ims, "n_frames"): @@ -2136,6 +2146,10 @@ def _save_all(im, fp, filename): ims.seek(idx) ims.load() _save(ims, tf, filename) + if progress: + frame_number += 1 + progress(getattr(ims, "filename", None), frame_number, n_frames) + tf.newFrame() finally: im.seek(cur_idx) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 612fc09467a..26f5704b854 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -177,6 +177,7 @@ def tell(self): def _save_all(im, fp, filename): encoderinfo = im.encoderinfo.copy() + progress = encoderinfo.get("progress") append_images = list(encoderinfo.get("append_images", [])) # If total frame count is 1, then save using the legacy API, which @@ -186,6 +187,8 @@ def _save_all(im, fp, filename): total += getattr(ims, "n_frames", 1) if total == 1: _save(im, fp, filename) + if progress: + progress(getattr(im, "filename", None), 1, 1) return background = (0, 0, 0, 0) @@ -300,6 +303,8 @@ def _save_all(im, fp, filename): else: timestamp += duration frame_idx += 1 + if progress: + progress(getattr(ims, "filename", None), frame_idx, total) finally: im.seek(cur_idx) From 3465a598e25383b78feca0bf6a2d942e2663bc95 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 2 Oct 2023 18:33:48 +1100 Subject: [PATCH 2/5] Pass back index of image in sequence, instead of filename --- Tests/test_file_apng.py | 16 ++++++++-------- Tests/test_file_gif.py | 6 +++--- Tests/test_file_mpo.py | 10 +++++----- Tests/test_file_pdf.py | 10 +++++----- Tests/test_file_tiff.py | 10 +++++----- Tests/test_file_webp.py | 6 +++--- src/PIL/GifImagePlugin.py | 8 +++----- src/PIL/MpoImagePlugin.py | 6 +++--- src/PIL/PdfImagePlugin.py | 6 ++---- src/PIL/PngImagePlugin.py | 8 +++----- src/PIL/TiffImagePlugin.py | 6 +++--- src/PIL/WebPImagePlugin.py | 6 +++--- 12 files changed, 46 insertions(+), 52 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index d30df9dc7cb..da02b42b25f 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -673,7 +673,7 @@ def callback(filename, frame_number, n_frames): progress.append((filename, frame_number, n_frames)) Image.new("RGB", (1, 1)).save(out, "PNG", save_all=True, progress=callback) - assert progress == [(None, 1, 1)] + assert progress == [(0, 1, 1)] out = BytesIO() progress = [] @@ -685,13 +685,13 @@ def callback(filename, frame_number, n_frames): ) assert progress == [ - ("Tests/images/apng/single_frame.png", 1, 7), - ("Tests/images/apng/single_frame.png", 2, 7), - ("Tests/images/apng/delay.png", 3, 7), - ("Tests/images/apng/delay.png", 4, 7), - ("Tests/images/apng/delay.png", 5, 7), - ("Tests/images/apng/delay.png", 6, 7), - ("Tests/images/apng/delay.png", 7, 7), + (0, 1, 7), + (1, 2, 7), + (2, 3, 7), + (2, 4, 7), + (2, 5, 7), + (2, 6, 7), + (2, 7, 7), ] diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 9b2280dacb3..7478176dd34 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -273,7 +273,7 @@ def callback(filename, frame_number, n_frames): progress.append((filename, frame_number, n_frames)) Image.new("RGB", (1, 1)).save(out, "GIF", save_all=True, progress=callback) - assert progress == [(None, 1, 1)] + assert progress == [(0, 1, 1)] out = BytesIO() progress = [] @@ -282,9 +282,9 @@ def callback(filename, frame_number, n_frames): with Image.open("Tests/images/chi.gif") as im2: im.save(out, "GIF", save_all=True, append_images=[im2], progress=callback) - expected = [("Tests/images/hopper.gif", 1, 32)] + expected = [(0, 1, 32)] for i in range(31): - expected.append(("Tests/images/chi.gif", i + 2, 32)) + expected.append((1, i + 2, 32)) assert progress == expected diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 2497fe0af94..bb659929673 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -288,7 +288,7 @@ def callback(filename, frame_number, n_frames): progress.append((filename, frame_number, n_frames)) Image.new("RGB", (1, 1)).save(out, "MPO", save_all=True, progress=callback) - assert progress == [(None, 1, 1)] + assert progress == [(0, 1, 1)] out = BytesIO() progress = [] @@ -298,8 +298,8 @@ def callback(filename, frame_number, n_frames): im.save(out, "MPO", save_all=True, append_images=[im2], progress=callback) assert progress == [ - ("Tests/images/sugarshack.mpo", 1, 4), - ("Tests/images/sugarshack.mpo", 2, 4), - ("Tests/images/frozenpond.mpo", 3, 4), - ("Tests/images/frozenpond.mpo", 4, 4), + (0, 1, 4), + (0, 2, 4), + (1, 3, 4), + (1, 4, 4), ] diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 96ef787fd10..71112ddb462 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -177,7 +177,7 @@ def callback(filename, frame_number, n_frames): progress.append((filename, frame_number, n_frames)) Image.new("RGB", (1, 1)).save(out, "PDF", save_all=True, progress=callback) - assert progress == [(None, 1, 1)] + assert progress == [(0, 1, 1)] out = BytesIO() progress = [] @@ -187,10 +187,10 @@ def callback(filename, frame_number, n_frames): im.save(out, "PDF", save_all=True, append_images=[im2], progress=callback) assert progress == [ - ("Tests/images/sugarshack.mpo", 1, 4), - ("Tests/images/sugarshack.mpo", 2, 4), - ("Tests/images/frozenpond.mpo", 3, 4), - ("Tests/images/frozenpond.mpo", 4, 4), + (0, 1, 4), + (0, 2, 4), + (1, 3, 4), + (1, 4, 4), ] diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 1e8fa944e16..e957f2371dc 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -696,7 +696,7 @@ def callback(filename, frame_number, n_frames): progress.append((filename, frame_number, n_frames)) Image.new("RGB", (1, 1)).save(out, "TIFF", save_all=True, progress=callback) - assert progress == [(None, 1, 1)] + assert progress == [(0, 1, 1)] out = BytesIO() progress = [] @@ -708,10 +708,10 @@ def callback(filename, frame_number, n_frames): ) assert progress == [ - ("Tests/images/hopper.tif", 1, 4), - ("Tests/images/multipage.tiff", 2, 4), - ("Tests/images/multipage.tiff", 3, 4), - ("Tests/images/multipage.tiff", 4, 4), + (0, 1, 4), + (1, 2, 4), + (1, 3, 4), + (1, 4, 4), ] def test_saving_icc_profile(self, tmp_path): diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 1bfc9488b52..420464e199d 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -131,7 +131,7 @@ def callback(filename, frame_number, n_frames): progress.append((filename, frame_number, n_frames)) Image.new("RGB", (1, 1)).save(out, "WEBP", save_all=True, progress=callback) - assert progress == [(None, 1, 1)] + assert progress == [(0, 1, 1)] out = BytesIO() progress = [] @@ -142,8 +142,8 @@ def callback(filename, frame_number, n_frames): expected = [] for i in range(42): - expected.append(("Tests/images/iss634.webp", i + 1, 43)) - expected.append((None, 43, 43)) + expected.append((0, i + 1, 43)) + expected.append((1, 43, 43)) assert progress == expected def test_icc_profile(self, tmp_path): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index d17d36319b2..7d2d9c3ae1e 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -587,7 +587,7 @@ def _write_multiple_frames(im, fp, palette): im_frames = [] frame_count = 0 background_im = None - for imSequence in imSequences: + for i, imSequence in enumerate(imSequences): for im_frame in ImageSequence.Iterator(imSequence): # a copy is required here since seek can still mutate the image im_frame = _normalize_mode(im_frame.copy()) @@ -618,9 +618,7 @@ def _write_multiple_frames(im, fp, palette): if encoderinfo.get("duration"): previous["encoderinfo"]["duration"] += encoderinfo["duration"] if progress: - progress( - getattr(imSequence, "filename", None), frame_count, n_frames - ) + progress(i, frame_count, n_frames) continue if encoderinfo.get("disposal") == 2: if background_im is None: @@ -635,7 +633,7 @@ def _write_multiple_frames(im, fp, palette): bbox = None im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if progress: - progress(getattr(imSequence, "filename", None), frame_count, n_frames) + progress(i, frame_count, n_frames) if len(im_frames) > 1: for frame_data in im_frames: diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index de9e6b74ea0..4a9498c8b84 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -51,7 +51,7 @@ def _save_all(im, fp, filename): if not animated: _save(im, fp, filename) if progress: - progress(getattr(im, "filename", None), 1, 1) + progress(0, 1, 1) return mpf_offset = 28 @@ -62,7 +62,7 @@ def _save_all(im, fp, filename): n_frames = 0 for imSequence in imSequences: n_frames += getattr(imSequence, "n_frames", 1) - for imSequence in imSequences: + for i, imSequence in enumerate(imSequences): for im_frame in ImageSequence.Iterator(imSequence): if not offsets: # APP2 marker @@ -83,7 +83,7 @@ def _save_all(im, fp, filename): offsets.append(fp.tell() - offsets[-1]) if progress: frame_number += 1 - progress(getattr(imSequence, "filename", None), frame_number, n_frames) + progress(i, frame_number, n_frames) ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd[0xB000] = b"0100" diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 5e230e7f407..27e79bd8bf2 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -248,7 +248,7 @@ def _save(im, fp, filename, save_all=False): progress = im.encoderinfo.get("progress") page_number = 0 - for im_sequence in ims: + for i, im_sequence in enumerate(ims): im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence] for im in im_pages: image_ref, procset = _write_image(im, filename, existing_pdf, image_refs) @@ -283,9 +283,7 @@ def _save(im, fp, filename, save_all=False): page_number += 1 if progress: - progress( - getattr(im_sequence, "filename", None), page_number, number_of_pages - ) + progress(i, page_number, number_of_pages) # # trailer diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 1dec51f5a7c..8d2173dc76e 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1104,7 +1104,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) im_frames = [] frame_count = 0 - for imSequence in imSequences: + for i, imSequence in enumerate(imSequences): for im_frame in ImageSequence.Iterator(imSequence): if im_frame.mode == rawmode: im_frame = im_frame.copy() @@ -1155,9 +1155,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) "duration", duration ) if progress: - progress( - getattr(imSequence, "filename", None), frame_count, n_frames - ) + progress(i, frame_count, n_frames) continue else: bbox = None @@ -1165,7 +1163,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if progress: - progress(getattr(imSequence, "filename", None), frame_count, n_frames) + progress(i, frame_count, n_frames) # animation control chunk( diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index d6e718ee99e..71af42b757e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -2122,7 +2122,7 @@ def _save_all(im, fp, filename): if not hasattr(im, "n_frames") and not append_images: _save(im, fp, filename) if progress: - progress(getattr(im, "filename", None), 1, 1) + progress(0, 1, 1) return cur_idx = im.tell() @@ -2134,7 +2134,7 @@ def _save_all(im, fp, filename): n_frames += getattr(ims, "n_frames", 1) try: with AppendingTiffWriter(fp) as tf: - for ims in imSequences: + for i, ims in enumerate(imSequences): ims.encoderinfo = encoderinfo ims.encoderconfig = encoderconfig if not hasattr(ims, "n_frames"): @@ -2148,7 +2148,7 @@ def _save_all(im, fp, filename): _save(ims, tf, filename) if progress: frame_number += 1 - progress(getattr(ims, "filename", None), frame_number, n_frames) + progress(i, frame_number, n_frames) tf.newFrame() finally: diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 26f5704b854..c3113ea89af 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -188,7 +188,7 @@ def _save_all(im, fp, filename): if total == 1: _save(im, fp, filename) if progress: - progress(getattr(im, "filename", None), 1, 1) + progress(0, 1, 1) return background = (0, 0, 0, 0) @@ -261,7 +261,7 @@ def _save_all(im, fp, filename): timestamp = 0 cur_idx = im.tell() try: - for ims in [im] + append_images: + for i, ims in enumerate([im] + append_images): # Get # of frames in this image nfr = getattr(ims, "n_frames", 1) @@ -304,7 +304,7 @@ def _save_all(im, fp, filename): timestamp += duration frame_idx += 1 if progress: - progress(getattr(ims, "filename", None), frame_idx, total) + progress(i, frame_idx, total) finally: im.seek(cur_idx) From b5f322831a84b269bd95c1f76c08637d299d5448 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 5 Oct 2023 18:40:01 +1100 Subject: [PATCH 3/5] Restored filename in progress callback --- Tests/test_file_apng.py | 62 ++++++++++++++++++++++++++++++++------ Tests/test_file_gif.py | 31 ++++++++++++++++--- Tests/test_file_mpo.py | 41 ++++++++++++++++++++----- Tests/test_file_pdf.py | 41 ++++++++++++++++++++----- Tests/test_file_tiff.py | 41 ++++++++++++++++++++----- Tests/test_file_webp.py | 31 ++++++++++++++++--- src/PIL/GifImagePlugin.py | 18 +++++++++-- src/PIL/MpoImagePlugin.py | 18 +++++++++-- src/PIL/PdfImagePlugin.py | 9 +++++- src/PIL/PngImagePlugin.py | 18 +++++++++-- src/PIL/TiffImagePlugin.py | 18 +++++++++-- src/PIL/WebPImagePlugin.py | 18 +++++++++-- 12 files changed, 294 insertions(+), 52 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index da02b42b25f..c8711afb3e5 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -669,11 +669,18 @@ def test_save_all_progress(): out = BytesIO() progress = [] - def callback(filename, frame_number, n_frames): - progress.append((filename, frame_number, n_frames)) + def callback(state): + progress.append(state) Image.new("RGB", (1, 1)).save(out, "PNG", save_all=True, progress=callback) - assert progress == [(0, 1, 1)] + assert progress == [ + { + "image_index": 0, + "image_filename": None, + "completed_frames": 1, + "total_frames": 1, + } + ] out = BytesIO() progress = [] @@ -685,13 +692,48 @@ def callback(filename, frame_number, n_frames): ) assert progress == [ - (0, 1, 7), - (1, 2, 7), - (2, 3, 7), - (2, 4, 7), - (2, 5, 7), - (2, 6, 7), - (2, 7, 7), + { + "image_index": 0, + "image_filename": "Tests/images/apng/single_frame.png", + "completed_frames": 1, + "total_frames": 7, + }, + { + "image_index": 1, + "image_filename": "Tests/images/apng/single_frame.png", + "completed_frames": 2, + "total_frames": 7, + }, + { + "image_index": 2, + "image_filename": "Tests/images/apng/delay.png", + "completed_frames": 3, + "total_frames": 7, + }, + { + "image_index": 2, + "image_filename": "Tests/images/apng/delay.png", + "completed_frames": 4, + "total_frames": 7, + }, + { + "image_index": 2, + "image_filename": "Tests/images/apng/delay.png", + "completed_frames": 5, + "total_frames": 7, + }, + { + "image_index": 2, + "image_filename": "Tests/images/apng/delay.png", + "completed_frames": 6, + "total_frames": 7, + }, + { + "image_index": 2, + "image_filename": "Tests/images/apng/delay.png", + "completed_frames": 7, + "total_frames": 7, + }, ] diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 7478176dd34..f2d1f499205 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -269,11 +269,18 @@ def test_save_all_progress(): out = BytesIO() progress = [] - def callback(filename, frame_number, n_frames): - progress.append((filename, frame_number, n_frames)) + def callback(state): + progress.append(state) Image.new("RGB", (1, 1)).save(out, "GIF", save_all=True, progress=callback) - assert progress == [(0, 1, 1)] + assert progress == [ + { + "image_index": 0, + "image_filename": None, + "completed_frames": 1, + "total_frames": 1, + } + ] out = BytesIO() progress = [] @@ -282,9 +289,23 @@ def callback(filename, frame_number, n_frames): with Image.open("Tests/images/chi.gif") as im2: im.save(out, "GIF", save_all=True, append_images=[im2], progress=callback) - expected = [(0, 1, 32)] + expected = [ + { + "image_index": 0, + "image_filename": "Tests/images/hopper.gif", + "completed_frames": 1, + "total_frames": 32, + } + ] for i in range(31): - expected.append((1, i + 2, 32)) + expected.append( + { + "image_index": 1, + "image_filename": "Tests/images/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 bb659929673..8330506373f 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -284,11 +284,18 @@ def test_save_all_progress(): out = BytesIO() progress = [] - def callback(filename, frame_number, n_frames): - progress.append((filename, frame_number, n_frames)) + def callback(state): + progress.append(state) Image.new("RGB", (1, 1)).save(out, "MPO", save_all=True, progress=callback) - assert progress == [(0, 1, 1)] + assert progress == [ + { + "image_index": 0, + "image_filename": None, + "completed_frames": 1, + "total_frames": 1, + } + ] out = BytesIO() progress = [] @@ -298,8 +305,28 @@ def callback(filename, frame_number, n_frames): im.save(out, "MPO", save_all=True, append_images=[im2], progress=callback) assert progress == [ - (0, 1, 4), - (0, 2, 4), - (1, 3, 4), - (1, 4, 4), + { + "image_index": 0, + "image_filename": "Tests/images/sugarshack.mpo", + "completed_frames": 1, + "total_frames": 4, + }, + { + "image_index": 0, + "image_filename": "Tests/images/sugarshack.mpo", + "completed_frames": 2, + "total_frames": 4, + }, + { + "image_index": 1, + "image_filename": "Tests/images/frozenpond.mpo", + "completed_frames": 3, + "total_frames": 4, + }, + { + "image_index": 1, + "image_filename": "Tests/images/frozenpond.mpo", + "completed_frames": 4, + "total_frames": 4, + }, ] diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 71112ddb462..178c0193fed 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -173,11 +173,18 @@ def test_save_all_progress(): out = BytesIO() progress = [] - def callback(filename, frame_number, n_frames): - progress.append((filename, frame_number, n_frames)) + def callback(state): + progress.append(state) Image.new("RGB", (1, 1)).save(out, "PDF", save_all=True, progress=callback) - assert progress == [(0, 1, 1)] + assert progress == [ + { + "image_index": 0, + "image_filename": None, + "completed_frames": 1, + "total_frames": 1, + } + ] out = BytesIO() progress = [] @@ -187,10 +194,30 @@ def callback(filename, frame_number, n_frames): im.save(out, "PDF", save_all=True, append_images=[im2], progress=callback) assert progress == [ - (0, 1, 4), - (0, 2, 4), - (1, 3, 4), - (1, 4, 4), + { + "image_index": 0, + "image_filename": "Tests/images/sugarshack.mpo", + "completed_frames": 1, + "total_frames": 4, + }, + { + "image_index": 0, + "image_filename": "Tests/images/sugarshack.mpo", + "completed_frames": 2, + "total_frames": 4, + }, + { + "image_index": 1, + "image_filename": "Tests/images/frozenpond.mpo", + "completed_frames": 3, + "total_frames": 4, + }, + { + "image_index": 1, + "image_filename": "Tests/images/frozenpond.mpo", + "completed_frames": 4, + "total_frames": 4, + }, ] diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index e957f2371dc..6cc4afc70bd 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -692,11 +692,18 @@ def test_save_all_progress(self): out = BytesIO() progress = [] - def callback(filename, frame_number, n_frames): - progress.append((filename, frame_number, n_frames)) + def callback(state): + progress.append(state) Image.new("RGB", (1, 1)).save(out, "TIFF", save_all=True, progress=callback) - assert progress == [(0, 1, 1)] + assert progress == [ + { + "image_index": 0, + "image_filename": None, + "completed_frames": 1, + "total_frames": 1, + } + ] out = BytesIO() progress = [] @@ -708,10 +715,30 @@ def callback(filename, frame_number, n_frames): ) assert progress == [ - (0, 1, 4), - (1, 2, 4), - (1, 3, 4), - (1, 4, 4), + { + "image_index": 0, + "image_filename": "Tests/images/hopper.tif", + "completed_frames": 1, + "total_frames": 4, + }, + { + "image_index": 1, + "image_filename": "Tests/images/multipage.tiff", + "completed_frames": 2, + "total_frames": 4, + }, + { + "image_index": 1, + "image_filename": "Tests/images/multipage.tiff", + "completed_frames": 3, + "total_frames": 4, + }, + { + "image_index": 1, + "image_filename": "Tests/images/multipage.tiff", + "completed_frames": 4, + "total_frames": 4, + }, ] def test_saving_icc_profile(self, tmp_path): diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 420464e199d..da757237034 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -127,11 +127,18 @@ def test_save_all_progress(self): out = BytesIO() progress = [] - def callback(filename, frame_number, n_frames): - progress.append((filename, frame_number, n_frames)) + def callback(state): + progress.append(state) Image.new("RGB", (1, 1)).save(out, "WEBP", save_all=True, progress=callback) - assert progress == [(0, 1, 1)] + assert progress == [ + { + "image_index": 0, + "image_filename": None, + "completed_frames": 1, + "total_frames": 1, + } + ] out = BytesIO() progress = [] @@ -142,8 +149,22 @@ def callback(filename, frame_number, n_frames): expected = [] for i in range(42): - expected.append((0, i + 1, 43)) - expected.append((1, 43, 43)) + expected.append( + { + "image_index": 0, + "image_filename": "Tests/images/iss634.webp", + "completed_frames": i + 1, + "total_frames": 43, + } + ) + expected.append( + { + "image_index": 1, + "image_filename": None, + "completed_frames": 43, + "total_frames": 43, + } + ) assert progress == expected def test_icc_profile(self, tmp_path): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 7d2d9c3ae1e..56cfec4e4d3 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -618,7 +618,14 @@ def _write_multiple_frames(im, fp, palette): if encoderinfo.get("duration"): previous["encoderinfo"]["duration"] += encoderinfo["duration"] if progress: - progress(i, frame_count, n_frames) + progress( + { + "image_index": i, + "image_filename": getattr(imSequence, "filename", None), + "completed_frames": frame_count, + "total_frames": n_frames, + } + ) continue if encoderinfo.get("disposal") == 2: if background_im is None: @@ -633,7 +640,14 @@ def _write_multiple_frames(im, fp, palette): bbox = None im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if progress: - progress(i, frame_count, n_frames) + progress( + { + "image_index": i, + "image_filename": getattr(imSequence, "filename", None), + "completed_frames": frame_count, + "total_frames": n_frames, + } + ) if len(im_frames) > 1: for frame_data in im_frames: diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 4a9498c8b84..509ec340a06 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -51,7 +51,14 @@ def _save_all(im, fp, filename): if not animated: _save(im, fp, filename) if progress: - progress(0, 1, 1) + progress( + { + "image_index": 0, + "image_filename": getattr(im, "filename", None), + "completed_frames": 1, + "total_frames": 1, + } + ) return mpf_offset = 28 @@ -83,7 +90,14 @@ def _save_all(im, fp, filename): offsets.append(fp.tell() - offsets[-1]) if progress: frame_number += 1 - progress(i, frame_number, n_frames) + progress( + { + "image_index": i, + "image_filename": getattr(imSequence, "filename", None), + "completed_frames": frame_number, + "total_frames": n_frames, + } + ) ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd[0xB000] = b"0100" diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 27e79bd8bf2..9b87f30ce17 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -283,7 +283,14 @@ def _save(im, fp, filename, save_all=False): page_number += 1 if progress: - progress(i, page_number, number_of_pages) + progress( + { + "image_index": i, + "image_filename": getattr(im_sequence, "filename", None), + "completed_frames": page_number, + "total_frames": number_of_pages, + } + ) # # trailer diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 8d2173dc76e..6a44f1b6ef9 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1155,7 +1155,14 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) "duration", duration ) if progress: - progress(i, frame_count, n_frames) + progress( + { + "image_index": i, + "image_filename": getattr(imSequence, "filename", None), + "completed_frames": frame_count, + "total_frames": n_frames, + } + ) continue else: bbox = None @@ -1163,7 +1170,14 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if progress: - progress(i, frame_count, n_frames) + progress( + { + "image_index": i, + "image_filename": getattr(imSequence, "filename", None), + "completed_frames": frame_count, + "total_frames": n_frames, + } + ) # animation control chunk( diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 71af42b757e..29968de285c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -2122,7 +2122,14 @@ def _save_all(im, fp, filename): if not hasattr(im, "n_frames") and not append_images: _save(im, fp, filename) if progress: - progress(0, 1, 1) + progress( + { + "image_index": 0, + "image_filename": getattr(im, "filename", None), + "completed_frames": 1, + "total_frames": 1, + } + ) return cur_idx = im.tell() @@ -2148,7 +2155,14 @@ def _save_all(im, fp, filename): _save(ims, tf, filename) if progress: frame_number += 1 - progress(i, frame_number, n_frames) + progress( + { + "image_index": i, + "image_filename": getattr(ims, "filename", None), + "completed_frames": frame_number, + "total_frames": n_frames, + } + ) tf.newFrame() finally: diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index c3113ea89af..ba92bb30453 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -188,7 +188,14 @@ def _save_all(im, fp, filename): if total == 1: _save(im, fp, filename) if progress: - progress(0, 1, 1) + progress( + { + "image_index": 0, + "image_filename": getattr(im, "filename", None), + "completed_frames": 1, + "total_frames": 1, + } + ) return background = (0, 0, 0, 0) @@ -304,7 +311,14 @@ def _save_all(im, fp, filename): timestamp += duration frame_idx += 1 if progress: - progress(i, frame_idx, total) + progress( + { + "image_index": i, + "image_filename": getattr(ims, "filename", None), + "completed_frames": frame_idx, + "total_frames": total, + } + ) finally: im.seek(cur_idx) From fe7c9d37c8d73b06e23706d6d73d26dd047d0295 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 6 Oct 2023 19:45:21 +1100 Subject: [PATCH 4/5] Moved progress code into common private method --- Tests/test_file_apng.py | 64 ++++++++++++-------------------------- Tests/test_file_mpo.py | 40 +++++++++--------------- Tests/test_file_pdf.py | 40 +++++++++--------------- Tests/test_file_tiff.py | 32 +++++++------------ src/PIL/GifImagePlugin.py | 24 +++----------- src/PIL/Image.py | 17 ++++++++++ src/PIL/MpoImagePlugin.py | 29 +++++------------ src/PIL/PdfImagePlugin.py | 11 +------ src/PIL/PngImagePlugin.py | 22 +++---------- src/PIL/TiffImagePlugin.py | 29 +++++------------ src/PIL/WebPImagePlugin.py | 21 ++----------- 11 files changed, 103 insertions(+), 226 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index c8711afb3e5..50155991d82 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -691,50 +691,26 @@ def callback(state): out, "PNG", save_all=True, append_images=[im, im2], progress=callback ) - assert progress == [ - { - "image_index": 0, - "image_filename": "Tests/images/apng/single_frame.png", - "completed_frames": 1, - "total_frames": 7, - }, - { - "image_index": 1, - "image_filename": "Tests/images/apng/single_frame.png", - "completed_frames": 2, - "total_frames": 7, - }, - { - "image_index": 2, - "image_filename": "Tests/images/apng/delay.png", - "completed_frames": 3, - "total_frames": 7, - }, - { - "image_index": 2, - "image_filename": "Tests/images/apng/delay.png", - "completed_frames": 4, - "total_frames": 7, - }, - { - "image_index": 2, - "image_filename": "Tests/images/apng/delay.png", - "completed_frames": 5, - "total_frames": 7, - }, - { - "image_index": 2, - "image_filename": "Tests/images/apng/delay.png", - "completed_frames": 6, - "total_frames": 7, - }, - { - "image_index": 2, - "image_filename": "Tests/images/apng/delay.png", - "completed_frames": 7, - "total_frames": 7, - }, - ] + expected = [] + for i in range(2): + expected.append( + { + "image_index": i, + "image_filename": "Tests/images/apng/single_frame.png", + "completed_frames": i + 1, + "total_frames": 7, + } + ) + for i in range(5): + expected.append( + { + "image_index": 2, + "image_filename": "Tests/images/apng/delay.png", + "completed_frames": i + 3, + "total_frames": 7, + } + ) + assert progress == expected def test_seek_after_close(): diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 8330506373f..f45076bc15f 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -304,29 +304,17 @@ def callback(state): with Image.open("Tests/images/frozenpond.mpo") as im2: im.save(out, "MPO", save_all=True, append_images=[im2], progress=callback) - assert progress == [ - { - "image_index": 0, - "image_filename": "Tests/images/sugarshack.mpo", - "completed_frames": 1, - "total_frames": 4, - }, - { - "image_index": 0, - "image_filename": "Tests/images/sugarshack.mpo", - "completed_frames": 2, - "total_frames": 4, - }, - { - "image_index": 1, - "image_filename": "Tests/images/frozenpond.mpo", - "completed_frames": 3, - "total_frames": 4, - }, - { - "image_index": 1, - "image_filename": "Tests/images/frozenpond.mpo", - "completed_frames": 4, - "total_frames": 4, - }, - ] + expected = [] + for i, filename in enumerate( + ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] + ): + for j in range(2): + expected.append( + { + "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 178c0193fed..c5b2bc45cc4 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -193,32 +193,20 @@ def callback(state): with Image.open("Tests/images/frozenpond.mpo") as im2: im.save(out, "PDF", save_all=True, append_images=[im2], progress=callback) - assert progress == [ - { - "image_index": 0, - "image_filename": "Tests/images/sugarshack.mpo", - "completed_frames": 1, - "total_frames": 4, - }, - { - "image_index": 0, - "image_filename": "Tests/images/sugarshack.mpo", - "completed_frames": 2, - "total_frames": 4, - }, - { - "image_index": 1, - "image_filename": "Tests/images/frozenpond.mpo", - "completed_frames": 3, - "total_frames": 4, - }, - { - "image_index": 1, - "image_filename": "Tests/images/frozenpond.mpo", - "completed_frames": 4, - "total_frames": 4, - }, - ] + expected = [] + for i, filename in enumerate( + ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] + ): + for j in range(2): + expected.append( + { + "image_index": i, + "image_filename": filename, + "completed_frames": i * 2 + j + 1, + "total_frames": 4, + } + ) + assert progress == expected def test_multiframe_normal_save(tmp_path): diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 6cc4afc70bd..d4b545d7b60 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -714,32 +714,24 @@ def callback(state): out, "TIFF", save_all=True, append_images=[im2], progress=callback ) - assert progress == [ + expected = [ { "image_index": 0, "image_filename": "Tests/images/hopper.tif", "completed_frames": 1, "total_frames": 4, - }, - { - "image_index": 1, - "image_filename": "Tests/images/multipage.tiff", - "completed_frames": 2, - "total_frames": 4, - }, - { - "image_index": 1, - "image_filename": "Tests/images/multipage.tiff", - "completed_frames": 3, - "total_frames": 4, - }, - { - "image_index": 1, - "image_filename": "Tests/images/multipage.tiff", - "completed_frames": 4, - "total_frames": 4, - }, + } ] + for i in range(3): + expected.append( + { + "image_index": 1, + "image_filename": "Tests/images/multipage.tiff", + "completed_frames": i + 2, + "total_frames": 4, + } + ) + assert progress == expected def test_saving_icc_profile(self, tmp_path): # Tests saving TIFF with icc_profile set. diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 56cfec4e4d3..f3a55c2feb1 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -577,12 +577,12 @@ def _write_multiple_frames(im, fp, palette): duration = im.encoderinfo.get("duration") disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) - progress = im.encoderinfo.get("progress") imSequences = [im] + list(im.encoderinfo.get("append_images", [])) + progress = im.encoderinfo.get("progress") if progress: - n_frames = 0 + total = 0 for imSequence in imSequences: - n_frames += getattr(imSequence, "n_frames", 1) + total += getattr(imSequence, "n_frames", 1) im_frames = [] frame_count = 0 @@ -618,14 +618,7 @@ def _write_multiple_frames(im, fp, palette): if encoderinfo.get("duration"): previous["encoderinfo"]["duration"] += encoderinfo["duration"] if progress: - progress( - { - "image_index": i, - "image_filename": getattr(imSequence, "filename", None), - "completed_frames": frame_count, - "total_frames": n_frames, - } - ) + im._save_all_progress(imSequence, i, frame_count, total) continue if encoderinfo.get("disposal") == 2: if background_im is None: @@ -640,14 +633,7 @@ def _write_multiple_frames(im, fp, palette): bbox = None im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if progress: - progress( - { - "image_index": i, - "image_filename": getattr(imSequence, "filename", None), - "completed_frames": frame_count, - "total_frames": n_frames, - } - ) + im._save_all_progress(imSequence, i, frame_count, total) if len(im_frames) > 1: for frame_data in im_frames: diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 244d2e43520..c413486804d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2446,6 +2446,23 @@ def save(self, fp, format=None, **params): if open_fp: fp.close() + def _save_all_progress( + self, im=None, im_index=0, completed=1, total=1, progress=None + ): + if not progress: + progress = self.encoderinfo.get("progress") + if not progress: + return + + progress( + { + "image_index": im_index, + "image_filename": getattr(im or self, "filename", None), + "completed_frames": completed, + "total_frames": total, + } + ) + def seek(self, frame): """ Seeks to the given frame in this sequence file. If you seek diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 509ec340a06..c36dc9b953a 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -41,7 +41,6 @@ def _save(im, fp, filename): def _save_all(im, fp, filename): - progress = im.encoderinfo.get("progress") append_images = im.encoderinfo.get("append_images", []) if not append_images: try: @@ -50,25 +49,18 @@ def _save_all(im, fp, filename): animated = False if not animated: _save(im, fp, filename) - if progress: - progress( - { - "image_index": 0, - "image_filename": getattr(im, "filename", None), - "completed_frames": 1, - "total_frames": 1, - } - ) + im._save_all_progress() return mpf_offset = 28 offsets = [] imSequences = [im] + list(append_images) + progress = im.encoderinfo.get("progress") if progress: - frame_number = 0 - n_frames = 0 + completed = 0 + total = 0 for imSequence in imSequences: - n_frames += getattr(imSequence, "n_frames", 1) + total += getattr(imSequence, "n_frames", 1) for i, imSequence in enumerate(imSequences): for im_frame in ImageSequence.Iterator(imSequence): if not offsets: @@ -89,15 +81,8 @@ def _save_all(im, fp, filename): im_frame.save(fp, "JPEG") offsets.append(fp.tell() - offsets[-1]) if progress: - frame_number += 1 - progress( - { - "image_index": i, - "image_filename": getattr(imSequence, "filename", None), - "completed_frames": frame_number, - "total_frames": n_frames, - } - ) + completed += 1 + im._save_all_progress(imSequence, i, completed, total, progress) ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd[0xB000] = b"0100" diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 9b87f30ce17..7c24c00fe5f 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -246,7 +246,6 @@ def _save(im, fp, filename, save_all=False): # catalog and list of pages existing_pdf.write_catalog() - progress = im.encoderinfo.get("progress") page_number = 0 for i, im_sequence in enumerate(ims): im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence] @@ -282,15 +281,7 @@ def _save(im, fp, filename, save_all=False): existing_pdf.write_obj(contents_refs[page_number], stream=page_contents) page_number += 1 - if progress: - progress( - { - "image_index": i, - "image_filename": getattr(im_sequence, "filename", None), - "completed_frames": page_number, - "total_frames": number_of_pages, - } - ) + im._save_all_progress(im_sequence, i, page_number, number_of_pages) # # trailer diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 6a44f1b6ef9..395601b997d 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1098,9 +1098,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) imSequences.append(im) imSequences += append_images if progress: - n_frames = 0 + total = 0 for imSequence in imSequences: - n_frames += getattr(imSequence, "n_frames", 1) + total += getattr(imSequence, "n_frames", 1) im_frames = [] frame_count = 0 @@ -1155,14 +1155,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) "duration", duration ) if progress: - progress( - { - "image_index": i, - "image_filename": getattr(imSequence, "filename", None), - "completed_frames": frame_count, - "total_frames": n_frames, - } - ) + im._save_all_progress(imSequence, i, frame_count, total) continue else: bbox = None @@ -1170,14 +1163,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if progress: - progress( - { - "image_index": i, - "image_filename": getattr(imSequence, "filename", None), - "completed_frames": frame_count, - "total_frames": n_frames, - } - ) + im._save_all_progress(imSequence, i, frame_count, total) # animation control chunk( diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 29968de285c..921992cca35 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -2117,28 +2117,20 @@ def fixOffsets(self, count, isShort=False, isLong=False): def _save_all(im, fp, filename): encoderinfo = im.encoderinfo.copy() encoderconfig = im.encoderconfig - progress = encoderinfo.get("progress") append_images = list(encoderinfo.get("append_images", [])) if not hasattr(im, "n_frames") and not append_images: _save(im, fp, filename) - if progress: - progress( - { - "image_index": 0, - "image_filename": getattr(im, "filename", None), - "completed_frames": 1, - "total_frames": 1, - } - ) + im._save_all_progress() return cur_idx = im.tell() imSequences = [im] + append_images + progress = encoderinfo.get("progress") if progress: - frame_number = 0 - n_frames = 0 + completed = 0 + total = 0 for ims in imSequences: - n_frames += getattr(ims, "n_frames", 1) + total += getattr(ims, "n_frames", 1) try: with AppendingTiffWriter(fp) as tf: for i, ims in enumerate(imSequences): @@ -2154,15 +2146,8 @@ def _save_all(im, fp, filename): ims.load() _save(ims, tf, filename) if progress: - frame_number += 1 - progress( - { - "image_index": i, - "image_filename": getattr(ims, "filename", None), - "completed_frames": frame_number, - "total_frames": n_frames, - } - ) + completed += 1 + im._save_all_progress(ims, i, completed, total) tf.newFrame() finally: diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index ba92bb30453..c35350a75aa 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -177,7 +177,6 @@ def tell(self): def _save_all(im, fp, filename): encoderinfo = im.encoderinfo.copy() - progress = encoderinfo.get("progress") append_images = list(encoderinfo.get("append_images", [])) # If total frame count is 1, then save using the legacy API, which @@ -187,15 +186,7 @@ def _save_all(im, fp, filename): total += getattr(ims, "n_frames", 1) if total == 1: _save(im, fp, filename) - if progress: - progress( - { - "image_index": 0, - "image_filename": getattr(im, "filename", None), - "completed_frames": 1, - "total_frames": 1, - } - ) + im._save_all_progress() return background = (0, 0, 0, 0) @@ -310,15 +301,7 @@ def _save_all(im, fp, filename): else: timestamp += duration frame_idx += 1 - if progress: - progress( - { - "image_index": i, - "image_filename": getattr(ims, "filename", None), - "completed_frames": frame_idx, - "total_frames": total, - } - ) + im._save_all_progress(ims, i, frame_idx, total) finally: im.seek(cur_idx) From b7a5b5984b6629a108d7ea198508c1293cc1f37a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 12 Feb 2024 19:24:01 +1100 Subject: [PATCH 5/5] Updated tests for os.path.realpath --- Tests/test_file_apng.py | 8 ++++++-- Tests/test_file_gif.py | 6 +++++- Tests/test_file_mpo.py | 8 +++++--- Tests/test_file_pdf.py | 8 +++++--- Tests/test_file_tiff.py | 10 ++++++++-- Tests/test_file_webp.py | 8 +++++++- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index b9918c22db1..bda3f808940 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -674,6 +674,10 @@ def test_save_all_progress() -> None: progress = [] def callback(state): + if state["image_filename"]: + state["image_filename"] = ( + state["image_filename"].replace("\\", "/").split("Tests/images/")[-1] + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "PNG", save_all=True, progress=callback) @@ -700,7 +704,7 @@ def callback(state): expected.append( { "image_index": i, - "image_filename": "Tests/images/apng/single_frame.png", + "image_filename": "apng/single_frame.png", "completed_frames": i + 1, "total_frames": 7, } @@ -709,7 +713,7 @@ def callback(state): expected.append( { "image_index": 2, - "image_filename": "Tests/images/apng/delay.png", + "image_filename": "apng/delay.png", "completed_frames": i + 3, "total_frames": 7, } diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 9ab843ec0b4..f38b2588674 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -297,6 +297,10 @@ def test_save_all_progress(): progress = [] def callback(state): + if state["image_filename"]: + state["image_filename"] = ( + state["image_filename"].replace("\\", "/").split("Tests/images/")[-1] + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "GIF", save_all=True, progress=callback) @@ -328,7 +332,7 @@ def callback(state): expected.append( { "image_index": 1, - "image_filename": "Tests/images/chi.gif", + "image_filename": "chi.gif", "completed_frames": i + 2, "total_frames": 32, } diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index f7e82addf37..e6d9639eaf1 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -288,6 +288,10 @@ def test_save_all_progress(): progress = [] def callback(state): + if state["image_filename"]: + state["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) @@ -308,9 +312,7 @@ def callback(state): im.save(out, "MPO", save_all=True, append_images=[im2], progress=callback) expected = [] - for i, filename in enumerate( - ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] - ): + for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]): for j in range(2): expected.append( { diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 0b28760bee8..49049dab9b0 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -177,6 +177,10 @@ def test_save_all_progress() -> None: progress = [] def callback(state): + if state["image_filename"]: + state["image_filename"] = ( + state["image_filename"].replace("\\", "/").split("Tests/images/")[-1] + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "PDF", save_all=True, progress=callback) @@ -197,9 +201,7 @@ def callback(state): im.save(out, "PDF", save_all=True, append_images=[im2], progress=callback) expected = [] - for i, filename in enumerate( - ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] - ): + for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]): for j in range(2): expected.append( { diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index a30c362e48f..38c89c867c6 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -704,6 +704,12 @@ def test_save_all_progress(self) -> None: progress = [] def callback(state): + if state["image_filename"]: + state["image_filename"] = ( + state["image_filename"] + .replace("\\", "/") + .split("Tests/images/")[-1] + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "TIFF", save_all=True, progress=callback) @@ -728,7 +734,7 @@ def callback(state): expected = [ { "image_index": 0, - "image_filename": "Tests/images/hopper.tif", + "image_filename": "hopper.tif", "completed_frames": 1, "total_frames": 4, } @@ -737,7 +743,7 @@ def callback(state): expected.append( { "image_index": 1, - "image_filename": "Tests/images/multipage.tiff", + "image_filename": "multipage.tiff", "completed_frames": i + 2, "total_frames": 4, } diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 28317945106..1595be59a5a 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -131,6 +131,12 @@ def test_save_all_progress(self) -> None: progress = [] def callback(state): + if state["image_filename"]: + state["image_filename"] = ( + state["image_filename"] + .replace("\\", "/") + .split("Tests/images/")[-1] + ) progress.append(state) Image.new("RGB", (1, 1)).save(out, "WEBP", save_all=True, progress=callback) @@ -155,7 +161,7 @@ def callback(state): expected.append( { "image_index": 0, - "image_filename": "Tests/images/iss634.webp", + "image_filename": "iss634.webp", "completed_frames": i + 1, "total_frames": 43, }