mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 10:16:17 +03:00
Merge pull request #4031 from radarhere/exif
Lazily use ImageFileDirectory_v1 values from Exif
This commit is contained in:
commit
e5f6b86413
|
@ -321,3 +321,13 @@ class TestPyDecoder(PillowTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
exif.get_ifd(0xA005), {1: "R98", 2: b"0100", 4097: 2272, 4098: 1704}
|
exif.get_ifd(0xA005), {1: "R98", 2: b"0100", 4097: 2272, 4098: 1704}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_exif_shared(self):
|
||||||
|
im = Image.open("Tests/images/exif.png")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertIs(im.getexif(), exif)
|
||||||
|
|
||||||
|
def test_exif_str(self):
|
||||||
|
im = Image.open("Tests/images/exif.png")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(str(exif), "{274: 1}")
|
||||||
|
|
|
@ -555,6 +555,7 @@ class Image(object):
|
||||||
self.category = NORMAL
|
self.category = NORMAL
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
|
self._exif = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
|
@ -1324,10 +1325,10 @@ class Image(object):
|
||||||
return self.im.getextrema()
|
return self.im.getextrema()
|
||||||
|
|
||||||
def getexif(self):
|
def getexif(self):
|
||||||
exif = Exif()
|
if self._exif is None:
|
||||||
if "exif" in self.info:
|
self._exif = Exif()
|
||||||
exif.load(self.info["exif"])
|
self._exif.load(self.info.get("exif"))
|
||||||
return exif
|
return self._exif
|
||||||
|
|
||||||
def getim(self):
|
def getim(self):
|
||||||
"""
|
"""
|
||||||
|
@ -3137,11 +3138,10 @@ class Exif(MutableMapping):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._data = {}
|
self._data = {}
|
||||||
self._ifds = {}
|
self._ifds = {}
|
||||||
|
self._info = None
|
||||||
|
self._loaded_exif = None
|
||||||
|
|
||||||
def _fixup_dict(self, src_dict):
|
def _fixup(self, value):
|
||||||
# Helper function for _getexif()
|
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
|
||||||
def _fixup(value):
|
|
||||||
try:
|
try:
|
||||||
if len(value) == 1 and not isinstance(value, dict):
|
if len(value) == 1 and not isinstance(value, dict):
|
||||||
return value[0]
|
return value[0]
|
||||||
|
@ -3149,13 +3149,16 @@ class Exif(MutableMapping):
|
||||||
pass
|
pass
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return {k: _fixup(v) for k, v in src_dict.items()}
|
def _fixup_dict(self, src_dict):
|
||||||
|
# Helper function for _getexif()
|
||||||
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
|
return {k: self._fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
def _get_ifd_dict(self, tag):
|
def _get_ifd_dict(self, tag):
|
||||||
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._data[tag])
|
self.fp.seek(self[tag])
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -3172,16 +3175,24 @@ class Exif(MutableMapping):
|
||||||
|
|
||||||
# The EXIF record consists of a TIFF file embedded in a JPEG
|
# The EXIF record consists of a TIFF file embedded in a JPEG
|
||||||
# application marker (!).
|
# application marker (!).
|
||||||
|
if data == self._loaded_exif:
|
||||||
|
return
|
||||||
|
self._loaded_exif = data
|
||||||
|
self._data.clear()
|
||||||
|
self._ifds.clear()
|
||||||
|
self._info = None
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
self.fp = io.BytesIO(data[6:])
|
self.fp = io.BytesIO(data[6:])
|
||||||
self.head = self.fp.read(8)
|
self.head = self.fp.read(8)
|
||||||
# process dictionary
|
# process dictionary
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
self._info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
||||||
self.endian = info._endian
|
self.endian = self._info._endian
|
||||||
self.fp.seek(info.next)
|
self.fp.seek(self._info.next)
|
||||||
info.load(self.fp)
|
self._info.load(self.fp)
|
||||||
self._data = dict(self._fixup_dict(info))
|
|
||||||
|
|
||||||
# get EXIF extension
|
# get EXIF extension
|
||||||
ifd = self._get_ifd_dict(0x8769)
|
ifd = self._get_ifd_dict(0x8769)
|
||||||
|
@ -3189,12 +3200,6 @@ class Exif(MutableMapping):
|
||||||
self._data.update(ifd)
|
self._data.update(ifd)
|
||||||
self._ifds[0x8769] = 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):
|
def tobytes(self, offset=0):
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
|
|
||||||
|
@ -3203,19 +3208,20 @@ class Exif(MutableMapping):
|
||||||
else:
|
else:
|
||||||
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._data.items():
|
for tag, value in self.items():
|
||||||
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._data:
|
if tag not in self._ifds and tag in self:
|
||||||
if tag == 0xA005: # interop
|
if tag in [0x8825, 0xA005]:
|
||||||
|
# gpsinfo, interop
|
||||||
self._ifds[tag] = self._get_ifd_dict(tag)
|
self._ifds[tag] = self._get_ifd_dict(tag)
|
||||||
elif tag == 0x927C: # makernote
|
elif tag == 0x927C: # makernote
|
||||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||||
|
|
||||||
if self._data[0x927C][:8] == b"FUJIFILM":
|
if self[0x927C][:8] == b"FUJIFILM":
|
||||||
exif_data = self._data[0x927C]
|
exif_data = self[0x927C]
|
||||||
ifd_offset = i32le(exif_data[8:12])
|
ifd_offset = i32le(exif_data[8:12])
|
||||||
ifd_data = exif_data[ifd_offset:]
|
ifd_data = exif_data[ifd_offset:]
|
||||||
|
|
||||||
|
@ -3252,8 +3258,8 @@ class Exif(MutableMapping):
|
||||||
ImageFileDirectory_v2(), data, False
|
ImageFileDirectory_v2(), data, False
|
||||||
)
|
)
|
||||||
self._ifds[0x927C] = dict(self._fixup_dict(makernote))
|
self._ifds[0x927C] = dict(self._fixup_dict(makernote))
|
||||||
elif self._data.get(0x010F) == "Nintendo":
|
elif self.get(0x010F) == "Nintendo":
|
||||||
ifd_data = self._data[0x927C]
|
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", ifd_data[:2])[0]):
|
||||||
|
@ -3291,16 +3297,29 @@ class Exif(MutableMapping):
|
||||||
return self._ifds.get(tag, {})
|
return self._ifds.get(tag, {})
|
||||||
|
|
||||||
def __str__(self):
|
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)
|
return str(self._data)
|
||||||
|
|
||||||
def __len__(self):
|
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):
|
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]
|
return self._data[tag]
|
||||||
|
|
||||||
def __contains__(self, 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:
|
if not py3:
|
||||||
|
|
||||||
|
@ -3308,10 +3327,17 @@ class Exif(MutableMapping):
|
||||||
return tag in self
|
return tag in self
|
||||||
|
|
||||||
def __setitem__(self, tag, value):
|
def __setitem__(self, tag, value):
|
||||||
|
if self._info is not None and tag in self._info:
|
||||||
|
del self._info[tag]
|
||||||
self._data[tag] = value
|
self._data[tag] = value
|
||||||
|
|
||||||
def __delitem__(self, tag):
|
def __delitem__(self, tag):
|
||||||
|
if self._info is not None and tag in self._info:
|
||||||
|
del self._info[tag]
|
||||||
del self._data[tag]
|
del self._data[tag]
|
||||||
|
|
||||||
def __iter__(self):
|
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)
|
||||||
|
|
|
@ -158,7 +158,7 @@ def APP(self, marker):
|
||||||
# If DPI isn't in JPEG header, fetch from EXIF
|
# If DPI isn't in JPEG header, fetch from EXIF
|
||||||
if "dpi" not in self.info and "exif" in self.info:
|
if "dpi" not in self.info and "exif" in self.info:
|
||||||
try:
|
try:
|
||||||
exif = self._getexif()
|
exif = self.getexif()
|
||||||
resolution_unit = exif[0x0128]
|
resolution_unit = exif[0x0128]
|
||||||
x_resolution = exif[0x011A]
|
x_resolution = exif[0x011A]
|
||||||
try:
|
try:
|
||||||
|
@ -485,19 +485,9 @@ def _fixup_dict(src_dict):
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
# Use the cached version if possible
|
|
||||||
try:
|
|
||||||
return self.info["parsed_exif"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
exif = dict(self.getexif())
|
return dict(self.getexif())
|
||||||
|
|
||||||
# Cache the result for future use
|
|
||||||
self.info["parsed_exif"] = exif
|
|
||||||
return exif
|
|
||||||
|
|
||||||
|
|
||||||
def _getmp(self):
|
def _getmp(self):
|
||||||
|
|
|
@ -86,13 +86,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
self.offset = self.__mpoffsets[frame]
|
self.offset = self.__mpoffsets[frame]
|
||||||
|
|
||||||
self.fp.seek(self.offset + 2) # skip SOI marker
|
self.fp.seek(self.offset + 2) # skip SOI marker
|
||||||
if "parsed_exif" in self.info:
|
|
||||||
del self.info["parsed_exif"]
|
|
||||||
if i16(self.fp.read(2)) == 0xFFE1: # APP1
|
if i16(self.fp.read(2)) == 0xFFE1: # APP1
|
||||||
n = i16(self.fp.read(2)) - 2
|
n = i16(self.fp.read(2)) - 2
|
||||||
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
||||||
|
|
||||||
exif = self._getexif()
|
exif = self.getexif()
|
||||||
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:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user