Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6850465
Added progress callback when save_all is used
radarhere Oct 2, 2023
3465a59
Pass back index of image in sequence, instead of filename
radarhere Oct 2, 2023
b5f3228
Restored filename in progress callback
radarhere Oct 5, 2023
fe7c9d3
Moved progress code into common private method
radarhere Oct 6, 2023
fae0653
Merge branch 'main' into progress
radarhere Dec 21, 2023
19c2721
Merge branch 'main' into progress
radarhere Dec 22, 2023
4a0a011
Merge branch 'main' into progress
radarhere Dec 27, 2023
69680db
Merge branch 'main' into progress
radarhere Jan 21, 2024
344c300
Merge branch 'main' into progress
radarhere Jan 31, 2024
3c80ec0
Merge branch 'main' into progress
radarhere Feb 11, 2024
b7a5b59
Updated tests for os.path.realpath
radarhere Feb 12, 2024
14560e1
Merge branch 'main' into progress
radarhere Mar 11, 2024
d52b3a2
Merge branch 'main' into progress
radarhere Apr 1, 2024
682a9ae
Merge branch 'main' into progress
radarhere Jun 12, 2024
2f27173
Merge branch 'main' into progress
radarhere Jun 26, 2024
26e2a9f
Merge branch 'main' into progress
radarhere Jul 3, 2024
8b4b7ce
Merge branch 'main' into progress
radarhere Jul 16, 2024
ebbdc6e
Merge branch 'main' into progress
radarhere Sep 26, 2024
fc5c096
Merge branch 'main' into progress
radarhere Oct 12, 2024
62786fd
Merge branch 'main' into progress
radarhere Dec 28, 2024
af7954b
Merge branch 'main' into progress
radarhere Mar 5, 2025
ac009d0
Merge branch 'main' into progress
radarhere Mar 18, 2025
24b9ca7
Merge branch 'main' into progress
radarhere Mar 21, 2025
09e4df1
Merge branch 'main' into progress
radarhere Apr 21, 2025
5da88ea
Added progress callback to AVIF
radarhere Apr 21, 2025
ed5e327
Merge branch 'main' into progress
radarhere Jun 28, 2025
23373c8
Merge branch 'main' into progress
radarhere Jul 7, 2025
8c3c981
Merge branch 'main' into progress
radarhere Dec 6, 2025
c3ab770
Merge branch 'main' into progress
radarhere Feb 16, 2026
55afac4
Use NamedTuple
radarhere Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 20 additions & 44 deletions Tests/test_file_apng.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
40 changes: 14 additions & 26 deletions Tests/test_file_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
40 changes: 14 additions & 26 deletions Tests/test_file_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
32 changes: 12 additions & 20 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 5 additions & 19 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
17 changes: 17 additions & 0 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,23 @@ def save(self, fp, format=None, **params):
if open_fp:
fp.close()

def _save_all_progress(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's with "save all"? This method just passes data into another function (supplied externally).

If anything, _report_progress would make much more sense here. (Or even just _progress)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It indicates that the method is used in relation to im.save(out, save_all=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…Ah. Alright, it does make sense in that context.

Though it would still make more sense to name it something like _frame_progress, I'd say; it's more generic and there's not really anything in the method itself that makes it necessarily about saving only.

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
Expand Down
29 changes: 7 additions & 22 deletions src/PIL/MpoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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"
Expand Down
11 changes: 1 addition & 10 deletions src/PIL/PdfImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
Loading