fr/fr_env/lib/python3.8/site-packages/imageio/plugins/swf.py

345 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# imageio is distributed under the terms of the (new) BSD License.
""" SWF plugin. Most of the actual work is done in _swf.py.
"""
import os
import zlib
import logging
from io import BytesIO
import numpy as np
from .. import formats
from ..core import Format, read_n_bytes, image_as_uint
logger = logging.getLogger(__name__)
_swf = None # lazily loaded in lib()
def load_lib():
global _swf
from . import _swf
return _swf
class SWFFormat(Format):
""" Shockwave flash (SWF) is a media format designed for rich and
interactive animations. This plugin makes use of this format to
store a series of images in a lossless format with good compression
(zlib). The resulting images can be shown as an animation using
a flash player (such as the browser).
SWF stores images in RGBA format. RGB or grayscale images are
automatically converted. SWF does not support meta data.
Parameters for reading
----------------------
loop : bool
If True, the video will rewind as soon as a frame is requested
beyond the last frame. Otherwise, IndexError is raised. Default False.
Parameters for saving
---------------------
fps : int
The speed to play the animation. Default 12.
loop : bool
If True, add a tag to the end of the file to play again from
the first frame. Most flash players will then play the movie
in a loop. Note that the imageio SWF Reader does not check this
tag. Default True.
html : bool
If the output is a file on the file system, write an html file
(in HTML5) that shows the animation. Default False.
compress : bool
Whether to compress the swf file. Default False. You probably don't
want to use this. This does not decrease the file size since
the images are already compressed. It will result in slower
read and write time. The only purpose of this feature is to
create compressed SWF files, so that we can test the
functionality to read them.
"""
def _can_read(self, request):
if request.mode[1] in (self.modes + "?"):
tmp = request.firstbytes[0:3].decode("ascii", "ignore")
if tmp in ("FWS", "CWS"):
return True
def _can_write(self, request):
if request.mode[1] in (self.modes + "?"):
if request.extension in self.extensions:
return True
# -- reader
class Reader(Format.Reader):
def _open(self, loop=False):
if not _swf:
load_lib()
self._arg_loop = bool(loop)
self._fp = self.request.get_file()
# Check file ...
tmp = self.request.firstbytes[0:3].decode("ascii", "ignore")
if tmp == "FWS":
pass # OK
elif tmp == "CWS":
# Compressed, we need to decompress
bb = self._fp.read()
bb = bb[:8] + zlib.decompress(bb[8:])
# Wrap up in a file object
self._fp = BytesIO(bb)
else:
raise IOError("This does not look like a valid SWF file")
# Skip first bytes. This also tests support got seeking ...
try:
self._fp.seek(8)
self._streaming_mode = False
except Exception:
self._streaming_mode = True
self._fp_read(8)
# Skip header
# Note that the number of frames is there, which we could
# potentially use, but the number of frames does not necessarily
# correspond to the number of images.
nbits = _swf.bits2int(self._fp_read(1), 5)
nbits = 5 + nbits * 4
Lrect = nbits / 8.0
if Lrect % 1:
Lrect += 1
Lrect = int(Lrect)
self._fp_read(Lrect + 3)
# Now the rest is basically tags ...
self._imlocs = [] # tuple (loc, sze, T, L1)
if not self._streaming_mode:
# Collect locations of frame, while skipping through the data
# This does not read any of the tag *data*.
try:
while True:
isimage, sze, T, L1 = self._read_one_tag()
loc = self._fp.tell()
if isimage:
# Still need to check if the format is right
format = ord(self._fp_read(3)[2:])
if format == 5: # RGB or RGBA lossless
self._imlocs.append((loc, sze, T, L1))
self._fp.seek(loc + sze) # Skip over tag
except IndexError:
pass # done reading
def _fp_read(self, n):
return read_n_bytes(self._fp, n)
def _close(self):
pass
def _get_length(self):
if self._streaming_mode:
return np.inf
else:
return len(self._imlocs)
def _get_data(self, index):
# Check index
if index < 0:
raise IndexError("Index in swf file must be > 0")
if not self._streaming_mode:
if self._arg_loop and self._imlocs:
index = index % len(self._imlocs)
if index >= len(self._imlocs):
raise IndexError("Index out of bounds")
if self._streaming_mode:
# Walk over tags until we find an image
while True:
isimage, sze, T, L1 = self._read_one_tag()
bb = self._fp_read(sze) # always read data
if isimage:
im = _swf.read_pixels(bb, 0, T, L1) # can be None
if im is not None:
return im, {}
else:
# Go to corresponding location, read data, and convert to image
loc, sze, T, L1 = self._imlocs[index]
self._fp.seek(loc)
bb = self._fp_read(sze)
# Read_pixels should return ndarry, since we checked format
im = _swf.read_pixels(bb, 0, T, L1)
return im, {}
def _read_one_tag(self):
"""
Return (True, loc, size, T, L1) if an image that we can read.
Return (False, loc, size, T, L1) if any other tag.
"""
# Get head
head = self._fp_read(6)
if not head: # pragma: no cover
raise IndexError("Reached end of swf movie")
# Determine type and length
T, L1, L2 = _swf.get_type_and_len(head)
if not L2: # pragma: no cover
raise RuntimeError("Invalid tag length, could not proceed")
# Read data
isimage = False
sze = L2 - 6
# bb = self._fp_read(L2 - 6)
# Parse tag
if T == 0:
raise IndexError("Reached end of swf movie")
elif T in [20, 36]:
isimage = True
# im = _swf.read_pixels(bb, 0, T, L1) # can be None
elif T in [6, 21, 35, 90]: # pragma: no cover
logger.warning("Ignoring JPEG image: cannot read JPEG.")
else:
pass # Not an image tag
# Done. Return image. Can be None
# return im
return isimage, sze, T, L1
def _get_meta_data(self, index):
return {} # This format does not support meta data
# -- writer
class Writer(Format.Writer):
def _open(self, fps=12, loop=True, html=False, compress=False):
if not _swf:
load_lib()
self._arg_fps = int(fps)
self._arg_loop = bool(loop)
self._arg_html = bool(html)
self._arg_compress = bool(compress)
self._fp = self.request.get_file()
self._framecounter = 0
self._framesize = (100, 100)
# For compress, we use an in-memory file object
if self._arg_compress:
self._fp_real = self._fp
self._fp = BytesIO()
def _close(self):
self._complete()
# Get size of (uncompressed) file
sze = self._fp.tell()
# set nframes, this is in the potentially compressed region
self._fp.seek(self._location_to_save_nframes)
self._fp.write(_swf.int2uint16(self._framecounter))
# Compress body?
if self._arg_compress:
bb = self._fp.getvalue()
self._fp = self._fp_real
self._fp.write(bb[:8])
self._fp.write(zlib.compress(bb[8:]))
sze = self._fp.tell() # renew sze value
# set size
self._fp.seek(4)
self._fp.write(_swf.int2uint32(sze))
self._fp = None # Disable
# Write html?
if self._arg_html and os.path.isfile(self.request.filename):
dirname, fname = os.path.split(self.request.filename)
filename = os.path.join(dirname, fname[:-4] + ".html")
w, h = self._framesize
html = HTML % (fname, w, h, fname)
with open(filename, "wb") as f:
f.write(html.encode("utf-8"))
def _write_header(self, framesize, fps):
self._framesize = framesize
# Called as soon as we know framesize; when we get first frame
bb = b""
bb += "FC"[self._arg_compress].encode("ascii")
bb += "WS".encode("ascii") # signature bytes
bb += _swf.int2uint8(8) # version
bb += "0000".encode("ascii") # FileLength (leave open for now)
bb += (
_swf.Tag().make_rect_record(0, framesize[0], 0, framesize[1]).tobytes()
)
bb += _swf.int2uint8(0) + _swf.int2uint8(fps) # FrameRate
self._location_to_save_nframes = len(bb)
bb += "00".encode("ascii") # nframes (leave open for now)
self._fp.write(bb)
# Write some initial tags
taglist = _swf.FileAttributesTag(), _swf.SetBackgroundTag(0, 0, 0)
for tag in taglist:
self._fp.write(tag.get_tag())
def _complete(self):
# What if no images were saved?
if not self._framecounter:
self._write_header((10, 10), self._arg_fps)
# Write stop tag if we do not loop
if not self._arg_loop:
self._fp.write(_swf.DoActionTag("stop").get_tag())
# finish with end tag
self._fp.write("\x00\x00".encode("ascii"))
def _append_data(self, im, meta):
# Correct shape and type
if im.ndim == 3 and im.shape[-1] == 1:
im = im[:, :, 0]
im = image_as_uint(im, bitdepth=8)
# Get frame size
wh = im.shape[1], im.shape[0]
# Write header on first frame
isfirstframe = False
if self._framecounter == 0:
isfirstframe = True
self._write_header(wh, self._arg_fps)
# Create tags
bm = _swf.BitmapTag(im)
sh = _swf.ShapeTag(bm.id, (0, 0), wh)
po = _swf.PlaceObjectTag(1, sh.id, move=(not isfirstframe))
sf = _swf.ShowFrameTag()
# Write tags
for tag in [bm, sh, po, sf]:
self._fp.write(tag.get_tag())
self._framecounter += 1
def set_meta_data(self, meta):
pass
HTML = """
<!DOCTYPE html>
<html>
<head>
<title>Show Flash animation %s</title>
</head>
<body>
<embed width="%i" height="%i" src="%s">
</html>
"""
# Register. You register an *instance* of a Format class. Here specify:
format = SWFFormat(
"swf", # shot name
"Shockwave flash", # one line descr.
".swf", # list of extensions as a space separated string
"I", # modes, characters in iIvV
)
formats.add_format(format)