Added getxmp() for PNG

This commit is contained in:
Andrew Murray 2021-06-12 13:57:14 +10:00
parent ae3bdf87f0
commit cd31dae0d1
7 changed files with 71 additions and 52 deletions

View File

@ -830,15 +830,22 @@ class TestFileJpeg:
assert isinstance(xmp, dict) assert isinstance(xmp, dict)
description = xmp["xmpmeta"]["RDF"]["Description"] description = xmp["xmpmeta"]["RDF"]["Description"]
assert description["DerivedFrom"] is None assert description["DerivedFrom"] == {
assert ( "documentID": "8367D410E636EA95B7DE7EBA1C43A412",
description["Look"]["Description"]["Group"]["Alt"]["li"] == "Profiles" "originalDocumentID": "8367D410E636EA95B7DE7EBA1C43A412",
) }
assert description["Look"]["Description"]["Group"]["Alt"]["li"] == {
"lang": "x-default",
"text": "Profiles",
}
assert description["ToneCurve"]["Seq"]["li"] == ["0, 0", "255, 255"] assert description["ToneCurve"]["Seq"]["li"] == ["0, 0", "255, 255"]
# Attribute # Attribute
assert description["Version"] == "10.4" 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") @pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg") @skip_unless_feature("jpg")

View File

@ -651,6 +651,16 @@ class TestFilePng:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert len(reloaded.png.im_palette[1]) == 3 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): def test_exif(self):
# With an EXIF chunk # With an EXIF chunk
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:

View File

@ -1317,6 +1317,33 @@ class Image:
return tuple(extrema) return tuple(extrema)
return self.im.getextrema() 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): def getexif(self):
if self._exif is None: if self._exif is None:
self._exif = Exif() self._exif = Exif()
@ -1332,15 +1359,15 @@ class Image:
if 0x0112 not in self._exif: if 0x0112 not in self._exif:
xmp_tags = self.info.get("XML:com.adobe.xmp") xmp_tags = self.info.get("XML:com.adobe.xmp")
if xmp_tags: if xmp_tags:
root = xml.etree.ElementTree.fromstring(xmp_tags) xmp = self._getxmp(xmp_tags)
for elem in root.iter(): if (
if elem.tag.endswith("}Description"): "xmpmeta" in xmp
orientation = elem.attrib.get( and "RDF" in xmp["xmpmeta"]
"{http://ns.adobe.com/tiff/1.0/}Orientation" and "Description" in xmp["xmpmeta"]["RDF"]
) ):
if orientation: description = xmp["xmpmeta"]["RDF"]["Description"]
self._exif[0x0112] = int(orientation) if "Orientation" in description:
break self._exif[0x0112] = int(description["Orientation"])
return self._exif return self._exif

View File

@ -31,7 +31,6 @@ import io
import struct import struct
import sys import sys
import warnings import warnings
import xml.etree.ElementTree
from . import Image from . import Image
from ._util import isPath from ._util import isPath
@ -310,30 +309,6 @@ class ImageFile(Image.Image):
return self.tell() != frame 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): class StubImageFile(ImageFile):
""" """

View File

@ -361,7 +361,6 @@ class JpegImageFile(ImageFile.ImageFile):
self.app = {} # compatibility self.app = {} # compatibility
self.applist = [] self.applist = []
self.icclist = [] self.icclist = []
self._xmp = None
while True: while True:
@ -484,15 +483,12 @@ class JpegImageFile(ImageFile.ImageFile):
:returns: XMP tags in a dictionary. :returns: XMP tags in a dictionary.
""" """
if self._xmp is None:
self._xmp = {}
for segment, content in self.applist: for segment, content in self.applist:
if segment == "APP1": if segment == "APP1":
marker, xmp_tags = content.rsplit(b"\x00", 1) marker, xmp_tags = content.rsplit(b"\x00", 1)
if marker == b"http://ns.adobe.com/xap/1.0/": if marker == b"http://ns.adobe.com/xap/1.0/":
self._getxmp(xmp_tags) return self._getxmp(xmp_tags)
return self._xmp return {}
def _getexif(self): def _getexif(self):

View File

@ -978,6 +978,17 @@ class PngImageFile(ImageFile.ImageFile):
return super().getexif() 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): def _close__fp(self):
try: try:
if self.__fp != self.fp: if self.__fp != self.fp:

View File

@ -1032,7 +1032,6 @@ class TiffImageFile(ImageFile.ImageFile):
self.__fp = self.fp self.__fp = self.fp
self._frame_pos = [] self._frame_pos = []
self._n_frames = None self._n_frames = None
self._xmp = None
logger.debug("*** TiffImageFile._open ***") logger.debug("*** TiffImageFile._open ***")
logger.debug(f"- __first: {self.__first}") logger.debug(f"- __first: {self.__first}")
@ -1107,13 +1106,7 @@ class TiffImageFile(ImageFile.ImageFile):
Returns a dictionary containing the XMP tags. Returns a dictionary containing the XMP tags.
:returns: XMP tags in a dictionary. :returns: XMP tags in a dictionary.
""" """
return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}
if self._xmp is None:
self._xmp = {}
if 700 in self.tag_v2:
self._getxmp(self.tag_v2[700])
return self._xmp
def load(self): def load(self):
if self.tile and self.use_load_libtiff: if self.tile and self.use_load_libtiff: