mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	Merge pull request #6748 from radarhere/exif_ifd
Added IFD enum to ExifTags
This commit is contained in:
		
						commit
						5257d561c0
					
				
							
								
								
									
										
											BIN
										
									
								
								Tests/images/flower_thumbnail.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/flower_thumbnail.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 35 KiB | 
|  | @ -442,6 +442,13 @@ class TestFileJpeg: | |||
|             info = im._getexif() | ||||
|             assert info[305] == "Adobe Photoshop CS Macintosh" | ||||
| 
 | ||||
|     def test_get_child_images(self): | ||||
|         with Image.open("Tests/images/flower.jpg") as im: | ||||
|             ims = im.get_child_images() | ||||
| 
 | ||||
|         assert len(ims) == 1 | ||||
|         assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png") | ||||
| 
 | ||||
|     def test_mp(self): | ||||
|         with Image.open("Tests/images/pil_sample_rgb.jpg") as im: | ||||
|             assert im._getmp() is None | ||||
|  |  | |||
|  | @ -7,7 +7,14 @@ import warnings | |||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError, features | ||||
| from PIL import ( | ||||
|     ExifTags, | ||||
|     Image, | ||||
|     ImageDraw, | ||||
|     ImagePalette, | ||||
|     UnidentifiedImageError, | ||||
|     features, | ||||
| ) | ||||
| 
 | ||||
| from .helper import ( | ||||
|     assert_image_equal, | ||||
|  | @ -808,6 +815,18 @@ class TestImage: | |||
|             reloaded_exif.load(exif.tobytes()) | ||||
|             assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) | ||||
| 
 | ||||
|     def test_exif_ifd1(self): | ||||
|         with Image.open("Tests/images/flower.jpg") as im: | ||||
|             exif = im.getexif() | ||||
|             assert exif.get_ifd(ExifTags.IFD.IFD1) == { | ||||
|                 513: 2036, | ||||
|                 514: 5448, | ||||
|                 259: 6, | ||||
|                 296: 2, | ||||
|                 282: 180.0, | ||||
|                 283: 180.0, | ||||
|             } | ||||
| 
 | ||||
|     def test_exif_ifd(self): | ||||
|         with Image.open("Tests/images/flower.jpg") as im: | ||||
|             exif = im.getexif() | ||||
|  |  | |||
|  | @ -31,6 +31,13 @@ which provide constants and clear-text names for various well-known EXIF tags. | |||
|     >>> Interop(4096).name | ||||
|     'RelatedImageFileFormat' | ||||
| 
 | ||||
| .. py:data:: IFD | ||||
| 
 | ||||
|     >>> from PIL.ExifTags import IFD | ||||
|     >>> IFD.Exif.value | ||||
|     34665 | ||||
|     >>> IFD(34665).name | ||||
|     'Exif' | ||||
| 
 | ||||
| Two of these values are also exposed as dictionaries. | ||||
| 
 | ||||
|  |  | |||
|  | @ -346,3 +346,11 @@ class Interop(IntEnum): | |||
|     RelatedImageFileFormat = 4096 | ||||
|     RelatedImageWidth = 4097 | ||||
|     RleatedImageHeight = 4098 | ||||
| 
 | ||||
| 
 | ||||
| class IFD(IntEnum): | ||||
|     Exif = 34665 | ||||
|     GPSInfo = 34853 | ||||
|     Makernote = 37500 | ||||
|     Interop = 40965 | ||||
|     IFD1 = -1 | ||||
|  |  | |||
|  | @ -47,7 +47,14 @@ except ImportError: | |||
| # VERSION was removed in Pillow 6.0.0. | ||||
| # PILLOW_VERSION was removed in Pillow 9.0.0. | ||||
| # Use __version__ instead. | ||||
| from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins | ||||
| from . import ( | ||||
|     ExifTags, | ||||
|     ImageMode, | ||||
|     TiffTags, | ||||
|     UnidentifiedImageError, | ||||
|     __version__, | ||||
|     _plugins, | ||||
| ) | ||||
| from ._binary import i32le, o32be, o32le | ||||
| from ._deprecate import deprecate | ||||
| from ._util import DeferredError, is_path | ||||
|  | @ -1447,6 +1454,49 @@ class Image: | |||
|         self._exif._loaded = False | ||||
|         self.getexif() | ||||
| 
 | ||||
