File: swf.py

package info (click to toggle)
python-imageio 2.4.1-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 4,824 kB
  • sloc: python: 18,306; makefile: 145
file content (343 lines) | stat: -rw-r--r-- 12,312 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# -*- 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.
"""

from __future__ import absolute_import, print_function, division

import os
import zlib
from io import BytesIO

import numpy as np

from .. import formats
from ..core import Format, read_n_bytes, image_as_uint


_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
                print("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)