Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

do not set empty xmp in the im.info dict #254

Merged
merged 2 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions docs/pillow-plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``:
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/HeifImage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
21 changes: 12 additions & 9 deletions pillow_heif/as_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -71,16 +72,18 @@ 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.
if pil_version[:4] in ("10.1", "10.2", "10.3"):

: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 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):
Expand Down
11 changes: 8 additions & 3 deletions pillow_heif/heif.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
5 changes: 4 additions & 1 deletion tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
12 changes: 6 additions & 6 deletions tests/metadata_etc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.")
Expand All @@ -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)
Expand All @@ -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.")
Expand Down
Loading