From 7433a975511a9bb6eee6934ae9145cab791d911b Mon Sep 17 00:00:00 2001 From: Alexander Piskun <13381981+bigcat88@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:11:22 +0300 Subject: [PATCH 1/2] do not set empty `xmp` in the `im.info` dict Signed-off-by: Alexander Piskun --- CHANGELOG.md | 5 +++++ docs/pillow-plugin.rst | 4 ++-- docs/reference/HeifImage.rst | 2 +- pillow_heif/as_plugin.py | 22 ++++++++++++---------- pillow_heif/heif.py | 11 ++++++++--- tests/helpers.py | 5 ++++- tests/metadata_etc_test.py | 12 ++++++------ 7 files changed, 38 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbf8f38..c7740459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,14 @@ All notable changes to this project will be documented in this file. ## [0.17.0 - 2024-07-02] +### Added + +- Support for `Pillow` **10.4.0** #254 + ### Changed - Minimum supported Pillow version raised to `10.1.0`. #251 +- `xmp` in `info` dictionary is not present if it is empty. #254 ### Fixed diff --git a/docs/pillow-plugin.rst b/docs/pillow-plugin.rst index 4274bbc8..247bb4f7 100644 --- a/docs/pillow-plugin.rst +++ b/docs/pillow-plugin.rst @@ -91,8 +91,8 @@ Removing EXIF and XMP information inside ``info`` dictionary: .. code-block:: python image = Image.open(Path("test.heic")) - image.info["exif"] = None - image.info["xmp"] = None + del image.info["exif"] + del image.info["xmp"] image.save("output.heic") Removing EXIF and XMP specifying them when calling ``save``: diff --git a/docs/reference/HeifImage.rst b/docs/reference/HeifImage.rst index 524f6db3..9a79c874 100644 --- a/docs/reference/HeifImage.rst +++ b/docs/reference/HeifImage.rst @@ -18,7 +18,7 @@ HeifImage object .. py:attribute:: info["xmp"] :type: bytes - XMP metadata. String in bytes in UTF-8 encoding. Can be `None` + XMP metadata. String in bytes in UTF-8 encoding. Absent if `xmp` data is missing. .. py:attribute:: info["metadata"] :type: list[dict] diff --git a/pillow_heif/as_plugin.py b/pillow_heif/as_plugin.py index 3896ef7b..1f57d189 100644 --- a/pillow_heif/as_plugin.py +++ b/pillow_heif/as_plugin.py @@ -5,6 +5,7 @@ from warnings import warn from PIL import Image, ImageFile, ImageSequence +from PIL import __version__ as pil_version from . import options from .constants import HeifCompressionFormat @@ -71,16 +72,17 @@ def load(self): self._heif_file = None return super().load() - def getxmp(self) -> dict: - """Returns a dictionary containing the XMP tags. Requires ``defusedxml`` to be installed. - - :returns: XMP tags in a dictionary. - """ - if self.info.get("xmp", None): - xmp_data = self.info["xmp"].rsplit(b"\x00", 1) - if xmp_data[0]: - return self._getxmp(xmp_data[0]) - return {} + if pil_version[:4] in ("10.1", "10.2", "10.3"): + def getxmp(self) -> dict: + """Returns a dictionary containing the XMP tags. Requires ``defusedxml`` to be installed. + + :returns: XMP tags in a dictionary. + """ + if self.info.get("xmp", None): + xmp_data = self.info["xmp"].rsplit(b"\x00", 1) + if xmp_data[0]: + return self._getxmp(xmp_data[0]) + return {} def seek(self, frame): if not self._seek_check(frame): diff --git a/pillow_heif/heif.py b/pillow_heif/heif.py index c688d3e0..fb58d679 100644 --- a/pillow_heif/heif.py +++ b/pillow_heif/heif.py @@ -155,11 +155,12 @@ def __init__(self, c_image): "primary": bool(c_image.primary), "bit_depth": int(c_image.bit_depth), "exif": _exif, - "xmp": _xmp, "metadata": _metadata, "thumbnails": _thumbnails, "depth_images": _depth_images, } + if _xmp: + self.info["xmp"] = _xmp save_colorspace_chroma(c_image, self.info) _color_profile: Dict[str, Any] = c_image.color_profile if _color_profile: @@ -424,7 +425,9 @@ def add_from_pillow(self, image: Image.Image) -> HeifImage: raise ValueError("Empty images are not supported.") _info = image.info.copy() _info["exif"] = _exif_from_pillow(image) - _info["xmp"] = _xmp_from_pillow(image) + _xmp = _xmp_from_pillow(image) + if _xmp: + _info["xmp"] = _xmp original_orientation = set_orientation(_info) _img = _pil_to_supported_mode(image) if original_orientation is not None and original_orientation != 1: @@ -442,7 +445,9 @@ def add_from_pillow(self, image: Image.Image) -> HeifImage: if key in image.info: added_image.info[key] = deepcopy(image.info[key]) added_image.info["exif"] = _exif_from_pillow(image) - added_image.info["xmp"] = _xmp_from_pillow(image) + _xmp = _xmp_from_pillow(image) + if _xmp: + added_image.info["xmp"] = _xmp return added_image @property diff --git a/tests/helpers.py b/tests/helpers.py index 0a221d87..ad50e57c 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -83,7 +83,10 @@ def compare_images_fields(image1: HeifImage, image2: HeifImage): difference = box - image2.info["thumbnails"][i2] assert abs(difference) <= thumb_size_max_differ assert image1.info["exif"] == image2.info["exif"] - assert image1.info["xmp"] == image2.info["xmp"] + if "xmp" in image1.info: + assert image1.info["xmp"] == image2.info["xmp"] + else: + assert "xmp" not in image2.info for block_i, block in enumerate(image1.info["metadata"]): assert block["data"] == image1.info["metadata"][block_i]["data"] assert block["content_type"] == image1.info["metadata"][block_i]["content_type"] diff --git a/tests/metadata_etc_test.py b/tests/metadata_etc_test.py index 7479f75a..36fc482a 100644 --- a/tests/metadata_etc_test.py +++ b/tests/metadata_etc_test.py @@ -86,9 +86,9 @@ def test_heif_info_changing(save_format): im_out = pillow_heif.open_heif(out_buf) for i in range(3): if i == 1: - assert im_out[i].info["primary"] and not im_out[i].info["exif"] and not im_out[i].info["xmp"] + assert im_out[i].info["primary"] and not im_out[i].info["exif"] and "xmp" not in im_out[i].info else: - assert not im_out[i].info["primary"] and not im_out[i].info["exif"] and not im_out[i].info["xmp"] + assert not im_out[i].info["primary"] and not im_out[i].info["exif"] and "xmp" not in im_out[i].info # Set exif and xmp of all images. Change Primary Image to be last. for i in range(3): im[i].info["xmp"] = xmp @@ -110,7 +110,7 @@ def test_heif_info_changing(save_format): assert im_out.info["primary"] assert im_out.primary_index == 0 for i in range(3): - assert not im_out[i].info["exif"] and not im_out[i].info["xmp"] + assert not im_out[i].info["exif"] and "xmp" not in im_out[i].info @pytest.mark.skipif(not aom(), reason="Requires AVIF support.") @@ -136,9 +136,9 @@ def test_pillow_info_changing(save_format): for i in range(3): im_out.seek(i) if i == 1: - assert im_out.info["primary"] and not im_out.info["exif"] and not im_out.info["xmp"] + assert im_out.info["primary"] and not im_out.info["exif"] and "xmp" not in im_out.info else: - assert not im_out.info["primary"] and not im_out.info["exif"] and not im_out.info["xmp"] + assert not im_out.info["primary"] and not im_out.info["exif"] and "xmp" not in im_out.info # Set exif and xmp of all images. Change Primary Image to be last. for i in range(3): im.seek(i) @@ -164,7 +164,7 @@ def test_pillow_info_changing(save_format): assert im_out.tell() == 0 for i in range(3): im_out.seek(i) - assert not im_out.info["exif"] and not im_out.info["xmp"] + assert not im_out.info["exif"] and "xmp" not in im_out.info @pytest.mark.skipif(not aom(), reason="Requires AVIF support.") From a758782b52251f15b5eacd0c53f12294becf555a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:14:30 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pillow_heif/as_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pillow_heif/as_plugin.py b/pillow_heif/as_plugin.py index 1f57d189..d8c48b19 100644 --- a/pillow_heif/as_plugin.py +++ b/pillow_heif/as_plugin.py @@ -73,6 +73,7 @@ def load(self): return super().load() if pil_version[:4] in ("10.1", "10.2", "10.3"): + def getxmp(self) -> dict: """Returns a dictionary containing the XMP tags. Requires ``defusedxml`` to be installed.