Skip to content

Commit c01496e

Browse files
do not set empty xmp in the im.info dict (#254)
* do not set empty `xmp` in the `im.info` dict Signed-off-by: Alexander Piskun <[email protected]> --------- Signed-off-by: Alexander Piskun <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent aeda972 commit c01496e

File tree

7 files changed

+38
-22
lines changed

7 files changed

+38
-22
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ All notable changes to this project will be documented in this file.
22

33
## [0.17.0 - 2024-07-02]
44

5+
### Added
6+
7+
- Support for `Pillow` **10.4.0** #254
8+
59
### Changed
610

711
- Minimum supported Pillow version raised to `10.1.0`. #251
12+
- `xmp` in `info` dictionary is not present if it is empty. #254
813

914
### Fixed
1015

docs/pillow-plugin.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ Removing EXIF and XMP information inside ``info`` dictionary:
9191
.. code-block:: python
9292
9393
image = Image.open(Path("test.heic"))
94-
image.info["exif"] = None
95-
image.info["xmp"] = None
94+
del image.info["exif"]
95+
del image.info["xmp"]
9696
image.save("output.heic")
9797
9898
Removing EXIF and XMP specifying them when calling ``save``:

docs/reference/HeifImage.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ HeifImage object
1818
.. py:attribute:: info["xmp"]
1919
:type: bytes
2020

21-
XMP metadata. String in bytes in UTF-8 encoding. Can be `None`
21+
XMP metadata. String in bytes in UTF-8 encoding. Absent if `xmp` data is missing.
2222

2323
.. py:attribute:: info["metadata"]
2424
:type: list[dict]

pillow_heif/as_plugin.py

+12-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from warnings import warn
66

77
from PIL import Image, ImageFile, ImageSequence
8+
from PIL import __version__ as pil_version
89

910
from . import options
1011
from .constants import HeifCompressionFormat
@@ -71,16 +72,18 @@ def load(self):
7172
self._heif_file = None
7273
return super().load()
7374

74-
def getxmp(self) -> dict:
75-
"""Returns a dictionary containing the XMP tags. Requires ``defusedxml`` to be installed.
75+
if pil_version[:4] in ("10.1", "10.2", "10.3"):
7676

77-
:returns: XMP tags in a dictionary.
78-
"""
79-
if self.info.get("xmp", None):
80-
xmp_data = self.info["xmp"].rsplit(b"\x00", 1)
81-
if xmp_data[0]:
82-
return self._getxmp(xmp_data[0])
83-
return {}
77+
def getxmp(self) -> dict:
78+
"""Returns a dictionary containing the XMP tags. Requires ``defusedxml`` to be installed.
79+
80+
:returns: XMP tags in a dictionary.
81+
"""
82+
if self.info.get("xmp", None):
83+
xmp_data = self.info["xmp"].rsplit(b"\x00", 1)
84+
if xmp_data[0]:
85+
return self._getxmp(xmp_data[0])
86+
return {}
8487

8588
def seek(self, frame):
8689
if not self._seek_check(frame):

pillow_heif/heif.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,12 @@ def __init__(self, c_image):
155155
"primary": bool(c_image.primary),
156156
"bit_depth": int(c_image.bit_depth),
157157
"exif": _exif,
158-
"xmp": _xmp,
159158
"metadata": _metadata,
160159
"thumbnails": _thumbnails,
161160
"depth_images": _depth_images,
162161
}
162+
if _xmp:
163+
self.info["xmp"] = _xmp
163164
save_colorspace_chroma(c_image, self.info)
164165
_color_profile: Dict[str, Any] = c_image.color_profile
165166
if _color_profile:
@@ -424,7 +425,9 @@ def add_from_pillow(self, image: Image.Image) -> HeifImage:
424425
raise ValueError("Empty images are not supported.")
425426
_info = image.info.copy()
426427
_info["exif"] = _exif_from_pillow(image)
427-
_info["xmp"] = _xmp_from_pillow(image)
428+
_xmp = _xmp_from_pillow(image)
429+
if _xmp:
430+
_info["xmp"] = _xmp
428431
original_orientation = set_orientation(_info)
429432
_img = _pil_to_supported_mode(image)
430433
if original_orientation is not None and original_orientation != 1:
@@ -442,7 +445,9 @@ def add_from_pillow(self, image: Image.Image) -> HeifImage:
442445
if key in image.info:
443446
added_image.info[key] = deepcopy(image.info[key])
444447
added_image.info["exif"] = _exif_from_pillow(image)
445-
added_image.info["xmp"] = _xmp_from_pillow(image)
448+
_xmp = _xmp_from_pillow(image)
449+
if _xmp:
450+
added_image.info["xmp"] = _xmp
446451
return added_image
447452

