mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-11 17:56:18 +03:00
Improved consistency of XMP handling
This commit is contained in:
parent
ca55eb50d9
commit
0f1a0fc501
|
@ -943,6 +943,7 @@ class TestFileJpeg:
|
||||||
):
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
|
assert "xmp" in im.info
|
||||||
xmp = im.getxmp()
|
xmp = im.getxmp()
|
||||||
|
|
||||||
description = xmp["xmpmeta"]["RDF"]["Description"]
|
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||||
|
|
|
@ -683,6 +683,7 @@ class TestFilePng:
|
||||||
):
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
|
assert "xmp" in im.info
|
||||||
xmp = im.getxmp()
|
xmp = im.getxmp()
|
||||||
|
|
||||||
description = xmp["xmpmeta"]["RDF"]["Description"]
|
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||||
|
|
|
@ -759,6 +759,7 @@ class TestFileTiff:
|
||||||
):
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
|
assert "xmp" in im.info
|
||||||
xmp = im.getxmp()
|
xmp = im.getxmp()
|
||||||
|
|
||||||
description = xmp["xmpmeta"]["RDF"]["Description"]
|
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||||
|
|
|
@ -129,6 +129,7 @@ def test_getxmp() -> None:
|
||||||
):
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
|
assert "xmp" in im.info
|
||||||
assert (
|
assert (
|
||||||
im.getxmp()["xmpmeta"]["xmptk"]
|
im.getxmp()["xmpmeta"]["xmptk"]
|
||||||
== "Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 "
|
== "Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 "
|
||||||
|
|
|
@ -897,6 +897,10 @@ class TestImage:
|
||||||
assert tag not in exif.get_ifd(0x8769)
|
assert tag not in exif.get_ifd(0x8769)
|
||||||
assert exif.get_ifd(0xA005)
|
assert exif.get_ifd(0xA005)
|
||||||
|
|
||||||
|
def test_empty_xmp(self) -> None:
|
||||||
|
with Image.open("Tests/images/hopper.gif") as im:
|
||||||
|
assert im.getxmp() == {}
|
||||||
|
|
||||||
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
||||||
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
||||||
im = Image.new("RGB", size)
|
im = Image.new("RGB", size)
|
||||||
|
|
|
@ -197,6 +197,7 @@ This helps to get the bounding box coordinates of the input image::
|
||||||
.. automethod:: PIL.Image.Image.getpalette
|
.. automethod:: PIL.Image.Image.getpalette
|
||||||
.. automethod:: PIL.Image.Image.getpixel
|
.. automethod:: PIL.Image.Image.getpixel
|
||||||
.. automethod:: PIL.Image.Image.getprojection
|
.. automethod:: PIL.Image.Image.getprojection
|
||||||
|
.. automethod:: PIL.Image.Image.getxmp
|
||||||
.. automethod:: PIL.Image.Image.histogram
|
.. automethod:: PIL.Image.Image.histogram
|
||||||
.. automethod:: PIL.Image.Image.paste
|
.. automethod:: PIL.Image.Image.paste
|
||||||
.. automethod:: PIL.Image.Image.point
|
.. automethod:: PIL.Image.Image.point
|
||||||
|
|
|
@ -18,9 +18,9 @@ is not secure.
|
||||||
|
|
||||||
- :py:meth:`~PIL.Image.Image.getexif` has used ``xml`` to potentially retrieve
|
- :py:meth:`~PIL.Image.Image.getexif` has used ``xml`` to potentially retrieve
|
||||||
orientation data since Pillow 7.2.0. It has been refactored to use ``re`` instead.
|
orientation data since Pillow 7.2.0. It has been refactored to use ``re`` instead.
|
||||||
- :py:meth:`~PIL.JpegImagePlugin.JpegImageFile.getxmp` was added in Pillow 8.2.0. It
|
- ``getxmp()`` was added to :py:class:`~PIL.JpegImagePlugin.JpegImageFile` in Pillow
|
||||||
will now use ``defusedxml`` instead. If the dependency is not present, an empty
|
8.2.0. It will now use ``defusedxml`` instead. If the dependency is not present, an
|
||||||
dictionary will be returned and a warning raised.
|
empty dictionary will be returned and a warning raised.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
|
@ -1439,7 +1439,14 @@ class Image:
|
||||||
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
|
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
|
||||||
return self.im.getextrema()
|
return self.im.getextrema()
|
||||||
|
|
||||||
def _getxmp(self, xmp_tags):
|
def getxmp(self):
|
||||||
|
"""
|
||||||
|
Returns a dictionary containing the XMP tags.
|
||||||
|
Requires defusedxml to be installed.
|
||||||
|
|
||||||
|
:returns: XMP tags in a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
def get_name(tag):
|
def get_name(tag):
|
||||||
return re.sub("^{[^}]+}", "", tag)
|
return re.sub("^{[^}]+}", "", tag)
|
||||||
|
|
||||||
|
@ -1466,9 +1473,10 @@ class Image:
|
||||||
if ElementTree is None:
|
if ElementTree is None:
|
||||||
warnings.warn("XMP data cannot be read without defusedxml dependency")
|
warnings.warn("XMP data cannot be read without defusedxml dependency")
|
||||||
return {}
|
return {}
|
||||||
else:
|
if "xmp" not in self.info:
|
||||||
root = ElementTree.fromstring(xmp_tags)
|
return {}
|
||||||
return {get_name(root.tag): get_value(root)}
|
root = ElementTree.fromstring(self.info["xmp"])
|
||||||
|
return {get_name(root.tag): get_value(root)}
|
||||||
|
|
||||||
def getexif(self) -> Exif:
|
def getexif(self) -> Exif:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -717,6 +717,9 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
|
||||||
exif_image.info["XML:com.adobe.xmp"] = re.sub(
|
exif_image.info["XML:com.adobe.xmp"] = re.sub(
|
||||||
pattern, "", exif_image.info["XML:com.adobe.xmp"]
|
pattern, "", exif_image.info["XML:com.adobe.xmp"]
|
||||||
)
|
)
|
||||||
|
exif_image.info["xmp"] = re.sub(
|
||||||
|
pattern.encode(), b"", exif_image.info["xmp"]
|
||||||
|
)
|
||||||
if not in_place:
|
if not in_place:
|
||||||
return transposed_image
|
return transposed_image
|
||||||
elif not in_place:
|
elif not in_place:
|
||||||
|
|
|
@ -94,6 +94,8 @@ def APP(self, marker):
|
||||||
else:
|
else:
|
||||||
self.info["exif"] = s
|
self.info["exif"] = s
|
||||||
self._exif_offset = self.fp.tell() - n + 6
|
self._exif_offset = self.fp.tell() - n + 6
|
||||||
|
elif marker == 0xFFE1 and s[:29] == b"http://ns.adobe.com/xap/1.0/\x00":
|
||||||
|
self.info["xmp"] = s.split(b"\x00")[1]
|
||||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||||
# extract FlashPix information (incomplete)
|
# extract FlashPix information (incomplete)
|
||||||
self.info["flashpix"] = s # FIXME: value will change
|
self.info["flashpix"] = s # FIXME: value will change
|
||||||
|
@ -499,21 +501,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
def _getmp(self):
|
def _getmp(self):
|
||||||
return _getmp(self)
|
return _getmp(self)
|
||||||
|
|
||||||
def getxmp(self):
|
|
||||||
"""
|
|
||||||
Returns a dictionary containing the XMP tags.
|
|
||||||
Requires defusedxml to be installed.
|
|
||||||
|
|
||||||
:returns: XMP tags in a dictionary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for segment, content in self.applist:
|
|
||||||
if segment == "APP1":
|
|
||||||
marker, xmp_tags = content.split(b"\x00")[:2]
|
|
||||||
if marker == b"http://ns.adobe.com/xap/1.0/":
|
|
||||||
return self._getxmp(xmp_tags)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
|
|
|
@ -606,6 +606,8 @@ class PngStream(ChunkStream):
|
||||||
return s
|
return s
|
||||||
else:
|
else:
|
||||||
return s
|
return s
|
||||||
|
if k == b"XML:com.adobe.xmp":
|
||||||
|
self.im_info["xmp"] = v
|
||||||
try:
|
try:
|
||||||
k = k.decode("latin-1", "strict")
|
k = k.decode("latin-1", "strict")
|
||||||
lang = lang.decode("utf-8", "strict")
|
lang = lang.decode("utf-8", "strict")
|
||||||
|
@ -1032,19 +1034,6 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
return super().getexif()
|
return super().getexif()
|
||||||
|
|
||||||
def getxmp(self):
|
|
||||||
"""
|
|
||||||
Returns a dictionary containing the XMP tags.
|
|
||||||
Requires defusedxml to be installed.
|
|
||||||
|
|
||||||
:returns: XMP tags in a dictionary.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
self._getxmp(self.info["XML:com.adobe.xmp"])
|
|
||||||
if "XML:com.adobe.xmp" in self.info
|
|
||||||
else {}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# PNG writer
|
# PNG writer
|
||||||
|
|
|
@ -1192,6 +1192,10 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.__frame += 1
|
self.__frame += 1
|
||||||
self.fp.seek(self._frame_pos[frame])
|
self.fp.seek(self._frame_pos[frame])
|
||||||
self.tag_v2.load(self.fp)
|
self.tag_v2.load(self.fp)
|
||||||
|
if XMP in self.tag_v2:
|
||||||
|
self.info["xmp"] = self.tag_v2[XMP]
|
||||||
|
elif "xmp" in self.info:
|
||||||
|
del self.info["xmp"]
|
||||||
self._reload_exif()
|
self._reload_exif()
|
||||||
# fill the legacy tag/ifd entries
|
# fill the legacy tag/ifd entries
|
||||||
self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
|
self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
|
||||||
|
@ -1202,15 +1206,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
"""Return the current frame number"""
|
"""Return the current frame number"""
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def getxmp(self):
|
|
||||||
"""
|
|
||||||
Returns a dictionary containing the XMP tags.
|
|
||||||
Requires defusedxml to be installed.
|
|
||||||
|
|
||||||
:returns: XMP tags in a dictionary.
|
|
||||||
"""
|
|
||||||
return self._getxmp(self.tag_v2[XMP]) if XMP in self.tag_v2 else {}
|
|
||||||
|
|
||||||
def get_photoshop_blocks(self):
|
def get_photoshop_blocks(self):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of Photoshop "Image Resource Blocks".
|
Returns a dictionary of Photoshop "Image Resource Blocks".
|
||||||
|
|
|
@ -100,15 +100,6 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
return None
|
return None
|
||||||
return self.getexif()._get_merged_dict()
|
return self.getexif()._get_merged_dict()
|
||||||
|
|
||||||
def getxmp(self):
|
|
||||||
"""
|
|
||||||
Returns a dictionary containing the XMP tags.
|
|
||||||
Requires defusedxml to be installed.
|
|
||||||
|
|
||||||
:returns: XMP tags in a dictionary.
|
|
||||||
"""
|
|
||||||
return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {}
|
|
||||||
|
|
||||||
def seek(self, frame: int) -> None:
|
def seek(self, frame: int) -> None:
|
||||||
if not self._seek_check(frame):
|
if not self._seek_check(frame):
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue
Block a user