Skip to content

Commit

Permalink
Merge pull request #5376 from radarhere/xmp
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk committed Apr 1, 2021
2 parents 43c4172 + ae7110a commit ef5f294
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 31 deletions.
7 changes: 7 additions & 0 deletions Tests/test_file_jpeg.py
Expand Up @@ -805,6 +805,13 @@ def read(n=-1):
# Assert the entire file has not been read
assert 0 < buffer.max_pos < size

def test_getxmp(self):
with Image.open("Tests/images/xmp_test.jpg") as im:
xmp = im.getxmp()

assert isinstance(xmp, dict)
assert xmp["Description"]["Version"] == "10.4"


@pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg")
Expand Down
9 changes: 0 additions & 9 deletions Tests/test_image_getxmp.py

This file was deleted.

14 changes: 14 additions & 0 deletions docs/releasenotes/8.2.0.rst
Expand Up @@ -112,6 +112,20 @@ separate histograms for each color channel, changing the tone of the image. The
``preserve_tone`` argument keeps the tone unchanged by using one luminance histogram
for all channels.

getxmp() for JPEG images
^^^^^^^^^^^^^^^^^^^^^^^^

A new method has been added to return
`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ for JPEG
images. It reads the XML data into a dictionary of names and values.

For example::

>>> from PIL import Image
>>> with Image.open("Tests/images/xmp_test.jpg") as im:
>>> print(im.getxmp())
{'RDF': {}, 'Description': {'Version': '10.4', 'ProcessVersion': '10.0', ...}, ...}

Security
========

Expand Down
22 changes: 0 additions & 22 deletions src/PIL/Image.py
Expand Up @@ -528,7 +528,6 @@ def __init__(self):
self.readonly = 0
self.pyaccess = None
self._exif = None
self._xmp = None

def __getattr__(self, name):
if name == "category":
Expand Down Expand Up @@ -1340,27 +1339,6 @@ 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.
Expand Down
23 changes: 23 additions & 0 deletions src/PIL/JpegImagePlugin.py
Expand Up @@ -39,6 +39,7 @@
import sys
import tempfile
import warnings
import xml.etree.ElementTree

from . import Image, ImageFile, TiffImagePlugin
from ._binary import i16be as i16
Expand Down Expand Up @@ -358,6 +359,7 @@ def _open(self):
self.app = {} # compatibility
self.applist = []
self.icclist = []
self._xmp = None

while True:

Expand Down Expand Up @@ -474,6 +476,27 @@ def _getexif(self):
def _getmp(self):
return _getmp(self)

def getxmp(self):
"""
Returns a dictionary containing the XMP tags.
: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 _getexif(self):
if "exif" not in self.info:
Expand Down

0 comments on commit ef5f294

Please sign in to comment.