Skip to content

Commit

Permalink
Fix for XMP metadata handling #69
Browse files Browse the repository at this point in the history
  • Loading branch information
bigcat88 committed Jan 21, 2023
1 parent 8a2586b commit 3ea939b
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 15 deletions.
29 changes: 21 additions & 8 deletions pillow_heif/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,27 @@ def set_orientation(info: dict, orientation: int = 1) -> Union[int, None]:
except Exception: # noqa # pylint: disable=broad-except
pass
if info.get("xmp", None):
xmp_data = info["xmp"].decode("utf-8")
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_data)
if match:
if original_orientation is None and int(match[2]) != 1:
original_orientation = int(match[2])
xmp_data = re.sub(r'tiff:Orientation="([0-9])"', "", xmp_data)
xmp_data = re.sub(r"<tiff:Orientation>([0-9])</tiff:Orientation>", "", xmp_data)
info["xmp"] = xmp_data.encode("utf-8")
xmp_data = info["xmp"].rsplit(b"\x00", 1)
if xmp_data[0]:
decoded_xmp_data = None
for encoding in ("utf-8", "latin1"):
try:
decoded_xmp_data = xmp_data[0].decode(encoding)
break
except Exception: # noqa # pylint: disable=broad-except
pass
if decoded_xmp_data:
_original_orientation = 1
match = re.search(r'tiff:Orientation(="|>)([0-9])', decoded_xmp_data)
if match:
_original_orientation = int(match[2])
if original_orientation is None and _original_orientation != 1:
original_orientation = _original_orientation
decoded_xmp_data = re.sub(r'tiff:Orientation="([0-9])"', "", decoded_xmp_data)
decoded_xmp_data = re.sub(r"<tiff:Orientation>([0-9])</tiff:Orientation>", "", decoded_xmp_data)
# should encode in "utf-8" anyway, as `defusedxml` do not work with `latin1` encoding.
if encoding != "utf-8" or _original_orientation != 1:
info["xmp"] = b"".join([decoded_xmp_data.encode("utf-8"), b"\x00" if len(xmp_data) > 1 else b""])
return original_orientation


Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ profile = "black"
master.py-version = "3.7"
master.extension-pkg-allow-list = ["_pillow_heif_cffi"]
design.max-attributes = 9
design.max-locals = 16
design.max-branches = 14
design.max-locals = 18
design.max-returns = 8
basic.good-names = [
"a", "b", "c", "d", "e", "f", "i", "j", "k", "v",
Expand Down
Binary file added tests/images/heif_other/xmp_latin1.heic
Binary file not shown.
22 changes: 16 additions & 6 deletions tests/metadata_xmp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ def test_pillow_xmp_add_remove():
im.save(out_heif, format="HEIF", xmp=xmp_data)
assert "xmp" not in im.info or im.info["xmp"] is None
im_heif = Image.open(out_heif)
assert im_heif.info["xmp"]
assert im_heif.info["xmp"] == xmp_data
# filling `info["xmp"]` before save.
im.info["xmp"] = xmp_data
im.save(out_heif, format="HEIF")
im_heif = Image.open(out_heif)
assert im_heif.info["xmp"]
assert im_heif.info["xmp"] == xmp_data
# setting `xmp` to `None` during save.
im_heif.save(out_heif_no_xmp, format="HEIF", xmp=None)
assert im_heif.info["xmp"]
assert im_heif.info["xmp"] == xmp_data
im_heif_no_xmp = Image.open(out_heif_no_xmp)
assert "xmp" not in im_heif_no_xmp.info or im_heif_no_xmp.info["xmp"] is None
# filling `info["xmp"]` with `None` before save.
Expand All @@ -85,16 +85,16 @@ def test_heif_xmp_add_remove():
im_heif.save(out_heif, xmp=xmp_data)
assert "xmp" not in im_heif.info or im_heif.info["xmp"] is None
im_heif = pillow_heif.open_heif(out_heif)
assert im_heif.info["xmp"]
assert im_heif.info["xmp"] == xmp_data
# test filling `info["xmp"]` before save.
im_heif = pillow_heif.from_pillow(Image.new("RGB", (15, 15), 0))
im_heif.info["xmp"] = xmp_data
im_heif.save(out_heif)
im_heif = pillow_heif.open_heif(out_heif)
assert im_heif.info["xmp"]
assert im_heif.info["xmp"] == xmp_data
# setting `xmp` to `None` during save.
im_heif.save(out_heif_no_xmp, xmp=None)
assert im_heif.info["xmp"]
assert im_heif.info["xmp"] == xmp_data
im_heif_no_xmp = pillow_heif.open_heif(out_heif_no_xmp)
assert "xmp" not in im_heif_no_xmp.info or im_heif_no_xmp.info["xmp"] is None
# filling `info["xmp"]` with `None` before save.
Expand All @@ -107,3 +107,13 @@ def test_heif_xmp_add_remove():
im_heif.save(out_heif_no_xmp)
im_heif_no_xmp = pillow_heif.open_heif(out_heif_no_xmp)
assert "xmp" not in im_heif_no_xmp.info or im_heif_no_xmp.info["xmp"] is None


@pytest.mark.skipif(not helpers.hevc_enc(), reason="Requires HEVC encoder.")
def test_heif_xmp_latin1_with_zero_byte():
im = Image.open("images/heif_other/xmp_latin1.heic")
out_heif = BytesIO()
im.save(out_heif, format="HEIF")
out_im = Image.open(out_heif)
assert im.getxmp() == out_im.getxmp() # noqa
assert out_im.info["xmp"][-1] == 0 # check null byte not to get lost

0 comments on commit 3ea939b

Please sign in to comment.