mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-15 10:42:19 +03:00
Parse Nintendo and Fujifilm MakerNote tags
This commit is contained in:
parent
e6a7dc8bb4
commit
64910d1921
BIN
Tests/images/fujifilm.mpo
Normal file
BIN
Tests/images/fujifilm.mpo
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 MiB |
|
@ -64,6 +64,18 @@ class TestFileMpo(PillowTestCase):
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
self.assertEqual(im.size, (680, 480))
|
self.assertEqual(im.size, (680, 480))
|
||||||
|
|
||||||
|
def test_parallax(self):
|
||||||
|
# Nintendo
|
||||||
|
im = Image.open("Tests/images/sugarshack.mpo")
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(exif.get_ifd(0x927c)[0x1101]["Parallax"], -44.798187255859375)
|
||||||
|
|
||||||
|
# Fujifilm
|
||||||
|
im = Image.open("Tests/images/fujifilm.mpo")
|
||||||
|
im.seek(1)
|
||||||
|
exif = im.getexif()
|
||||||
|
self.assertEqual(exif.get_ifd(0x927c)[0xb211], -3.125)
|
||||||
|
|
||||||
def test_mp(self):
|
def test_mp(self):
|
||||||
for test_file in test_files:
|
for test_file in test_files:
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
|
|
@ -27,11 +27,13 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
from . import Image
|
from . import Image, TiffTags
|
||||||
|
from ._binary import i32le
|
||||||
from ._util import isPath, py3
|
from ._util import isPath, py3
|
||||||
import io
|
import io
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
import warnings
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 3
|
# Python 3
|
||||||
|
@ -688,10 +690,12 @@ class PyDecoder(object):
|
||||||
|
|
||||||
|
|
||||||
class Exif(MutableMapping):
|
class Exif(MutableMapping):
|
||||||
_data = {}
|
|
||||||
_ifds = {}
|
|
||||||
endian = "<"
|
endian = "<"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._data = {}
|
||||||
|
self._ifds = {}
|
||||||
|
|
||||||
def _fixup_dict(self, src_dict):
|
def _fixup_dict(self, src_dict):
|
||||||
# Helper function for _getexif()
|
# Helper function for _getexif()
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
|
@ -705,17 +709,17 @@ class Exif(MutableMapping):
|
||||||
|
|
||||||
return {k: _fixup(v) for k, v in src_dict.items()}
|
return {k: _fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
def _get_ifd_dict(self, fp, head, 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.
|
||||||
fp.seek(self._data[tag])
|
self.fp.seek(self._data[tag])
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
||||||
info.load(fp)
|
info.load(self.fp)
|
||||||
return self._fixup_dict(info)
|
return self._fixup_dict(info)
|
||||||
|
|
||||||
def load(self, data):
|
def load(self, data):
|
||||||
|
@ -725,35 +729,28 @@ 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 (!).
|
||||||
fp = io.BytesIO(data[6:])
|
self.fp = io.BytesIO(data[6:])
|
||||||
head = fp.read(8)
|
self.head = self.fp.read(8)
|
||||||
# process dictionary
|
# process dictionary
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v1(head)
|
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
|
||||||
self.endian = info._endian
|
self.endian = info._endian
|
||||||
fp.seek(info.next)
|
self.fp.seek(info.next)
|
||||||
info.load(fp)
|
info.load(self.fp)
|
||||||
self._data = dict(self._fixup_dict(info))
|
self._data = dict(self._fixup_dict(info))
|
||||||
|
|
||||||
# get EXIF extension
|
# get EXIF extension
|
||||||
ifd = self._get_ifd_dict(fp, head, 0x8769)
|
ifd = self._get_ifd_dict(0x8769)
|
||||||
if ifd:
|
if ifd:
|
||||||
self._data.update(ifd)
|
self._data.update(ifd)
|
||||||
self._ifds[0x8769] = ifd
|
self._ifds[0x8769] = ifd
|
||||||
|
|
||||||
# get gpsinfo extension
|
# get gpsinfo extension
|
||||||
ifd = self._get_ifd_dict(fp, head, 0x8825)
|
ifd = self._get_ifd_dict(0x8825)
|
||||||
if ifd:
|
if ifd:
|
||||||
self._data[0x8825] = ifd
|
self._data[0x8825] = ifd
|
||||||
self._ifds[0x8825] = ifd
|
self._ifds[0x8825] = ifd
|
||||||
|
|
||||||
for tag in [
|
|
||||||
0xa005, # interop
|
|
||||||
]:
|
|
||||||
ifd = self._get_ifd_dict(fp, head, tag)
|
|
||||||
if ifd:
|
|
||||||
self._ifds[tag] = ifd
|
|
||||||
|
|
||||||
def toBytes(self, offset=0):
|
def toBytes(self, offset=0):
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
if self.endian == "<":
|
if self.endian == "<":
|
||||||
|
@ -766,6 +763,83 @@ class Exif(MutableMapping):
|
||||||
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 == 0xa005: # interop
|
||||||
|
self._ifds[tag] = self._get_ifd_dict(tag)
|
||||||
|
elif tag == 0x927c: # makernote
|
||||||
|
from . import TiffImagePlugin
|
||||||
|
if self._data[0x927c][:8] == b"FUJIFILM":
|
||||||
|
exif_data = self._data[0x927c]
|
||||||
|
ifd_offset = i32le(exif_data[8:12])
|
||||||
|
ifd_data = exif_data[ifd_offset:]
|
||||||
|
|
||||||
|
makernote = {}
|
||||||
|
for i in range(0, struct.unpack("<H", ifd_data[:2])[0]):
|
||||||
|
ifd_tag, typ, count, data = struct.unpack(
|
||||||
|
"<HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
|
||||||
|
try:
|
||||||
|
unit_size, handler =\
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
|
||||||
|
typ
|
||||||
|
]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
size = count * unit_size
|
||||||
|
if size > 4:
|
||||||
|
offset, = struct.unpack("<L", data)
|
||||||
|
data = ifd_data[offset-12:offset+size-12]
|
||||||
|
else:
|
||||||
|
data = data[:size]
|
||||||
|
|
||||||
|
if len(data) != size:
|
||||||
|
warnings.warn("Possibly corrupt EXIF MakerNote data. "
|
||||||
|
"Expecting to read %d bytes but only got %d."
|
||||||
|
" Skipping tag %s"
|
||||||
|
% (size, len(data), ifd_tag))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
makernote[ifd_tag] = handler(
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2(), data, False)
|
||||||
|
self._ifds[0x927c] = dict(self._fixup_dict(makernote))
|
||||||
|
elif self._data.get(0x010f) == "Nintendo":
|
||||||
|
ifd_data = self._data[0x927c]
|
||||||
|
|
||||||
|
makernote = {}
|
||||||
|
for i in range(0, struct.unpack(">H", ifd_data[:2])[0]):
|
||||||
|
ifd_tag, typ, count, data = struct.unpack(
|
||||||
|
">HHL4s", ifd_data[i*12 + 2:(i+1)*12 + 2])
|
||||||
|
if ifd_tag == 0x1101:
|
||||||
|
# CameraInfo
|
||||||
|
offset, = struct.unpack(">L", data)
|
||||||
|
self.fp.seek(offset)
|
||||||
|
|
||||||
|
camerainfo = {'ModelID': self.fp.read(4)}
|
||||||
|
|
||||||
|
self.fp.read(4)
|
||||||
|
# Seconds since 2000
|
||||||
|
camerainfo['TimeStamp'] = i32le(self.fp.read(12))
|
||||||
|
|
||||||
|
self.fp.read(4)
|
||||||
|
camerainfo['InternalSerialNumber'] = self.fp.read(4)
|
||||||
|
|
||||||
|
self.fp.read(12)
|
||||||
|
parallax = self.fp.read(4)
|
||||||
|
handler =\
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2._load_dispatch[
|
||||||
|
TiffTags.FLOAT
|
||||||
|
][1]
|
||||||
|
camerainfo['Parallax'] = handler(
|
||||||
|
TiffImagePlugin.ImageFileDirectory_v2(),
|
||||||
|
parallax, False)
|
||||||
|
|
||||||
|
self.fp.read(4)
|
||||||
|
camerainfo['Category'] = self.fp.read(2)
|
||||||
|
|
||||||
|
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
||||||
|
self._ifds[0x927c] = makernote
|
||||||
return self._ifds.get(tag, {})
|
return self._ifds.get(tag, {})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user