diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index f4c295e0d..a090d73e4 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -830,15 +830,22 @@ class TestFileJpeg: assert isinstance(xmp, dict) description = xmp["xmpmeta"]["RDF"]["Description"] - assert description["DerivedFrom"] is None - assert ( - description["Look"]["Description"]["Group"]["Alt"]["li"] == "Profiles" - ) + assert description["DerivedFrom"] == { + "documentID": "8367D410E636EA95B7DE7EBA1C43A412", + "originalDocumentID": "8367D410E636EA95B7DE7EBA1C43A412", + } + assert description["Look"]["Description"]["Group"]["Alt"]["li"] == { + "lang": "x-default", + "text": "Profiles", + } assert description["ToneCurve"]["Seq"]["li"] == ["0, 0", "255, 255"] # Attribute assert description["Version"] == "10.4" + with Image.open("Tests/images/hopper.jpg") as im: + assert im.getxmp() == {} + @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a75abbe96..a7e5684d3 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -651,6 +651,16 @@ class TestFilePng: with Image.open(out) as reloaded: assert len(reloaded.png.im_palette[1]) == 3 + def test_xmp(self): + with Image.open("Tests/images/color_snakes.png") as im: + xmp = im.getxmp() + + assert isinstance(xmp, dict) + + description = xmp["xmpmeta"]["RDF"]["Description"] + assert description["PixelXDimension"] == "10" + assert description["subject"]["Seq"] is None + def test_exif(self): # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 28bcf4f00..a73de5ed3 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1317,6 +1317,33 @@ class Image: return tuple(extrema) return self.im.getextrema() + def _getxmp(self, xmp_tags): + def get_name(tag): + return tag.split("}")[1] + + def get_value(element): + value = {get_name(k): v for k, v in element.attrib.items()} + children = list(element) + if children: + for child in children: + name = get_name(child.tag) + child_value = get_value(child) + if name in value: + if not isinstance(value[name], list): + value[name] = [value[name]] + value[name].append(child_value) + else: + value[name] = child_value + elif value: + if element.text: + value["text"] = element.text + else: + return element.text + return value + + root = xml.etree.ElementTree.fromstring(xmp_tags) + return {get_name(root.tag): get_value(root)} + def getexif(self): if self._exif is None: self._exif = Exif() @@ -1332,15 +1359,15 @@ class Image: if 0x0112 not in self._exif: xmp_tags = self.info.get("XML:com.adobe.xmp") if xmp_tags: - root = xml.etree.ElementTree.fromstring(xmp_tags) - for elem in root.iter(): - if elem.tag.endswith("}Description"): - orientation = elem.attrib.get( - "{http://ns.adobe.com/tiff/1.0/}Orientation" - ) - if orientation: - self._exif[0x0112] = int(orientation) - break + xmp = self._getxmp(xmp_tags) + if ( + "xmpmeta" in xmp + and "RDF" in xmp["xmpmeta"] + and "Description" in xmp["xmpmeta"]["RDF"] + ): + description = xmp["xmpmeta"]["RDF"]["Description"] + if "Orientation" in description: + self._exif[0x0112] = int(description["Orientation"]) return self._exif diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index cfd712305..daf732de1 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -31,7 +31,6 @@ import io import struct import sys import warnings -import xml.etree.ElementTree from . import Image from ._util import isPath @@ -310,30 +309,6 @@ class ImageFile(Image.Image): return self.tell() != frame - def _getxmp(self, xmp_tags): - def get_name(tag): - return tag.split("}")[1] - - def get_value(element): - children = list(element) - if children: - value = {get_name(k): v for k, v in element.attrib.items()} - for child in children: - name = get_name(child.tag) - child_value = get_value(child) - if name in value: - if not isinstance(value[name], list): - value[name] = [value[name]] - value[name].append(child_value) - else: - value[name] = child_value - return value - else: - return element.text - - root = xml.etree.ElementTree.fromstring(xmp_tags) - self._xmp[get_name(root.tag)] = get_value(root) - class StubImageFile(ImageFile): """ diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index afa92304c..ec4e12f7b 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -361,7 +361,6 @@ class JpegImageFile(ImageFile.ImageFile): self.app = {} # compatibility self.applist = [] self.icclist = [] - self._xmp = None while True: @@ -484,15 +483,12 @@ class JpegImageFile(ImageFile.ImageFile): :returns: XMP tags in a dictionary. """ - if self._xmp is None: - self._xmp = {} - for segment, content in self.applist: if segment == "APP1": marker, xmp_tags = content.rsplit(b"\x00", 1) if marker == b"http://ns.adobe.com/xap/1.0/": - self._getxmp(xmp_tags) - return self._xmp + return self._getxmp(xmp_tags) + return {} def _getexif(self): diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index cd97d0914..899d91108 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -978,6 +978,17 @@ class PngImageFile(ImageFile.ImageFile): return super().getexif() + def getxmp(self): + """ + Returns a dictionary containing the XMP tags. + :returns: XMP tags in a dictionary. + """ + return ( + self._getxmp(self.info["XML:com.adobe.xmp"]) + if "XML:com.adobe.xmp" in self.info + else {} + ) + def _close__fp(self): try: if self.__fp != self.fp: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index d41a4dbfc..9f86587f1 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1032,7 +1032,6 @@ class TiffImageFile(ImageFile.ImageFile): self.__fp = self.fp self._frame_pos = [] self._n_frames = None - self._xmp = None logger.debug("*** TiffImageFile._open ***") logger.debug(f"- __first: {self.__first}") @@ -1107,13 +1106,7 @@ class TiffImageFile(ImageFile.ImageFile): Returns a dictionary containing the XMP tags. :returns: XMP tags in a dictionary. """ - - if self._xmp is None: - self._xmp = {} - - if 700 in self.tag_v2: - self._getxmp(self.tag_v2[700]) - return self._xmp + return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {} def load(self): if self.tile and self.use_load_libtiff: