Repository: python-pillow/Pillow

Issue:
Feature request: implement im.getxmp() to return all embedded XMP meta data as XML
Implement **image.getxmp()** similar to image.getexif(), that **returns all embedded XMP meta data out of an image as XML**

Something like:
```python
def getxmp(self):
    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/':
                root = xml.etree.ElementTree.fromstring(xmp_tags)
return root

# Based off https://stackoverflow.com/a/32001778
```

FYI: This didn't work for me:
```xmp_tags = self.info.get("XML:com.adobe.xmp")```

I am sure this feature request has been asked before... but a search of 'XMP' in issues yielded nothing.  Just asking for MVP, not write support, or tag comprehension. 

XMP documentation: 
Official:  https://www.adobe.com/devnet/xmp.html
Helpful: https://exiftool.org/TagNames/XMP.html

Requesting output similar to the output of:
exiftool.exe -xmp:all -X image.jpg


Discussion:
Hello @laynr @hugovk I'd like to take this issue, I'm already working on this.

I already got the xmp tags out of the file, I'm just wondering which output structure would be best for the getxmp() to return

I was thinking that it could be an object like the getexif(), but in this one I already got the tags name, so, instead of the tag number I could implement the actual tag name and its value
Btw I also could just simply return its xml tree 
Great thanks @UrielMaD! 

It is probably best to keep it similar to getexif() if they return an object and adding tags name, instead of the tag number would be awesome... 

That said, there is definite value in just returning the XML tree.  For one, returning the XML tree may be the most future proof  as you wouldn't need to stay current on new tags names.

I guess if you return an object, one of the objects functions can be to return the XML tree - perhaps that would be the best of both worlds!  (but more work)

For my personal project I just used the XML tree as the parsers for XML are well established.   

One thing I am noticing is that there can be multiple XMP sections in one file and not necessarily adjacent.   

Thank you for taking this on.  I believe it will be very useful for many people!
Thank you @layn, then I'll send a PR implementing xml object, I can also return the whole xml tree as string.

I get the tag names directly from what's in the xml tree so I will return only the tags that comes in that file, so if there's more xmp tags added in the future it will still return the new ones as they just come as tags attributes.

Resolving patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -539,6 +539,7 @@ def __init__(self):
         self.readonly = 0
         self.pyaccess = None
         self._exif = None
+        self._xmp = None
 
     @property
     def width(self):
@@ -1318,6 +1319,27 @@ def getexif(self):
 
         return self._exif
 
+    def getxmp(self):
+        """
+        Returns a dictionary containing the xmp tags for a given image.
+        :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/":
+                    root = xml.etree.ElementTree.fromstring(xmp_tags)
+                    for element in root.findall(".//"):
+                        self._xmp[element.tag.split("}")[1]] = {
+                            child.split("}")[1]: value
+                            for child, value in element.attrib.items()
+                        }
+        return self._xmp
+
     def getim(self):
         """
         Returns a capsule that points to the internal image memory.


Test patch:
diff --git a/Tests/images/xmp_test.jpg b/Tests/images/xmp_test.jpg
new file mode 100644
Binary files /dev/null and b/Tests/images/xmp_test.jpg differ
diff --git a/Tests/test_image_getxmp.py b/Tests/test_image_getxmp.py
new file mode 100644
--- /dev/null
+++ b/Tests/test_image_getxmp.py
@@ -0,0 +1,9 @@
+from PIL import Image
+
+
+def test_getxmp():
+    with Image.open("Tests/images/xmp_test.jpg") as im:
+        xmp = im.getxmp()
+
+        assert isinstance(xmp, dict)
+        assert xmp["Description"]["Version"] == "10.4"