updates
This commit is contained in:
@@ -17,49 +17,47 @@
|
||||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import struct
|
||||
from typing import IO, Any, cast
|
||||
|
||||
from . import (
|
||||
ExifTags,
|
||||
Image,
|
||||
ImageFile,
|
||||
ImageSequence,
|
||||
JpegImagePlugin,
|
||||
TiffImagePlugin,
|
||||
)
|
||||
from ._binary import i16be as i16
|
||||
from ._binary import o32le
|
||||
|
||||
# def _accept(prefix):
|
||||
# return JpegImagePlugin._accept(prefix)
|
||||
from ._util import DeferredError
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
JpegImagePlugin._save(im, fp, filename)
|
||||
|
||||
|
||||
def _save_all(im, fp, filename):
|
||||
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
append_images = im.encoderinfo.get("append_images", [])
|
||||
if not append_images:
|
||||
try:
|
||||
animated = im.is_animated
|
||||
except AttributeError:
|
||||
animated = False
|
||||
if not animated:
|
||||
_save(im, fp, filename)
|
||||
return
|
||||
if not append_images and not getattr(im, "is_animated", False):
|
||||
_save(im, fp, filename)
|
||||
return
|
||||
|
||||
mpf_offset = 28
|
||||
offsets = []
|
||||
for imSequence in itertools.chain([im], append_images):
|
||||
for im_frame in ImageSequence.Iterator(imSequence):
|
||||
offsets: list[int] = []
|
||||
im_sequences = [im, *append_images]
|
||||
total = sum(getattr(seq, "n_frames", 1) for seq in im_sequences)
|
||||
for im_sequence in im_sequences:
|
||||
for im_frame in ImageSequence.Iterator(im_sequence):
|
||||
if not offsets:
|
||||
# APP2 marker
|
||||
ifd_length = 66 + 16 * total
|
||||
im_frame.encoderinfo["extra"] = (
|
||||
b"\xFF\xE2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
|
||||
b"\xff\xe2"
|
||||
+ struct.pack(">H", 6 + ifd_length)
|
||||
+ b"MPF\0"
|
||||
+ b" " * ifd_length
|
||||
)
|
||||
exif = im_frame.encoderinfo.get("exif")
|
||||
if isinstance(exif, Image.Exif):
|
||||
@@ -71,7 +69,9 @@ def _save_all(im, fp, filename):
|
||||
JpegImagePlugin._save(im_frame, fp, filename)
|
||||
offsets.append(fp.tell())
|
||||
else:
|
||||
encoderinfo = im_frame._attach_default_encoderinfo(im)
|
||||
im_frame.save(fp, "JPEG")
|
||||
im_frame.encoderinfo = encoderinfo
|
||||
offsets.append(fp.tell() - offsets[-1])
|
||||
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
@@ -92,7 +92,7 @@ def _save_all(im, fp, filename):
|
||||
ifd[0xB002] = mpentries
|
||||
|
||||
fp.seek(mpf_offset)
|
||||
fp.write(b"II\x2A\x00" + o32le(8) + ifd.tobytes(8))
|
||||
fp.write(b"II\x2a\x00" + o32le(8) + ifd.tobytes(8))
|
||||
fp.seek(0, os.SEEK_END)
|
||||
|
||||
|
||||
@@ -105,14 +105,16 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||
format_description = "MPO (CIPA DC-007)"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def _open(self):
|
||||
def _open(self) -> None:
|
||||
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
||||
JpegImagePlugin.JpegImageFile._open(self)
|
||||
self._after_jpeg_open()
|
||||
|
||||
def _after_jpeg_open(self, mpheader=None):
|
||||
self._initial_size = self.size
|
||||
def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None:
|
||||
self.mpinfo = mpheader if mpheader is not None else self._getmp()
|
||||
if self.mpinfo is None:
|
||||
msg = "Image appears to be a malformed MPO file"
|
||||
raise ValueError(msg)
|
||||
self.n_frames = self.mpinfo[0xB001]
|
||||
self.__mpoffsets = [
|
||||
mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
|
||||
@@ -130,43 +132,45 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||
# for now we can only handle reading and individual frame extraction
|
||||
self.readonly = 1
|
||||
|
||||
def load_seek(self, pos):
|
||||
def load_seek(self, pos: int) -> None:
|
||||
if isinstance(self._fp, DeferredError):
|
||||
raise self._fp.ex
|
||||
self._fp.seek(pos)
|
||||
|
||||
def seek(self, frame):
|
||||
def seek(self, frame: int) -> None:
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
if isinstance(self._fp, DeferredError):
|
||||
raise self._fp.ex
|
||||
self.fp = self._fp
|
||||
self.offset = self.__mpoffsets[frame]
|
||||
|
||||
original_exif = self.info.get("exif")
|
||||
if "exif" in self.info:
|
||||
del self.info["exif"]
|
||||
|
||||
self.fp.seek(self.offset + 2) # skip SOI marker
|
||||
segment = self.fp.read(2)
|
||||
if not segment:
|
||||
if not self.fp.read(2):
|
||||
msg = "No data found for frame"
|
||||
raise ValueError(msg)
|
||||
self._size = self._initial_size
|
||||
if i16(segment) == 0xFFE1: # APP1
|
||||
n = i16(self.fp.read(2)) - 2
|
||||
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
||||
self.fp.seek(self.offset)
|
||||
JpegImagePlugin.JpegImageFile._open(self)
|
||||
if self.info.get("exif") != original_exif:
|
||||
self._reload_exif()
|
||||
|
||||
mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
|
||||
if mptype.startswith("Large Thumbnail"):
|
||||
exif = self.getexif().get_ifd(ExifTags.IFD.Exif)
|
||||
if 40962 in exif and 40963 in exif:
|
||||
self._size = (exif[40962], exif[40963])
|
||||
elif "exif" in self.info:
|
||||
del self.info["exif"]
|
||||
self._reload_exif()
|
||||
|
||||
self.tile = [("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))]
|
||||
self.tile = [
|
||||
ImageFile._Tile("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])
|
||||
]
|
||||
self.__frame = frame
|
||||
|
||||
def tell(self):
|
||||
def tell(self) -> int:
|
||||
return self.__frame
|
||||
|
||||
@staticmethod
|
||||
def adopt(jpeg_instance, mpheader=None):
|
||||
def adopt(
|
||||
jpeg_instance: JpegImagePlugin.JpegImageFile,
|
||||
mpheader: dict[int, Any] | None = None,
|
||||
) -> MpoImageFile:
|
||||
"""
|
||||
Transform the instance of JpegImageFile into
|
||||
an instance of MpoImageFile.
|
||||
@@ -178,8 +182,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||
double call to _open.
|
||||
"""
|
||||
jpeg_instance.__class__ = MpoImageFile
|
||||
jpeg_instance._after_jpeg_open(mpheader)
|
||||
return jpeg_instance
|
||||
mpo_instance = cast(MpoImageFile, jpeg_instance)
|
||||
mpo_instance._after_jpeg_open(mpheader)
|
||||
return mpo_instance
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user