|     def get_child_images(self): | ||||
|         child_images = [] | ||||
|         exif = self.getexif() | ||||
|         ifds = [] | ||||
|         if ExifTags.Base.SubIFDs in exif: | ||||
|             subifd_offsets = exif[ExifTags.Base.SubIFDs] | ||||
|             if subifd_offsets: | ||||
|                 if not isinstance(subifd_offsets, tuple): | ||||
|                     subifd_offsets = (subifd_offsets,) | ||||
|                 for subifd_offset in subifd_offsets: | ||||
|                     ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset)) | ||||
|         ifd1 = exif.get_ifd(ExifTags.IFD.IFD1) | ||||
|         if ifd1 and ifd1.get(513): | ||||
|             ifds.append((ifd1, exif._info.next)) | ||||
| 
 | ||||
|         offset = None | ||||
|         for ifd, ifd_offset in ifds: | ||||
|             current_offset = self.fp.tell() | ||||
|             if offset is None: | ||||
|                 offset = current_offset | ||||
| 
 | ||||
|             fp = self.fp | ||||
|             thumbnail_offset = ifd.get(513) | ||||
|             if thumbnail_offset is not None: | ||||
|                 try: | ||||
|                     thumbnail_offset += self._exif_offset | ||||
|                 except AttributeError: | ||||
|                     pass | ||||
|                 self.fp.seek(thumbnail_offset) | ||||
|                 data = self.fp.read(ifd.get(514)) | ||||
|                 fp = io.BytesIO(data) | ||||
| 
 | ||||
|             with open(fp) as im: | ||||
|                 if thumbnail_offset is None: | ||||
|                     im._frame_pos = [ifd_offset] | ||||
|                     im._seek(0) | ||||
|                 im.load() | ||||
|                 child_images.append(im) | ||||
| 
 | ||||
|         if offset is not None: | ||||
|             self.fp.seek(offset) | ||||
|         return child_images | ||||
| 
 | ||||
