mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-14 01:04:45 +03:00
Ensure that optional APP1 EXIF marker is first, followed by APP2 MPF
This commit is contained in:
parent
dc30ccc6b2
commit
dbc0657f46
|
@ -82,6 +82,7 @@ def test_app(test_file):
|
||||||
def test_exif(test_file):
|
def test_exif(test_file):
|
||||||
with Image.open(test_file) as im_original:
|
with Image.open(test_file) as im_original:
|
||||||
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
|
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
|
||||||
|
assert tuple(app[0] for app in im_reloaded.applist[:2]) == ("APP1", "APP2")
|
||||||
|
|
||||||
for im in (im_original, im_reloaded):
|
for im in (im_original, im_reloaded):
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
|
@ -258,6 +259,7 @@ def test_save_all():
|
||||||
for test_file in test_files:
|
for test_file in test_files:
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im_reloaded = roundtrip(im, save_all=True)
|
im_reloaded = roundtrip(im, save_all=True)
|
||||||
|
assert im_reloaded.applist[0][0] == "APP2"
|
||||||
|
|
||||||
im.seek(0)
|
im.seek(0)
|
||||||
assert_image_similar(im, im_reloaded, 30)
|
assert_image_similar(im, im_reloaded, 30)
|
||||||
|
|
|
@ -713,7 +713,7 @@ def _save(im, fp, filename):
|
||||||
qtables = getattr(im, "quantization", None)
|
qtables = getattr(im, "quantization", None)
|
||||||
qtables = validate_qtables(qtables)
|
qtables = validate_qtables(qtables)
|
||||||
|
|
||||||
extra = info.get("extra", b"")
|
extra = b""
|
||||||
|
|
||||||
icc_profile = info.get("icc_profile")
|
icc_profile = info.get("icc_profile")
|
||||||
if icc_profile:
|
if icc_profile:
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
|
@ -31,7 +31,7 @@ from . import (
|
||||||
TiffImagePlugin,
|
TiffImagePlugin,
|
||||||
)
|
)
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import o32le
|
from ._binary import o16be, o32le
|
||||||
|
|
||||||
# def _accept(prefix):
|
# def _accept(prefix):
|
||||||
# return JpegImagePlugin._accept(prefix)
|
# return JpegImagePlugin._accept(prefix)
|
||||||
|
@ -52,48 +52,75 @@ def _save_all(im, fp, filename):
|
||||||
_save(im, fp, filename)
|
_save(im, fp, filename)
|
||||||
return
|
return
|
||||||
|
|
||||||
mpf_offset = 28
|
data = []
|
||||||
offsets = []
|
|
||||||
for imSequence in itertools.chain([im], append_images):
|
for imSequence in itertools.chain([im], append_images):
|
||||||
for im_frame in ImageSequence.Iterator(imSequence):
|
for im_frame in ImageSequence.Iterator(imSequence):
|
||||||
if not offsets:
|
b = io.BytesIO()
|
||||||
# APP2 marker
|
|
||||||
im_frame.encoderinfo["extra"] = (
|
exif = b""
|
||||||
b"\xFF\xE2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
|
if not data:
|
||||||
)
|
# Do not pass EXIF to JpegImagePlugin
|
||||||
exif = im_frame.encoderinfo.get("exif")
|
try:
|
||||||
|
exif = im_frame.encoderinfo["exif"]
|
||||||
|
del im_frame.encoderinfo["exif"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
JpegImagePlugin._save(im_frame, b, filename)
|
||||||
|
else:
|
||||||
|
im_frame.save(b, "JPEG")
|
||||||
|
|
||||||
|
jpeg = b.getvalue()
|
||||||
|
|
||||||
|
# SOI
|
||||||
|
start = jpeg[:2]
|
||||||
|
|
||||||
|
# APP1
|
||||||
|
if exif:
|
||||||
if isinstance(exif, Image.Exif):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
im_frame.encoderinfo["exif"] = exif
|
exif = b"\xFF\xE1" + o16be(2 + len(exif)) + exif
|
||||||
if exif:
|
|
||||||
mpf_offset += 4 + len(exif)
|
|
||||||
|
|
||||||
JpegImagePlugin._save(im_frame, fp, filename)
|
end = jpeg[2:]
|
||||||
offsets.append(fp.tell())
|
data.append((start, exif, end))
|
||||||
else:
|
|
||||||
im_frame.save(fp, "JPEG")
|
|
||||||
offsets.append(fp.tell() - offsets[-1])
|
|
||||||
|
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
ifd[0xB000] = b"0100"
|
ifd[0xB000] = b"0100"
|
||||||
ifd[0xB001] = len(offsets)
|
ifd[0xB001] = len(data)
|
||||||
|
|
||||||
mpentries = b""
|
mpentries = b""
|
||||||
data_offset = 0
|
data_offset = 0
|
||||||
for i, size in enumerate(offsets):
|
for i, frame_data in enumerate(data):
|
||||||
|
size = sum(len(part) for part in frame_data)
|
||||||
if i == 0:
|
if i == 0:
|
||||||
|
# APP2 will be appended later
|
||||||
|
size += 90
|
||||||
|
|
||||||
mptype = 0x030000 # Baseline MP Primary Image
|
mptype = 0x030000 # Baseline MP Primary Image
|
||||||
else:
|
else:
|
||||||
mptype = 0x000000 # Undefined
|
mptype = 0x000000 # Undefined
|
||||||
mpentries += struct.pack("<LLLHH", mptype, size, data_offset, 0, 0)
|
mpentries += struct.pack("<LLLHH", mptype, size, data_offset, 0, 0)
|
||||||
if i == 0:
|
if i == 0:
|
||||||
data_offset -= mpf_offset
|
exif = frame_data[1]
|
||||||
|
data_offset -= 10 + len(exif)
|
||||||
data_offset += size
|
data_offset += size
|
||||||
ifd[0xB002] = mpentries
|
ifd[0xB002] = mpentries
|
||||||
|
|
||||||
fp.seek(mpf_offset)
|
for i, frame_data in enumerate(data):
|
||||||
fp.write(b"II\x2A\x00" + o32le(8) + ifd.tobytes(8))
|
start, exif, end = frame_data
|
||||||
fp.seek(0, os.SEEK_END)
|
|
||||||
|
fp.write(start + exif)
|
||||||
|
if i == 0:
|
||||||
|
# APP2
|
||||||
|
fp.write(
|
||||||
|
b"\xFF\xE2"
|
||||||
|
+ o16be(6 + 82)
|
||||||
|
+ b"MPF\0"
|
||||||
|
+ b"II\x2A\x00"
|
||||||
|
+ o32le(8)
|
||||||
|
+ ifd.tobytes(8)
|
||||||
|
)
|
||||||
|
fp.write(end)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
Loading…
Reference in New Issue
Block a user