448453
@property

tests/helpers.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def compare_images_fields(image1: HeifImage, image2: HeifImage):
8383
difference = box - image2.info["thumbnails"][i2]
8484
assert abs(difference) <= thumb_size_max_differ
8585
assert image1.info["exif"] == image2.info["exif"]
86-
assert image1.info["xmp"] == image2.info["xmp"]
86+
if "xmp" in image1.info:
87+
assert image1.info["xmp"] == image2.info["xmp"]
88+
else:
89+
assert "xmp" not in image2.info
8790
for block_i, block in enumerate(image1.info["metadata"]):
8891
assert block["data"] == image1.info["metadata"][block_i]["data"]
8992
assert block["content_type"] == image1.info["metadata"][block_i]["content_type"]

tests/metadata_etc_test.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def test_heif_info_changing(save_format):
8686
im_out = pillow_heif.open_heif(out_buf)
8787
for i in range(3):
8888
if i == 1:
89-
assert im_out[i].info["primary"] and not im_out[i].info["exif"] and not im_out[i].info["xmp"]
89+
assert im_out[i].info["primary"] and not im_out[i].info["exif"] and "xmp" not in im_out[i].info
9090
else:
91-
assert not im_out[i].info["primary"] and not im_out[i].info["exif"] and not im_out[i].info["xmp"]
91+
assert not im_out[i].info["primary"] and not im_out[i].info["exif"] and "xmp" not in im_out[i].info
9292
# Set exif and xmp of all images. Change Primary Image to be last.
9393
for i in range(3):
9494
im[i].info["xmp"] = xmp
@@ -110,7 +110,7 @@ def test_heif_info_changing(save_format):
110110
assert im_out.info["primary"]
111111
assert im_out.primary_index == 0
112112
for i in range(3):
113-
assert not im_out[i].info["exif"] and not im_out[i].info["xmp"]
113+
assert not im_out[i].info["exif"] and "xmp" not in im_out[i].info
114114

115115

116116
@pytest.mark.skipif(not aom(), reason="Requires AVIF support.")
@@ -136,9 +136,9 @@ def test_pillow_info_changing(save_format):
136136
for i in range(3):
137137
im_out.seek(i)
138138
if i == 1:
139-
assert im_out.info["primary"] and not im_out.info["exif"] and not im_out.info["xmp"]
139+
assert im_out.info["primary"] and not im_out.info["exif"] and "xmp" not in im_out.info
140140
else:
141-
assert not im_out.info["primary"] and not im_out.info["exif"] and not im_out.info["xmp"]
141+
assert not im_out.info["primary"] and not im_out.info["exif"] and "xmp" not in im_out.info
142142
# Set exif and xmp of all images. Change Primary Image to be last.
143143
for i in range(3):
144144
im.seek(i)
@@ -164,7 +164,7 @@ def test_pillow_info_changing(save_format):
164164
assert im_out.tell() == 0
165165
for i in range(3):
166166
im_out.seek(i)
167-
assert not im_out.info["exif"] and not im_out.info["xmp"]
167+
assert not im_out.info["exif"] and "xmp" not in im_out.info
168168

169169

170170
@pytest.mark.skipif(not aom(), reason="Requires AVIF support.")

0 commit comments

Comments
 (0)