|     def getim(self): | ||||
|         """ | ||||
|         Returns a capsule that points to the internal image memory. | ||||
|  | @ -3598,14 +3648,16 @@ class Exif(MutableMapping): | |||
|         merged_dict = dict(self) | ||||
| 
 | ||||
|         # get EXIF extension | ||||
|         if 0x8769 in self: | ||||
|             ifd = self._get_ifd_dict(self[0x8769]) | ||||
|         if ExifTags.IFD.Exif in self: | ||||
|             ifd = self._get_ifd_dict(self[ExifTags.IFD.Exif]) | ||||
|             if ifd: | ||||
|                 merged_dict.update(ifd) | ||||
| 
 | ||||
|         # GPS | ||||
|         if 0x8825 in self: | ||||
|             merged_dict[0x8825] = self._get_ifd_dict(self[0x8825]) | ||||
|         if ExifTags.IFD.GPSInfo in self: | ||||
|             merged_dict[ExifTags.IFD.GPSInfo] = self._get_ifd_dict( | ||||
|                 self[ExifTags.IFD.GPSInfo] | ||||
|             ) | ||||
| 
 | ||||
|         return merged_dict | ||||
| 
 | ||||
|  | @ -3615,31 +3667,34 @@ class Exif(MutableMapping): | |||
|         head = self._get_head() | ||||
|         ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) | ||||
|         for tag, value in self.items(): | ||||
|             if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict): | ||||
|             if tag in [ | ||||
|                 ExifTags.IFD.Exif, | ||||
|                 ExifTags.IFD.GPSInfo, | ||||
|             ] and not isinstance(value, dict): | ||||
|                 value = self.get_ifd(tag) | ||||
|                 if ( | ||||
|                     tag == 0x8769 | ||||
|                     and 0xA005 in value | ||||
|                     and not isinstance(value[0xA005], dict) | ||||
|                     tag == ExifTags.IFD.Exif | ||||
|                     and ExifTags.IFD.Interop in value | ||||
|                     and not isinstance(value[ExifTags.IFD.Interop], dict) | ||||
|                 ): | ||||
|                     value = value.copy() | ||||
|                     value[0xA005] = self.get_ifd(0xA005) | ||||
|                     value[ExifTags.IFD.Interop] = self.get_ifd(ExifTags.IFD.Interop) | ||||
|             ifd[tag] = value | ||||
|         return b"Exif\x00\x00" + head + ifd.tobytes(offset) | ||||
| 
 | ||||
|     def get_ifd(self, tag): | ||||
|         if tag not in self._ifds: | ||||
|             if tag in [0x8769, 0x8825]: | ||||
|                 # exif, gpsinfo | ||||
|             if tag == ExifTags.IFD.IFD1: | ||||
|                 if self._info is not None: | ||||
|                     self._ifds[tag] = self._get_ifd_dict(self._info.next) | ||||
|             elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]: | ||||
|                 if tag in self: | ||||
|                     self._ifds[tag] = self._get_ifd_dict(self[tag]) | ||||
|             elif tag in [0xA005, 0x927C]: | ||||
|                 # interop, makernote | ||||
|                 if 0x8769 not in self._ifds: | ||||
|                     self.get_ifd(0x8769) | ||||
|                 tag_data = self._ifds[0x8769][tag] | ||||
|                 if tag == 0x927C: | ||||
|                     # makernote | ||||
|             elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]: | ||||
|                 if ExifTags.IFD.Exif not in self._ifds: | ||||
|                     self.get_ifd(ExifTags.IFD.Exif) | ||||
|                 tag_data = self._ifds[ExifTags.IFD.Exif][tag] | ||||
|                 if tag == ExifTags.IFD.Makernote: | ||||
|                     from .TiffImagePlugin import ImageFileDirectory_v2 | ||||
| 
 | ||||
|                     if tag_data[:8] == b"FUJIFILM": | ||||
|  | @ -3715,7 +3770,7 @@ class Exif(MutableMapping): | |||
|                                 makernote = {0x1101: dict(self._fixup_dict(camerainfo))} | ||||
|                         self._ifds[tag] = makernote | ||||
|                 else: | ||||
|                     # interop | ||||
|                     # Interop | ||||
|                     self._ifds[tag] = self._get_ifd_dict(tag_data) | ||||
|         return self._ifds.get(tag, {}) | ||||
| 
 | ||||
|  |  | |||
|  | @ -90,6 +90,7 @@ def APP(self, marker): | |||
|         if "exif" not in self.info: | ||||
|             # extract EXIF information (incomplete) | ||||
|             self.info["exif"] = s  # FIXME: value will change | ||||
|             self._exif_offset = self.fp.tell() - n + 6 | ||||
|     elif marker == 0xFFE2 and s[:5] == b"FPXR\0": | ||||
|         # extract FlashPix information (incomplete) | ||||
|         self.info["flashpix"] = s  # FIXME: value will change | ||||
|  |  | |||
|  | @ -22,7 +22,14 @@ import itertools | |||
| import os | ||||
| import struct | ||||
| 
 | ||||
| from . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin | ||||
| from . import ( | ||||
|     ExifTags, | ||||
|     Image, | ||||
|     ImageFile, | ||||
|     ImageSequence, | ||||
|     JpegImagePlugin, | ||||
|     TiffImagePlugin, | ||||
| ) | ||||
| from ._binary import i16be as i16 | ||||
| from ._binary import o32le | ||||
| 
 | ||||
|  | @ -137,7 +144,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | |||
| 
 | ||||
|             mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"] | ||||
|             if mptype.startswith("Large Thumbnail"): | ||||
|                 exif = self.getexif().get_ifd(0x8769) | ||||
|                 exif = self.getexif().get_ifd(ExifTags.IFD.Exif) | ||||
|                 if 40962 in exif and 40963 in exif: | ||||
|                     self._size = (exif[40962], exif[40963]) | ||||
|         elif "exif" in self.info: | ||||
|  |  | |||
|  | @ -1153,39 +1153,6 @@ class TiffImageFile(ImageFile.ImageFile): | |||
|         """Return the current frame number""" | ||||
|         return self.__frame | ||||
| 
 | ||||
|     def get_child_images(self): | ||||
|         if SUBIFD not in self.tag_v2: | ||||
|             return [] | ||||
|         child_images = [] | ||||
|         exif = self.getexif() | ||||
|         offset = None | ||||
|         for im_offset in self.tag_v2[SUBIFD]: | ||||
|             # reset buffered io handle in case fp | ||||
|             # was passed to libtiff, invalidating the buffer | ||||
|             current_offset = self._fp.tell() | ||||
|             if offset is None: | ||||
|                 offset = current_offset | ||||
| 
 | ||||
|             fp = self._fp | ||||
|             ifd = exif._get_ifd_dict(im_offset) | ||||
|             jpegInterchangeFormat = ifd.get(513) | ||||
|             if jpegInterchangeFormat is not None: | ||||
|                 fp.seek(jpegInterchangeFormat) | ||||
|                 jpeg_data = fp.read(ifd.get(514)) | ||||
| 
 | ||||
|                 fp = io.BytesIO(jpeg_data) | ||||
| 
 | ||||
|             with Image.open(fp) as im: | ||||
|                 if jpegInterchangeFormat is None: | ||||
|                     im._frame_pos = [im_offset] | ||||
|                     im._seek(0) | ||||
|                 im.load() | ||||
|                 child_images.append(im) | ||||
| 
 | ||||
|         if offset is not None: | ||||
|             self._fp.seek(offset) | ||||
|         return child_images | ||||
| 
 | ||||
|     def getxmp(self): | ||||
|         """ | ||||
|         Returns a dictionary containing the XMP tags. | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user