swf.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. # -*- coding: utf-8 -*-
  2. # imageio is distributed under the terms of the (new) BSD License.
  3. """Read/Write SWF files.
  4. Backend: internal
  5. Shockwave flash (SWF) is a media format designed for rich and
  6. interactive animations. This plugin makes use of this format to
  7. store a series of images in a lossless format with good compression
  8. (zlib). The resulting images can be shown as an animation using
  9. a flash player (such as the browser).
  10. SWF stores images in RGBA format. RGB or grayscale images are
  11. automatically converted. SWF does not support meta data.
  12. Parameters for reading
  13. ----------------------
  14. loop : bool
  15. If True, the video will rewind as soon as a frame is requested
  16. beyond the last frame. Otherwise, IndexError is raised. Default False.
  17. Parameters for saving
  18. ---------------------
  19. fps : int
  20. The speed to play the animation. Default 12.
  21. loop : bool
  22. If True, add a tag to the end of the file to play again from
  23. the first frame. Most flash players will then play the movie
  24. in a loop. Note that the imageio SWF Reader does not check this
  25. tag. Default True.
  26. html : bool
  27. If the output is a file on the file system, write an html file
  28. (in HTML5) that shows the animation. Default False.
  29. compress : bool
  30. Whether to compress the swf file. Default False. You probably don't
  31. want to use this. This does not decrease the file size since
  32. the images are already compressed. It will result in slower
  33. read and write time. The only purpose of this feature is to
  34. create compressed SWF files, so that we can test the
  35. functionality to read them.
  36. """
  37. import os
  38. import zlib
  39. import logging
  40. from io import BytesIO
  41. import numpy as np
  42. from ..core import Format, read_n_bytes, image_as_uint
  43. logger = logging.getLogger(__name__)
  44. _swf = None # lazily loaded in lib()
  45. def load_lib():
  46. global _swf
  47. from . import _swf
  48. return _swf
  49. class SWFFormat(Format):
  50. """See :mod:`imageio.plugins.swf`"""
  51. def _can_read(self, request):
  52. tmp = request.firstbytes[0:3].decode("ascii", "ignore")
  53. if tmp in ("FWS", "CWS"):
  54. return True
  55. def _can_write(self, request):
  56. if request.extension in self.extensions:
  57. return True
  58. # -- reader
  59. class Reader(Format.Reader):
  60. def _open(self, loop=False):
  61. if not _swf:
  62. load_lib()
  63. self._arg_loop = bool(loop)
  64. self._fp = self.request.get_file()
  65. # Check file ...
  66. tmp = self.request.firstbytes[0:3].decode("ascii", "ignore")
  67. if tmp == "FWS":
  68. pass # OK
  69. elif tmp == "CWS":
  70. # Compressed, we need to decompress
  71. bb = self._fp.read()
  72. bb = bb[:8] + zlib.decompress(bb[8:])
  73. # Wrap up in a file object
  74. self._fp = BytesIO(bb)
  75. else:
  76. raise IOError("This does not look like a valid SWF file")
  77. # Skip first bytes. This also tests support got seeking ...
  78. try:
  79. self._fp.seek(8)
  80. self._streaming_mode = False
  81. except Exception:
  82. self._streaming_mode = True
  83. self._fp_read(8)
  84. # Skip header
  85. # Note that the number of frames is there, which we could
  86. # potentially use, but the number of frames does not necessarily
  87. # correspond to the number of images.
  88. nbits = _swf.bits2int(self._fp_read(1), 5)
  89. nbits = 5 + nbits * 4
  90. Lrect = nbits / 8.0
  91. if Lrect % 1:
  92. Lrect += 1
  93. Lrect = int(Lrect)
  94. self._fp_read(Lrect + 3)
  95. # Now the rest is basically tags ...
  96. self._imlocs = [] # tuple (loc, sze, T, L1)
  97. if not self._streaming_mode:
  98. # Collect locations of frame, while skipping through the data
  99. # This does not read any of the tag *data*.
  100. try:
  101. while True:
  102. isimage, sze, T, L1 = self._read_one_tag()
  103. loc = self._fp.tell()
  104. if isimage:
  105. # Still need to check if the format is right
  106. format = ord(self._fp_read(3)[2:])
  107. if format == 5: # RGB or RGBA lossless
  108. self._imlocs.append((loc, sze, T, L1))
  109. self._fp.seek(loc + sze) # Skip over tag
  110. except IndexError:
  111. pass # done reading
  112. def _fp_read(self, n):
  113. return read_n_bytes(self._fp, n)
  114. def _close(self):
  115. pass
  116. def _get_length(self):
  117. if self._streaming_mode:
  118. return np.inf
  119. else:
  120. return len(self._imlocs)
  121. def _get_data(self, index):
  122. # Check index
  123. if index < 0:
  124. raise IndexError("Index in swf file must be > 0")
  125. if not self._streaming_mode:
  126. if self._arg_loop and self._imlocs:
  127. index = index % len(self._imlocs)
  128. if index >= len(self._imlocs):
  129. raise IndexError("Index out of bounds")
  130. if self._streaming_mode:
  131. # Walk over tags until we find an image
  132. while True:
  133. isimage, sze, T, L1 = self._read_one_tag()
  134. bb = self._fp_read(sze) # always read data
  135. if isimage:
  136. im = _swf.read_pixels(bb, 0, T, L1) # can be None
  137. if im is not None:
  138. return im, {}
  139. else:
  140. # Go to corresponding location, read data, and convert to image
  141. loc, sze, T, L1 = self._imlocs[index]
  142. self._fp.seek(loc)
  143. bb = self._fp_read(sze)
  144. # Read_pixels should return ndarry, since we checked format
  145. im = _swf.read_pixels(bb, 0, T, L1)
  146. return im, {}
  147. def _read_one_tag(self):
  148. """
  149. Return (True, loc, size, T, L1) if an image that we can read.
  150. Return (False, loc, size, T, L1) if any other tag.
  151. """
  152. # Get head
  153. head = self._fp_read(6)
  154. if not head: # pragma: no cover
  155. raise IndexError("Reached end of swf movie")
  156. # Determine type and length
  157. T, L1, L2 = _swf.get_type_and_len(head)
  158. if not L2: # pragma: no cover
  159. raise RuntimeError("Invalid tag length, could not proceed")
  160. # Read data
  161. isimage = False
  162. sze = L2 - 6
  163. # bb = self._fp_read(L2 - 6)
  164. # Parse tag
  165. if T == 0:
  166. raise IndexError("Reached end of swf movie")
  167. elif T in [20, 36]:
  168. isimage = True
  169. # im = _swf.read_pixels(bb, 0, T, L1) # can be None
  170. elif T in [6, 21, 35, 90]: # pragma: no cover
  171. logger.warning("Ignoring JPEG image: cannot read JPEG.")
  172. else:
  173. pass # Not an image tag
  174. # Done. Return image. Can be None
  175. # return im
  176. return isimage, sze, T, L1
  177. def _get_meta_data(self, index):
  178. return {} # This format does not support meta data
  179. # -- writer
  180. class Writer(Format.Writer):
  181. def _open(self, fps=12, loop=True, html=False, compress=False):
  182. if not _swf:
  183. load_lib()
  184. self._arg_fps = int(fps)
  185. self._arg_loop = bool(loop)
  186. self._arg_html = bool(html)
  187. self._arg_compress = bool(compress)
  188. self._fp = self.request.get_file()
  189. self._framecounter = 0
  190. self._framesize = (100, 100)
  191. # For compress, we use an in-memory file object
  192. if self._arg_compress:
  193. self._fp_real = self._fp
  194. self._fp = BytesIO()
  195. def _close(self):
  196. self._complete()
  197. # Get size of (uncompressed) file
  198. sze = self._fp.tell()
  199. # set nframes, this is in the potentially compressed region
  200. self._fp.seek(self._location_to_save_nframes)
  201. self._fp.write(_swf.int2uint16(self._framecounter))
  202. # Compress body?
  203. if self._arg_compress:
  204. bb = self._fp.getvalue()
  205. self._fp = self._fp_real
  206. self._fp.write(bb[:8])
  207. self._fp.write(zlib.compress(bb[8:]))
  208. sze = self._fp.tell() # renew sze value
  209. # set size
  210. self._fp.seek(4)
  211. self._fp.write(_swf.int2uint32(sze))
  212. self._fp = None # Disable
  213. # Write html?
  214. if self._arg_html and os.path.isfile(self.request.filename):
  215. dirname, fname = os.path.split(self.request.filename)
  216. filename = os.path.join(dirname, fname[:-4] + ".html")
  217. w, h = self._framesize
  218. html = HTML % (fname, w, h, fname)
  219. with open(filename, "wb") as f:
  220. f.write(html.encode("utf-8"))
  221. def _write_header(self, framesize, fps):
  222. self._framesize = framesize
  223. # Called as soon as we know framesize; when we get first frame
  224. bb = b""
  225. bb += "FC"[self._arg_compress].encode("ascii")
  226. bb += "WS".encode("ascii") # signature bytes
  227. bb += _swf.int2uint8(8) # version
  228. bb += "0000".encode("ascii") # FileLength (leave open for now)
  229. bb += (
  230. _swf.Tag().make_rect_record(0, framesize[0], 0, framesize[1]).tobytes()
  231. )
  232. bb += _swf.int2uint8(0) + _swf.int2uint8(fps) # FrameRate
  233. self._location_to_save_nframes = len(bb)
  234. bb += "00".encode("ascii") # nframes (leave open for now)
  235. self._fp.write(bb)
  236. # Write some initial tags
  237. taglist = _swf.FileAttributesTag(), _swf.SetBackgroundTag(0, 0, 0)
  238. for tag in taglist:
  239. self._fp.write(tag.get_tag())
  240. def _complete(self):
  241. # What if no images were saved?
  242. if not self._framecounter:
  243. self._write_header((10, 10), self._arg_fps)
  244. # Write stop tag if we do not loop
  245. if not self._arg_loop:
  246. self._fp.write(_swf.DoActionTag("stop").get_tag())
  247. # finish with end tag
  248. self._fp.write("\x00\x00".encode("ascii"))
  249. def _append_data(self, im, meta):
  250. # Correct shape and type
  251. if im.ndim == 3 and im.shape[-1] == 1:
  252. im = im[:, :, 0]
  253. im = image_as_uint(im, bitdepth=8)
  254. # Get frame size
  255. wh = im.shape[1], im.shape[0]
  256. # Write header on first frame
  257. isfirstframe = False
  258. if self._framecounter == 0:
  259. isfirstframe = True
  260. self._write_header(wh, self._arg_fps)
  261. # Create tags
  262. bm = _swf.BitmapTag(im)
  263. sh = _swf.ShapeTag(bm.id, (0, 0), wh)
  264. po = _swf.PlaceObjectTag(1, sh.id, move=(not isfirstframe))
  265. sf = _swf.ShowFrameTag()
  266. # Write tags
  267. for tag in [bm, sh, po, sf]:
  268. self._fp.write(tag.get_tag())
  269. self._framecounter += 1
  270. def set_meta_data(self, meta):
  271. pass
  272. HTML = """
  273. <!DOCTYPE html>
  274. <html>
  275. <head>
  276. <title>Show Flash animation %s</title>
  277. </head>
  278. <body>
  279. <embed width="%i" height="%i" src="%s">
  280. </html>
  281. """