mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-30 23:47:27 +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