Skip to content

Commit 3ea939b

Browse files
committed
Fix for XMP metadata handling #69
1 parent 8a2586b commit 3ea939b

File tree

4 files changed

+39
-15
lines changed

4 files changed

+39
-15
lines changed

pillow_heif/misc.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,27 @@ def set_orientation(info: dict, orientation: int = 1) -> Union[int, None]:
5151
except Exception: # noqa # pylint: disable=broad-except
5252
pass
5353
if info.get("xmp", None):
54-
xmp_data = info["xmp"].decode("utf-8")
55-
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_data)
56-
if match:
57-
if original_orientation is None and int(match[2]) != 1:
58-
original_orientation = int(match[2])
59-
xmp_data = re.sub(r'tiff:Orientation="([0-9])"', "", xmp_data)
60-
xmp_data = re.sub(r"<tiff:Orientation>([0-9])</tiff:Orientation>", "", xmp_data)
61-
info["xmp"] = xmp_data.encode("utf-8")
54+
xmp_data = info["xmp"].rsplit(b"\x00", 1)
55+
if xmp_data[0]:
56+
decoded_xmp_data = None
57+
for encoding in ("utf-8", "latin1"):
58+
try:
59+
decoded_xmp_data = xmp_data[0].decode(encoding)
60+
break
61+
except Exception: # noqa # pylint: disable=broad-except
62+
pass
63+
if decoded_xmp_data:
64+
_original_orientation = 1
65+
match = re.search(r'tiff:Orientation(="|>)([0-9])', decoded_xmp_data)
66+
if match:
67+
_original_orientation = int(match[2])
68+
if original_orientation is None and _original_orientation != 1:
69+
original_orientation = _original_orientation
70+
decoded_xmp_data = re.sub(r'tiff:Orientation="([0-9])"', "", decoded_xmp_data)
71+
decoded_xmp_data = re.sub(r"<tiff:Orientation>([0-9])</tiff:Orientation>", "", decoded_xmp_data)
72+
# should encode in "utf-8" anyway, as `defusedxml` do not work with `latin1` encoding.
73+
if encoding != "utf-8" or _original_orientation != 1:
74+
info["xmp"] = b"".join([decoded_xmp_data.encode("utf-8"), b"\x00" if len(xmp_data) > 1 else b""])
6275
return original_orientation
6376

6477

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ profile = "black"
3131
master.py-version = "3.7"
3232
master.extension-pkg-allow-list = ["_pillow_heif_cffi"]
3333
design.max-attributes = 9
34-
design.max-locals = 16
34+
design.max-branches = 14
35+
design.max-locals = 18
3536
design.max-returns = 8
3637
basic.good-names = [
3738
"a", "b", "c", "d", "e", "f", "i", "j", "k", "v",
1.44 KB
Binary file not shown.

tests/metadata_xmp_test.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ def test_pillow_xmp_add_remove():
5252
im.save(out_heif, format="HEIF", xmp=xmp_data)
5353
assert "xmp" not in im.info or im.info["xmp"] is None
5454
im_heif = Image.open(out_heif)
55-
assert im_heif.info["xmp"]
55+
assert im_heif.info["xmp"] == xmp_data
5656
# filling `info["xmp"]` before save.
5757
im.info["xmp"] = xmp_data
5858
im.save(out_heif, format="HEIF")
5959
im_heif = Image.open(out_heif)
60-
assert im_heif.info["xmp"]
60+
assert im_heif.info["xmp"] == xmp_data
6161
# setting `xmp` to `None` during save.
6262
im_heif.save(out_heif_no_xmp, format="HEIF", xmp=None)
63-
assert im_heif.info["xmp"]
63+
assert im_heif.info["xmp"] == xmp_data
6464
im_heif_no_xmp = Image.open(out_heif_no_xmp)
6565
assert "xmp" not in im_heif_no_xmp.info or im_heif_no_xmp.info["xmp"] is None
6666
# filling `info["xmp"]` with `None` before save.
@@ -85,16 +85,16 @@ def test_heif_xmp_add_remove():
8585
im_heif.save(out_heif, xmp=xmp_data)
8686
assert "xmp" not in im_heif.info or im_heif.info["xmp"] is None
8787
im_heif = pillow_heif.open_heif(out_heif)
88-
assert im_heif.info["xmp"]
88+
assert im_heif.info["xmp"] == xmp_data
8989
# test filling `info["xmp"]` before save.
9090
im_heif = pillow_heif.from_pillow(Image.new("RGB", (15, 15), 0))
9191
im_heif.info["xmp"] = xmp_data
9292
im_heif.save(out_heif)
9393
im_heif = pillow_heif.open_heif(out_heif)
94-
assert im_heif.info["xmp"]
94+
assert im_heif.info["xmp"] == xmp_data
9595
# setting `xmp` to `None` during save.
9696
im_heif.save(out_heif_no_xmp, xmp=None)
97-
assert im_heif.info["xmp"]
97+
assert im_heif.info["xmp"] == xmp_data
9898
im_heif_no_xmp = pillow_heif.open_heif(out_heif_no_xmp)
9999
assert "xmp" not in im_heif_no_xmp.info or im_heif_no_xmp.info["xmp"] is None
100100
# filling `info["xmp"]` with `None` before save.
@@ -107,3 +107,13 @@ def test_heif_xmp_add_remove():
107107
im_heif.save(out_heif_no_xmp)
108108
im_heif_no_xmp = pillow_heif.open_heif(out_heif_no_xmp)
109109
assert "xmp" not in im_heif_no_xmp.info or im_heif_no_xmp.info["xmp"] is None
110+
111+
112+
@pytest.mark.skipif(not helpers.hevc_enc(), reason="Requires HEVC encoder.")
113+
def test_heif_xmp_latin1_with_zero_byte():
114+
im = Image.open("images/heif_other/xmp_latin1.heic")
115+
out_heif = BytesIO()
116+
im.save(out_heif, format="HEIF")
117+
out_im = Image.open(out_heif)
118+
assert im.getxmp() == out_im.getxmp() # noqa
119+
assert out_im.info["xmp"][-1] == 0 # check null byte not to get lost

0 commit comments

Comments
 (0)