Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move getxmp() to JpegImageFile #5376

Merged
merged 3 commits into from Apr 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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