mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 02:06:18 +03:00
Merge pull request #6748 from radarhere/exif_ifd
Added IFD enum to ExifTags
This commit is contained in:
commit
5257d561c0
BIN
Tests/images/flower_thumbnail.png
Normal file
BIN
Tests/images/flower_thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
|
@ -442,6 +442,13 @@ class TestFileJpeg:
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
assert info[305] == "Adobe Photoshop CS Macintosh"
|
assert info[305] == "Adobe Photoshop CS Macintosh"
|
||||||
|
|
||||||
|
def test_get_child_images(self):
|
||||||
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
|
ims = im.get_child_images()
|
||||||
|
|
||||||
|
assert len(ims) == 1
|
||||||
|
assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png")
|
||||||
|
|
||||||
def test_mp(self):
|
def test_mp(self):
|
||||||
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
||||||
assert im._getmp() is None
|
assert im._getmp() is None
|
||||||
|
|
|
@ -7,7 +7,14 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError, features
|
from PIL import (
|
||||||
|
ExifTags,
|
||||||
|
Image,
|
||||||
|
ImageDraw,
|
||||||
|
ImagePalette,
|
||||||
|
UnidentifiedImageError,
|
||||||
|
features,
|
||||||
|
)
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -808,6 +815,18 @@ class TestImage:
|
||||||
reloaded_exif.load(exif.tobytes())
|
reloaded_exif.load(exif.tobytes())
|
||||||
assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
|
assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
|
||||||
|
|
||||||
|
def test_exif_ifd1(self):
|
||||||
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
|
exif = im.getexif()
|
||||||
|
assert exif.get_ifd(ExifTags.IFD.IFD1) == {
|
||||||
|
513: 2036,
|
||||||
|
514: 5448,
|
||||||
|
259: 6,
|
||||||
|
296: 2,
|
||||||
|
282: 180.0,
|
||||||
|
283: 180.0,
|
||||||
|
}
|
||||||
|
|
||||||
def test_exif_ifd(self):
|
def test_exif_ifd(self):
|
||||||
with Image.open("Tests/images/flower.jpg") as im:
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
exif = im.getexif()
|
exif = im.getexif()
|
||||||
|
|
|
@ -31,6 +31,13 @@ which provide constants and clear-text names for various well-known EXIF tags.
|
||||||
>>> Interop(4096).name
|
>>> Interop(4096).name
|
||||||
'RelatedImageFileFormat'
|
'RelatedImageFileFormat'
|
||||||
|
|
||||||
|
.. py:data:: IFD
|
||||||
|
|
||||||
|
>>> from PIL.ExifTags import IFD
|
||||||
|
>>> IFD.Exif.value
|
||||||
|
34665
|
||||||
|
>>> IFD(34665).name
|
||||||
|
'Exif'
|
||||||
|
|
||||||
Two of these values are also exposed as dictionaries.
|
Two of these values are also exposed as dictionaries.
|
||||||
|
|
||||||
|
|
|
@ -346,3 +346,11 @@ class Interop(IntEnum):
|
||||||
RelatedImageFileFormat = 4096
|
RelatedImageFileFormat = 4096
|
||||||
RelatedImageWidth = 4097
|
RelatedImageWidth = 4097
|
||||||
RleatedImageHeight = 4098
|
RleatedImageHeight = 4098
|
||||||
|
|
||||||
|
|
||||||
|
class IFD(IntEnum):
|
||||||
|
Exif = 34665
|
||||||
|
GPSInfo = 34853
|
||||||
|
Makernote = 37500
|
||||||
|
Interop = 40965
|
||||||
|
IFD1 = -1
|
||||||
|
|
|
@ -47,7 +47,14 @@ except ImportError:
|
||||||
# VERSION was removed in Pillow 6.0.0.
|
# VERSION was removed in Pillow 6.0.0.
|
||||||
# PILLOW_VERSION was removed in Pillow 9.0.0.
|
# PILLOW_VERSION was removed in Pillow 9.0.0.
|
||||||
# Use __version__ instead.
|
# Use __version__ instead.
|
||||||
from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins
|
from . import (
|
||||||
|
ExifTags,
|
||||||
|
ImageMode,
|
||||||
|
TiffTags,
|
||||||
|
UnidentifiedImageError,
|
||||||
|
__version__,
|
||||||
|
_plugins,
|
||||||
|
)
|
||||||
from ._binary import i32le, o32be, o32le
|
from ._binary import i32le, o32be, o32le
|
||||||
from ._deprecate import deprecate
|
from ._deprecate import deprecate
|
||||||
from ._util import DeferredError, is_path
|
from ._util import DeferredError, is_path
|
||||||
|
@ -1447,6 +1454,49 @@ class Image:
|
||||||
self._exif._loaded = False
|
self._exif._loaded = False
|
||||||
self.getexif()
|
self.getexif()
|
||||||
|
|
||||||
|
def get_child_images(self):
|
||||||
|
child_images = []
|
||||||
|
exif = self.getexif()
|
||||||
|
ifds = []
|
||||||
|
if ExifTags.Base.SubIFDs in exif:
|
||||||
|
subifd_offsets = exif[ExifTags.Base.SubIFDs]
|
||||||
|
if subifd_offsets:
|
||||||
|
if not isinstance(subifd_offsets, tuple):
|
||||||
|
subifd_offsets = (subifd_offsets,)
|
||||||
|
for subifd_offset in subifd_offsets:
|
||||||
|
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
|
||||||
|
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
|
||||||
|
if ifd1 and ifd1.get(513):
|
||||||
|
ifds.append((ifd1, exif._info.next))
|
||||||
|
|
||||||
|
offset = None
|
||||||
|
for ifd, ifd_offset in ifds:
|
||||||
|
current_offset = self.fp.tell()
|
||||||
|
if offset is None:
|
||||||
|
offset = current_offset
|
||||||
|
|
||||||
|
fp = self.fp
|
||||||
|
thumbnail_offset = ifd.get(513)
|
||||||
|
if thumbnail_offset is not None:
|
||||||
|
try:
|
||||||
|
thumbnail_offset += self._exif_offset
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.fp.seek(thumbnail_offset)
|
||||||
|
data = self.fp.read(ifd.get(514))
|
||||||
|
fp = io.BytesIO(data)
|
||||||
|
|
||||||
|
with open(fp) as im:
|
||||||
|
if thumbnail_offset is None:
|
||||||
|
im._frame_pos = [ifd_offset]
|
||||||
|
im._seek(0)
|
||||||
|
im.load()
|
||||||
|
child_images.append(im)
|
||||||
|
|
||||||
|
if offset is not None:
|
||||||
|
self.fp.seek(offset)
|
||||||
|
return child_images
|
||||||
|
|
||||||
def getim(self):
|
def getim(self):
|
||||||
"""
|
"""
|
||||||
Returns a capsule that points to the internal image memory.
|
Returns a capsule that points to the internal image memory.
|
||||||
|
@ -3598,14 +3648,16 @@ class Exif(MutableMapping):
|
||||||
merged_dict = dict(self)
|
merged_dict = dict(self)
|
||||||
|
|
||||||
# get EXIF extension
|
# get EXIF extension
|
||||||
if 0x8769 in self:
|
if ExifTags.IFD.Exif in self:
|
||||||
ifd = self._get_ifd_dict(self[0x8769])
|
ifd = self._get_ifd_dict(self[ExifTags.IFD.Exif])
|
||||||
if ifd:
|
if ifd:
|
||||||
merged_dict.update(ifd)
|
merged_dict.update(ifd)
|
||||||
|
|
||||||
# GPS
|
# GPS
|
||||||
if 0x8825 in self:
|
if ExifTags.IFD.GPSInfo in self:
|
||||||
merged_dict[0x8825] = self._get_ifd_dict(self[0x8825])
|
merged_dict[ExifTags.IFD.GPSInfo] = self._get_ifd_dict(
|
||||||
|
self[ExifTags.IFD.GPSInfo]
|
||||||
|
)
|
||||||
|
|
||||||
return merged_dict
|
return merged_dict
|
||||||
|
|
||||||
|
@ -3615,31 +3667,34 @@ class Exif(MutableMapping):
|
||||||
head = self._get_head()
|
head = self._get_head()
|
||||||
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):
|
if tag in [
|
||||||
|
ExifTags.IFD.Exif,
|
||||||
|
ExifTags.IFD.GPSInfo,
|
||||||
|
] and not isinstance(value, dict):
|
||||||
value = self.get_ifd(tag)
|
value = self.get_ifd(tag)
|
||||||
if (
|
if (
|
||||||
tag == 0x8769
|
tag == ExifTags.IFD.Exif
|
||||||
and 0xA005 in value
|
and ExifTags.IFD.Interop in value
|
||||||
and not isinstance(value[0xA005], dict)
|
and not isinstance(value[ExifTags.IFD.Interop], dict)
|
||||||
):
|
):
|
||||||
value = value.copy()
|
value = value.copy()
|
||||||
value[0xA005] = self.get_ifd(0xA005)
|
value[ExifTags.IFD.Interop] = self.get_ifd(ExifTags.IFD.Interop)
|
||||||
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:
|
if tag not in self._ifds:
|
||||||
if tag in [0x8769, 0x8825]:
|
if tag == ExifTags.IFD.IFD1:
|
||||||
# exif, gpsinfo
|
if self._info is not None:
|
||||||
|
self._ifds[tag] = self._get_ifd_dict(self._info.next)
|
||||||
|
elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]:
|
||||||
if tag in self:
|
if tag in self:
|
||||||
self._ifds[tag] = self._get_ifd_dict(self[tag])
|
self._ifds[tag] = self._get_ifd_dict(self[tag])
|
||||||
elif tag in [0xA005, 0x927C]:
|
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]:
|
||||||
# interop, makernote
|
if ExifTags.IFD.Exif not in self._ifds:
|
||||||
if 0x8769 not in self._ifds:
|
self.get_ifd(ExifTags.IFD.Exif)
|
||||||
self.get_ifd(0x8769)
|
tag_data = self._ifds[ExifTags.IFD.Exif][tag]
|
||||||
tag_data = self._ifds[0x8769][tag]
|
if tag == ExifTags.IFD.Makernote:
|
||||||
if tag == 0x927C:
|
|
||||||
# makernote
|
|
||||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||||
|
|
||||||
if tag_data[:8] == b"FUJIFILM":
|
if tag_data[:8] == b"FUJIFILM":
|
||||||
|
@ -3715,7 +3770,7 @@ class Exif(MutableMapping):
|
||||||
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
||||||
self._ifds[tag] = makernote
|
self._ifds[tag] = makernote
|
||||||
else:
|
else:
|
||||||
# interop
|
# Interop
|
||||||
self._ifds[tag] = self._get_ifd_dict(tag_data)
|
self._ifds[tag] = self._get_ifd_dict(tag_data)
|
||||||
return self._ifds.get(tag, {})
|
return self._ifds.get(tag, {})
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ def APP(self, marker):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
# extract EXIF information (incomplete)
|
# extract EXIF information (incomplete)
|
||||||
self.info["exif"] = s # FIXME: value will change
|
self.info["exif"] = s # FIXME: value will change
|
||||||
|
self._exif_offset = self.fp.tell() - n + 6
|
||||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||||
# extract FlashPix information (incomplete)
|
# extract FlashPix information (incomplete)
|
||||||
self.info["flashpix"] = s # FIXME: value will change
|
self.info["flashpix"] = s # FIXME: value will change
|
||||||
|
|
|
@ -22,7 +22,14 @@ import itertools
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin
|
from . import (
|
||||||
|
ExifTags,
|
||||||
|
Image,
|
||||||
|
ImageFile,
|
||||||
|
ImageSequence,
|
||||||
|
JpegImagePlugin,
|
||||||
|
TiffImagePlugin,
|
||||||
|
)
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import o32le
|
from ._binary import o32le
|
||||||
|
|
||||||
|
@ -137,7 +144,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().get_ifd(0x8769)
|
exif = self.getexif().get_ifd(ExifTags.IFD.Exif)
|
||||||
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:
|
||||||
|
|
|
@ -1153,39 +1153,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
"""Return the current frame number"""
|
"""Return the current frame number"""
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def get_child_images(self):
|
|
||||||
if SUBIFD not in self.tag_v2:
|
|
||||||
return []
|
|
||||||
child_images = []
|
|
||||||
exif = self.getexif()
|
|
||||||
offset = None
|
|
||||||
for im_offset in self.tag_v2[SUBIFD]:
|
|
||||||
# reset buffered io handle in case fp
|
|
||||||
# was passed to libtiff, invalidating the buffer
|
|
||||||
current_offset = self._fp.tell()
|
|
||||||
if offset is None:
|
|
||||||
offset = current_offset
|
|
||||||
|
|
||||||
fp = self._fp
|
|
||||||
ifd = exif._get_ifd_dict(im_offset)
|
|
||||||
jpegInterchangeFormat = ifd.get(513)
|
|
||||||
if jpegInterchangeFormat is not None:
|
|
||||||
fp.seek(jpegInterchangeFormat)
|
|
||||||
jpeg_data = fp.read(ifd.get(514))
|
|
||||||
|
|
||||||
fp = io.BytesIO(jpeg_data)
|
|
||||||
|
|
||||||
with Image.open(fp) as im:
|
|
||||||
if jpegInterchangeFormat is None:
|
|
||||||
im._frame_pos = [im_offset]
|
|
||||||
im._seek(0)
|
|
||||||
im.load()
|
|
||||||
child_images.append(im)
|
|
||||||
|
|
||||||
if offset is not None:
|
|
||||||
self._fp.seek(offset)
|
|
||||||
return child_images
|
|
||||||
|
|
||||||
def getxmp(self):
|
def getxmp(self):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary containing the XMP tags.
|
Returns a dictionary containing the XMP tags.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user