mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-11 04:07:21 +03:00
Fix EXIF support.
This commit is contained in:
parent
974bcc074b
commit
56a3f0f2ab
|
@ -394,13 +394,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
return _getmp(self)
|
return _getmp(self)
|
||||||
|
|
||||||
|
|
||||||
def _fixup(value):
|
|
||||||
# Helper function for _getexif() and _getmp()
|
|
||||||
if len(value) == 1:
|
|
||||||
return value[0]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
# Extract EXIF information. This method is highly experimental,
|
# Extract EXIF information. This method is highly experimental,
|
||||||
# and is likely to be replaced with something better in a future
|
# and is likely to be replaced with something better in a future
|
||||||
|
@ -414,12 +407,10 @@ def _getexif(self):
|
||||||
return None
|
return None
|
||||||
file = io.BytesIO(data[6:])
|
file = io.BytesIO(data[6:])
|
||||||
head = file.read(8)
|
head = file.read(8)
|
||||||
exif = {}
|
|
||||||
# process dictionary
|
# process dictionary
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
exif = dict(info)
|
||||||
exif[key] = _fixup(value)
|
|
||||||
# get exif extension
|
# get exif extension
|
||||||
try:
|
try:
|
||||||
# exif field 0x8769 is an offset pointer to the location
|
# exif field 0x8769 is an offset pointer to the location
|
||||||
|
@ -431,8 +422,7 @@ def _getexif(self):
|
||||||
else:
|
else:
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
exif.update(info)
|
||||||
exif[key] = _fixup(value)
|
|
||||||
# get gpsinfo extension
|
# get gpsinfo extension
|
||||||
try:
|
try:
|
||||||
# exif field 0x8825 is an offset pointer to the location
|
# exif field 0x8825 is an offset pointer to the location
|
||||||
|
@ -444,9 +434,7 @@ def _getexif(self):
|
||||||
else:
|
else:
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
exif[0x8825] = gps = {}
|
exif[0x8825] = dict(info)
|
||||||
for key, value in info.items():
|
|
||||||
gps[key] = _fixup(value)
|
|
||||||
return exif
|
return exif
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,12 +452,10 @@ def _getmp(self):
|
||||||
file_contents = io.BytesIO(data)
|
file_contents = io.BytesIO(data)
|
||||||
head = file_contents.read(8)
|
head = file_contents.read(8)
|
||||||
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||||
mp = {}
|
|
||||||
# process dictionary
|
# process dictionary
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file_contents)
|
info.load(file_contents)
|
||||||
for key, value in info.items():
|
mp = dict(info)
|
||||||
mp[key] = _fixup(value)
|
|
||||||
# it's an error not to have a number of images
|
# it's an error not to have a number of images
|
||||||
try:
|
try:
|
||||||
quant = mp[0xB001]
|
quant = mp[0xB001]
|
||||||
|
|
|
@ -253,18 +253,28 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
really sure what they're doing.
|
really sure what they're doing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, prefix=II):
|
def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None):
|
||||||
|
"""Initialize an ImageFileDirectory.
|
||||||
|
|
||||||
|
To construct an ImageFileDirectory from a real file, pass the 8-byte
|
||||||
|
magic header to the constructor. To only set the endianness, pass it
|
||||||
|
as the 'prefix' keyword argument.
|
||||||
|
|
||||||
|
:ifh: One of the accepted magic headers (cf. PREFIXES); also sets
|
||||||
|
endianness.
|
||||||
|
:prefix: Override the endianness of the file.
|
||||||
"""
|
"""
|
||||||
:prefix: "II"|"MM" tiff endianness
|
if ifh[:4] not in PREFIXES:
|
||||||
"""
|
raise SyntaxError("not a TIFF file (header %r not valid)" % ifh)
|
||||||
self._prefix = prefix
|
self._prefix = prefix if prefix is not None else ifh[:2]
|
||||||
if prefix == MM:
|
if self._prefix == MM:
|
||||||
self._endian = ">"
|
self._endian = ">"
|
||||||
elif prefix == II:
|
elif self._prefix == II:
|
||||||
self._endian = "<"
|
self._endian = "<"
|
||||||
else:
|
else:
|
||||||
raise ValueError("not a TIFF IFD")
|
raise SyntaxError("not a TIFF IFD")
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.next, = self._unpack("L", ifh[4:])
|
||||||
|
|
||||||
prefix = property(lambda self: self._prefix)
|
prefix = property(lambda self: self._prefix)
|
||||||
offset = property(lambda self: self._offset)
|
offset = property(lambda self: self._offset)
|
||||||
|
@ -360,10 +370,10 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return itertools.chain(list(self._tags), list(self._tagdata))
|
return itertools.chain(list(self._tags), list(self._tagdata))
|
||||||
|
|
||||||
def unpack(self, fmt, data):
|
def _unpack(self, fmt, data):
|
||||||
return struct.unpack(self._endian + fmt, data)
|
return struct.unpack(self._endian + fmt, data)
|
||||||
|
|
||||||
def pack(self, fmt, *values):
|
def _pack(self, fmt, *values):
|
||||||
return struct.pack(self._endian + fmt, *values)
|
return struct.pack(self._endian + fmt, *values)
|
||||||
|
|
||||||
def _register_loader(idx, size):
|
def _register_loader(idx, size):
|
||||||
|
@ -387,9 +397,9 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
TYPES[idx] = name
|
TYPES[idx] = name
|
||||||
size = struct.calcsize("=" + fmt)
|
size = struct.calcsize("=" + fmt)
|
||||||
_load_dispatch[idx] = size, lambda self, data: (
|
_load_dispatch[idx] = size, lambda self, data: (
|
||||||
self.unpack("{}{}".format(len(data) // size, fmt), data))
|
self._unpack("{}{}".format(len(data) // size, fmt), data))
|
||||||
_write_dispatch[idx] = lambda self, *values: (
|
_write_dispatch[idx] = lambda self, *values: (
|
||||||
b"".join(self.pack(fmt, value) for value in values))
|
b"".join(self._pack(fmt, value) for value in values))
|
||||||
|
|
||||||
list(map(_register_basic,
|
list(map(_register_basic,
|
||||||
[(1, "B", "byte"), (3, "H", "short"), (4, "L", "long"),
|
[(1, "B", "byte"), (3, "H", "short"), (4, "L", "long"),
|
||||||
|
@ -411,12 +421,12 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
|
|
||||||
@_register_loader(5, 8)
|
@_register_loader(5, 8)
|
||||||
def load_rational(self, data):
|
def load_rational(self, data):
|
||||||
vals = self.unpack("{}L".format(len(data) // 4), data)
|
vals = self._unpack("{}L".format(len(data) // 4), data)
|
||||||
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2]))
|
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
@_register_writer(5)
|
@_register_writer(5)
|
||||||
def write_rational(self, *values):
|
def write_rational(self, *values):
|
||||||
return b"".join(self.pack("2L", *_limit_rational(frac, 2 ** 31))
|
return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 31))
|
||||||
for frac in values)
|
for frac in values)
|
||||||
|
|
||||||
@_register_loader(7, 1)
|
@_register_loader(7, 1)
|
||||||
|
@ -429,12 +439,12 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
|
|
||||||
@_register_loader(10, 8)
|
@_register_loader(10, 8)
|
||||||
def load_signed_rational(self, data):
|
def load_signed_rational(self, data):
|
||||||
vals = self.unpack("{}l".format(len(data) // 4), data)
|
vals = self._unpack("{}l".format(len(data) // 4), data)
|
||||||
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2]))
|
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
@_register_writer(10)
|
@_register_writer(10)
|
||||||
def write_signed_rational(self, *values):
|
def write_signed_rational(self, *values):
|
||||||
return b"".join(self.pack("2L", *_limit_rational(frac, 2 ** 30))
|
return b"".join(self._pack("2L", *_limit_rational(frac, 2 ** 30))
|
||||||
for frac in values)
|
for frac in values)
|
||||||
|
|
||||||
def load(self, fp):
|
def load(self, fp):
|
||||||
|
@ -442,9 +452,9 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
self.reset()
|
self.reset()
|
||||||
self._offset = fp.tell()
|
self._offset = fp.tell()
|
||||||
|
|
||||||
for i in range(self.unpack("H", fp.read(2))[0]):
|
for i in range(self._unpack("H", fp.read(2))[0]):
|
||||||
tag, typ, count, data = self.unpack("HHL4s", fp.read(12))
|
tag, typ, count, data = self._unpack("HHL4s", fp.read(12))
|
||||||
if DEBUG:
|
if Image.DEBUG:
|
||||||
tagname = TAGS.get(tag, TagInfo()).name
|
tagname = TAGS.get(tag, TagInfo()).name
|
||||||
typname = TYPES.get(typ, "unknown")
|
typname = TYPES.get(typ, "unknown")
|
||||||
print("tag: %s (%d) - type: %s (%d)" %
|
print("tag: %s (%d) - type: %s (%d)" %
|
||||||
|
@ -459,8 +469,8 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
size = count * unit_size
|
size = count * unit_size
|
||||||
if size > 4:
|
if size > 4:
|
||||||
here = fp.tell()
|
here = fp.tell()
|
||||||
offset, = self.unpack("L", data)
|
offset, = self._unpack("L", data)
|
||||||
if DEBUG:
|
if Image.DEBUG:
|
||||||
print("Tag Location: %s - Data Location: %s" %
|
print("Tag Location: %s - Data Location: %s" %
|
||||||
(here, offset), end=" ")
|
(here, offset), end=" ")
|
||||||
fp.seek(offset)
|
fp.seek(offset)
|
||||||
|
@ -484,12 +494,16 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
else:
|
else:
|
||||||
print("- value:", self[tag])
|
print("- value:", self[tag])
|
||||||
|
|
||||||
self.next, = self.unpack("L", fp.read(4))
|
self.next, = self._unpack("L", fp.read(4))
|
||||||
|
|
||||||
def save(self, fp):
|
def save(self, fp):
|
||||||
|
|
||||||
|
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
||||||
|
# tiff header -- PIL always starts the first IFD at offset 8
|
||||||
|
fp.write(self._prefix + self._pack("HL", 42, 8))
|
||||||
|
|
||||||
# FIXME What about tagdata?
|
# FIXME What about tagdata?
|
||||||
fp.write(self.pack("H", len(self._tags)))
|
fp.write(self._pack("H", len(self._tags)))
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
offset = fp.tell() + len(self._tags) * 12 + 4
|
offset = fp.tell() + len(self._tags) * 12 + 4
|
||||||
|
@ -521,7 +535,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
if len(data) <= 4:
|
if len(data) <= 4:
|
||||||
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
|
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
|
||||||
else:
|
else:
|
||||||
entries.append((tag, typ, count, self.pack("L", offset), data))
|
entries.append((tag, typ, count, self._pack("L", offset), data))
|
||||||
offset += (len(data) + 1) // 2 * 2 # pad to word
|
offset += (len(data) + 1) // 2 * 2 # pad to word
|
||||||
|
|
||||||
# update strip offset data to point beyond auxiliary data
|
# update strip offset data to point beyond auxiliary data
|
||||||
|
@ -530,14 +544,14 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
if data:
|
if data:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"multistrip support not yet implemented")
|
"multistrip support not yet implemented")
|
||||||
value = self.pack("L", self.unpack("L", value)[0] + offset)
|
value = self._pack("L", self._unpack("L", value)[0] + offset)
|
||||||
entries[stripoffsets] = tag, typ, count, value, data
|
entries[stripoffsets] = tag, typ, count, value, data
|
||||||
|
|
||||||
# pass 2: write entries to file
|
# pass 2: write entries to file
|
||||||
for tag, typ, count, value, data in entries:
|
for tag, typ, count, value, data in entries:
|
||||||
if DEBUG > 1:
|
if DEBUG > 1:
|
||||||
print(tag, typ, count, repr(value), repr(data))
|
print(tag, typ, count, repr(value), repr(data))
|
||||||
fp.write(self.pack("HHL4s", tag, typ, count, value))
|
fp.write(self._pack("HHL4s", tag, typ, count, value))
|
||||||
|
|
||||||
# -- overwrite here for multi-page --
|
# -- overwrite here for multi-page --
|
||||||
fp.write(b"\0\0\0\0") # end of entries
|
fp.write(b"\0\0\0\0") # end of entries
|
||||||
|
@ -573,14 +587,11 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# Header
|
# Header
|
||||||
ifh = self.fp.read(8)
|
ifh = self.fp.read(8)
|
||||||
|
|
||||||
if ifh[:4] not in PREFIXES:
|
|
||||||
raise SyntaxError("not a TIFF file")
|
|
||||||
|
|
||||||
# image file directory (tag dictionary)
|
# image file directory (tag dictionary)
|
||||||
self.tag = self.ifd = ImageFileDirectory(ifh[:2])
|
self.tag = self.ifd = ImageFileDirectory(ifh)
|
||||||
|
|
||||||
# setup frame pointers
|
# setup frame pointers
|
||||||
self.__first, = self.__next, = self.ifd.unpack("L", ifh[4:])
|
self.__first = self.__next = self.ifd.next
|
||||||
self.__frame = -1
|
self.__frame = -1
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
self._frame_pos = []
|
self._frame_pos = []
|
||||||
|
@ -991,7 +1002,7 @@ def _save(im, fp, filename):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise IOError("cannot write mode %s as TIFF" % im.mode)
|
raise IOError("cannot write mode %s as TIFF" % im.mode)
|
||||||
|
|
||||||
ifd = ImageFileDirectory(prefix)
|
ifd = ImageFileDirectory(prefix=prefix)
|
||||||
|
|
||||||
compression = im.encoderinfo.get('compression', im.info.get('compression',
|
compression = im.encoderinfo.get('compression', im.info.get('compression',
|
||||||
'raw'))
|
'raw'))
|
||||||
|
@ -1001,12 +1012,6 @@ def _save(im, fp, filename):
|
||||||
# required for color libtiff images
|
# required for color libtiff images
|
||||||
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
|
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
|
||||||
|
|
||||||
# -- multi-page -- skip TIFF header on subsequent pages
|
|
||||||
if not libtiff and fp.tell() == 0:
|
|
||||||
# tiff header (write via IFD to get everything right)
|
|
||||||
# PIL always starts the first IFD at offset 8
|
|
||||||
fp.write(ifd.prefix + ifd.pack("HL", 42, 8))
|
|
||||||
|
|
||||||
ifd[IMAGEWIDTH] = im.size[0]
|
ifd[IMAGEWIDTH] = im.size[0]
|
||||||
ifd[IMAGELENGTH] = im.size[1]
|
ifd[IMAGELENGTH] = im.size[1]
|
||||||
|
|
||||||
|
|
|
@ -129,9 +129,9 @@ TAGS = {
|
||||||
33432: ("Copyright", 2, 1),
|
33432: ("Copyright", 2, 1),
|
||||||
|
|
||||||
# FIXME add more tags here
|
# FIXME add more tags here
|
||||||
|
34665: ("ExifIFD", 3, 1),
|
||||||
50741: ("MakerNoteSafety", 3, 1, {0: "Unsafe", 1: "Safe"}),
|
50741: ("MakerNoteSafety", 3, 1, {0: "Unsafe", 1: "Safe"}),
|
||||||
50780: ("BestQualityScale", 5, 1),
|
50780: ("BestQualityScale", 5, 1),
|
||||||
# private tags registered with Adobe
|
|
||||||
50838: ("ImageJMetaDataByteCounts", 4, 1),
|
50838: ("ImageJMetaDataByteCounts", 4, 1),
|
||||||
50839: ("ImageJMetaData", 7, 1)
|
50839: ("ImageJMetaData", 7, 1)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user