From 92663180482504dd05110390886bb6c683609def Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 7 May 2025 20:33:44 +1000 Subject: [PATCH] Do not set info["exif"] to None --- Tests/test_file_jxl_metadata.py | 49 +++++++++++++++++++++++++-------- src/PIL/JpegXlImagePlugin.py | 14 ++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Tests/test_file_jxl_metadata.py b/Tests/test_file_jxl_metadata.py index f6be96c80..8fc540642 100644 --- a/Tests/test_file_jxl_metadata.py +++ b/Tests/test_file_jxl_metadata.py @@ -4,7 +4,7 @@ from types import ModuleType import pytest -from PIL import Image +from PIL import Image, JpegXlImagePlugin from .helper import skip_unless_feature @@ -73,25 +73,52 @@ def test_read_icc_profile() -> None: def test_getxmp() -> None: with Image.open("Tests/images/flower.jxl") as im: assert "xmp" not in im.info - assert im.getxmp() == {} + if ElementTree is None: + with pytest.warns( + UserWarning, + match="XMP data cannot be read without defusedxml dependency", + ): + xmp = im.getxmp() + else: + xmp = im.getxmp() + assert xmp == {} with Image.open("Tests/images/flower2.jxl") as im: - if ElementTree: - assert ( - im.getxmp()["xmpmeta"]["xmptk"] - == "Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 " - ) - else: + if ElementTree is None: with pytest.warns( UserWarning, match="XMP data cannot be read without defusedxml dependency", ): assert im.getxmp() == {} + else: + assert "xmp" in im.info + assert ( + im.getxmp()["xmpmeta"]["xmptk"] + == "Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 " + ) +def test_4_byte_exif(monkeypatch: pytest.MonkeyPatch) -> None: + class _mock_jpegxl: + class PILJpegXlDecoder: + def __init__(self, b: bytes) -> None: + pass -def test_fix_exif_fail() -> None: - with Image.open("Tests/images/flower2.jxl") as image: - assert image._fix_exif(b"\0\0\0\0") is None + def get_info(self) -> tuple[int, int, str, int, int, int, int, int]: + return (1, 1, "L", 0, 0, 0, 0, 0) + + def get_icc(self) -> None: + pass + + def get_exif(self) -> None: + return b"\0\0\0\0" + + def get_xmp(self) -> None: + pass + + monkeypatch.setattr(JpegXlImagePlugin, "_jpegxl", _mock_jpegxl) + + with Image.open("Tests/images/hopper.jxl") as image: + assert "exif" not in image.info def test_read_exif_metadata_empty() -> None: diff --git a/src/PIL/JpegXlImagePlugin.py b/src/PIL/JpegXlImagePlugin.py index 84fc9361f..0a8c415b6 100644 --- a/src/PIL/JpegXlImagePlugin.py +++ b/src/PIL/JpegXlImagePlugin.py @@ -67,20 +67,16 @@ class JpegXlImageFile(ImageFile.ImageFile): if icc := self._decoder.get_icc(): self.info["icc_profile"] = icc if exif := self._decoder.get_exif(): - self.info["exif"] = self._fix_exif(exif) + # jpeg xl does some weird shenanigans when storing exif + # it omits first 6 bytes of tiff header but adds 4 byte offset instead + if len(exif) > 4: + exif_start_offset = struct.unpack(">I", exif[:4])[0] + self.info["exif"] = exif[exif_start_offset + 4 :] if xmp := self._decoder.get_xmp(): self.info["xmp"] = xmp self._rewind() - def _fix_exif(self, exif: bytes) -> bytes | None: - # jpeg xl does some weird shenanigans when storing exif - # it omits first 6 bytes of tiff header but adds 4 byte offset instead - if len(exif) <= 4: - return None - exif_start_offset = struct.unpack(">I", exif[:4])[0] - return exif[exif_start_offset + 4 :] - def _getexif(self) -> dict[int, Any] | None: if "exif" not in self.info: return None