mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 09:57:43 +03:00 
			
		
		
		
	Merge pull request #4947 from radarhere/exif
This commit is contained in:
		
						commit
						5a209081b2
					
				| 
						 | 
					@ -264,11 +264,11 @@ class TestFileJpeg:
 | 
				
			||||||
            assert exif[0x0112] == Image.TRANSVERSE
 | 
					            assert exif[0x0112] == Image.TRANSVERSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Assert that the GPS IFD is present and empty
 | 
					            # Assert that the GPS IFD is present and empty
 | 
				
			||||||
            assert exif[0x8825] == {}
 | 
					            assert exif.get_ifd(0x8825) == {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            transposed = ImageOps.exif_transpose(im)
 | 
					            transposed = ImageOps.exif_transpose(im)
 | 
				
			||||||
        exif = transposed.getexif()
 | 
					        exif = transposed.getexif()
 | 
				
			||||||
        assert exif[0x8825] == {}
 | 
					        assert exif.get_ifd(0x8825) == {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Assert that it was transposed
 | 
					        # Assert that it was transposed
 | 
				
			||||||
        assert 0x0112 not in exif
 | 
					        assert 0x0112 not in exif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -667,43 +667,43 @@ class TestImage:
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            assert 258 not in exif
 | 
					            assert 258 not in exif
 | 
				
			||||||
            assert 274 in exif
 | 
					            assert 274 in exif
 | 
				
			||||||
            assert 40960 in exif
 | 
					            assert 282 in exif
 | 
				
			||||||
            assert exif[40963] == 450
 | 
					            assert exif[296] == 2
 | 
				
			||||||
            assert exif[11] == "gThumb 3.0.1"
 | 
					            assert exif[11] == "gThumb 3.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.jpg")
 | 
					            out = str(tmp_path / "temp.jpg")
 | 
				
			||||||
            exif[258] = 8
 | 
					            exif[258] = 8
 | 
				
			||||||
            del exif[274]
 | 
					            del exif[274]
 | 
				
			||||||
            del exif[40960]
 | 
					            del exif[282]
 | 
				
			||||||
            exif[40963] = 455
 | 
					            exif[296] = 455
 | 
				
			||||||
            exif[11] = "Pillow test"
 | 
					            exif[11] = "Pillow test"
 | 
				
			||||||
            im.save(out, exif=exif)
 | 
					            im.save(out, exif=exif)
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            reloaded_exif = reloaded.getexif()
 | 
					            reloaded_exif = reloaded.getexif()
 | 
				
			||||||
            assert reloaded_exif[258] == 8
 | 
					            assert reloaded_exif[258] == 8
 | 
				
			||||||
            assert 274 not in reloaded_exif
 | 
					            assert 274 not in reloaded_exif
 | 
				
			||||||
            assert 40960 not in reloaded_exif
 | 
					            assert 282 not in reloaded_exif
 | 
				
			||||||
            assert reloaded_exif[40963] == 455
 | 
					            assert reloaded_exif[296] == 455
 | 
				
			||||||
            assert reloaded_exif[11] == "Pillow test"
 | 
					            assert reloaded_exif[11] == "Pillow test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:  # Big endian
 | 
					        with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:  # Big endian
 | 
				
			||||||
            exif = im.getexif()
 | 
					            exif = im.getexif()
 | 
				
			||||||
            assert 258 not in exif
 | 
					            assert 258 not in exif
 | 
				
			||||||
            assert 40962 in exif
 | 
					            assert 306 in exif
 | 
				
			||||||
            assert exif[40963] == 200
 | 
					            assert exif[274] == 1
 | 
				
			||||||
            assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
 | 
					            assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out = str(tmp_path / "temp.jpg")
 | 
					            out = str(tmp_path / "temp.jpg")
 | 
				
			||||||
            exif[258] = 8
 | 
					            exif[258] = 8
 | 
				
			||||||
            del exif[34665]
 | 
					            del exif[306]
 | 
				
			||||||
            exif[40963] = 455
 | 
					            exif[274] = 455
 | 
				
			||||||
            exif[305] = "Pillow test"
 | 
					            exif[305] = "Pillow test"
 | 
				
			||||||
            im.save(out, exif=exif)
 | 
					            im.save(out, exif=exif)
 | 
				
			||||||
        with Image.open(out) as reloaded:
 | 
					        with Image.open(out) as reloaded:
 | 
				
			||||||
            reloaded_exif = reloaded.getexif()
 | 
					            reloaded_exif = reloaded.getexif()
 | 
				
			||||||
            assert reloaded_exif[258] == 8
 | 
					            assert reloaded_exif[258] == 8
 | 
				
			||||||
            assert 34665 not in reloaded_exif
 | 
					            assert 306 not in reloaded_exif
 | 
				
			||||||
            assert reloaded_exif[40963] == 455
 | 
					            assert reloaded_exif[274] == 455
 | 
				
			||||||
            assert reloaded_exif[305] == "Pillow test"
 | 
					            assert reloaded_exif[305] == "Pillow test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_unless_feature("webp")
 | 
					    @skip_unless_feature("webp")
 | 
				
			||||||
| 
						 | 
					@ -756,6 +756,19 @@ class TestImage:
 | 
				
			||||||
                4098: 1704,
 | 
					                4098: 1704,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            reloaded_exif = Image.Exif()
 | 
				
			||||||
 | 
					            reloaded_exif.load(exif.tobytes())
 | 
				
			||||||
 | 
					            assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_exif_ifd(self):
 | 
				
			||||||
 | 
					        with Image.open("Tests/images/flower.jpg") as im:
 | 
				
			||||||
 | 
					            exif = im.getexif()
 | 
				
			||||||
 | 
					        del exif.get_ifd(0x8769)[0xA005]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reloaded_exif = Image.Exif()
 | 
				
			||||||
 | 
					        reloaded_exif.load(exif.tobytes())
 | 
				
			||||||
 | 
					        assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.mark.parametrize(
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
        "test_module",
 | 
					        "test_module",
 | 
				
			||||||
        [PIL, Image],
 | 
					        [PIL, Image],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3307,11 +3307,11 @@ class Exif(MutableMapping):
 | 
				
			||||||
        # returns a dict with any single item tuples/lists as individual values
 | 
					        # returns a dict with any single item tuples/lists as individual values
 | 
				
			||||||
        return {k: self._fixup(v) for k, v in src_dict.items()}
 | 
					        return {k: self._fixup(v) for k, v in src_dict.items()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_ifd_dict(self, tag):
 | 
					    def _get_ifd_dict(self, offset):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            # an offset pointer to the location of the nested embedded IFD.
 | 
					            # an offset pointer to the location of the nested embedded IFD.
 | 
				
			||||||
            # It should be a long, but may be corrupted.
 | 
					            # It should be a long, but may be corrupted.
 | 
				
			||||||
            self.fp.seek(self[tag])
 | 
					            self.fp.seek(offset)
 | 
				
			||||||
        except (KeyError, TypeError):
 | 
					        except (KeyError, TypeError):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -3349,11 +3349,20 @@ class Exif(MutableMapping):
 | 
				
			||||||
        self.fp.seek(self._info.next)
 | 
					        self.fp.seek(self._info.next)
 | 
				
			||||||
        self._info.load(self.fp)
 | 
					        self._info.load(self.fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_merged_dict(self):
 | 
				
			||||||
 | 
					        merged_dict = dict(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # get EXIF extension
 | 
					        # get EXIF extension
 | 
				
			||||||
        ifd = self._get_ifd_dict(0x8769)
 | 
					        if 0x8769 in self:
 | 
				
			||||||
 | 
					            ifd = self._get_ifd_dict(self[0x8769])
 | 
				
			||||||
            if ifd:
 | 
					            if ifd:
 | 
				
			||||||
            self._data.update(ifd)
 | 
					                merged_dict.update(ifd)
 | 
				
			||||||
            self._ifds[0x8769] = ifd
 | 
					
 | 
				
			||||||
 | 
					        # GPS
 | 
				
			||||||
 | 
					        if 0x8825 in self:
 | 
				
			||||||
 | 
					            merged_dict[0x8825] = self._get_ifd_dict(self[0x8825])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return merged_dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def tobytes(self, offset=8):
 | 
					    def tobytes(self, offset=8):
 | 
				
			||||||
        from . import TiffImagePlugin
 | 
					        from . import TiffImagePlugin
 | 
				
			||||||
| 
						 | 
					@ -3364,21 +3373,36 @@ class Exif(MutableMapping):
 | 
				
			||||||
            head = b"MM\x00\x2A\x00\x00\x00\x08"
 | 
					            head = b"MM\x00\x2A\x00\x00\x00\x08"
 | 
				
			||||||
        ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
 | 
					        ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
 | 
				
			||||||
        for tag, value in self.items():
 | 
					        for tag, value in self.items():
 | 
				
			||||||
 | 
					            if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict):
 | 
				
			||||||
 | 
					                value = self.get_ifd(tag)
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                    tag == 0x8769
 | 
				
			||||||
 | 
					                    and 0xA005 in value
 | 
				
			||||||
 | 
					                    and not isinstance(value[0xA005], dict)
 | 
				
			||||||
 | 
					                ):
 | 
				
			||||||
 | 
					                    value = value.copy()
 | 
				
			||||||
 | 
					                    value[0xA005] = self.get_ifd(0xA005)
 | 
				
			||||||
            ifd[tag] = value
 | 
					            ifd[tag] = value
 | 
				
			||||||
        return b"Exif\x00\x00" + head + ifd.tobytes(offset)
 | 
					        return b"Exif\x00\x00" + head + ifd.tobytes(offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ifd(self, tag):
 | 
					    def get_ifd(self, tag):
 | 
				
			||||||
        if tag not in self._ifds and tag in self:
 | 
					        if tag not in self._ifds:
 | 
				
			||||||
            if tag in [0x8825, 0xA005]:
 | 
					            if tag in [0x8769, 0x8825]:
 | 
				
			||||||
                # gpsinfo, interop
 | 
					                # exif, gpsinfo
 | 
				
			||||||
                self._ifds[tag] = self._get_ifd_dict(tag)
 | 
					                if tag in self:
 | 
				
			||||||
            elif tag == 0x927C:  # makernote
 | 
					                    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
 | 
				
			||||||
                    from .TiffImagePlugin import ImageFileDirectory_v2
 | 
					                    from .TiffImagePlugin import ImageFileDirectory_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if self[0x927C][:8] == b"FUJIFILM":
 | 
					                    if tag_data[:8] == b"FUJIFILM":
 | 
				
			||||||
                    exif_data = self[0x927C]
 | 
					                        ifd_offset = i32le(tag_data, 8)
 | 
				
			||||||
                    ifd_offset = i32le(exif_data, 8)
 | 
					                        ifd_data = tag_data[ifd_offset:]
 | 
				
			||||||
                    ifd_data = exif_data[ifd_offset:]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        makernote = {}
 | 
					                        makernote = {}
 | 
				
			||||||
                        for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
 | 
					                        for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
 | 
				
			||||||
| 
						 | 
					@ -3386,9 +3410,10 @@ class Exif(MutableMapping):
 | 
				
			||||||
                                "<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
 | 
					                                "<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                            try:
 | 
					                            try:
 | 
				
			||||||
                            unit_size, handler = ImageFileDirectory_v2._load_dispatch[
 | 
					                                (
 | 
				
			||||||
                                typ
 | 
					                                    unit_size,
 | 
				
			||||||
                            ]
 | 
					                                    handler,
 | 
				
			||||||
 | 
					                                ) = ImageFileDirectory_v2._load_dispatch[typ]
 | 
				
			||||||
                            except KeyError:
 | 
					                            except KeyError:
 | 
				
			||||||
                                continue
 | 
					                                continue
 | 
				
			||||||
                            size = count * unit_size
 | 
					                            size = count * unit_size
 | 
				
			||||||
| 
						 | 
					@ -3412,14 +3437,12 @@ class Exif(MutableMapping):
 | 
				
			||||||
                            makernote[ifd_tag] = handler(
 | 
					                            makernote[ifd_tag] = handler(
 | 
				
			||||||
                                ImageFileDirectory_v2(), data, False
 | 
					                                ImageFileDirectory_v2(), data, False
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                    self._ifds[0x927C] = dict(self._fixup_dict(makernote))
 | 
					                        self._ifds[tag] = dict(self._fixup_dict(makernote))
 | 
				
			||||||
                    elif self.get(0x010F) == "Nintendo":
 | 
					                    elif self.get(0x010F) == "Nintendo":
 | 
				
			||||||
                    ifd_data = self[0x927C]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        makernote = {}
 | 
					                        makernote = {}
 | 
				
			||||||
                    for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
 | 
					                        for i in range(0, struct.unpack(">H", tag_data[:2])[0]):
 | 
				
			||||||
                            ifd_tag, typ, count, data = struct.unpack(
 | 
					                            ifd_tag, typ, count, data = struct.unpack(
 | 
				
			||||||
                            ">HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
 | 
					                                ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                            if ifd_tag == 0x1101:
 | 
					                            if ifd_tag == 0x1101:
 | 
				
			||||||
                                # CameraInfo
 | 
					                                # CameraInfo
 | 
				
			||||||
| 
						 | 
					@ -3448,7 +3471,10 @@ class Exif(MutableMapping):
 | 
				
			||||||
                                camerainfo["Category"] = self.fp.read(2)
 | 
					                                camerainfo["Category"] = self.fp.read(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
 | 
					                                makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
 | 
				
			||||||
                    self._ifds[0x927C] = makernote
 | 
					                        self._ifds[tag] = makernote
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # interop
 | 
				
			||||||
 | 
					                    self._ifds[tag] = self._get_ifd_dict(tag_data)
 | 
				
			||||||
        return self._ifds.get(tag, {})
 | 
					        return self._ifds.get(tag, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
| 
						 | 
					@ -3468,8 +3494,6 @@ class Exif(MutableMapping):
 | 
				
			||||||
    def __getitem__(self, tag):
 | 
					    def __getitem__(self, tag):
 | 
				
			||||||
        if self._info is not None and tag not in self._data and tag in self._info:
 | 
					        if self._info is not None and tag not in self._data and tag in self._info:
 | 
				
			||||||
            self._data[tag] = self._fixup(self._info[tag])
 | 
					            self._data[tag] = self._fixup(self._info[tag])
 | 
				
			||||||
            if tag == 0x8825:
 | 
					 | 
				
			||||||
                self._data[tag] = self.get_ifd(tag)
 | 
					 | 
				
			||||||
            del self._info[tag]
 | 
					            del self._info[tag]
 | 
				
			||||||
        return self._data[tag]
 | 
					        return self._data[tag]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -478,7 +478,7 @@ class JpegImageFile(ImageFile.ImageFile):
 | 
				
			||||||
def _getexif(self):
 | 
					def _getexif(self):
 | 
				
			||||||
    if "exif" not in self.info:
 | 
					    if "exif" not in self.info:
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
    return dict(self.getexif())
 | 
					    return self.getexif()._get_merged_dict()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _getmp(self):
 | 
					def _getmp(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,7 +84,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
 | 
					            mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
 | 
				
			||||||
            if mptype.startswith("Large Thumbnail"):
 | 
					            if mptype.startswith("Large Thumbnail"):
 | 
				
			||||||
                exif = self.getexif()
 | 
					                exif = self.getexif().get_ifd(0x8769)
 | 
				
			||||||
                if 40962 in exif and 40963 in exif:
 | 
					                if 40962 in exif and 40963 in exif:
 | 
				
			||||||
                    self._size = (exif[40962], exif[40963])
 | 
					                    self._size = (exif[40962], exif[40963])
 | 
				
			||||||
        elif "exif" in self.info:
 | 
					        elif "exif" in self.info:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -968,7 +968,7 @@ class PngImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            self.load()
 | 
					            self.load()
 | 
				
			||||||
        if "exif" not in self.info and "Raw profile type exif" not in self.info:
 | 
					        if "exif" not in self.info and "Raw profile type exif" not in self.info:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
        return dict(self.getexif())
 | 
					        return self.getexif()._get_merged_dict()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getexif(self):
 | 
					    def getexif(self):
 | 
				
			||||||
        if "exif" not in self.info:
 | 
					        if "exif" not in self.info:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -184,6 +184,7 @@ TAGS_V2 = {
 | 
				
			||||||
    34665: ("ExifIFD", LONG, 1),
 | 
					    34665: ("ExifIFD", LONG, 1),
 | 
				
			||||||
    34675: ("ICCProfile", UNDEFINED, 1),
 | 
					    34675: ("ICCProfile", UNDEFINED, 1),
 | 
				
			||||||
    34853: ("GPSInfoIFD", LONG, 1),
 | 
					    34853: ("GPSInfoIFD", LONG, 1),
 | 
				
			||||||
 | 
					    40965: ("InteroperabilityIFD", LONG, 1),
 | 
				
			||||||
    # MPInfo
 | 
					    # MPInfo
 | 
				
			||||||
    45056: ("MPFVersion", UNDEFINED, 1),
 | 
					    45056: ("MPFVersion", UNDEFINED, 1),
 | 
				
			||||||
    45057: ("NumberOfImages", LONG, 1),
 | 
					    45057: ("NumberOfImages", LONG, 1),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile):
 | 
				
			||||||
    def _getexif(self):
 | 
					    def _getexif(self):
 | 
				
			||||||
        if "exif" not in self.info:
 | 
					        if "exif" not in self.info:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
        return dict(self.getexif())
 | 
					        return self.getexif()._get_merged_dict()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def seek(self, frame):
 | 
					    def seek(self, frame):
 | 
				
			||||||
        if not self._seek_check(frame):
 | 
					        if not self._seek_check(frame):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user