diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 8b92aae45a7..d652bd978e5 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3137,25 +3137,26 @@ class Exif(MutableMapping): def __init__(self): self._data = {} self._ifds = {} + self.info = None + + def _fixup(self, value): + try: + if len(value) == 1 and not isinstance(value, dict): + return value[0] + except Exception: + pass + return value def _fixup_dict(self, src_dict): # Helper function for _getexif() # returns a dict with any single item tuples/lists as individual values - def _fixup(value): - try: - if len(value) == 1 and not isinstance(value, dict): - return value[0] - except Exception: - pass - return value - - return {k: _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): try: # an offset pointer to the location of the nested embedded IFD. # It should be a long, but may be corrupted. - self.fp.seek(self._data[tag]) + self.fp.seek(self[tag]) except (KeyError, TypeError): pass else: @@ -3177,11 +3178,10 @@ def load(self, data): # process dictionary from . import TiffImagePlugin - info = TiffImagePlugin.ImageFileDirectory_v1(self.head) - self.endian = info._endian - self.fp.seek(info.next) - info.load(self.fp) - self._data = dict(self._fixup_dict(info)) + self.info = TiffImagePlugin.ImageFileDirectory_v1(self.head) + self.endian = self.info._endian + self.fp.seek(self.info.next) + self.info.load(self.fp) # get EXIF extension ifd = self._get_ifd_dict(0x8769) @@ -3189,12 +3189,6 @@ def load(self, data): self._data.update(ifd) self._ifds[0x8769] = ifd - # get gpsinfo extension - ifd = self._get_ifd_dict(0x8825) - if ifd: - self._data[0x8825] = ifd - self._ifds[0x8825] = ifd - def tobytes(self, offset=0): from . import TiffImagePlugin @@ -3203,19 +3197,20 @@ def tobytes(self, offset=0): else: head = b"MM\x00\x2A\x00\x00\x00\x08" ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) - for tag, value in self._data.items(): + for tag, value in self.items(): ifd[tag] = value return b"Exif\x00\x00" + head + ifd.tobytes(offset) def get_ifd(self, tag): - if tag not in self._ifds and tag in self._data: - if tag == 0xA005: # interop + if tag not in self._ifds and tag in self: + if tag in [0x8825, 0xA005]: + # gpsinfo, interop self._ifds[tag] = self._get_ifd_dict(tag) elif tag == 0x927C: # makernote from .TiffImagePlugin import ImageFileDirectory_v2 - if self._data[0x927C][:8] == b"FUJIFILM": - exif_data = self._data[0x927C] + if self[0x927C][:8] == b"FUJIFILM": + exif_data = self[0x927C] ifd_offset = i32le(exif_data[8:12]) ifd_data = exif_data[ifd_offset:] @@ -3252,8 +3247,8 @@ def get_ifd(self, tag): ImageFileDirectory_v2(), data, False ) self._ifds[0x927C] = dict(self._fixup_dict(makernote)) - elif self._data.get(0x010F) == "Nintendo": - ifd_data = self._data[0x927C] + elif self.get(0x010F) == "Nintendo": + ifd_data = self[0x927C] makernote = {} for i in range(0, struct.unpack(">H", ifd_data[:2])[0]): @@ -3291,16 +3286,29 @@ def get_ifd(self, tag): return self._ifds.get(tag, {}) def __str__(self): + if self.info is not None: + # Load all keys into self._data + for tag in self.info.keys(): + self[tag] + return str(self._data) def __len__(self): - return len(self._data) + keys = set(self._data) + if self.info is not None: + keys.update(self.info) + return len(keys) def __getitem__(self, tag): + 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]) + if tag == 0x8825: + self._data[tag] = self.get_ifd(tag) + del self.info[tag] return self._data[tag] def __contains__(self, tag): - return tag in self._data + return tag in self._data or (self.info is not None and tag in self.info) if not py3: @@ -3308,10 +3316,23 @@ def has_key(self, tag): return tag in self def __setitem__(self, tag, value): + if self.info is not None: + try: + del self.info[tag] + except KeyError: + pass self._data[tag] = value def __delitem__(self, tag): + if self.info is not None: + try: + del self.info[tag] + except KeyError: + pass del self._data[tag] def __iter__(self): - return iter(set(self._data)) + keys = set(self._data) + if self.info is not None: + keys.update(self.info) + return iter(keys) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index f1a2f78132a..1770fc2105b 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -158,7 +158,7 @@ def APP(self, marker): # If DPI isn't in JPEG header, fetch from EXIF if "dpi" not in self.info and "exif" in self.info: try: - exif = self._getexif() + exif = self.getexif() resolution_unit = exif[0x0128] x_resolution = exif[0x011A] try: diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 81b37172a18..24e953013dc 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -92,7 +92,7 @@ def seek(self, frame): n = i16(self.fp.read(2)) - 2 self.info["exif"] = ImageFile._safe_read(self.fp, n) - exif = self._getexif() + exif = self.getexif() if 40962 in exif and 40963 in exif: self._size = (exif[40962], exif[40963]) elif "exif" in self.info: