Skip to content

Do not write IFD1 as a tag when converting EXIF to bytes - #10188

Merged
radarhere merged 3 commits into
python-pillow:mainfrom
sungbin1015:exif-ifd1-tobytes
Oct 12, 2026
Merged

radarhere merged 3 commits into
python-pillow:mainfrom
sungbin1015:exif-ifd1-tobytes

Conversation

@sungbin1015

Copy link
Copy Markdown
Contributor

Changes proposed in this pull request:

  • Skip ExifTags.IFD.IFD1 when Exif.tobytes() copies _ifds into IFD0
  • Extend test_exif_ifd1 to convert to bytes after reading IFD1

Since 11.1.0, looking at the thumbnail IFD and then saving with the same Exif instance fails:

from io import BytesIO

from PIL import ExifTags, Image

with Image.open("Tests/images/flower.jpg") as im:
    exif = im.getexif()
    exif.get_ifd(ExifTags.IFD.IFD1)
    im.save(BytesIO(), "JPEG", exif=exif)
  File "src/PIL/TiffImagePlugin.py", line 791, in _pack
    return struct.pack(self._endian + fmt, *values)
struct.error: ushort format requires 0 <= number <= 65535

get_ifd() stores IFD1 in _ifds under its enum value, -1, and the loop added in #8635 writes every _ifds entry that isn't a key in the Exif instance into IFD0, so -1 ends up being packed as a tag number. IFD1 wasn't written by tobytes() before that either, so I've just left it out of the loop. 11.0.0 saves the snippet above fine.

The EXIF, JPEG, MPO, PNG, TIFF and WebP metadata tests pass locally.

Didn't find this reported. #10177 was the nearest recent change, but that's a KeyError inside get_ifd() for Interop and MakerNote. I did notice the same loop puts 0xA005 and 0x927C into IFD0 after those two are read, without raising anything, and I haven't touched that here.

@radarhere radarhere added the Exif label Oct 12, 2026
@radarhere

Copy link
Copy Markdown
Member

Hmm. If I adjust the test to

def test_exif_ifd1(self) -> None:
	with Image.open("Tests/images/flower.jpg") as im:
		exif = im.getexif()
		before_ifd1_read = exif.tobytes()
		assert exif.get_ifd(ExifTags.IFD.IFD1) == {
			513: 2036,
			514: 5448,
			259: 6,
			296: 2,
			282: 180.0,
			283: 180.0,
		}

		# Assert that reading IFD1 does not change the data
		assert exif.tobytes() == before_ifd1_read

it fails?

@sungbin1015

Copy link
Copy Markdown
Contributor Author

Good point, it does. That one isn't IFD1 though: tobytes() itself reads the Exif and Interop IFDs into _ifds, and the next call then writes Interop into IFD0 as 0xA005, so two tobytes() calls in a row already differ on main. I've changed the loop to only write Exif and GPSInfo, which covers both, and switched the test to yours.

Comment thread src/PIL/Image.py Outdated
@radarhere
radarhere enabled auto-merge (squash) October 12, 2026 10:17
@radarhere
radarhere merged commit 71dd8ba into python-pillow:main Oct 12, 2026
52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants