瀏覽代碼

补充swf

fangjiasheng 3 年之前
父節點
當前提交
e653706428

+ 3 - 0
format_convert/swf/__init__.py

@@ -0,0 +1,3 @@
+__version__ = "1.5.4"
+
+from . import *

+ 188 - 0
format_convert/swf/actions.py

@@ -0,0 +1,188 @@
+
+class Action(object):
+    def __init__(self, code, length):
+        self._code = code
+        self._length = length
+    
+    @property
+    def code(self):
+        return self._code
+    
+    @property   
+    def length(self):
+        return self._length;
+    
+    @property
+    def version(self):
+        return 3
+        
+    def parse(self, data):
+        # Do nothing. Many Actions don't have a payload. 
+        # For the ones that have one we override this method.
+        pass
+    
+    def __repr__(self):
+        return "[Action] Code: 0x%x, Length: %d" % (self._code, self._length)
+
+class ActionUnknown(Action):
+    ''' Dummy class to read unknown actions '''
+    def __init__(self, code, length):
+        super(ActionUnknown, self).__init__(code, length)
+    
+    def parse(self, data):
+        if self._length > 0:
+            #print "skipping %d bytes..." % self._length
+            data.skip_bytes(self._length)
+    
+    def __repr__(self):
+        return "[ActionUnknown] Code: 0x%x, Length: %d" % (self._code, self._length)
+        
+class Action4(Action):
+    ''' Base class for SWF 4 actions '''
+    def __init__(self, code, length):
+        super(Action4, self).__init__(code, length)
+    
+    @property
+    def version(self):
+        return 4
+
+class Action5(Action):
+    ''' Base class for SWF 5 actions '''
+    def __init__(self, code, length):
+        super(Action5, self).__init__(code, length)
+
+    @property
+    def version(self):
+        return 5
+        
+class Action6(Action):
+    ''' Base class for SWF 6 actions '''
+    def __init__(self, code, length):
+        super(Action6, self).__init__(code, length)
+
+    @property
+    def version(self):
+        return 6
+        
+class Action7(Action):
+    ''' Base class for SWF 7 actions '''
+    def __init__(self, code, length):
+        super(Action7, self).__init__(code, length)
+
+    @property
+    def version(self):
+        return 7
+                
+# ========================================================= 
+# SWF 3 actions
+# =========================================================
+class ActionGetURL(Action):
+    CODE = 0x83
+    def __init__(self, code, length):
+        self.urlString = None
+        self.targetString = None
+        super(ActionGetURL, self).__init__(code, length)
+        
+    def parse(self, data):
+        self.urlString = data.readString()
+        self.targetString = data.readString()
+        
+class ActionGotoFrame(Action):
+    CODE = 0x81
+    def __init__(self, code, length):
+        self.frame = 0
+        super(ActionGotoFrame, self).__init__(code, length)
+
+    def parse(self, data): 
+        self.frame = data.readUI16()
+        
+class ActionGotoLabel(Action):
+    CODE = 0x8c
+    def __init__(self, code, length):
+        self.label = None
+        super(ActionGotoLabel, self).__init__(code, length)
+
+    def parse(self, data): 
+        self.label = data.readString()
+        
+class ActionNextFrame(Action):
+    CODE = 0x04
+    def __init__(self, code, length):
+        super(ActionNextFrame, self).__init__(code, length)
+
+class ActionPlay(Action):
+    CODE = 0x06
+    def __init__(self, code, length):
+        super(ActionPlay, self).__init__(code, length)
+    
+    def __repr__(self):
+        return "[ActionPlay] Code: 0x%x, Length: %d" % (self._code, self._length)
+            
+class ActionPreviousFrame(Action):
+    CODE = 0x05
+    def __init__(self, code, length):
+        super(ActionPreviousFrame, self).__init__(code, length)
+                
+class ActionSetTarget(Action):
+    CODE = 0x8b
+    def __init__(self, code, length):
+        self.targetName = None
+        super(ActionSetTarget, self).__init__(code, length)
+
+    def parse(self, data):
+        self.targetName = data.readString()      
+        
+class ActionStop(Action):
+    CODE = 0x07
+    def __init__(self, code, length):
+        super(ActionStop, self).__init__(code, length)
+    
+    def __repr__(self):
+        return "[ActionStop] Code: 0x%x, Length: %d" % (self._code, self._length)
+             
+class ActionStopSounds(Action):
+    CODE = 0x09
+    def __init__(self, code, length):
+        super(ActionStopSounds, self).__init__(code, length)   
+        
+class ActionToggleQuality(Action):
+    CODE = 0x08
+    def __init__(self, code, length):
+        super(ActionToggleQuality, self).__init__(code, length)
+        
+class ActionWaitForFrame(Action):
+    CODE = 0x8a
+    def __init__(self, code, length):
+        self.frame = 0
+        self.skipCount = 0
+        super(ActionWaitForFrame, self).__init__(code, length)
+
+    def parse(self, data):
+        self.frame = data.readUI16()
+        self.skipCount = data.readUI8()
+                              
+# ========================================================= 
+# SWF 4 actions
+# =========================================================
+class ActionAdd(Action4):
+    CODE = 0x0a
+    def __init__(self, code, length):
+        super(ActionAdd, self).__init__(code, length)
+
+class ActionAnd(Action4):
+    CODE = 0x10
+    def __init__(self, code, length):
+        super(ActionAnd, self).__init__(code, length)
+                       
+# urgh! some 100 to go...
+
+ActionTable = {}
+for name, value in dict(locals()).items():
+    if type(value) == type and issubclass(value, Action) and hasattr(value, 'CODE'):
+        ActionTable[value.CODE] = value
+
+class SWFActionFactory(object):
+    @classmethod
+    def create(cls, code, length):
+        return ActionTable.get(code, ActionUnknown)(code, length)
+        

+ 344 - 0
format_convert/swf/consts.py

@@ -0,0 +1,344 @@
+
+class Enum(object):
+    @classmethod
+    def tostring(cls, type):
+        return cls._mapping.get(type, 'unknown')
+
+class BitmapFormat(Enum):
+    BIT_8 = 3
+    BIT_15 = 4
+    BIT_24 = 5
+    
+    _mapping = {
+        BIT_8: 'BIT_8',
+        BIT_15: 'BIT_15',
+        BIT_24: 'BIT_24',
+    }
+
+class BitmapType(Enum):
+    JPEG = 1  
+    GIF89A = 2
+    PNG = 3
+    
+    _mapping = {
+        JPEG: 'JPEG',
+        GIF89A: 'GIF89A',
+        PNG: 'PNG',
+    }
+    
+    FileExtensions = {
+        JPEG: '.jpeg',
+        GIF89A: '.gif',
+        PNG: '.png'
+    }
+
+class GradientSpreadMode(Enum):
+    PAD = 0 
+    REFLECT = 1
+    REPEAT = 2
+    
+    _mapping = {
+        PAD: 'pad',
+        REFLECT: 'reflect',
+        REPEAT: 'repeat',
+    }
+
+class GradientType(Enum):
+    LINEAR = 1
+    RADIAL = 2
+    
+    _mapping = {
+        LINEAR: 'LINEAR',
+        RADIAL: 'RADIAL',
+    }
+                
+class LineScaleMode(Enum):
+    NONE = 0
+    HORIZONTAL = 1 
+    NORMAL = 2
+    VERTICAL = 3
+    
+    _mapping = {
+        NONE: 'none',
+        HORIZONTAL: 'horizontal',
+        NORMAL: 'normal',
+        VERTICAL: 'vertical',
+    }
+                        
+class SpreadMethod(Enum):
+    PAD = 0 
+    REFLECT = 1
+    REPEAT = 2
+    
+    _mapping = {
+        PAD: 'pad',
+        REFLECT: 'reflect',
+        REPEAT: 'repeat',
+    }
+                
+class InterpolationMethod(Enum):
+    RGB = 0
+    LINEAR_RGB = 1
+    
+    _mapping = {
+        RGB: 'RGB',
+        LINEAR_RGB: 'LINEAR_RGB',
+    }
+                        
+class LineJointStyle(Enum):
+    ROUND = 0
+    BEVEL = 1
+    MITER = 2
+    
+    _mapping = {
+        ROUND: 'ROUND',
+        BEVEL: 'BEVEL',
+        MITER: 'MITER',
+    }
+        
+class LineCapsStyle(Enum):
+    ROUND = 0
+    NO = 1
+    SQUARE = 2
+    
+    _mapping = {
+        ROUND: 'ROUND',
+        NO: 'NO',
+        SQUARE: 'SQUARE',
+    }
+        
+class TextAlign(Enum):
+    LEFT = 0
+    RIGHT = 1
+    CENTER = 2
+    JUSTIFY = 3
+    
+    _mapping = {
+        LEFT: 'left',
+        RIGHT: 'right',
+        CENTER: 'center',
+        JUSTIFY: 'justify',
+    }
+        
+class BlendMode(Enum):
+    Normal = 0
+    Normal_1 = 1
+    Layer = 2
+    Multiply = 3
+    Screen = 4
+    Lighten = 5
+    Darken = 6
+    Difference = 7
+    Add = 8
+    Subtract = 9
+    Invert = 10
+    Alpha = 11
+    Erase = 12
+    Overlay = 13
+    Hardlight = 14
+    
+    _mapping = {
+        Normal: "Normal",
+        Normal_1: "Normal",
+        Layer: "Layer",
+        Multiply: "Multiply",
+        Screen: "Screen",
+        Lighten: "Lighten",
+        Darken: "Darken",
+        Difference: "Difference",
+        Add: "Add",
+        Subtract: "Subtract",
+        Invert: "Invert",
+        Alpha: "Alpha",
+        Erase: "Erase",
+        Overlay: "Overlay",
+        Hardlight: "Hardlight",
+    }
+
+class AudioSampleRate(Enum):
+    Hz5k512 = 0
+    Hz11k025 = 1
+    Hz22k05 = 2
+    Hz44k1 = 3
+    
+    _mapping = {
+        Hz5k512: '5.512kHz',
+        Hz11k025: '11.025kHz',
+        Hz22k05: '22.05kHz',
+        Hz44k1: '44.1kHz',
+    }
+    
+    Rates = {
+        Hz5k512: 5512,
+        Hz11k025: 11025,
+        Hz22k05: 22050,
+        Hz44k1: 44100,
+    }
+
+class AudioChannels(Enum):
+    Mono = 0
+    Stereo = 1
+    
+    _mapping = {
+        Mono: 'Mono',
+        Stereo: 'Stereo',
+    }
+    
+    Channels = {
+        Mono: 1,
+        Stereo: 2
+    }
+
+class AudioSampleSize(Enum):
+    b8 = 0
+    b16 = 1
+    
+    _mapping = {
+        b8: '8-bit',
+        b16: '16-bit',
+    }
+    
+    Bits = {
+        b8: 8,
+        b16: 16
+    }
+
+class AudioCodec(Enum):
+    UncompressedNativeEndian = 0
+    ADPCM = 1
+    MP3 = 2
+    UncompressedLittleEndian = 3
+    Nellymoser16kHz = 4
+    Nellymoser8kHz = 5
+    Nellymoser = 6
+    Speex = 11
+    
+    _mapping = {
+        UncompressedNativeEndian: 'UncompressedNativeEndian',
+        ADPCM: 'ADPCM',
+        MP3: 'MP3',
+        UncompressedLittleEndian: 'UncompressedLittleEndian',
+        Nellymoser16kHz: 'Nellymoser16kHz',
+        Nellymoser8kHz: 'Nellymoser8kHz',
+        Nellymoser: 'Nellymoser',
+        Speex: 'Speex',
+    }
+    
+    MinimumVersions = {
+        UncompressedNativeEndian: 1,
+        ADPCM: 1,
+        MP3: 4,
+        UncompressedLittleEndian: 4,
+        Nellymoser16kHz: 10,
+        Nellymoser8kHz: 10,
+        Nellymoser: 6,
+        Speex: 10,
+    }
+    
+    FileExtensions = {
+        MP3: '.mp3',
+        
+        # arbitrary container
+        UncompressedNativeEndian: '.wav',   
+        UncompressedLittleEndian: '.wav',
+        ADPCM: '.wav',
+        
+        # fictitious
+        Nellymoser16kHz: '.nel',
+        Nellymoser8kHz: '.nel',
+        Nellymoser: '.nel',
+        Speex: '.spx',
+    }
+    
+    MimeTypes = {
+        MP3: 'audio/mpeg',
+        UncompressedNativeEndian: 'audio/wav',   
+        UncompressedLittleEndian: 'audio/wav',
+        ADPCM: 'audio/wav',
+        
+        # assume ogg container?
+        Speex: 'audio/ogg',
+        
+        # punt
+        Nellymoser16kHz: 'application/octet-stream',
+        Nellymoser8kHz: 'application/octet-stream',
+        Nellymoser: 'application/octet-stream',
+    }
+
+class ProductEdition(Enum):
+    DeveloperEdition = 0
+    FullCommercialEdition = 1
+    NonCommercialEdition = 2
+    EducationalEdition = 3
+    NotForResaleEdition = 4
+    TrialEdition = 5
+    NoEdition = 6
+    
+    _mapping = {
+        DeveloperEdition: 'Developer edition',
+        FullCommercialEdition: 'Full commercial',
+        NonCommercialEdition: 'Non-commercial',
+        EducationalEdition: 'Educational',
+        NotForResaleEdition: 'Not for resale',
+        TrialEdition: 'Trial',
+        NoEdition: 'None',
+    }
+
+class ProductKind(Enum):
+    Unknown = 0
+    FlexForJ2EE = 1
+    FlexForDotNET = 2
+    AdobeFlex = 3
+    
+    _mapping = {
+        Unknown: 'Unknown',
+        FlexForJ2EE: 'Flex for J2EE',
+        FlexForDotNET: 'Flex for .NET',
+        AdobeFlex: 'Adobe Flex',
+    }
+
+class VideoCodec(Enum):
+    SorensonH263 = 2
+    ScreenVideo = 3
+    VP6 = 4
+    VP6Alpha = 5
+    
+    _mapping = {
+        SorensonH263: 'Sorenson H.263',
+        ScreenVideo: 'Screen video',
+        VP6: 'VP6',
+        VP6Alpha: 'VP6 with alpha',
+    }
+    
+    MinimumVersions = {
+        SorensonH263: 6,
+        ScreenVideo: 7,
+        VP6: 8,
+        VP6Alpha: 8,
+    }
+
+class MPEGVersion(Enum):
+    MPEG2_5 = 0
+    RFU = 1
+    MPEG2 = 2
+    MPEG1 = 3
+    
+    _mapping = {
+        MPEG2_5: 'MPEG2.5',
+        RFU: 'Reserved',
+        MPEG2: 'MPEG2',
+        MPEG1: 'MPEG1',
+    }
+
+class MPEGLayer(Enum):
+    RFU = 0
+    Layer3 = 1
+    Layer2 = 2
+    Layer1 = 3
+    
+    _mapping = {
+        RFU: 'Reserved',
+        Layer3: 'Layer 3',
+        Layer2: 'Layer 2',
+        Layer1: 'Layer 1',
+    }

+ 1065 - 0
format_convert/swf/export.py

@@ -0,0 +1,1065 @@
+"""
+This module defines exporters for the SWF fileformat.
+"""
+from .consts import *
+from .geom import *
+from .utils import *
+from .data import *
+from .tag import *
+from .filters import *
+from lxml import objectify
+from lxml import etree
+import base64
+try:
+    import Image
+except ImportError:
+    from PIL import Image
+try:
+    from cBytesIO import BytesIO
+except ImportError:
+    from io import BytesIO
+import math
+import re
+import copy
+import cgi
+
+SVG_VERSION = "1.1"
+SVG_NS      = "http://www.w3.org/2000/svg"
+XLINK_NS    = "http://www.w3.org/1999/xlink"
+XLINK_HREF  = "{%s}href" % XLINK_NS
+NS = {"svg" : SVG_NS, "xlink" : XLINK_NS}
+
+PIXELS_PER_TWIP = 20
+EM_SQUARE_LENGTH = 1024
+
+MINIMUM_STROKE_WIDTH = 0.5
+
+CAPS_STYLE = {
+    0 : 'round',
+    1 : 'butt',
+    2 : 'square'
+}
+
+JOIN_STYLE = {
+    0 : 'round',
+    1 : 'bevel',
+    2 : 'miter'
+}
+
+class DefaultShapeExporter(object):
+    """
+    The default (abstract) Shape exporter class.
+    All shape exporters should extend this class.
+
+
+    """
+    def __init__(self, swf=None, debug=False, force_stroke=False):
+        self.swf = None
+        self.debug = debug
+        self.force_stroke = force_stroke
+
+    def begin_bitmap_fill(self, bitmap_id, matrix=None, repeat=False, smooth=False):
+        pass
+    def begin_fill(self, color, alpha=1.0):
+        pass
+    def begin_gradient_fill(self, type, colors, alphas, ratios,
+                            matrix=None,
+                            spreadMethod=SpreadMethod.PAD,
+                            interpolationMethod=InterpolationMethod.RGB,
+                            focalPointRatio=0.0):
+        pass
+    def line_style(self,
+                    thickness=float('nan'), color=0, alpha=1.0,
+                    pixelHinting=False,
+                    scaleMode=LineScaleMode.NORMAL,
+                    startCaps=None, endCaps=None,
+                    joints=None, miterLimit=3.0):
+        pass
+    def line_gradient_style(self,
+                    thickness=float('nan'), color=0, alpha=1.0,
+                    pixelHinting=False,
+                    scaleMode=LineScaleMode.NORMAL,
+                    startCaps=None, endCaps=None,
+                    joints=None, miterLimit=3.0,
+                    type = 1, colors = [], alphas = [], ratios = [],
+                    matrix=None,
+                    spreadMethod=SpreadMethod.PAD,
+                    interpolationMethod=InterpolationMethod.RGB,
+                    focalPointRatio=0.0):
+        pass
+    def line_bitmap_style(self,
+                    thickness=float('nan'),
+                    pixelHinting=False,
+                    scaleMode=LineScaleMode.NORMAL,
+                    startCaps=None, endCaps=None,
+                    joints=None, miterLimit = 3.0,
+                    bitmap_id=None, matrix=None, repeat=False, smooth=False):
+        pass
+    def end_fill(self):
+        pass
+
+    def begin_fills(self):
+        pass
+    def end_fills(self):
+        pass
+    def begin_lines(self):
+        pass
+    def end_lines(self):
+        pass
+
+    def begin_shape(self):
+        pass
+    def end_shape(self):
+        pass
+
+    def move_to(self, x, y):
+        #print "move_to", x, y
+        pass
+    def line_to(self, x, y):
+        #print "line_to", x, y
+        pass
+    def curve_to(self, cx, cy, ax, ay):
+        #print "curve_to", cx, cy, ax, ay
+        pass
+
+class DefaultSVGShapeExporter(DefaultShapeExporter):
+    def __init__(self, defs=None):
+        self.defs = defs
+        self.current_draw_command = ""
+        self.path_data = ""
+        self._e = objectify.ElementMaker(annotate=False,
+                        namespace=SVG_NS, nsmap={None : SVG_NS, "xlink" : XLINK_NS})
+        super(DefaultSVGShapeExporter, self).__init__()
+
+    def move_to(self, x, y):
+        self.current_draw_command = ""
+        self.path_data += "M" + \
+            str(NumberUtils.round_pixels_20(x)) + " " + \
+            str(NumberUtils.round_pixels_20(y)) + " "
+
+    def line_to(self, x, y):
+        if self.current_draw_command != "L":
+            self.current_draw_command = "L"
+            self.path_data += "L"
+        self.path_data += "" + \
+            str(NumberUtils.round_pixels_20(x)) + " " + \
+            str(NumberUtils.round_pixels_20(y)) + " "
+
+    def curve_to(self, cx, cy, ax, ay):
+        if self.current_draw_command != "Q":
+            self.current_draw_command = "Q"
+            self.path_data += "Q"
+        self.path_data += "" + \
+            str(NumberUtils.round_pixels_20(cx)) + " " + \
+            str(NumberUtils.round_pixels_20(cy)) + " " + \
+            str(NumberUtils.round_pixels_20(ax)) + " " + \
+            str(NumberUtils.round_pixels_20(ay)) + " "
+
+    def begin_bitmap_fill(self, bitmap_id, matrix=None, repeat=False, smooth=False):
+        self.finalize_path()
+
+    def begin_fill(self, color, alpha=1.0):
+        self.finalize_path()
+
+    def end_fill(self):
+        pass
+        #self.finalize_path()
+
+    def begin_fills(self):
+        pass
+    def end_fills(self):
+        self.finalize_path()
+
+    def begin_gradient_fill(self, type, colors, alphas, ratios,
+                            matrix=None,
+                            spreadMethod=SpreadMethod.PAD,
+                            interpolationMethod=InterpolationMethod.RGB,
+                            focalPointRatio=0.0):
+        self.finalize_path()
+
+    def line_style(self,
+                    thickness=float('nan'), color=0, alpha=1.0,
+                    pixelHinting=False,
+                    scaleMode=LineScaleMode.NORMAL,
+                    startCaps=None, endCaps=None,
+                    joints=None, miterLimit=3.0):
+        self.finalize_path()
+
+    def end_lines(self):
+        self.finalize_path()
+
+    def end_shape(self):
+        self.finalize_path()
+
+    def finalize_path(self):
+        self.current_draw_command = ""
+        self.path_data = ""
+
+class SVGShapeExporter(DefaultSVGShapeExporter):
+    def __init__(self):
+        self.path = None
+        self.num_patterns = 0
+        self.num_gradients = 0
+        self._gradients = {}
+        self._gradient_ids = {}
+        self.paths = {}
+        self.fills_ended = False
+        super(SVGShapeExporter, self).__init__()
+
+    def begin_shape(self):
+        self.g = self._e.g()
+
+    def begin_fill(self, color, alpha=1.0):
+        self.finalize_path()
+        self.path.set("fill", ColorUtils.to_rgb_string(color))
+        if alpha < 1.0:
+            self.path.set("fill-opacity", str(alpha))
+        elif self.force_stroke:
+            self.path.set("stroke", ColorUtils.to_rgb_string(color))
+            self.path.set("stroke-width", "1")
+        else:
+            self.path.set("stroke", "none")
+
+    def begin_gradient_fill(self, type, colors, alphas, ratios,
+                            matrix=None,
+                            spreadMethod=SpreadMethod.PAD,
+                            interpolationMethod=InterpolationMethod.RGB,
+                            focalPointRatio=0.0):
+        self.finalize_path()
+        gradient_id = self.export_gradient(type, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio)
+        self.path.set("stroke", "none")
+        self.path.set("fill", "url(#%s)" % gradient_id)
+
+    def export_gradient(self, type, colors, alphas, ratios,
+                        matrix=None,
+                        spreadMethod=SpreadMethod.PAD,
+                        interpolationMethod=InterpolationMethod.RGB,
+                        focalPointRatio=0.0):
+        self.num_gradients += 1
+        gradient_id = "gradient%d" % self.num_gradients
+        gradient = self._e.linearGradient() if type == GradientType.LINEAR \
+            else self._e.radialGradient()
+        gradient.set("gradientUnits", "userSpaceOnUse")
+
+        if type == GradientType.LINEAR:
+            gradient.set("x1", "-819.2")
+            gradient.set("x2", "819.2")
+        else:
+            gradient.set("r", "819.2")
+            gradient.set("cx", "0")
+            gradient.set("cy", "0")
+            if focalPointRatio < 0.0 or focalPointRatio > 0.0:
+                gradient.set("fx", str(819.2 * focalPointRatio))
+                gradient.set("fy", "0")
+
+        if spreadMethod == SpreadMethod.PAD:
+            gradient.set("spreadMethod", "pad")
+        elif spreadMethod == SpreadMethod.REFLECT:
+            gradient.set("spreadMethod", "reflect")
+        elif spreadMethod == SpreadMethod.REPEAT:
+            gradient.set("spreadMethod", "repeat")
+
+        if interpolationMethod == InterpolationMethod.LINEAR_RGB:
+            gradient.set("color-interpolation", "linearRGB")
+
+        if matrix is not None:
+            sm = _swf_matrix_to_svg_matrix(matrix)
+            gradient.set("gradientTransform", sm);
+
+        for i in range(0, len(colors)):
+            entry = self._e.stop()
+            offset = ratios[i] / 255.0
+            entry.set("offset", str(offset))
+            if colors[i] != 0.0:
+                entry.set("stop-color", ColorUtils.to_rgb_string(colors[i]))
+            if alphas[i] != 1.0:
+                entry.set("stop-opacity", str(alphas[i]))
+            gradient.append(entry)
+
+        # prevent same gradient in <defs />
+        key = etree.tostring(gradient)
+        if key in self._gradients:
+            gradient_id = self._gradient_ids[key]
+        else:
+            self._gradients[key] = copy.copy(gradient)
+            self._gradient_ids[key] = gradient_id
+            gradient.set("id", gradient_id)
+            self.defs.append(gradient)
+
+        return gradient_id
+
+    def export_pattern(self, bitmap_id, matrix, repeat=False, smooth=False):
+        self.num_patterns += 1
+        bitmap_id = "c%d" % bitmap_id
+        e = self.defs.xpath("./svg:image[@id='%s']" % bitmap_id, namespaces=NS)
+        if len(e) < 1:
+            raise Exception("SVGShapeExporter::begin_bitmap_fill Could not find bitmap!")
+        image = e[0]
+        pattern_id = "pat%d" % (self.num_patterns)
+        pattern = self._e.pattern()
+        pattern.set("id", pattern_id)
+        pattern.set("width", image.get("width"))
+        pattern.set("height", image.get("height"))
+        pattern.set("patternUnits", "userSpaceOnUse")
+        #pattern.set("patternContentUnits", "objectBoundingBox")
+        if matrix is not None:
+            pattern.set("patternTransform", _swf_matrix_to_svg_matrix(matrix, True, True, True))
+            pass
+        use = self._e.use()
+        use.set(XLINK_HREF, "#%s" % bitmap_id)
+        pattern.append(use)
+        self.defs.append(pattern)
+
+        return pattern_id
+
+    def begin_bitmap_fill(self, bitmap_id, matrix=None, repeat=False, smooth=False):
+        self.finalize_path()
+        pattern_id = self.export_pattern(bitmap_id, matrix, repeat, smooth)
+        self.path.set("stroke", "none")
+        self.path.set("fill", "url(#%s)" % pattern_id)
+
+    def line_style(self,
+                    thickness=float('nan'), color=0, alpha=1.0,
+                    pixelHinting=False,
+                    scaleMode=LineScaleMode.NORMAL,
+                    startCaps=None, endCaps=None,
+                    joints=None, miterLimit=3.0):
+        self.finalize_path()
+        self.path.set("fill", "none")
+        self.path.set("stroke", ColorUtils.to_rgb_string(color))
+        thickness = 1 if math.isnan(thickness) else thickness
+        thickness = MINIMUM_STROKE_WIDTH if thickness < MINIMUM_STROKE_WIDTH else thickness
+        self.path.set("stroke-width", str(thickness))
+        if alpha < 1.0:
+            self.path.set("stroke-opacity", str(alpha))
+
+    def line_gradient_style(self,
+                    thickness=float('nan'),
+                    pixelHinting = False,
+                    scaleMode=LineScaleMode.NORMAL,
+                    startCaps=0, endCaps=0,
+                    joints=0, miterLimit=3.0,
+                    type = 1,
+                    colors = [],
+                    alphas = [],
+                    ratios = [],
+                    matrix=None,
+                    spreadMethod=SpreadMethod.PAD,
+                    interpolationMethod=InterpolationMethod.RGB,
+                    focalPointRatio=0.0):
+        self.finalize_path()
+        gradient_id = self.export_gradient(type, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio)
+        self.path.set("fill", "none")
+        self.path.set("stroke-linejoin", JOIN_STYLE[joints])
+        self.path.set("stroke-linecap", CAPS_STYLE[startCaps])
+        self.path.set("stroke", "url(#%s)" % gradient_id)
+        thickness = 1 if math.isnan(thickness) else thickness
+        thickness = MINIMUM_STROKE_WIDTH if thickness < MINIMUM_STROKE_WIDTH else thickness
+        self.path.set("stroke-width", str(thickness))
+
+    def line_bitmap_style(self,
+                    thickness=float('nan'),
+                    pixelHinting=False,
+                    scaleMode=LineScaleMode.NORMAL,
+                    startCaps=None, endCaps=None,
+                    joints=None, miterLimit = 3.0,
+                    bitmap_id=None, matrix=None, repeat=False, smooth=False):
+        self.finalize_path()
+        pattern_id = self.export_pattern(bitmap_id, matrix, repeat, smooth)
+        self.path.set("fill", "none")
+        self.path.set("stroke", "url(#%s)" % pattern_id)
+        self.path.set("stroke-linejoin", JOIN_STYLE[joints])
+        self.path.set("stroke-linecap", CAPS_STYLE[startCaps])
+        thickness = 1 if math.isnan(thickness) else thickness
+        thickness = MINIMUM_STROKE_WIDTH if thickness < MINIMUM_STROKE_WIDTH else thickness
+        self.path.set("stroke-width", str(thickness))
+
+    def begin_fills(self):
+        self.fills_ended = False
+    def end_fills(self):
+        self.finalize_path()
+        self.fills_ended = True
+
+    def finalize_path(self):
+        if self.path is not None and len(self.path_data) > 0:
+            self.path_data = self.path_data.rstrip()
+            self.path.set("d", self.path_data)
+            self.g.append(self.path)
+        self.path = self._e.path()
+        super(SVGShapeExporter, self).finalize_path()
+
+
+class BaseExporter(object):
+    def __init__(self, swf=None, shape_exporter=None, force_stroke=False):
+        self.shape_exporter = SVGShapeExporter() if shape_exporter is None else shape_exporter
+        self.clip_depth = 0
+        self.mask_id = None
+        self.jpegTables = None
+        self.force_stroke = force_stroke
+        if swf is not None:
+            self.export(swf)
+
+    def export(self, swf, force_stroke=False):
+        self.force_stroke = force_stroke
+        self.export_define_shapes(swf.tags)
+        self.export_display_list(self.get_display_tags(swf.tags))
+
+    def export_define_bits(self, tag):
+        png_buffer = BytesIO()
+        image = None
+        if isinstance(tag, TagDefineBitsJPEG3):
+
+            tag.bitmapData.seek(0)
+            tag.bitmapAlphaData.seek(0, 2)
+            num_alpha = tag.bitmapAlphaData.tell()
+            tag.bitmapAlphaData.seek(0)
+            image = Image.open(tag.bitmapData)
+            if num_alpha > 0:
+                image_width = image.size[0]
+                image_height = image.size[1]
+                image_data = image.getdata()
+                image_data_len = len(image_data)
+                if num_alpha == image_data_len:
+                    buff = b""
+                    for i in range(0, num_alpha):
+                        alpha = ord(tag.bitmapAlphaData.read(1))
+                        rgb = list(image_data[i])
+                        buff += struct.pack("BBBB", rgb[0], rgb[1], rgb[2], alpha)
+                    image = Image.frombytes("RGBA", (image_width, image_height), buff)
+        elif isinstance(tag, TagDefineBitsJPEG2):
+            tag.bitmapData.seek(0)
+            image = Image.open(tag.bitmapData)
+        else:
+            tag.bitmapData.seek(0)
+            if self.jpegTables is not None:
+                buff = BytesIO()
+                self.jpegTables.seek(0)
+                buff.write(self.jpegTables.read())
+                buff.write(tag.bitmapData.read())
+                buff.seek(0)
+                image = Image.open(buff)
+            else:
+                image = Image.open(tag.bitmapData)
+
+        self.export_image(tag, image)
+
+    def export_define_bits_lossless(self, tag):
+        tag.bitmapData.seek(0)
+        image = Image.open(tag.bitmapData)
+        self.export_image(tag, image)
+
+    def export_define_sprite(self, tag, parent=None):
+        display_tags = self.get_display_tags(tag.tags)
+        self.export_display_list(display_tags, parent)
+
+    def export_define_shape(self, tag):
+        self.shape_exporter.debug = isinstance(tag, TagDefineShape4)
+        tag.shapes.export(self.shape_exporter)
+
+    def export_define_shapes(self, tags):
+        for tag in tags:
+            if isinstance(tag, SWFTimelineContainer):
+                self.export_define_sprite(tag)
+                self.export_define_shapes(tag.tags)
+            elif isinstance(tag, TagDefineShape):
+                self.export_define_shape(tag)
+            elif isinstance(tag, TagJPEGTables):
+                if tag.length > 0:
+                    self.jpegTables = tag.jpegTables
+            elif isinstance(tag, TagDefineBits):
+                self.export_define_bits(tag)
+            elif isinstance(tag, TagDefineBitsLossless):
+                self.export_define_bits_lossless(tag)
+            elif isinstance(tag, TagDefineFont):
+                self.export_define_font(tag)
+            elif isinstance(tag, TagDefineText):
+                self.export_define_text(tag)
+
+    def export_display_list(self, tags, parent=None):
+        self.clip_depth = 0
+        for tag in tags:
+            self.export_display_list_item(tag, parent)
+
+    def export_display_list_item(self, tag, parent=None):
+        pass
+
+    def export_image(self, tag, image=None):
+        pass
+
+    def get_display_tags(self, tags, z_sorted=True):
+        dp_tuples = []
+        for tag in tags:
+            if isinstance(tag, TagPlaceObject):
+                dp_tuples.append((tag, tag.depth))
+            elif isinstance(tag, TagShowFrame):
+                break
+        if z_sorted:
+            dp_tuples = sorted(dp_tuples, key=lambda tag_info: tag_info[1])
+        display_tags = []
+        for item in dp_tuples:
+            display_tags.append(item[0])
+        return display_tags
+
+    def serialize(self):
+        return None
+
+class SVGExporter(BaseExporter):
+    def __init__(self, swf=None, margin=0):
+        self._e = objectify.ElementMaker(annotate=False,
+                        namespace=SVG_NS, nsmap={None : SVG_NS, "xlink" : XLINK_NS})
+        self._margin = margin
+        super(SVGExporter, self).__init__(swf)
+
+    def export(self, swf, force_stroke=False):
+        """ Exports the specified SWF to SVG.
+
+        @param swf  The SWF.
+        @param force_stroke Whether to force strokes on non-stroked fills.
+        """
+        self.svg = self._e.svg(version=SVG_VERSION)
+        self.force_stroke = force_stroke
+        self.defs = self._e.defs()
+        self.root = self._e.g()
+        self.svg.append(self.defs)
+        self.svg.append(self.root)
+        self.shape_exporter.defs = self.defs
+        self._num_filters = 0
+        self.fonts = dict([(x.characterId,x) for x in swf.all_tags_of_type(TagDefineFont)])
+        self.fontInfos = dict([(x.characterId,x) for x in swf.all_tags_of_type(TagDefineFontInfo)])
+
+        # GO!
+        super(SVGExporter, self).export(swf, force_stroke)
+
+        # Setup svg @width, @height and @viewBox
+        # and add the optional margin
+        self.bounds = SVGBounds(self.svg)
+        self.svg.set("width", "%dpx" % round(self.bounds.width))
+        self.svg.set("height", "%dpx" % round(self.bounds.height))
+        if self._margin > 0:
+            self.bounds.grow(self._margin)
+        vb = [self.bounds.minx, self.bounds.miny,
+              self.bounds.width, self.bounds.height]
+        self.svg.set("viewBox", "%s" % " ".join(map(str,vb)))
+
+        # Return the SVG as BytesIO
+        return self._serialize()
+
+    def _serialize(self):
+        return BytesIO(etree.tostring(self.svg,
+                encoding="UTF-8", xml_declaration=True))
+
+    def export_define_sprite(self, tag, parent=None):
+        id = "c%d"%tag.characterId
+        g = self._e.g(id=id)
+        self.defs.append(g)
+        self.clip_depth = 0
+        super(SVGExporter, self).export_define_sprite(tag, g)
+
+    def export_define_font(self, tag):
+        if not tag.characterId in self.fontInfos:
+            return
+        fontInfo = self.fontInfos[tag.characterId]
+        if not fontInfo.useGlyphText:
+            return
+
+        defs = self._e.defs(id="font_{0}".format(tag.characterId))
+
+        for index, glyph in enumerate(tag.glyphShapeTable):
+            # Export the glyph as a shape and add the path to the "defs"
+            # element to be referenced later when exporting text.
+            code_point = fontInfo.codeTable[index]
+            pathGroup = glyph.export().g.getchildren()
+
+            if len(pathGroup):
+                path = pathGroup[0]
+
+                path.set("id", "font_{0}_{1}".format(tag.characterId, code_point))
+
+                # SWF glyphs are always defined on an EM square of 1024 by 1024 units.
+                path.set("transform", "scale({0})".format(float(1)/EM_SQUARE_LENGTH))
+
+                # We'll be setting the color on the USE element that
+                # references this element.
+                del path.attrib["stroke"]
+                del path.attrib["fill"]
+
+                defs.append(path)
+
+        self.defs.append(defs)
+
+    def export_define_text(self, tag):
+        g = self._e.g(id="c{0}".format(int(tag.characterId)))
+        g.set("class", "text_content")
+
+        x = 0
+        y = 0
+
+        for rec in tag.records:
+            if rec.hasXOffset:
+                x = rec.xOffset/PIXELS_PER_TWIP
+            if rec.hasYOffset:
+                y = rec.yOffset/PIXELS_PER_TWIP
+
+            size = rec.textHeight/PIXELS_PER_TWIP
+            if rec.fontId not in self.fontInfos:
+                continue
+            fontInfo = self.fontInfos[rec.fontId]
+
+            if not fontInfo.useGlyphText:
+                inner_text = ""
+                xValues = []
+
+            for glyph in rec.glyphEntries:
+                code_point = fontInfo.codeTable[glyph.index]
+
+                # Ignore control characters
+                if code_point in range(32):
+                    continue
+
+                if fontInfo.useGlyphText:
+                    use = self._e.use()
+                    use.set(XLINK_HREF, "#font_{0}_{1}".format(rec.fontId, code_point))
+
+                    use.set(
+                        'transform',
+                        "scale({0}) translate({1} {2})".format(
+                            size, float(x)/size, float(y)/size
+                        )
+                    )
+
+                    color = ColorUtils.to_rgb_string(ColorUtils.rgb(rec.textColor))
+                    use.set("style", "fill: {0}; stroke: {0}".format(color))
+
+                    g.append(use)
+                else:
+                    inner_text += chr(code_point)
+                    xValues.append(str(x))
+
+                x = x + float(glyph.advance)/PIXELS_PER_TWIP
+
+            if not fontInfo.useGlyphText:
+                text = self._e.text(inner_text)
+
+                text.set("font-family", fontInfo.fontName)
+                text.set("font-size", str(size))
+                text.set("fill", ColorUtils.to_rgb_string(ColorUtils.rgb(rec.textColor)))
+
+                text.set("y", str(y))
+                text.set("x", " ".join(xValues))
+
+                if fontInfo.bold:
+                    text.set("font-weight", "bold")
+                if fontInfo.italic:
+                    text.set("font-style", "italic")
+
+                g.append(text)
+
+        self.defs.append(g)
+
+    def export_define_shape(self, tag):
+        self.shape_exporter.force_stroke = self.force_stroke
+        super(SVGExporter, self).export_define_shape(tag)
+        shape = self.shape_exporter.g
+        shape.set("id", "c%d" % tag.characterId)
+        self.defs.append(shape)
+
+    def export_display_list_item(self, tag, parent=None):
+        g = self._e.g()
+        use = self._e.use()
+        is_mask = False
+
+        if tag.hasMatrix:
+            use.set("transform", _swf_matrix_to_svg_matrix(tag.matrix))
+        if tag.hasClipDepth:
+            self.mask_id = "mask%d" % tag.characterId
+            self.clip_depth = tag.clipDepth
+            g = self._e.mask(id=self.mask_id)
+            # make sure the mask is completely filled white
+            paths = self.defs.xpath("./svg:g[@id='c%d']/svg:path" % tag.characterId, namespaces=NS)
+            for path in paths:
+                path.set("fill", "#ffffff")
+        elif tag.depth <= self.clip_depth and self.mask_id is not None:
+            g.set("mask", "url(#%s)" % self.mask_id)
+
+        filters = []
+        filter_cxform = None
+        self._num_filters += 1
+        filter_id = "filter%d" % self._num_filters
+        svg_filter = self._e.filter(id=filter_id)
+
+        if tag.hasColorTransform:
+            filter_cxform = self.export_color_transform(tag.colorTransform, svg_filter)
+            filters.append(filter_cxform)
+        if tag.hasFilterList and len(tag.filters) > 0:
+            cxform = "color-xform" if tag.hasColorTransform else None
+            f = self.export_filters(tag, svg_filter, cxform)
+            if len(f) > 0:
+                filters.extend(f)
+        if tag.hasColorTransform or (tag.hasFilterList and len(filters) > 0):
+            self.defs.append(svg_filter)
+            use.set("filter", "url(#%s)" % filter_id)
+
+        use.set(XLINK_HREF, "#c%s" % tag.characterId)
+        g.append(use)
+
+        if is_mask:
+            self.defs.append(g)
+        else:
+            if parent is not None:
+                parent.append(g)
+            else:
+                self.root.append(g)
+        return use
+
+    def export_color_transform(self, cxform, svg_filter, result='color-xform'):
+        fe_cxform = self._e.feColorMatrix()
+        fe_cxform.set("in", "SourceGraphic")
+        fe_cxform.set("type", "matrix")
+        fe_cxform.set("values", " ".join(map(str, cxform.matrix)))
+        fe_cxform.set("result", "cxform")
+
+        fe_composite = self._e.feComposite(operator="in")
+        fe_composite.set("in2", "SourceGraphic")
+        fe_composite.set("result", result)
+
+        svg_filter.append(fe_cxform)
+        svg_filter.append(fe_composite)
+        return result
+
+    def export_filters(self, tag, svg_filter, cxform=None):
+        num_filters = len(tag.filters)
+        elements = []
+        attr_in = None
+        for i in range(0, num_filters):
+            swf_filter = tag.filters[i]
+            #print swf_filter
+            if isinstance(swf_filter, FilterDropShadow):
+                elements.append(self.export_filter_dropshadow(swf_filter, svg_filter, cxform))
+                #print swf_filter.strength
+                pass
+            elif isinstance(swf_filter, FilterBlur):
+                pass
+            elif isinstance(swf_filter, FilterGlow):
+                #attr_in = SVGFilterFactory.export_glow_filter(self._e, svg_filter, attr_in=attr_in)
+                #elements.append(attr_in)
+                pass
+            elif isinstance(swf_filter, FilterBevel):
+                pass
+            elif isinstance(swf_filter, FilterGradientGlow):
+                pass
+            elif isinstance(swf_filter, FilterConvolution):
+                pass
+            elif isinstance(swf_filter, FilterColorMatrix):
+                attr_in = SVGFilterFactory.export_color_matrix_filter(self._e, svg_filter, swf_filter.colorMatrix, svg_filter, attr_in=attr_in)
+                elements.append(attr_in)
+                pass
+            elif isinstance(swf_filter, FilterGradientBevel):
+                pass
+            else:
+                raise Exception("unknown filter: ", swf_filter)
+        return elements
+
+#   <filter id="test-filter" x="-50%" y="-50%" width="200%" height="200%">
+#		<feGaussianBlur in="SourceAlpha" stdDeviation="6" result="blur"/>
+#		<feOffset dy="0" dx="0"/>
+#		<feComposite in2="SourceAlpha" operator="arithmetic"
+#			k2="-1" k3="1" result="shadowDiff"/>
+#		<feFlood flood-color="black" flood-opacity="1"/>
+#		<feComposite in2="shadowDiff" operator="in"/>
+#	</filter>;
+
+    def export_filter_dropshadow(self, swf_filter, svg_filter, blend_in=None, result="offsetBlur"):
+        gauss = self._e.feGaussianBlur()
+        gauss.set("in", "SourceAlpha")
+        gauss.set("stdDeviation", "6")
+        gauss.set("result", "blur")
+        if swf_filter.knockout:
+            composite0 = self._e.feComposite(
+                in2="SourceAlpha", operator="arithmetic",
+                k2="-1", k3="1", result="shadowDiff")
+            flood = self._e.feFlood()
+            flood.set("flood-color", "black")
+            flood.set("flood-opacity", "1")
+            composite1 = self._e.feComposite(
+                in2="shadowDiff", operator="in", result=result)
+            svg_filter.append(gauss)
+            svg_filter.append(composite0)
+            svg_filter.append(flood)
+            svg_filter.append(composite1)
+        else:
+            SVGFilterFactory.create_drop_shadow_filter(self._e, svg_filter,
+                None,
+                swf_filter.blurX/20.0,
+                swf_filter.blurY/20.0,
+                blend_in,
+                result)
+        #print etree.tostring(svg_filter, pretty_print=True)
+        return result
+
+    def export_image(self, tag, image=None):
+        if image is not None:
+            buff = BytesIO()
+            image.save(buff, "PNG")
+            buff.seek(0)
+            with open("C:\\Users\\Administrator\\Desktop\\a.png","wb") as f:
+                f.write(buff.getvalue())
+            data_url = _encode_png(buff.read())
+            img = self._e.image()
+            img.set("id", "c%s" % tag.characterId)
+            img.set("x", "0")
+            img.set("y", "0 ")
+            img.set("width", "%s" % str(image.size[0]))
+            img.set("height", "%s" % str(image.size[1]))
+            img.set(XLINK_HREF, "%s" % data_url)
+            self.defs.append(img)
+
+class SingleShapeSVGExporter(SVGExporter):
+    """
+    An SVG exporter which knows how to export a single shape.
+    """
+    def __init__(self, margin=0):
+        super(SingleShapeSVGExporter, self).__init__(margin = margin)
+
+    def export_single_shape(self, shape_tag, swf):
+        from swf.movie import SWF
+
+        # find a typical use of this shape
+        example_place_objects = [x for x in swf.all_tags_of_type(TagPlaceObject) if x.hasCharacter and x.characterId == shape_tag.characterId]
+
+        if len(example_place_objects):
+            place_object = example_place_objects[0]
+            characters = swf.build_dictionary()
+            ids_to_export = place_object.get_dependencies()
+            ids_exported = set()
+            tags_to_export = []
+
+            # this had better form a dag!
+            while len(ids_to_export):
+                id = ids_to_export.pop()
+                if id in ids_exported or id not in characters:
+                    continue
+                tag = characters[id]
+                ids_to_export.update(tag.get_dependencies())
+                tags_to_export.append(tag)
+                ids_exported.add(id)
+            tags_to_export.reverse()
+            tags_to_export.append(place_object)
+        else:
+            place_object = TagPlaceObject()
+            place_object.hasCharacter = True
+            place_object.characterId = shape_tag.characterId
+            tags_to_export = [ shape_tag, place_object ]
+
+        stunt_swf = SWF()
+        stunt_swf.tags = tags_to_export
+
+        return super(SingleShapeSVGExporter, self).export(stunt_swf)
+
+class SVGFilterFactory(object):
+    # http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Filters
+    # http://dev.opera.com/articles/view/svg-evolution-3-applying-polish/
+
+    @classmethod
+    def create_drop_shadow_filter(cls, e, filter, attr_in=None, blurX=0, blurY=0, blend_in=None, result=None):
+        gaussianBlur = SVGFilterFactory.create_gaussian_blur(e, attr_deviaton="1", result="blur-out")
+        offset = SVGFilterFactory.create_offset(e, "blur-out", blurX, blurY, "the-shadow")
+        blend = SVGFilterFactory.create_blend(e, blend_in, attr_in2="the-shadow", result=result)
+        filter.append(gaussianBlur)
+        filter.append(offset)
+        filter.append(blend)
+        return result
+
+    @classmethod
+    def export_color_matrix_filter(cls, e, filter, matrix, svg_filter, attr_in=None, result='color-matrix'):
+        attr_in = "SourceGraphic" if attr_in is None else attr_in
+        fe_cxform = e.feColorMatrix()
+        fe_cxform.set("in", attr_in)
+        fe_cxform.set("type", "matrix")
+        fe_cxform.set("values", " ".join(map(str, matrix)))
+        fe_cxform.set("result", result)
+        filter.append(fe_cxform)
+        #print etree.tostring(filter, pretty_print=True)
+        return result
+
+    @classmethod
+    def export_glow_filter(cls, e, filter, attr_in=None, result="glow-out"):
+        attr_in = "SourceGraphic" if attr_in is None else attr_in
+        gaussianBlur = SVGFilterFactory.create_gaussian_blur(e, attr_in=attr_in, attr_deviaton="1", result=result)
+        filter.append(gaussianBlur)
+        return result
+
+    @classmethod
+    def create_blend(cls, e, attr_in=None, attr_in2="BackgroundImage", mode="normal", result=None):
+        blend = e.feBlend()
+        attr_in = "SourceGraphic" if attr_in is None else attr_in
+        blend.set("in", attr_in)
+        blend.set("in2", attr_in2)
+        blend.set("mode", mode)
+        if result is not None:
+            blend.set("result", result)
+        return blend
+
+    @classmethod
+    def create_gaussian_blur(cls, e, attr_in="SourceAlpha", attr_deviaton="3", result=None):
+        gaussianBlur = e.feGaussianBlur()
+        gaussianBlur.set("in", attr_in)
+        gaussianBlur.set("stdDeviation", attr_deviaton)
+        if result is not None:
+            gaussianBlur.set("result", result)
+        return gaussianBlur
+
+    @classmethod
+    def create_offset(cls, e, attr_in=None, dx=0, dy=0, result=None):
+        offset = e.feOffset()
+        if attr_in is not None:
+            offset.set("in", attr_in)
+        offset.set("dx", "%d" % round(dx))
+        offset.set("dy", "%d" % round(dy))
+        if result is not None:
+            offset.set("result", result)
+        return offset
+
+class SVGBounds(object):
+    def __init__(self, svg=None):
+        self.minx = 1000000.0
+        self.miny = 1000000.0
+        self.maxx = -self.minx
+        self.maxy = -self.miny
+        self._stack = []
+        self._matrix = self._calc_combined_matrix()
+        if svg is not None:
+            self._svg = svg;
+            self._parse(svg)
+
+    def add_point(self, x, y):
+        self.minx = x if x < self.minx else self.minx
+        self.miny = y if y < self.miny else self.miny
+        self.maxx = x if x > self.maxx else self.maxx
+        self.maxy = y if y > self.maxy else self.maxy
+
+    def set(self, minx, miny, maxx, maxy):
+        self.minx = minx
+        self.miny = miny
+        self.maxx = maxx
+        self.maxy = maxy
+
+    def grow(self, margin):
+        self.minx -= margin
+        self.miny -= margin
+        self.maxx += margin
+        self.maxy += margin
+
+    @property
+    def height(self):
+        return self.maxy - self.miny
+
+    def merge(self, other):
+        self.minx = other.minx if other.minx < self.minx else self.minx
+        self.miny = other.miny if other.miny < self.miny else self.miny
+        self.maxx = other.maxx if other.maxx > self.maxx else self.maxx
+        self.maxy = other.maxy if other.maxy > self.maxy else self.maxy
+
+    def shrink(self, margin):
+        self.minx += margin
+        self.miny += margin
+        self.maxx -= margin
+        self.maxy -= margin
+
+    @property
+    def width(self):
+        return self.maxx - self.minx
+
+    def _parse(self, element):
+
+        if element.get("transform") and element.get("transform").find("matrix") < 0:
+            pass
+
+        if element.get("transform") and element.get("transform").find("matrix") >= 0:
+            self._push_transform(element.get("transform"))
+
+        if element.tag == "{%s}path" % SVG_NS:
+            self._handle_path_data(str(element.get("d")))
+        elif element.tag == "{%s}use" % SVG_NS:
+            href = element.get(XLINK_HREF)
+            if href:
+                href = href.replace("#", "")
+                els = self._svg.xpath("./svg:defs//svg:g[@id='%s']" % href,
+                        namespaces=NS)
+                if len(els) > 0:
+                    self._parse(els[0])
+
+        for child in element.getchildren():
+            if child.tag == "{%s}defs" % SVG_NS: continue
+            self._parse(child)
+
+        if element.get("transform") and element.get("transform").find("matrix") >= 0:
+            self._pop_transform()
+
+    def _build_matrix(self, transform):
+        if transform.find("matrix") >= 0:
+            raw = str(transform).replace("matrix(", "").replace(")", "")
+            f = map(float, re.split("\s+|,", raw))
+            return Matrix2(f[0], f[1], f[2], f[3], f[4], f[5])
+
+    def _calc_combined_matrix(self):
+        m = Matrix2()
+        for mat in self._stack:
+            m.append_matrix(mat)
+        return m
+
+    def _handle_path_data(self, d):
+        parts = re.split("[\s]+", d)
+        for i in range(0, len(parts), 2):
+            try:
+                p0 = parts[i]
+                p1 = parts[i+1]
+                p0 = p0.replace("M", "").replace("L", "").replace("Q", "")
+                p1 = p1.replace("M", "").replace("L", "").replace("Q", "")
+
+                v = [float(p0), float(p1)]
+                w = self._matrix.multiply_point(v)
+                self.minx = w[0] if w[0] < self.minx else self.minx
+                self.miny = w[1] if w[1] < self.miny else self.miny
+                self.maxx = w[0] if w[0] > self.maxx else self.maxx
+                self.maxy = w[1] if w[1] > self.maxy else self.maxy
+            except:
+                continue
+
+    def _pop_transform(self):
+        m = self._stack.pop()
+        self._matrix = self._calc_combined_matrix()
+        return m
+
+    def _push_transform(self, transform):
+        self._stack.append(self._build_matrix(transform))
+        self._matrix = self._calc_combined_matrix()
+
+def _encode_jpeg(data):
+    return "data:image/jpeg;base64," + str(base64.encodestring(data)[:-1])
+
+def _encode_png(data):
+    return "data:image/png;base64," + str(base64.encodestring(data)[:-1])
+
+def _swf_matrix_to_matrix(swf_matrix=None, need_scale=False, need_translate=True, need_rotation=False, unit_div=20.0):
+
+    if swf_matrix is None:
+        values = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]
+    else:
+        values = swf_matrix.to_array()
+        if need_rotation:
+            values[1] /= unit_div
+            values[2] /= unit_div
+        if need_scale:
+            values[0] /= unit_div
+            values[3] /= unit_div
+        if need_translate:
+            values[4] /= unit_div
+            values[5] /= unit_div
+
+    return values
+
+def _swf_matrix_to_svg_matrix(swf_matrix=None, need_scale=False, need_translate=True, need_rotation=False, unit_div=20.0):
+    values = _swf_matrix_to_matrix(swf_matrix, need_scale, need_translate, need_rotation, unit_div)
+    str_values = ",".join(map(str, values))
+    return "matrix(%s)" % str_values
+

+ 371 - 0
format_convert/swf/geom.py

@@ -0,0 +1,371 @@
+import math
+
+SNAP = 0.001
+
+class Vector2(object):
+    def __init__(self, x=0.0, y=0.0):
+        self.x = x
+        self.y = y
+        
+class Vector3(object):
+    def __init__(self, x=0, y=0, z=0):
+        self.x = x
+        self.y = y
+        self.z = z
+    
+    def clone(self):
+        return Vector3(self.x, self.y, self.z)
+    
+    def cross(self, v1, v2):
+        self.x = v1.y * v2.z - v1.z * v2.y
+        self.y = v1.z * v2.x - v1.x * v2.z
+        self.z = v1.x * v2.y - v1.y * v2.x
+        return self
+    
+    def distance(self, v):
+        dx = self.x - v.x
+        dy = self.y - v.y
+        dz = self.z - v.z
+        return math.sqrt(dx*dx + dy*dy + dz*dz)
+    
+    def distanceSq(self, v):
+        dx = self.x - v.x
+        dy = self.y - v.y
+        dz = self.z - v.z
+        return (dx*dx + dy*dy + dz*dz)
+    
+    def dot(self, v):
+        return self.x * v.x + self.y * v.y + self.z * v.z
+    
+    def length(self):
+        return math.sqrt(self.x*self.x + self.y*self.y + self.z * self.z)
+    
+    def lengthSq(self):
+        return (self.x*self.x + self.y*self.y + self.z * self.z)
+    
+    def addScalar(self, s):
+        self.x += s
+        self.y += s
+        self.z += s
+        return self
+    
+    def divScalar(self, s):
+        self.x /= s
+        self.y /= s
+        self.z /= s
+        return self
+    
+    def multScalar(self, s):
+        self.x *= s
+        self.y *= s
+        self.z *= s
+        return self
+    
+    def sub(self, a, b):
+        self.x = a.x - b.x
+        self.y = a.y - b.y
+        self.z = a.z - b.z
+        return self
+    
+    def subScalar(self, s):
+        self.x -= s
+        self.y -= s
+        self.z -= s
+        return self
+    
+    def equals(self, v, e=None):
+        e = SNAP if e is None else e
+        if v.x > self.x-e and v.x < self.x+e and \
+           v.y > self.y-e and v.y < self.y+e and \
+           v.z > self.z-e and v.z < self.z+e:
+            return True
+        else:
+            return False
+        
+    def normalize(self):
+        len = self.length()
+        if len > 0.0:
+            self.multScalar(1.0 / len)
+        return self
+    
+    def set(self, x, y, z):
+        self.x = x
+        self.y = y
+        self.z = z
+
+    def tostring(self):
+        return "%0.3f %0.3f %0.3f" % (self.x, self.y, self.z)
+        
+class Matrix2(object):
+    """
+    Matrix2
+    """
+    def __init__(self, a=1.0, b=0.0, c=0.0, d=1.0, tx=0.0, ty=0.0):
+        self.a = a
+        self.b = b
+        self.c = c 
+        self.d = d
+        self.tx = tx
+        self.ty = ty
+        
+    def append(self, a, b, c, d, tx, ty):
+        a1 = self.a
+        b1 = self.b
+        c1 = self.c
+        d1 = self.d
+
+        self.a  = a*a1+b*c1
+        self.b  = a*b1+b*d1
+        self.c  = c*a1+d*c1
+        self.d  = c*b1+d*d1
+        self.tx = tx*a1+ty*c1+self.tx
+        self.ty = tx*b1+ty*d1+self.ty
+     
+    def append_matrix(self, m):
+        self.append(m.a, m.b, m.c, m.d, m.tx, m.ty)
+    
+    def multiply_point(self, vec):
+        return [
+            self.a*vec[0] + self.c*vec[1] + self.tx,
+            self.b*vec[0] + self.d*vec[1] + self.ty
+        ]
+        
+    def prepend(self, a, b, c, d, tx, ty):
+        tx1 = self.tx
+        if (a != 1.0 or b != 0.0 or c != 0.0 or d != 1.0):
+            a1 = self.a
+            c1 = self.c
+            self.a  = a1*a+self.b*c
+            self.b  = a1*b+self.b*d
+            self.c  = c1*a+self.d*c
+            self.d  = c1*b+self.d*d
+        self.tx = tx1*a+self.ty*c+tx
+        self.ty = tx1*b+self.ty*d+ty
+        
+    def prepend_matrix(self, m):
+        self.prepend(m.a, m.b, m.c, m.d, m.tx, m.ty)
+        
+    def rotate(self, angle):
+        cos = math.cos(angle)
+        sin = math.sin(angle)
+        a1 = self.a
+        c1 = self.c
+        tx1 = self.tx
+        self.a = a1*cos-self.b*sin
+        self.b = a1*sin+self.b*cos
+        self.c = c1*cos-self.d*sin
+        self.d = c1*sin+self.d*cos
+        self.tx = tx1*cos-self.ty*sin
+        self.ty = tx1*sin+self.ty*cos
+    
+    def scale(self, x, y):
+        self.a *= x;
+        self.d *= y;
+        self.tx *= x;
+        self.ty *= y;
+     
+    def translate(self, x, y):   
+        self.tx += x;
+        self.ty += y;
+              
+class Matrix4(object):
+    """
+    Matrix4
+    """
+    def __init__(self, data=None):
+        if not data is None and len(data) == 16:
+            self.n11 = data[0]; self.n12 = data[1]; self.n13 = data[2]; self.n14 = data[3]
+            self.n21 = data[4]; self.n22 = data[5]; self.n23 = data[6]; self.n24 = data[7]
+            self.n31 = data[8]; self.n32 = data[9]; self.n33 = data[10]; self.n34 = data[11]
+            self.n41 = data[12]; self.n42 = data[13]; self.n43 = data[14]; self.n44 = data[15]
+        else:
+            self.n11 = 1.0; self.n12 = 0.0; self.n13 = 0.0; self.n14 = 0.0
+            self.n21 = 0.0; self.n22 = 1.0; self.n23 = 0.0; self.n24 = 0.0
+            self.n31 = 0.0; self.n32 = 0.0; self.n33 = 1.0; self.n34 = 0.0
+            self.n41 = 0.0; self.n42 = 0.0; self.n43 = 0.0; self.n44 = 1.0
+    
+    def clone(self):
+        return Matrix4(self.flatten())
+    
+    def flatten(self):
+        return [self.n11, self.n12, self.n13, self.n14, \
+                self.n21, self.n22, self.n23, self.n24, \
+                self.n31, self.n32, self.n33, self.n34, \
+                self.n41, self.n42, self.n43, self.n44]
+         
+    def identity(self):
+        self.n11 = 1.0; self.n12 = 0.0; self.n13 = 0.0; self.n14 = 0.0
+        self.n21 = 0.0; self.n22 = 1.0; self.n23 = 0.0; self.n24 = 0.0
+        self.n31 = 0.0; self.n32 = 0.0; self.n33 = 1.0; self.n34 = 0.0
+        self.n41 = 0.0; self.n42 = 0.0; self.n43 = 0.0; self.n44 = 1.0
+        return self
+    
+    def multiply(self, a, b):
+        a11 = a.n11; a12 = a.n12; a13 = a.n13; a14 = a.n14
+        a21 = a.n21; a22 = a.n22; a23 = a.n23; a24 = a.n24
+        a31 = a.n31; a32 = a.n32; a33 = a.n33; a34 = a.n34
+        a41 = a.n41; a42 = a.n42; a43 = a.n43; a44 = a.n44
+        b11 = b.n11; b12 = b.n12; b13 = b.n13; b14 = b.n14
+        b21 = b.n21; b22 = b.n22; b23 = b.n23; b24 = b.n24
+        b31 = b.n31; b32 = b.n32; b33 = b.n33; b34 = b.n34
+        b41 = b.n41; b42 = b.n42; b43 = b.n43; b44 = b.n44
+
+        self.n11 = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41
+        self.n12 = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42
+        self.n13 = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43
+        self.n14 = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44
+
+        self.n21 = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41
+        self.n22 = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42
+        self.n23 = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43
+        self.n24 = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44
+
+        self.n31 = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41
+        self.n32 = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42
+        self.n33 = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43
+        self.n34 = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44
+
+        self.n41 = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41
+        self.n42 = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42
+        self.n43 = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43
+        self.n44 = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44
+        return self
+    
+    def multiplyVector3(self, vec):
+        vx = vec[0]
+        vy = vec[1]
+        vz = vec[2]
+        d = 1.0 / (self.n41 * vx + self.n42 * vy + self.n43 * vz + self.n44)
+        x = (self.n11 * vx + self.n12 * vy + self.n13 * vz + self.n14) * d
+        y = (self.n21 * vx + self.n22 * vy + self.n23 * vz + self.n24) * d
+        z = (self.n31 * vx + self.n32 * vy + self.n33 * vz + self.n34) * d
+        return [x, y, z]
+    
+    def multiplyVec3(self, vec):
+        vx = vec.x 
+        vy = vec.y
+        vz = vec.z
+        d = 1.0 / (self.n41 * vx + self.n42 * vy + self.n43 * vz + self.n44)
+        x = (self.n11 * vx + self.n12 * vy + self.n13 * vz + self.n14) * d
+        y = (self.n21 * vx + self.n22 * vy + self.n23 * vz + self.n24) * d
+        z = (self.n31 * vx + self.n32 * vy + self.n33 * vz + self.n34) * d
+        return Vector3(x, y, z)
+    
+    def multiplyVector4(self, v):
+        vx = v[0]; vy = v[1]; vz = v[2]; vw = v[3];
+
+        x = self.n11 * vx + self.n12 * vy + self.n13 * vz + self.n14 * vw;
+        y = self.n21 * vx + self.n22 * vy + self.n23 * vz + self.n24 * vw;
+        z = self.n31 * vx + self.n32 * vy + self.n33 * vz + self.n34 * vw;
+        w = self.n41 * vx + self.n42 * vy + self.n43 * vz + self.n44 * vw;
+
+        return [x, y, z, w];
+    
+    def det(self):
+        #( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+        return (
+            self.n14 * self.n23 * self.n32 * self.n41-
+            self.n13 * self.n24 * self.n32 * self.n41-
+            self.n14 * self.n22 * self.n33 * self.n41+
+            self.n12 * self.n24 * self.n33 * self.n41+
+
+            self.n13 * self.n22 * self.n34 * self.n41-
+            self.n12 * self.n23 * self.n34 * self.n41-
+            self.n14 * self.n23 * self.n31 * self.n42+
+            self.n13 * self.n24 * self.n31 * self.n42+
+
+            self.n14 * self.n21 * self.n33 * self.n42-
+            self.n11 * self.n24 * self.n33 * self.n42-
+            self.n13 * self.n21 * self.n34 * self.n42+
+            self.n11 * self.n23 * self.n34 * self.n42+
+
+            self.n14 * self.n22 * self.n31 * self.n43-
+            self.n12 * self.n24 * self.n31 * self.n43-
+            self.n14 * self.n21 * self.n32 * self.n43+
+            self.n11 * self.n24 * self.n32 * self.n43+
+
+            self.n12 * self.n21 * self.n34 * self.n43-
+            self.n11 * self.n22 * self.n34 * self.n43-
+            self.n13 * self.n22 * self.n31 * self.n44+
+            self.n12 * self.n23 * self.n31 * self.n44+
+
+            self.n13 * self.n21 * self.n32 * self.n44-
+            self.n11 * self.n23 * self.n32 * self.n44-
+            self.n12 * self.n21 * self.n33 * self.n44+
+            self.n11 * self.n22 * self.n33 * self.n44)
+        
+    def lookAt(self, eye, center, up):
+        x = Vector3(); y = Vector3(); z = Vector3();
+        z.sub(eye, center).normalize();
+        x.cross(up, z).normalize();
+        y.cross(z, x).normalize();
+        #eye.normalize()
+        self.n11 = x.x; self.n12 = x.y; self.n13 = x.z; self.n14 = -x.dot(eye);
+        self.n21 = y.x; self.n22 = y.y; self.n23 = y.z; self.n24 = -y.dot(eye);
+        self.n31 = z.x; self.n32 = z.y; self.n33 = z.z; self.n34 = -z.dot(eye);
+        self.n41 = 0.0; self.n42 = 0.0; self.n43 = 0.0; self.n44 = 1.0;
+        return self;
+    
+    def multiplyScalar(self, s):
+        self.n11 *= s; self.n12 *= s; self.n13 *= s; self.n14 *= s;
+        self.n21 *= s; self.n22 *= s; self.n23 *= s; self.n24 *= s;
+        self.n31 *= s; self.n32 *= s; self.n33 *= s; self.n34 *= s;
+        self.n41 *= s; self.n42 *= s; self.n43 *= s; self.n44 *= s;
+        return self
+    
+    @classmethod
+    def inverse(cls, m1):
+        # TODO: make this more efficient
+        #( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+        m2 = Matrix4();
+        m2.n11 = m1.n23*m1.n34*m1.n42 - m1.n24*m1.n33*m1.n42 + m1.n24*m1.n32*m1.n43 - m1.n22*m1.n34*m1.n43 - m1.n23*m1.n32*m1.n44 + m1.n22*m1.n33*m1.n44;
+        m2.n12 = m1.n14*m1.n33*m1.n42 - m1.n13*m1.n34*m1.n42 - m1.n14*m1.n32*m1.n43 + m1.n12*m1.n34*m1.n43 + m1.n13*m1.n32*m1.n44 - m1.n12*m1.n33*m1.n44;
+        m2.n13 = m1.n13*m1.n24*m1.n42 - m1.n14*m1.n23*m1.n42 + m1.n14*m1.n22*m1.n43 - m1.n12*m1.n24*m1.n43 - m1.n13*m1.n22*m1.n44 + m1.n12*m1.n23*m1.n44;
+        m2.n14 = m1.n14*m1.n23*m1.n32 - m1.n13*m1.n24*m1.n32 - m1.n14*m1.n22*m1.n33 + m1.n12*m1.n24*m1.n33 + m1.n13*m1.n22*m1.n34 - m1.n12*m1.n23*m1.n34;
+        m2.n21 = m1.n24*m1.n33*m1.n41 - m1.n23*m1.n34*m1.n41 - m1.n24*m1.n31*m1.n43 + m1.n21*m1.n34*m1.n43 + m1.n23*m1.n31*m1.n44 - m1.n21*m1.n33*m1.n44;
+        m2.n22 = m1.n13*m1.n34*m1.n41 - m1.n14*m1.n33*m1.n41 + m1.n14*m1.n31*m1.n43 - m1.n11*m1.n34*m1.n43 - m1.n13*m1.n31*m1.n44 + m1.n11*m1.n33*m1.n44;
+        m2.n23 = m1.n14*m1.n23*m1.n41 - m1.n13*m1.n24*m1.n41 - m1.n14*m1.n21*m1.n43 + m1.n11*m1.n24*m1.n43 + m1.n13*m1.n21*m1.n44 - m1.n11*m1.n23*m1.n44;
+        m2.n24 = m1.n13*m1.n24*m1.n31 - m1.n14*m1.n23*m1.n31 + m1.n14*m1.n21*m1.n33 - m1.n11*m1.n24*m1.n33 - m1.n13*m1.n21*m1.n34 + m1.n11*m1.n23*m1.n34;
+        m2.n31 = m1.n22*m1.n34*m1.n41 - m1.n24*m1.n32*m1.n41 + m1.n24*m1.n31*m1.n42 - m1.n21*m1.n34*m1.n42 - m1.n22*m1.n31*m1.n44 + m1.n21*m1.n32*m1.n44;
+        m2.n32 = m1.n14*m1.n32*m1.n41 - m1.n12*m1.n34*m1.n41 - m1.n14*m1.n31*m1.n42 + m1.n11*m1.n34*m1.n42 + m1.n12*m1.n31*m1.n44 - m1.n11*m1.n32*m1.n44;
+        m2.n33 = m1.n13*m1.n24*m1.n41 - m1.n14*m1.n22*m1.n41 + m1.n14*m1.n21*m1.n42 - m1.n11*m1.n24*m1.n42 - m1.n12*m1.n21*m1.n44 + m1.n11*m1.n22*m1.n44;
+        m2.n34 = m1.n14*m1.n22*m1.n31 - m1.n12*m1.n24*m1.n31 - m1.n14*m1.n21*m1.n32 + m1.n11*m1.n24*m1.n32 + m1.n12*m1.n21*m1.n34 - m1.n11*m1.n22*m1.n34;
+        m2.n41 = m1.n23*m1.n32*m1.n41 - m1.n22*m1.n33*m1.n41 - m1.n23*m1.n31*m1.n42 + m1.n21*m1.n33*m1.n42 + m1.n22*m1.n31*m1.n43 - m1.n21*m1.n32*m1.n43;
+        m2.n42 = m1.n12*m1.n33*m1.n41 - m1.n13*m1.n32*m1.n41 + m1.n13*m1.n31*m1.n42 - m1.n11*m1.n33*m1.n42 - m1.n12*m1.n31*m1.n43 + m1.n11*m1.n32*m1.n43;
+        m2.n43 = m1.n13*m1.n22*m1.n41 - m1.n12*m1.n23*m1.n41 - m1.n13*m1.n21*m1.n42 + m1.n11*m1.n23*m1.n42 + m1.n12*m1.n21*m1.n43 - m1.n11*m1.n22*m1.n43;
+        m2.n44 = m1.n12*m1.n23*m1.n31 - m1.n13*m1.n22*m1.n31 + m1.n13*m1.n21*m1.n32 - m1.n11*m1.n23*m1.n32 - m1.n12*m1.n21*m1.n33 + m1.n11*m1.n22*m1.n33;
+        m2.multiplyScalar(1.0 / m1.det());
+        return m2;
+    
+    @classmethod
+    def rotationMatrix(cls, x, y, z, angle):
+        rot = Matrix4()
+        c = math.cos(angle)
+        s = math.sin(angle)
+        t = 1 - c
+        rot.n11 = t * x * x + c
+        rot.n12 = t * x * y - s * z
+        rot.n13 = t * x * z + s * y
+        rot.n21 = t * x * y + s * z
+        rot.n22 = t * y * y + c
+        rot.n23 = t * y * z - s * x
+        rot.n31 = t * x * z - s * y
+        rot.n32 = t * y * z + s * x
+        rot.n33 = t * z * z + c
+        return rot
+    
+    @classmethod
+    def scaleMatrix(cls, x, y, z):
+        m = Matrix4()
+        m.n11 = x
+        m.n22 = y
+        m.n33 = z
+        return m
+    
+    @classmethod
+    def translationMatrix(cls, x, y, z):
+        m = Matrix4()
+        m.n14 = x
+        m.n24 = y
+        m.n34 = z
+        return m

+ 171 - 0
format_convert/swf/movie.py

@@ -0,0 +1,171 @@
+"""
+SWF
+"""
+from .tag import SWFTimelineContainer
+from .stream import SWFStream
+from .export import SVGExporter
+try:
+    import cStringIO as StringIO
+except ImportError:
+    from io import BytesIO
+
+class SWFHeaderException(Exception):
+    """ Exception raised in case of an invalid SWFHeader """
+    def __init__(self, message):
+         super(SWFHeaderException, self).__init__(message)
+
+class SWFHeader(object):
+    """ SWF header """
+    def __init__(self, stream):
+        a = stream.readUI8()
+        b = stream.readUI8()
+        c = stream.readUI8()
+        if not a in [0x43, 0x46, 0x5A] or b != 0x57 or c != 0x53:
+            # Invalid signature! ('FWS' or 'CWS' or 'ZFS')
+            raise SWFHeaderException("not a SWF file! (invalid signature)")
+
+        self._compressed_zlib = (a == 0x43)
+        self._compressed_lzma = (a == 0x5A)
+        self._version = stream.readUI8()
+        self._file_length = stream.readUI32()
+        if not (self._compressed_zlib or self._compressed_lzma):
+            self._frame_size = stream.readRECT()
+            self._frame_rate = stream.readFIXED8()
+            self._frame_count = stream.readUI16()
+
+    @property
+    def frame_size(self):
+        """ Return frame size as a SWFRectangle """
+        return self._frame_size
+
+    @property
+    def frame_rate(self):
+        """ Return frame rate """
+        return self._frame_rate
+
+    @property
+    def frame_count(self):
+        """ Return number of frames """
+        return self._frame_count
+                
+    @property
+    def file_length(self):
+        """ Return uncompressed file length """
+        return self._file_length
+                    
+    @property
+    def version(self):
+        """ Return SWF version """
+        return self._version
+                
+    @property
+    def compressed(self):
+        """ Whether the SWF is compressed """
+        return self._compressed_zlib or self._compressed_lzma
+
+    @property
+    def compressed_zlib(self):
+        """ Whether the SWF is compressed using ZLIB """
+        return self._compressed_zlib
+
+    @property
+    def compressed_lzma(self):
+        """ Whether the SWF is compressed using LZMA """
+        return self._compressed_lzma
+        
+    def __str__(self):
+        return "   [SWFHeader]\n" + \
+            "       Version: %d\n" % self.version + \
+            "       FileLength: %d\n" % self.file_length + \
+            "       FrameSize: %s\n" % self.frame_size.__str__() + \
+            "       FrameRate: %d\n" % self.frame_rate + \
+            "       FrameCount: %d\n" % self.frame_count
+
+class SWF(SWFTimelineContainer):
+    """
+    SWF class
+    
+    The SWF (pronounced 'swiff') file format delivers vector graphics, text, 
+    video, and sound over the Internet and is supported by Adobe Flash
+    Player software. The SWF file format is designed to be an efficient 
+    delivery format, not a format for exchanging graphics between graphics 
+    editors.
+    
+    @param file: a file object with read(), seek(), tell() methods.
+    """
+    def __init__(self, file=None):
+        super(SWF, self).__init__()
+        self._data = None if file is None else SWFStream(file)
+        self._header = None
+        if self._data is not None:
+            self.parse(self._data)
+    
+    @property
+    def data(self):
+        """
+        Return the SWFStream object (READ ONLY)
+        """
+        return self._data
+    
+    @property
+    def header(self):
+        """ Return the SWFHeader """
+        return self._header
+        
+    def export(self, exporter=None, force_stroke=False):
+        """
+        Export this SWF using the specified exporter. 
+        When no exporter is passed in the default exporter used 
+        is swf.export.SVGExporter.
+        
+        Exporters should extend the swf.export.BaseExporter class.
+        
+        @param exporter : the exporter to use
+        @param force_stroke : set to true to force strokes on fills,
+                              useful for some edge cases.
+        """
+        exporter = SVGExporter() if exporter is None else exporter
+        if self._data is None:
+            raise Exception("This SWF was not loaded! (no data)")
+        if len(self.tags) == 0:
+            raise Exception("This SWF doesn't contain any tags!")
+        return exporter.export(self, force_stroke)
+            
+    def parse_file(self, filename):
+        """ Parses the SWF from a filename """
+        self.parse(open(filename, 'rb'))
+        
+    def parse(self, data):
+        """ 
+        Parses the SWF.
+        
+        The @data parameter can be a file object or a SWFStream
+        """
+        self._data = data = data if isinstance(data, SWFStream) else SWFStream(data)
+        self._header = SWFHeader(self._data)
+        if self._header.compressed:
+            temp = BytesIO()
+            if self._header.compressed_zlib:
+                import zlib
+                data = data.f.read()
+                zip = zlib.decompressobj()
+                temp.write(zip.decompress(data))
+            else:
+                import pylzma
+                data.readUI32() #consume compressed length
+                data = data.f.read()
+                temp.write(pylzma.decompress(data))
+            temp.seek(0)
+            data = SWFStream(temp)
+        self._header._frame_size = data.readRECT()
+        self._header._frame_rate = data.readFIXED8()
+        self._header._frame_count = data.readUI16()
+        self.parse_tags(data)
+        
+    def __str__(self):
+        s = "[SWF]\n"
+        s += self._header.__str__()
+        for tag in self.tags:
+            s += tag.__str__() + "\n"
+        return s
+        

+ 81 - 0
format_convert/swf/sound.py

@@ -0,0 +1,81 @@
+import consts
+import tag
+import wave
+import stream
+
+supportedCodecs = (
+    consts.AudioCodec.MP3,
+    consts.AudioCodec.UncompressedNativeEndian,
+    consts.AudioCodec.UncompressedLittleEndian,
+)
+
+uncompressed = (
+    consts.AudioCodec.UncompressedNativeEndian,
+    consts.AudioCodec.UncompressedLittleEndian,
+)
+
+REASON_OK = None
+REASON_EMPTY = 'stream is empty'
+
+def get_header(stream_or_tag):
+    if isinstance(stream_or_tag, list):
+        assert len(stream_or_tag) > 0, 'empty stream'
+        return stream_or_tag[0]
+    else:
+        assert isinstance(stream_or_tag, tag.TagDefineSound), 'sound is not a stream or DefineSound tag'
+        return stream_or_tag
+
+def reason_unsupported(stream_or_tag):
+    header = get_header(stream_or_tag)
+    is_stream = isinstance(stream_or_tag, list)
+    
+    if header.soundFormat not in supportedCodecs:
+        return 'codec %s (%d) not supported' % (consts.AudioCodec.tostring(header.soundFormat),
+                                                header.soundFormat)
+    
+    if is_stream and len(stream_or_tag) == 1:
+        return REASON_EMPTY
+    
+    return REASON_OK
+        
+def supported(stream_or_tag):
+    return reason_unsupported(stream_or_tag) is None
+    
+def junk(stream_or_tag):
+    return reason_unsupported(stream_or_tag) == REASON_EMPTY
+
+def get_wave_for_header(header, output):
+    w = wave.open(output, 'w')
+    w.setframerate(consts.AudioSampleRate.Rates[header.soundRate])
+    w.setnchannels(consts.AudioChannels.Channels[header.soundChannels])
+    w.setsampwidth(consts.AudioSampleSize.Bits[header.soundSampleSize] / 8)
+    return w
+    
+def write_stream_to_file(stream, output):
+    header = get_header(stream)
+    
+    w = None
+    if header.soundFormat in uncompressed:
+        w = get_wave_for_header(header, output)
+    
+    for block in stream[1:]:
+        block.complete_parse_with_header(header)
+        
+        if header.soundFormat == consts.AudioCodec.MP3:
+            output.write(block.mpegFrames)
+        else:
+            w.writeframes(block.data.read())
+    
+    if w:
+        w.close()
+
+def write_sound_to_file(st, output):
+    assert isinstance(st, tag.TagDefineSound)
+    if st.soundFormat == consts.AudioCodec.MP3:
+        swfs = stream.SWFStream(st.soundData)
+        seekSamples = swfs.readSI16()
+        output.write(swfs.read())
+    elif st.soundFormat in uncompressed:
+        w = get_wave_for_header(st, output)
+        w.writeframes(st.soundData.read())
+        w.close()

+ 2655 - 0
format_convert/swf/tag.py

@@ -0,0 +1,2655 @@
+from .consts import *
+from .data import *
+from .utils import *
+from .stream import *
+try:
+    import Image
+except ImportError:
+    from PIL import Image
+import struct
+
+try:
+    import cStringIO as StringIO
+except ImportError:
+    from io import BytesIO
+
+class TagFactory(object):
+    @classmethod
+    def create(cls, type):
+        """ Return the created tag by specifying an integer """
+        if type == 0: return TagEnd()
+        elif type == 1: return TagShowFrame()
+        elif type == 2: return TagDefineShape()
+        elif type == 4: return TagPlaceObject()
+        elif type == 5: return TagRemoveObject()
+        elif type == 6: return TagDefineBits()
+        elif type == 7: return TagDefineButton()
+        elif type == 8: return TagJPEGTables()
+        elif type == 9: return TagSetBackgroundColor()
+        elif type == 10: return TagDefineFont()
+        elif type == 11: return TagDefineText()
+        elif type == 12: return TagDoAction()
+        elif type == 13: return TagDefineFontInfo()
+        elif type == 14: return TagDefineSound()
+        elif type == 15: return TagStartSound()
+        elif type == 17: return TagDefineButtonSound()
+        elif type == 18: return TagSoundStreamHead()
+        elif type == 19: return TagSoundStreamBlock()
+        elif type == 20: return TagDefineBitsLossless()
+        elif type == 21: return TagDefineBitsJPEG2()
+        elif type == 22: return TagDefineShape2()
+        elif type == 24: return TagProtect()
+        elif type == 26: return TagPlaceObject2()
+        elif type == 28: return TagRemoveObject2()
+        elif type == 32: return TagDefineShape3()
+        elif type == 33: return TagDefineText2()
+        elif type == 34: return TagDefineButton2()
+        elif type == 35: return TagDefineBitsJPEG3()
+        elif type == 36: return TagDefineBitsLossless2()
+        elif type == 37: return TagDefineEditText()
+        elif type == 39: return TagDefineSprite()
+        elif type == 41: return TagProductInfo()
+        elif type == 43: return TagFrameLabel()
+        elif type == 45: return TagSoundStreamHead2()
+        elif type == 46: return TagDefineMorphShape()
+        elif type == 48: return TagDefineFont2()
+        elif type == 56: return TagExportAssets()
+        elif type == 58: return TagEnableDebugger()
+        elif type == 59: return TagDoInitAction()
+        elif type == 60: return TagDefineVideoStream()
+        elif type == 61: return TagVideoFrame()
+        elif type == 63: return TagDebugID()
+        elif type == 64: return TagEnableDebugger2()
+        elif type == 65: return TagScriptLimits()
+        elif type == 69: return TagFileAttributes()
+        elif type == 70: return TagPlaceObject3()
+        elif type == 73: return TagDefineFontAlignZones()
+        elif type == 74: return TagCSMTextSettings()
+        elif type == 75: return TagDefineFont3()
+        elif type == 76: return TagSymbolClass()
+        elif type == 77: return TagMetadata()
+        elif type == 78: return TagDefineScalingGrid()
+        elif type == 82: return TagDoABC()
+        elif type == 83: return TagDefineShape4()
+        elif type == 84: return TagDefineMorphShape2()
+        elif type == 86: return TagDefineSceneAndFrameLabelData()
+        elif type == 87: return TagDefineBinaryData()
+        elif type == 88: return TagDefineFontName()
+        elif type == 89: return TagStartSound2()
+        else: return None
+
+class Tag(object):
+    def __init__(self):
+        pass
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    @property
+    def name(self):
+        """ The tag name """
+        return ""
+
+    def parse(self, data, length, version=1):
+        """ Parses this tag """
+        pass
+
+    def get_dependencies(self):
+        """ Returns the character ids this tag refers to """
+        return set()
+
+    def __str__(self):
+        return "[%02d:%s]" % (self.type, self.name)
+
+class DefinitionTag(Tag):
+
+    def __init__(self):
+        super(DefinitionTag, self).__init__()
+        self._characterId = -1
+
+    @property
+    def characterId(self):
+        """ Return the character ID """
+        return self._characterId
+
+    @characterId.setter
+    def characterId(self, value):
+        """ Sets the character ID """
+        self._characterId = value
+
+    def parse(self, data, length, version=1):
+        pass
+
+    def get_dependencies(self):
+        s = super(DefinitionTag, self).get_dependencies()
+        s.add(self.characterId)
+        return s
+
+class DisplayListTag(Tag):
+    characterId = -1
+    def __init__(self):
+        super(DisplayListTag, self).__init__()
+
+    def parse(self, data, length, version=1):
+        pass
+
+    def get_dependencies(self):
+        s = super(DisplayListTag, self).get_dependencies()
+        s.add(self.characterId)
+        return s
+
+class SWFTimelineContainer(DefinitionTag):
+    def __init__(self):
+        self.tags = []
+        super(SWFTimelineContainer, self).__init__()
+
+    def get_dependencies(self):
+        """ Returns the character ids this tag refers to """
+        s = super(SWFTimelineContainer, self).get_dependencies()
+        for dt in self.all_tags_of_type(DefinitionTag):
+            s.update(dt.get_dependencies())
+        return s
+
+    def parse_tags(self, data, version=1):
+        pos = data.tell()
+        self.file_length = self._get_file_length(data, pos)
+        tag = None
+        while type(tag) != TagEnd:
+            tag = self.parse_tag(data)
+            if tag:
+                #print tag.name
+                self.tags.append(tag)
+
+    def parse_tag(self, data):
+        pos = data.tell()
+        eof = (pos > self.file_length)
+        if eof:
+            #print "WARNING: end of file encountered, no end tag."
+            return TagEnd()
+        raw_tag = data.readraw_tag()
+        tag_type = raw_tag.header.type
+        tag = TagFactory.create(tag_type)
+        if tag is not None:
+            #print tag.name
+            data.seek(raw_tag.pos_content)
+            data.reset_bits_pending()
+            tag.parse(data, raw_tag.header.content_length, tag.version)
+            #except:
+            #    print "=> tag_error", tag.name
+            data.seek(pos + raw_tag.header.tag_length)
+        else:
+            #print "[WARNING] unhandled tag %s" % (hex(tag_type))
+            data.skip_bytes(raw_tag.header.tag_length)
+        data.seek(pos + raw_tag.header.tag_length)
+        return tag
+
+    def _get_file_length(self, data, pos):
+        data.f.seek(0, 2)
+        length = data.tell()
+        data.f.seek(pos)
+        return length
+
+    def all_tags_of_type(self, type_or_types, recurse_into_sprites = True):
+        """
+        Generator for all tags of the given type_or_types.
+
+        Generates in breadth-first order, optionally including all sub-containers.
+        """
+        for t in self.tags:
+            if isinstance(t, type_or_types):
+                yield t
+        if recurse_into_sprites:
+            for t in self.tags:
+                # recurse into nested sprites
+                if isinstance(t, SWFTimelineContainer):
+                    for containedtag in t.all_tags_of_type(type_or_types):
+                        yield containedtag
+
+    def build_dictionary(self):
+        """
+        Return a dictionary of characterIds to their defining tags.
+        """
+        d = {}
+        for t in self.all_tags_of_type(DefinitionTag, recurse_into_sprites = False):
+            if t.characterId in d:
+                #print 'redefinition of characterId %d:' % (t.characterId)
+                #print '  was:', d[t.characterId]
+                #print 'redef:', t
+                raise ValueError('illegal redefinition of character')
+            d[t.characterId] = t
+        return d
+
+    def collect_sound_streams(self):
+        """
+        Return a list of sound streams in this timeline and its children.
+        The streams are returned in order with respect to the timeline.
+
+        A stream is returned as a list: the first element is the tag
+        which introduced that stream; other elements are the tags
+        which made up the stream body (if any).
+        """
+        rc = []
+        current_stream = None
+        # looking in all containers for frames
+        for tag in self.all_tags_of_type((TagSoundStreamHead, TagSoundStreamBlock)):
+            if isinstance(tag, TagSoundStreamHead):
+                # we have a new stream
+                current_stream = [ tag ]
+                rc.append(current_stream)
+            if isinstance(tag, TagSoundStreamBlock):
+                # we have a frame for the current stream
+                current_stream.append(tag)
+        return rc
+
+    def collect_video_streams(self):
+        """
+        Return a list of video streams in this timeline and its children.
+        The streams are returned in order with respect to the timeline.
+
+        A stream is returned as a list: the first element is the tag
+        which introduced that stream; other elements are the tags
+        which made up the stream body (if any).
+        """
+        rc = []
+        streams_by_id = {}
+
+        # scan first for all streams
+        for t in self.all_tags_of_type(TagDefineVideoStream):
+            stream = [ t ]
+            streams_by_id[t.characterId] = stream
+            rc.append(stream)
+
+        # then find the frames
+        for t in self.all_tags_of_type(TagVideoFrame):
+            # we have a frame for the /named/ stream
+            assert t.streamId in streams_by_id
+            streams_by_id[t.streamId].append(t)
+
+        return rc
+
+class TagEnd(Tag):
+    """
+    The End tag marks the end of a file. This must always be the last tag in a file.
+    The End tag is also required to end a sprite definition.
+    The minimum file format version is SWF 1.
+    """
+    TYPE = 0
+    def __init__(self):
+        super(TagEnd, self).__init__()
+
+    @property
+    def name(self):
+        """ The tag name """
+        return "End"
+
+    @property
+    def type(self):
+        return TagEnd.TYPE
+
+    def __str__(self):
+        return "[%02d:%s]" % (self.type, self.name)
+
+class TagShowFrame(Tag):
+    """
+    The ShowFrame tag instructs Flash Player to display the contents of the
+    display list. The file is paused for the duration of a single frame.
+    The minimum file format version is SWF 1.
+    """
+    TYPE = 1
+    def __init__(self):
+        super(TagShowFrame, self).__init__()
+
+    @property
+    def name(self):
+        return "ShowFrame"
+
+    @property
+    def type(self):
+        return TagShowFrame.TYPE
+
+    def __str__(self):
+        return "[%02d:%s]" % (self.type, self.name)
+
+class TagDefineShape(DefinitionTag):
+    """
+    The DefineShape tag defines a shape for later use by control tags such as
+    PlaceObject. The ShapeId uniquely identifies this shape as 'character' in
+    the Dictionary. The ShapeBounds field is the rectangle that completely
+    encloses the shape. The SHAPEWITHSTYLE structure includes all the paths,
+    fill styles and line styles that make up the shape.
+    The minimum file format version is SWF 1.
+    """
+    TYPE = 2
+
+    def __init__(self):
+        self._shapes = None
+        self._shape_bounds = None
+        super(TagDefineShape, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineShape"
+
+    @property
+    def type(self):
+        return TagDefineShape.TYPE
+
+    @property
+    def shapes(self):
+        """ Return SWFShape """
+        return self._shapes
+
+    @property
+    def shape_bounds(self):
+        """ Return the bounds of this tag as a SWFRectangle """
+        return self._shape_bounds
+
+    def export(self, handler=None):
+        """ Export this tag """
+        return self.shapes.export(handler)
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self._shape_bounds = data.readRECT()
+        self._shapes = data.readSHAPEWITHSTYLE(self.level)
+
+    def get_dependencies(self):
+        s = super(TagDefineShape, self).get_dependencies()
+        s.update(self.shapes.get_dependencies())
+        return s
+
+    def __str__(self):
+        s = super(TagDefineShape, self).__str__( ) + " " + \
+            "ID: %d" % self.characterId + ", " + \
+            "Bounds: " + self._shape_bounds.__str__()
+        #s += "\n%s" % self._shapes.__str__()
+        return s
+
+class TagPlaceObject(DisplayListTag):
+    """
+    The PlaceObject tag adds a character to the display list. The CharacterId
+    identifies the character to be added. The Depth field specifies the
+    stacking order of the character. The Matrix field species the position,
+    scale, and rotation of the character. If the size of the PlaceObject tag
+    exceeds the end of the transformation matrix, it is assumed that a
+    ColorTransform field is appended to the record. The ColorTransform field
+    specifies a color effect (such as transparency) that is applied to the character.
+    The same character can be added more than once to the display list with
+    a different depth and transformation matrix.
+    """
+    TYPE = 4
+    hasClipActions = False
+    hasClipDepth = False
+    hasName = False
+    hasRatio = False
+    hasColorTransform = False
+    hasMatrix = False
+    hasCharacter = False
+    hasMove = False
+    hasImage = False
+    hasClassName = False
+    hasCacheAsBitmap = False
+    hasBlendMode = False
+    hasFilterList = False
+    depth = 0
+    matrix = None
+    colorTransform = None
+    # Forward declarations for TagPlaceObject2
+    ratio = 0
+    instanceName = None
+    clipDepth = 0
+    clipActions = None
+    # Forward declarations for TagPlaceObject3
+    className = None
+    blendMode = 0
+    bitmapCache = 0
+
+    def __init__(self):
+        self._surfaceFilterList = []
+        super(TagPlaceObject, self).__init__()
+
+    def parse(self, data, length, version=1):
+        """ Parses this tag """
+        pos = data.tell()
+        self.characterId = data.readUI16()
+        self.depth = data.readUI16();
+        self.matrix = data.readMATRIX();
+        self.hasCharacter = True;
+        self.hasMatrix = True;
+        if data.tell() - pos < length:
+            colorTransform = data.readCXFORM()
+            self.hasColorTransform = True
+
+    def get_dependencies(self):
+        s = super(TagPlaceObject, self).get_dependencies()
+        if self.hasCharacter:
+            s.add(self.characterId)
+        return s
+
+    @property
+    def filters(self):
+        """ Returns a list of filter """
+        return self._surfaceFilterList
+
+    @property
+    def name(self):
+        return "PlaceObject"
+
+    @property
+    def type(self):
+        return TagPlaceObject.TYPE
+
+    def __str__(self):
+        s = super(TagPlaceObject, self).__str__() + " " + \
+            "Depth: %d, " % self.depth + \
+            "CharacterID: %d" % self.characterId
+        if self.hasName:
+            s+= ", InstanceName: %s" % self.instanceName
+        if self.hasMatrix:
+            s += ", Matrix: %s" % self.matrix.__str__()
+        if self.hasClipDepth:
+            s += ", ClipDepth: %d" % self.clipDepth
+        if self.hasColorTransform:
+            s += ", ColorTransform: %s" % self.colorTransform.__str__()
+        if self.hasFilterList:
+            s += ", Filters: %d" % len(self.filters)
+        if self.hasBlendMode:
+            s += ", Blendmode: %d" % self.blendMode
+        return s
+
+class TagRemoveObject(DisplayListTag):
+    """
+    The RemoveObject tag removes the specified character (at the specified depth)
+    from the display list.
+    The minimum file format version is SWF 1.
+    """
+    TYPE = 5
+    depth = 0
+    def __init__(self):
+        super(TagRemoveObject, self).__init__()
+
+    @property
+    def name(self):
+        return "RemoveObject"
+
+    @property
+    def type(self):
+        return TagRemoveObject.TYPE
+
+    def parse(self, data, length, version=1):
+        """ Parses this tag """
+        self.characterId = data.readUI16()
+        self.depth = data.readUI16()
+
+class TagDefineBits(DefinitionTag):
+    """
+    This tag defines a bitmap character with JPEG compression. It contains only
+    the JPEG compressed image data (from the Frame Header onward). A separate
+    JPEGTables tag contains the JPEG encoding data used to encode this image
+    (the Tables/Misc segment).
+    NOTE:
+        Only one JPEGTables tag is allowed in a SWF file, and thus all bitmaps
+        defined with DefineBits must share common encoding tables.
+    The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
+    with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
+    SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8 before
+    the JPEG SOI marker.
+    """
+    TYPE = 6
+    bitmapData = None
+    def __init__(self):
+        self.bitmapData = BytesIO()
+        self.bitmapType = BitmapType.JPEG
+        super(TagDefineBits, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineBits"
+
+    @property
+    def type(self):
+        return TagDefineBits.TYPE
+
+    def parse(self, data, length, version=1):
+        self.bitmapData = BytesIO()
+        self.characterId = data.readUI16()
+        if length > 2:
+            self.bitmapData.write(data.f.read(length - 2))
+            self.bitmapData.seek(0)
+
+class TagJPEGTables(DefinitionTag):
+    """
+    This tag defines the JPEG encoding table (the Tables/Misc segment) for all
+    JPEG images defined using the DefineBits tag. There may only be one
+    JPEGTables tag in a SWF file.
+    The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
+    with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
+    SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8 before
+    the JPEG SOI marker.
+    The minimum file format version for this tag is SWF 1.
+    """
+    TYPE = 8
+    jpegTables = None
+    length = 0
+
+    def __init__(self):
+        super(TagJPEGTables, self).__init__()
+        self.jpegTables = BytesIO()
+
+    @property
+    def name(self):
+        return "JPEGTables"
+
+    @property
+    def type(self):
+        return TagJPEGTables.TYPE
+
+    def parse(self, data, length, version=1):
+        self.length = length
+        if length > 0:
+            self.jpegTables.write(data.f.read(length))
+            self.jpegTables.seek(0)
+
+    def __str__(self):
+        s = super(TagJPEGTables, self).__str__()
+        s += " Length: %d" % self.length
+        return s
+
+class TagSetBackgroundColor(Tag):
+    """
+    The SetBackgroundColor tag sets the background color of the display.
+    The minimum file format version is SWF 1.
+    """
+    TYPE = 9
+    color = 0
+    def __init__(self):
+        super(TagSetBackgroundColor, self).__init__()
+
+    def parse(self, data, length, version=1):
+        self.color = data.readRGB()
+
+    @property
+    def name(self):
+        return "SetBackgroundColor"
+
+    @property
+    def type(self):
+        return TagSetBackgroundColor.TYPE
+
+    def __str__(self):
+        s = super(TagSetBackgroundColor, self).__str__()
+        s += " Color: " + ColorUtils.to_rgb_string(self.color)
+        return s
+
+class TagDefineFont(DefinitionTag):
+    """
+    The DefineFont tag defines the shape outlines of each glyph used in a
+    particular font. Only the glyphs that are used by subsequent DefineText
+    tags are actually defined.
+    DefineFont tags cannot be used for dynamic text. Dynamic text requires
+    the DefineFont2 tag.
+    The minimum file format version is SWF 1.
+    """
+    TYPE= 10
+    offsetTable = []
+    glyphShapeTable = []
+    def __init__(self):
+        super(TagDefineFont, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineFont"
+
+    @property
+    def type(self):
+        return TagDefineFont.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    @property
+    def unitDivisor(self):
+        return 1
+
+    def parse(self, data, length, version=1):
+        self.glyphShapeTable = []
+        self.offsetTable = []
+        self.characterId = data.readUI16()
+
+        # Because the glyph shape table immediately follows the offset table,
+        # the number of entries in each table (the number of glyphs in the
+        # font) can be inferred by dividing the first entry in the offset
+        # table by two.
+        self.offsetTable.append(data.readUI16())
+        numGlyphs = self.offsetTable[0] / 2
+
+        for i in range(1, numGlyphs):
+            self.offsetTable.append(data.readUI16())
+
+        for i in range(numGlyphs):
+            self.glyphShapeTable.append(data.readSHAPE(self.unitDivisor))
+
+class TagDefineText(DefinitionTag):
+    """
+    The DefineText tag defines a block of static text. It describes the font,
+    size, color, and exact position of every character in the text object.
+    The minimum file format version is SWF 1.
+    """
+    TYPE = 11
+    textBounds = None
+    textMatrix = None
+
+    def __init__(self):
+        self._records = []
+        super(TagDefineText, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineText"
+
+    @property
+    def type(self):
+        return TagDefineText.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    def get_dependencies(self):
+        s = super(TagDefineText, self).get_dependencies()
+        for r in self.records:
+            s.update(r.get_dependencies())
+        return s
+
+    @property
+    def records(self):
+        """ Return list of SWFTextRecord """
+        return self._records
+
+    def parse(self, data, length, version=1):
+        self._records = []
+        self.characterId = data.readUI16()
+        self.textBounds = data.readRECT()
+        self.textMatrix = data.readMATRIX()
+        glyphBits = data.readUI8()
+        advanceBits = data.readUI8()
+        record = None
+        record = data.readTEXTRECORD(glyphBits, advanceBits, record, self.level)
+        while not record is None:
+            self._records.append(record)
+            record = data.readTEXTRECORD(glyphBits, advanceBits, record, self.level)
+
+class TagDoAction(Tag):
+    """
+    DoAction instructs Flash Player to perform a list of actions when the
+    current frame is complete. The actions are performed when the ShowFrame
+    tag is encountered, regardless of where in the frame the DoAction tag appears.
+    Starting with SWF 9, if the ActionScript3 field of the FileAttributes tag is 1,
+    the contents of the DoAction tag will be ignored.
+    """
+    TYPE = 12
+    def __init__(self):
+        self._actions = []
+        super(TagDoAction, self).__init__()
+
+    @property
+    def name(self):
+        return "DoAction"
+
+    @property
+    def type(self):
+        """ Return the SWF tag type """
+        return TagDoAction.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        """ Return the minimum SWF version """
+        return 3
+
+    @property
+    def actions(self):
+        """ Return list of SWFActionRecord """
+        return self._actions
+
+    def parse(self, data, length, version=1):
+        self._actions = data.readACTIONRECORDs()
+
+class TagDefineFontInfo(Tag):
+    """
+    The DefineFontInfo tag defines a mapping from a glyph font (defined with DefineFont) to a
+    device font. It provides a font name and style to pass to the playback platform's text engine,
+    and a table of character codes that identifies the character represented by each glyph in the
+    corresponding DefineFont tag, allowing the glyph indices of a DefineText tag to be converted
+    to character strings.
+    The presence of a DefineFontInfo tag does not force a glyph font to become a device font; it
+    merely makes the option available. The actual choice between glyph and device usage is made
+    according to the value of devicefont (see the introduction) or the value of UseOutlines in a
+    DefineEditText tag. If a device font is unavailable on a playback platform, Flash Player will
+    fall back to glyph text.
+    """
+    TYPE = 13
+    def __init__(self):
+        super(TagDefineFontInfo, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineFontInfo"
+
+    @property
+    def type(self):
+        return TagDefineFontInfo.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    @property
+    def unitDivisor(self):
+        return 1
+
+    def get_dependencies(self):
+        s = super(TagDefineFontInfo, self).get_dependencies()
+        s.add(self.characterId)
+        return s
+
+    def parse(self, data, length, version=1):
+        self.codeTable = []
+
+        # FontID
+        self.characterId = data.readUI16()
+
+        fontNameLen = data.readUI8()
+
+        self.fontName = ""
+        self.useGlyphText = False
+
+        # Read in font name, one character at a time. If any of the
+        # characters are non-ASCII, assume that glyph text should be
+        # used rather than device text.
+        for i in range(fontNameLen):
+            ord = data.readUI8()
+
+            if ord in range(128):
+                self.fontName += chr(ord)
+            else:
+                self.useGlyphText = True
+
+        if self.useGlyphText:
+            self.fontName = "Font_{0}".format(self.characterId)
+
+        flags = data.readUI8()
+
+        self.smallText = ((flags & 0x20) != 0)
+        self.shiftJIS = ((flags & 0x10) != 0)
+        self.ansi  = ((flags & 0x08) != 0)
+        self.italic = ((flags & 0x04) != 0)
+        self.bold = ((flags & 0x02) != 0)
+        self.wideCodes = ((flags & 0x01) != 0)
+
+        if self.wideCodes:
+            numGlyphs = (length - 2 - 1 - fontNameLen - 1) / 2
+        else:
+            numGlyphs = length - 2 - 1 - fontNameLen - 1
+
+        for i in range(0, numGlyphs):
+            self.codeTable.append(data.readUI16() if self.wideCodes else data.readUI8())
+
+class TagDefineBitsLossless(DefinitionTag):
+    """
+    Defines a lossless bitmap character that contains RGB bitmap data compressed
+    with ZLIB. The data format used by the ZLIB library is described by
+    Request for Comments (RFCs) documents 1950 to 1952.
+    Two kinds of bitmaps are supported. Colormapped images define a colormap of
+    up to 256 colors, each represented by a 24-bit RGB value, and then use
+    8-bit pixel values to index into the colormap. Direct images store actual
+    pixel color values using 15 bits (32,768 colors) or 24 bits (about 17 million colors).
+    The minimum file format version for this tag is SWF 2.
+    """
+    TYPE = 20
+    bitmapData = None
+    image_buffer = ""
+    bitmap_format = 0
+    bitmap_width = 0
+    bitmap_height = 0
+    bitmap_color_size = 0
+    zlib_bitmap_data = None
+    padded_width = 0
+    def __init__(self):
+        super(TagDefineBitsLossless, self).__init__()
+
+    def parse(self, data, length, version=1):
+        import zlib
+        self.image_buffer = ""
+        self.characterId = data.readUI16()
+        self.bitmap_format = data.readUI8()
+        self.bitmap_width = data.readUI16()
+        self.bitmap_height = data.readUI16()
+        if self.bitmap_format == BitmapFormat.BIT_8:
+            self.bitmap_color_size = data.readUI8()
+            self.zlib_bitmap_data = data.f.read(length-8)
+        else:
+            self.zlib_bitmap_data = data.f.read(length-7)
+
+        # decompress zlib encoded bytes
+        compressed_length = len(self.zlib_bitmap_data)
+        zip = zlib.decompressobj()
+        temp = BytesIO()
+        temp.write(zip.decompress(self.zlib_bitmap_data))
+        temp.seek(0, 2)
+        uncompressed_length = temp.tell()
+        temp.seek(0)
+
+        # padding : should be aligned to 32 bit boundary
+        self.padded_width = self.bitmap_width
+        while self.padded_width % 4 != 0:
+            self.padded_width += 1
+        t = self.padded_width * self.bitmap_height
+
+        is_lossless2 = (type(self) == TagDefineBitsLossless2)
+        im = None
+        self.bitmapData = BytesIO()
+
+        indexed_colors = []
+        if self.bitmap_format == BitmapFormat.BIT_8:
+            for i in range(0, self.bitmap_color_size + 1):
+                r = ord(temp.read(1))
+                g = ord(temp.read(1))
+                b = ord(temp.read(1))
+                a = ord(temp.read(1)) if is_lossless2 else 0xff
+                indexed_colors.append(struct.pack("BBBB", r, g, b, a))
+
+            # create the image buffer
+            s = BytesIO()
+            for i in range(t):
+                a = ord(temp.read(1))
+                s.write(indexed_colors[a%len(indexed_colors)])
+            self.image_buffer = s.getvalue()
+            s.close()
+
+            im = Image.frombytes("RGBA", (self.padded_width, self.bitmap_height), self.image_buffer)
+            im = im.crop((0, 0, self.bitmap_width, self.bitmap_height))
+
+        elif self.bitmap_format == BitmapFormat.BIT_15:
+            raise Exception("DefineBitsLossless: BIT_15 not yet implemented")
+        elif self.bitmap_format == BitmapFormat.BIT_24:
+            # we have no padding, since PIX24s are 32-bit aligned
+            t = self.bitmap_width * self.bitmap_height
+            # read PIX24's
+            s = BytesIO()
+            for i in range(0, t):
+                if not is_lossless2:
+                    temp.read(1) # reserved, always 0
+                a = ord(temp.read(1)) if is_lossless2 else 0xff
+                r = ord(temp.read(1))
+                g = ord(temp.read(1))
+                b = ord(temp.read(1))
+                s.write(struct.pack("BBBB", r, g, b, a))
+            self.image_buffer = s.getvalue()
+            im = Image.frombytes("RGBA", (self.bitmap_width, self.bitmap_height), self.image_buffer)
+        else:
+            raise Exception("unhandled bitmap format! %s %d" % (BitmapFormat.tostring(self.bitmap_format), self.bitmap_format))
+
+        if not im is None:
+            im.save(self.bitmapData, "PNG")
+            self.bitmapData.seek(0)
+            self.bitmapType = ImageUtils.get_image_type(self.bitmapData)
+
+    @property
+    def name(self):
+        return "DefineBitsLossless"
+
+    @property
+    def type(self):
+        return TagDefineBitsLossless.TYPE
+
+class TagDefineBitsJPEG2(TagDefineBits):
+    """
+    This tag defines a bitmap character with JPEG compression. It differs from
+    DefineBits in that it contains both the JPEG encoding table and the JPEG
+    image data. This tag allows multiple JPEG images with differing encoding
+    tables to be defined within a single SWF file.
+    The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
+    with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
+    SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8
+    before the JPEG SOI marker.
+    In addition to specifying JPEG data, DefineBitsJPEG2 can also contain PNG
+    image data and non-animated GIF89a image data.
+
+    - If ImageData begins with the eight bytes 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A,
+      the ImageData contains PNG data.
+    - If ImageData begins with the six bytes 0x47 0x49 0x46 0x38 0x39 0x61, the ImageData
+      contains GIF89a data.
+
+    The minimum file format version for this tag is SWF 2. The minimum file format
+    version for embedding PNG of GIF89a data is SWF 8.
+    """
+    TYPE = 21
+    bitmapType = 0
+
+    def __init__(self):
+        super(TagDefineBitsJPEG2, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineBitsJPEG2"
+
+    @property
+    def type(self):
+        return TagDefineBitsJPEG2.TYPE
+
+    @property
+    def version(self):
+        return 2 if self.bitmapType == BitmapType.JPEG else 8
+
+    @property
+    def level(self):
+        return 2
+
+    def parse(self, data, length, version=1):
+        super(TagDefineBitsJPEG2, self).parse(data, length, version)
+        self.bitmapType = ImageUtils.get_image_type(self.bitmapData)
+
+class TagDefineShape2(TagDefineShape):
+    """
+    DefineShape2 extends the capabilities of DefineShape with the ability
+    to support more than 255 styles in the style list and multiple style
+    lists in a single shape.
+    The minimum file format version is SWF 2.
+    """
+    TYPE = 22
+
+    def __init__(self):
+        super(TagDefineShape2, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineShape2"
+
+    @property
+    def type(self):
+        return TagDefineShape2.TYPE
+
+    @property
+    def level(self):
+        return 2
+
+    @property
+    def version(self):
+        return 2
+
+class TagPlaceObject2(TagPlaceObject):
+    """
+    The PlaceObject2 tag extends the functionality of the PlaceObject tag.
+    The PlaceObject2 tag can both add a character to the display list, and
+    modify the attributes of a character that is already on the display list.
+    The PlaceObject2 tag changed slightly from SWF 4 to SWF 5. In SWF 5,
+    clip actions were added.
+    The tag begins with a group of flags that indicate which fields are
+    present in the tag. The optional fields are CharacterId, Matrix,
+    ColorTransform, Ratio, ClipDepth, Name, and ClipActions.
+    The Depth field is the only field that is always required.
+    The depth value determines the stacking order of the character.
+    Characters with lower depth values are displayed underneath characters
+    with higher depth values. A depth value of 1 means the character is
+    displayed at the bottom of the stack. Any given depth can have only one
+    character. This means a character that is already on the display list can
+    be identified by its depth alone (that is, a CharacterId is not required).
+    The PlaceFlagMove and PlaceFlagHasCharacter tags indicate whether a new
+    character is being added to the display list, or a character already on the
+    display list is being modified. The meaning of the flags is as follows:
+
+    - PlaceFlagMove = 0 and PlaceFlagHasCharacter = 1 A new character
+      (with ID of CharacterId) is placed on the display list at the specified
+      depth. Other fields set the attributes of this new character.
+    - PlaceFlagMove = 1 and PlaceFlagHasCharacter = 0
+      The character at the specified depth is modified. Other fields modify the
+      attributes of this character. Because any given depth can have only one
+      character, no CharacterId is required.
+    - PlaceFlagMove = 1 and PlaceFlagHasCharacter = 1
+      The character at the specified Depth is removed, and a new character
+      (with ID of CharacterId) is placed at that depth. Other fields set the
+      attributes of this new character.
+      For example, a character that is moved over a series of frames has
+      PlaceFlagHasCharacter set in the first frame, and PlaceFlagMove set in
+      subsequent frames. The first frame places the new character at the desired
+      depth, and sets the initial transformation matrix. Subsequent frames replace
+      the transformation matrix of the character at the desired depth.
+
+    The optional fields in PlaceObject2 have the following meaning:
+    - The CharacterId field specifies the character to be added to the display list.
+      CharacterId is used only when a new character is being added. If a character
+      that is already on the display list is being modified, the CharacterId field is absent.
+    - The Matrix field specifies the position, scale and rotation of the character
+      being added or modified.
+    - The ColorTransform field specifies the color effect applied to the character
+      being added or modified.
+    - The Ratio field specifies a morph ratio for the character being added or modified.
+      This field applies only to characters defined with DefineMorphShape, and controls
+      how far the morph has progressed. A ratio of zero displays the character at the start
+      of the morph. A ratio of 65535 displays the character at the end of the morph.
+      For values between zero and 65535 Flash Player interpolates between the start and end
+      shapes, and displays an in- between shape.
+    - The ClipDepth field specifies the top-most depth that will be masked by the character
+      being added. A ClipDepth of zero indicates that this is not a clipping character.
+    - The Name field specifies a name for the character being added or modified. This field
+      is typically used with sprite characters, and is used to identify the sprite for
+      SetTarget actions. It allows the main file (or other sprites) to perform actions
+      inside the sprite (see 'Sprites and Movie Clips' on page 231).
+    - The ClipActions field, which is valid only for placing sprite characters, defines
+      one or more event handlers to be invoked when certain events occur.
+    """
+    TYPE = 26
+    def __init__(self):
+        super(TagPlaceObject2, self).__init__()
+
+    def parse(self, data, length, version=1):
+        flags = data.readUI8()
+        self.hasClipActions = (flags & 0x80) != 0
+        self.hasClipDepth = (flags & 0x40) != 0
+        self.hasName = (flags & 0x20) != 0
+        self.hasRatio = (flags & 0x10) != 0
+        self.hasColorTransform = (flags & 0x08) != 0
+        self.hasMatrix = (flags & 0x04) != 0
+        self.hasCharacter = (flags & 0x02) != 0
+        self.hasMove = (flags & 0x01) != 0
+        self.depth = data.readUI16()
+        if self.hasCharacter:
+            self.characterId = data.readUI16()
+        if self.hasMatrix:
+            self.matrix = data.readMATRIX()
+        if self.hasColorTransform:
+            self.colorTransform = data.readCXFORMWITHALPHA()
+        if self.hasRatio:
+            self.ratio = data.readUI16()
+        if self.hasName:
+            self.instanceName = data.readString()
+        if self.hasClipDepth:
+            self.clipDepth = data.readUI16()
+        if self.hasClipActions:
+            self.clipActions = data.readCLIPACTIONS(version);
+            #raise Exception("PlaceObject2: ClipActions not yet implemented!")
+
+    @property
+    def name(self):
+        return "PlaceObject2"
+
+    @property
+    def type(self):
+        return TagPlaceObject2.TYPE
+
+    @property
+    def level(self):
+        return 2
+
+    @property
+    def version(self):
+        return 3
+
+class TagRemoveObject2(TagRemoveObject):
+    """
+    The RemoveObject2 tag removes the character at the specified depth
+    from the display list.
+    The minimum file format version is SWF 3.
+    """
+    TYPE = 28
+
+    def __init__(self):
+        super(TagRemoveObject2, self).__init__()
+
+    @property
+    def name(self):
+        return "RemoveObject2"
+
+    @property
+    def type(self):
+        return TagRemoveObject2.TYPE
+
+    @property
+    def level(self):
+        return 2
+
+    @property
+    def version(self):
+        return 3
+
+    def parse(self, data, length, version=1):
+        self.depth = data.readUI16()
+
+class TagDefineShape3(TagDefineShape2):
+    """
+    DefineShape3 extends the capabilities of DefineShape2 by extending
+    all of the RGB color fields to support RGBA with opacity information.
+    The minimum file format version is SWF 3.
+    """
+    TYPE = 32
+    def __init__(self):
+        super(TagDefineShape3, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineShape3"
+
+    @property
+    def type(self):
+        return TagDefineShape3.TYPE
+
+    @property
+    def level(self):
+        return 3
+
+    @property
+    def version(self):
+        return 3
+
+class TagDefineText2(TagDefineText):
+    """
+    The DefineText tag defines a block of static text. It describes the font,
+    size, color, and exact position of every character in the text object.
+    The minimum file format version is SWF 3.
+    """
+    TYPE = 33
+    def __init__(self):
+        super(TagDefineText2, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineText2"
+
+    @property
+    def type(self):
+        return TagDefineText2.TYPE
+
+    @property
+    def level(self):
+        return 2
+
+    @property
+    def version(self):
+        return 3
+
+class TagDefineBitsJPEG3(TagDefineBitsJPEG2):
+    """
+    This tag defines a bitmap character with JPEG compression. This tag
+    extends DefineBitsJPEG2, adding alpha channel (opacity) data.
+    Opacity/transparency information is not a standard feature in JPEG images,
+    so the alpha channel information is encoded separately from the JPEG data,
+    and compressed using the ZLIB standard for compression. The data format
+    used by the ZLIB library is described by Request for Comments (RFCs)
+    documents 1950 to 1952.
+    The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
+    with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
+    SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8
+    before the JPEG SOI marker.
+    In addition to specifying JPEG data, DefineBitsJPEG2 can also contain
+    PNG image data and non-animated GIF89a image data.
+    - If ImageData begins with the eight bytes 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A,
+      the ImageData contains PNG data.
+    - If ImageData begins with the six bytes 0x47 0x49 0x46 0x38 0x39 0x61,
+      the ImageData contains GIF89a data.
+    If ImageData contains PNG or GIF89a data, the optional BitmapAlphaData is
+    not supported.
+    The minimum file format version for this tag is SWF 3. The minimum file
+    format version for embedding PNG of GIF89a data is SWF 8.
+    """
+    TYPE = 35
+    def __init__(self):
+        self.bitmapAlphaData = BytesIO()
+        super(TagDefineBitsJPEG3, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineBitsJPEG3"
+
+    @property
+    def type(self):
+        return TagDefineBitsJPEG3.TYPE
+
+    @property
+    def version(self):
+        return 3 if self.bitmapType == BitmapType.JPEG else 8
+
+    @property
+    def level(self):
+        return 3
+
+    def parse(self, data, length, version=1):
+        import zlib
+        self.characterId = data.readUI16()
+        alphaOffset = data.readUI32()
+        self.bitmapAlphaData = BytesIO()
+        self.bitmapData = BytesIO()
+        self.bitmapData.write(data.f.read(alphaOffset))
+        self.bitmapData.seek(0)
+        self.bitmapType = ImageUtils.get_image_type(self.bitmapData)
+        alphaDataSize = length - alphaOffset - 6
+        if alphaDataSize > 0:
+            self.bitmapAlphaData.write(data.f.read(alphaDataSize))
+            self.bitmapAlphaData.seek(0)
+            # decompress zlib encoded bytes
+            zip = zlib.decompressobj()
+            temp = BytesIO()
+            temp.write(zip.decompress(self.bitmapAlphaData.read()))
+            temp.seek(0)
+            self.bitmapAlphaData = temp
+
+class TagDefineBitsLossless2(TagDefineBitsLossless):
+    """
+    DefineBitsLossless2 extends DefineBitsLossless with support for
+    opacity (alpha values). The colormap colors in colormapped images
+    are defined using RGBA values, and direct images store 32-bit
+    ARGB colors for each pixel. The intermediate 15-bit color depth
+    is not available in DefineBitsLossless2.
+    The minimum file format version for this tag is SWF 3.
+    """
+    TYPE = 36
+    def __init__(self):
+        super(TagDefineBitsLossless2, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineBitsLossless2"
+
+    @property
+    def type(self):
+        return TagDefineBitsLossless2.TYPE
+
+    @property
+    def level(self):
+        return 2
+
+    @property
+    def version(self):
+        return 3
+
+class TagDefineSprite(SWFTimelineContainer):
+    """
+    The DefineSprite tag defines a sprite character. It consists of
+    a character ID and a frame count, followed by a series of control
+    tags. The sprite is terminated with an End tag.
+    The length specified in the Header reflects the length of the
+    entire DefineSprite tag, including the ControlTags field.
+    Definition tags (such as DefineShape) are not allowed in the
+    DefineSprite tag. All of the characters that control tags refer to
+    in the sprite must be defined in the main body of the file before
+    the sprite is defined.
+    The minimum file format version is SWF 3.
+    """
+    TYPE = 39
+    frameCount = 0
+    def __init__(self):
+        super(TagDefineSprite, self).__init__()
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self.frameCount = data.readUI16()
+        self.parse_tags(data, version)
+
+    def get_dependencies(self):
+        s = super(TagDefineSprite, self).get_dependencies()
+        s.add(self.characterId)
+        return s
+
+    @property
+    def name(self):
+        return "DefineSprite"
+
+    @property
+    def type(self):
+        return TagDefineSprite.TYPE
+
+    def __str__(self):
+        s = super(TagDefineSprite, self).__str__() + " " + \
+            "ID: %d" % self.characterId
+        return s
+
+class TagFrameLabel(Tag):
+    """
+    The FrameLabel tag gives the specified Name to the current frame.
+    ActionGoToLabel uses this name to identify the frame.
+    The minimum file format version is SWF 3.
+    """
+    TYPE = 43
+    frameName = ""
+    namedAnchorFlag = False
+    def __init__(self):
+        super(TagFrameLabel, self).__init__()
+
+    @property
+    def name(self):
+        return "FrameLabel"
+
+    @property
+    def type(self):
+        return TagFrameLabel.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 3
+
+    def parse(self, data, length, version=1):
+        start = data.tell()
+        self.frameName = data.readString()
+        if (data.tell() - start) < length:
+            data.readUI8() # Named anchor flag, always 1
+            self.namedAnchorFlag = True
+
+class TagDefineMorphShape(DefinitionTag):
+    """
+    The DefineMorphShape tag defines the start and end states of a morph
+    sequence. A morph object should be displayed with the PlaceObject2 tag,
+    where the ratio field specifies how far the morph has progressed.
+    The minimum file format version is SWF 3.
+    """
+    TYPE = 46
+    def __init__(self):
+        self._morphFillStyles = []
+        self._morphLineStyles = []
+        super(TagDefineMorphShape, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineMorphShape"
+
+    @property
+    def type(self):
+        return TagDefineMorphShape.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 3
+
+    @property
+    def morph_fill_styles(self):
+        """ Return list of SWFMorphFillStyle """
+        return self._morphFillStyles
+
+    @property
+    def morph_line_styles(self):
+        """ Return list of SWFMorphLineStyle """
+        return self._morphLineStyles
+
+    def parse(self, data, length, version=1):
+        self._morphFillStyles = []
+        self._morphLineStyles = []
+        self.characterId = data.readUI16()
+        self.startBounds = data.readRECT()
+        self.endBounds = data.readRECT()
+        offset = data.readUI32()
+
+        self._morphFillStyles = data.readMORPHFILLSTYLEARRAY()
+        self._morphLineStyles = data.readMORPHLINESTYLEARRAY(version = 1)
+        self.startEdges = data.readSHAPE();
+        self.endEdges = data.readSHAPE();
+
+class TagDefineFont2(TagDefineFont):
+    TYPE= 48
+    def __init__(self):
+        self.glyphShapeTable = []
+        super(TagDefineFont2, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineFont2"
+
+    @property
+    def type(self):
+        return TagDefineFont2.TYPE
+
+    @property
+    def level(self):
+        return 2
+
+    @property
+    def version(self):
+        return 3
+
+    @property
+    def unitDivisor(self):
+        return 20
+
+    def parse(self, data, length, version=1):
+        self.glyphShapeTable = []
+        self.codeTable = []
+        self.fontAdvanceTable = []
+        self.fontBoundsTable = []
+        self.fontKerningTable = []
+
+        self.characterId = data.readUI16()
+
+        flags = data.readUI8()
+
+        self.hasLayout = ((flags & 0x80) != 0)
+        self.shiftJIS = ((flags & 0x40) != 0)
+        self.smallText = ((flags & 0x20) != 0)
+        self.ansi = ((flags & 0x10) != 0)
+        self.wideOffsets = ((flags & 0x08) != 0)
+        self.wideCodes = ((flags & 0x04) != 0)
+        self.italic = ((flags & 0x02) != 0)
+        self.bold = ((flags & 0x01) != 0)
+        self.languageCode = data.readLANGCODE()
+
+        fontNameLen = data.readUI8()
+        fontNameRaw = BytesIO()
+        fontNameRaw.write(data.f.read(fontNameLen))
+        fontNameRaw.seek(0)
+        self.fontName = fontNameRaw.read()
+
+        numGlyphs = data.readUI16()
+        numSkip = 2 if self.wideOffsets else 1
+        # don't # Skip offsets. We don't need them.
+        # Adobe Flash Player works in this way
+
+        startOfOffsetTable = data.f.tell()
+        offsetTable = []
+        for i in range(0, numGlyphs):
+            offsetTable.append(data.readUI32() if self.wideOffsets else data.readUI16())
+
+        codeTableOffset = data.readUI32() if self.wideOffsets else data.readUI16()
+        for i in range(0, numGlyphs):
+            data.f.seek(startOfOffsetTable + offsetTable[i])
+            self.glyphShapeTable.append(data.readSHAPE(self.unitDivisor))
+        data.f.seek(startOfOffsetTable + codeTableOffset)
+        for i in range(0, numGlyphs):
+            self.codeTable.append(data.readUI16() if self.wideCodes else data.readUI8())
+
+        if self.hasLayout:
+            self.ascent = data.readSI16()
+            self.descent = data.readSI16()
+            self.leading = data.readSI16()
+            for i in range(0, numGlyphs):
+                self.fontAdvanceTable.append(data.readSI16())
+            for i in range(0, numGlyphs):
+                self.fontBoundsTable.append(data.readRECT())
+            kerningCount = data.readUI16()
+            for i in range(0, kerningCount):
+                self.fontKerningTable.append(data.readKERNINGRECORD(self.wideCodes))
+
+class TagFileAttributes(Tag):
+    """
+    The FileAttributes tag defines characteristics of the SWF file. This tag
+    is required for SWF 8 and later and must be the first tag in the SWF file.
+    Additionally, the FileAttributes tag can optionally be included in all SWF
+    file versions.
+    The HasMetadata flag identifies whether the SWF file contains the Metadata
+    tag. Flash Player does not care about this bit field or the related tag but
+    it is useful for search engines.
+    The UseNetwork flag signifies whether Flash Player should grant the SWF file
+    local or network file access if the SWF file is loaded locally. The default
+    behavior is to allow local SWF files to interact with local files only, and
+    not with the network. However, by setting the UseNetwork flag, the local SWF
+    can forfeit its local file system access in exchange for access to the
+    network. Any version of SWF can use the UseNetwork flag to set the file
+    access for locally loaded SWF files that are running in Flash Player 8 or later.
+    """
+    TYPE = 69
+    def __init__(self):
+        super(TagFileAttributes, self).__init__()
+
+    @property
+    def name(self):
+        return "FileAttributes"
+
+    @property
+    def type(self):
+        return TagFileAttributes.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 8
+
+    def parse(self, data, length, version=1):
+        flags = data.readUI8()
+        self.useDirectBlit = ((flags & 0x40) != 0)
+        self.useGPU = ((flags & 0x20) != 0)
+        self.hasMetadata = ((flags & 0x10) != 0)
+        self.actionscript3 = ((flags & 0x08) != 0)
+        self.useNetwork = ((flags & 0x01) != 0)
+        data.skip_bytes(3)
+
+    def __str__(self):
+        s = super(TagFileAttributes, self).__str__() + \
+            " useDirectBlit: %d, " % self.useDirectBlit + \
+            "useGPU: %d, " % self.useGPU + \
+            "hasMetadata: %d, " % self.hasMetadata + \
+            "actionscript3: %d, " % self.actionscript3 + \
+            "useNetwork: %d" % self.useNetwork
+        return s
+
+class TagPlaceObject3(TagPlaceObject2):
+    TYPE = 70
+    def __init__(self):
+        super(TagPlaceObject3, self).__init__()
+
+    def parse(self, data, length, version=1):
+        flags = data.readUI8()
+        self.hasClipActions = ((flags & 0x80) != 0)
+        self.hasClipDepth = ((flags & 0x40) != 0)
+        self.hasName = ((flags & 0x20) != 0)
+        self.hasRatio = ((flags & 0x10) != 0)
+        self.hasColorTransform = ((flags & 0x08) != 0)
+        self.hasMatrix = ((flags & 0x04) != 0)
+        self.hasCharacter = ((flags & 0x02) != 0)
+        self.hasMove = ((flags & 0x01) != 0)
+        flags2 = data.readUI8();
+        self.hasImage = ((flags2 & 0x10) != 0)
+        self.hasClassName = ((flags2 & 0x08) != 0)
+        self.hasCacheAsBitmap = ((flags2 & 0x04) != 0)
+        self.hasBlendMode = ((flags2 & 0x2) != 0)
+        self.hasFilterList = ((flags2 & 0x1) != 0)
+        self.depth = data.readUI16()
+
+        if self.hasClassName:
+            self.className = data.readString()
+        if self.hasCharacter:
+            self.characterId = data.readUI16()
+        if self.hasMatrix:
+            self.matrix = data.readMATRIX()
+        if self.hasColorTransform:
+            self.colorTransform = data.readCXFORMWITHALPHA()
+        if self.hasRatio:
+            self.ratio = data.readUI16()
+        if self.hasName:
+            self.instanceName = data.readString()
+        if self.hasClipDepth:
+            self.clipDepth = data.readUI16();
+        if self.hasFilterList:
+            numberOfFilters = data.readUI8()
+            for i in range(0, numberOfFilters):
+                self._surfaceFilterList.append(data.readFILTER())
+        if self.hasBlendMode:
+            self.blendMode = data.readUI8()
+        if self.hasCacheAsBitmap:
+            self.bitmapCache = data.readUI8()
+        if self.hasClipActions:
+            self.clipActions = data.readCLIPACTIONS(version)
+            #raise Exception("PlaceObject3: ClipActions not yet implemented!")
+
+    @property
+    def name(self):
+        return "PlaceObject3"
+
+    @property
+    def type(self):
+        return TagPlaceObject3.TYPE
+
+class TagDefineFontAlignZones(Tag):
+    TYPE = 73
+    def __init__(self):
+        super(TagDefineFontAlignZones, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineFontAlignZones"
+
+    @property
+    def type(self):
+        return TagDefineFontAlignZones.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 8
+
+    def parse(self, data, length, version=1):
+        self.zoneTable = []
+
+        self.fontId = data.readUI16()
+        self.csmTableHint = (data.readUI8() >> 6)
+
+        recordsEndPos = data.tell() + length - 3;
+        while data.tell() < recordsEndPos:
+            self.zoneTable.append(data.readZONERECORD())
+
+class TagCSMTextSettings(Tag):
+    TYPE = 74
+    def __init__(self):
+        super(TagCSMTextSettings, self).__init__()
+
+    @property
+    def name(self):
+        return "CSMTextSettings"
+
+    @property
+    def type(self):
+        return TagCSMTextSettings.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 8
+
+    def parse(self, data, length, version=1):
+        self.textId = data.readUI16()
+        self.useFlashType = data.readUB(2)
+        self.gridFit = data.readUB(3);
+        data.readUB(3) # reserved, always 0
+        self.thickness = data.readFIXED()
+        self.sharpness = data.readFIXED()
+        data.readUI8() # reserved, always 0
+
+class TagDefineFont3(TagDefineFont2):
+    TYPE = 75
+    def __init__(self):
+        super(TagDefineFont3, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineFont3"
+
+    @property
+    def type(self):
+        return TagDefineFont3.TYPE
+
+    @property
+    def level(self):
+        return 2
+
+    @property
+    def version(self):
+        return 8
+
+class TagSymbolClass(Tag):
+    TYPE = 76
+    def __init__(self):
+        self.symbols = []
+        super(TagSymbolClass, self).__init__()
+
+    @property
+    def name(self):
+        return "SymbolClass"
+
+    @property
+    def type(self):
+        return TagSymbolClass.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 9 # educated guess (not specified in SWF10 spec)
+
+    def parse(self, data, length, version=1):
+        self.symbols = []
+        numSymbols = data.readUI16()
+        for i in range(0, numSymbols):
+            self.symbols.append(data.readSYMBOL())
+
+class TagMetadata(Tag):
+    TYPE = 77
+    def __init__(self):
+        super(TagMetadata, self).__init__()
+
+    @property
+    def name(self):
+        return "Metadata"
+
+    @property
+    def type(self):
+        return TagMetadata.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    def parse(self, data, length, version=1):
+        self.xmlString = data.readString()
+
+    def __str__(self):
+        s = super(TagMetadata, self).__str__()
+        s += " xml: %r" % self.xmlString
+        return s
+
+class TagDoABC(Tag):
+    TYPE = 82
+    def __init__(self):
+        super(TagDoABC, self).__init__()
+
+    @property
+    def name(self):
+        return "DoABC"
+
+    @property
+    def type(self):
+        return TagDoABC.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 9
+
+    def parse(self, data, length, version=1):
+        pos = data.tell()
+        flags = data.readUI32()
+        self.lazyInitializeFlag = ((flags & 0x01) != 0)
+        self.abcName = data.readString()
+        self.bytes = data.f.read(length - (data.tell() - pos))
+
+class TagDefineShape4(TagDefineShape3):
+    TYPE = 83
+    def __init__(self):
+        super(TagDefineShape4, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineShape4"
+
+    @property
+    def type(self):
+        return TagDefineShape4.TYPE
+
+    @property
+    def level(self):
+        return 4
+
+    @property
+    def version(self):
+        return 8
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self._shape_bounds = data.readRECT()
+        self.edge_bounds = data.readRECT()
+        flags = data.readUI8()
+        self.uses_fillwinding_rule = ((flags & 0x04) != 0)
+        self.uses_non_scaling_strokes = ((flags & 0x02) != 0)
+        self.uses_scaling_strokes = ((flags & 0x01) != 0)
+        self._shapes = data.readSHAPEWITHSTYLE(self.level)
+
+class TagDefineSceneAndFrameLabelData(Tag):
+    TYPE = 86
+    def __init__(self):
+        self.scenes = []
+        self.frameLabels = []
+        super(TagDefineSceneAndFrameLabelData, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineSceneAndFrameLabelData"
+
+    @property
+    def type(self):
+        return TagDefineSceneAndFrameLabelData.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 9
+
+    def parse(self, data, length, version=1):
+        self.sceneCount = data.readEncodedU32()
+
+        if self.sceneCount >= 0x80000000:
+            #print "WARNING: Negative sceneCount value: %x found!. SWF file exploiting CVE-2007-0071?" % self.sceneCount
+            return
+
+        self.scenes = []
+        self.frameLabels = []
+        for i in range(0, self.sceneCount):
+            sceneOffset = data.readEncodedU32()
+            sceneName = data.readString()
+            self.scenes.append(SWFScene(sceneOffset, sceneName))
+
+        frameLabelCount = data.readEncodedU32()
+        for i in range(0, frameLabelCount):
+            frameNumber = data.readEncodedU32();
+            frameLabel = data.readString();
+            self.frameLabels.append(SWFFrameLabel(frameNumber, frameLabel))
+
+class TagDefineBinaryData(DefinitionTag):
+    """
+	The DefineBinaryData tag permits arbitrary binary data to be embedded in a SWF file. DefineBinaryData is a definition tag, like DefineShape and DefineSprite. It associates a blob of binary data with a standard SWF 16-bit character ID. The character ID is entered into the SWF file's character dictionary. DefineBinaryData is intended to be used in conjunction with the SymbolClass tag. The SymbolClass tag can be used to associate a DefineBinaryData tag with an AS3 class definition. The AS3 class must be a subclass of ByteArray. When the class is instantiated, it will be populated automatically with the contents of the binary data resource.
+    """
+    TYPE = 87
+    def __init__(self):
+        super(TagDefineBinaryData, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineBinaryData"
+
+    @property
+    def type(self):
+        return TagDefineBinaryData.TYPE
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self.reserved = data.readUI32()
+        self.data = data.read(length - 6)
+
+class TagDefineFontName(Tag):
+    TYPE = 88
+    def __init__(self):
+        super(TagDefineFontName, self).__init__()
+
+    @property
+    def name(self):
+        return "DefineFontName"
+
+    @property
+    def type(self):
+        return TagDefineFontName.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 9
+
+    def get_dependencies(self):
+        s = super(TagDefineFontName, self).get_dependencies()
+        s.add(self.fontId)
+        return s
+
+    def parse(self, data, length, version=1):
+        self.fontId = data.readUI16()
+        self.fontName = data.readString()
+        self.fontCopyright = data.readString()
+
+class TagDefineSound(Tag):
+    TYPE = 14
+    def __init__(self):
+        super(TagDefineSound, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineSound"
+
+    @property
+    def type(self):
+        return TagDefineSound.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    def parse(self, data, length, version=1):
+        assert length > 7
+        self.soundId = data.readUI16()
+        self.soundFormat = data.readUB(4)
+        self.soundRate = data.readUB(2)
+        self.soundSampleSize = data.readUB(1)
+        self.soundChannels = data.readUB(1)
+        self.soundSamples = data.readUI32()
+        # used 2 + 1 + 4 bytes here
+        self.soundData = BytesIO(data.read(length - 7))
+
+    def __str__(self):
+        s = super(TagDefineSound, self).__str__()
+        s += " soundFormat: %s" % AudioCodec.tostring(self.soundFormat)
+        s += " soundRate: %s" % AudioSampleRate.tostring(self.soundRate)
+        s += " soundSampleSize: %s" % AudioSampleSize.tostring(self.soundSampleSize)
+        s += " soundChannels: %s" % AudioChannels.tostring(self.soundChannels)
+        return s
+
+class TagStartSound(Tag):
+    TYPE = 15
+    def __init__(self):
+        super(TagStartSound, self).__init__()
+
+    @property
+    def name(self):
+        return "TagStartSound"
+
+    @property
+    def type(self):
+        return TagStartSound.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    def parse(self, data, length, version=1):
+        self.soundId = data.readUI16()
+        self.soundInfo = data.readSOUNDINFO()
+
+class TagStartSound2(Tag):
+    TYPE = 89
+    def __init__(self):
+        super(TagStartSound2, self).__init__()
+
+    @property
+    def name(self):
+        return "TagStartSound2"
+
+    @property
+    def type(self):
+        return TagStartSound2.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 9
+
+    def parse(self, data, length, version=1):
+        self.soundClassName = data.readString()
+        self.soundInfo = data.readSOUNDINFO()
+
+class TagSoundStreamHead(Tag):
+    TYPE = 18
+    def __init__(self):
+        super(TagSoundStreamHead, self).__init__()
+
+    @property
+    def name(self):
+        return "TagSoundStreamHead"
+
+    @property
+    def type(self):
+        return TagSoundStreamHead.TYPE
+
+    @property
+    def level(self):
+        return 1
+
+    @property
+    def version(self):
+        return 1
+
+    def parse(self, data, length, version=1):
+        # byte 1
+        self.reserved0 = data.readUB(4)
+        self.playbackRate = data.readUB(2)
+        self.playbackSampleSize = data.readUB(1)
+        self.playbackChannels = data.readUB(1)
+
+        # byte 2
+        self.soundFormat = data.readUB(4)
+        self.soundRate = data.readUB(2)
+        self.soundSampleSize = data.readUB(1)
+        self.soundChannels = data.readUB(1)
+
+        self.samples = data.readUI16()
+        self.latencySeek = data.readSI16() if self.soundFormat == AudioCodec.MP3 else None
+        hdr = 6 if self.soundFormat == AudioCodec.MP3 else 4
+        assert hdr == length
+
+    def __str__(self):
+        s = super(TagSoundStreamHead, self).__str__()
+        s += " playbackRate: %s" % AudioSampleRate.tostring(self.playbackRate)
+        s += " playbackSampleSize: %s" % AudioSampleSize.tostring(self.playbackSampleSize)
+        s += " playbackChannels: %s" % AudioChannels.tostring(self.playbackChannels)
+        s += " soundFormat: %s" % AudioCodec.tostring(self.soundFormat)
+        s += " soundRate: %s" % AudioSampleRate.tostring(self.soundRate)
+        s += " soundSampleSize: %s" % AudioSampleSize.tostring(self.soundSampleSize)
+        s += " soundChannels: %s" % AudioChannels.tostring(self.soundChannels)
+        return s
+
+class TagSoundStreamHead2(TagSoundStreamHead):
+    """
+    The SoundStreamHead2 tag is identical to the SoundStreamHead tag, except it allows
+    different values for StreamSoundCompression and StreamSoundSize (SWF 3 file format).
+    """
+    TYPE = 45
+
+    def __init__(self):
+        super(TagSoundStreamHead2, self).__init__()
+
+    @property
+    def name(self):
+        return "TagSoundStreamHead2"
+
+    @property
+    def type(self):
+        return TagSoundStreamHead2.TYPE
+
+class TagSoundStreamBlock(Tag):
+    """
+    The SoundStreamHead2 tag is identical to the SoundStreamHead tag, except it allows
+    different values for StreamSoundCompression and StreamSoundSize (SWF 3 file format).
+    """
+    TYPE = 19
+
+    def __init__(self):
+        super(TagSoundStreamBlock, self).__init__()
+
+    @property
+    def name(self):
+        return "TagSoundStreamBlock"
+
+    @property
+    def type(self):
+        return TagSoundStreamBlock.TYPE
+
+    def parse(self, data, length, version=1):
+        # unfortunately we can't see our associated SoundStreamHead from here,
+        # so just stash the data
+        self.data = BytesIO(data.read(length))
+
+    def complete_parse_with_header(self, head):
+        stream = SWFStream(self.data)
+        if head.soundFormat in (AudioCodec.UncompressedNativeEndian,
+                                AudioCodec.UncompressedLittleEndian):
+            pass # data is enough
+        elif head.soundFormat == AudioCodec.MP3:
+            self.sampleCount = stream.readUI16()
+            self.seekSize = stream.readSI16()
+            self.mpegFrames = stream.read()
+
+class TagDefineBinaryData(DefinitionTag):
+    """
+    The DefineBinaryData tag permits arbitrary binary data to be embedded in a SWF file.
+    DefineBinaryData is a definition tag, like DefineShape and DefineSprite. It associates a blob
+    of binary data with a standard SWF 16-bit character ID. The character ID is entered into the
+    SWF file's character dictionary.
+    """
+    TYPE = 87
+
+    def __init__(self):
+        super(TagDefineBinaryData, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineBinaryData"
+
+    @property
+    def type(self):
+        return TagDefineBinaryData.TYPE
+
+    def parse(self, data, length, version=1):
+        assert length >= 6
+        self.characterId = data.readUI16()
+        self.reserved = data.readUI32()
+        self.data = data.read(length - 4 - 2)
+
+class TagProductInfo(Tag):
+    """
+    Undocumented in SWF10.
+    """
+    TYPE = 41
+
+    def __init__(self):
+        super(TagProductInfo, self).__init__()
+
+    @property
+    def name(self):
+        return "TagProductInfo"
+
+    @property
+    def type(self):
+        return TagProductInfo.TYPE
+
+    def parse(self, data, length, version=1):
+        self.product = data.readUI32()
+        self.edition = data.readUI32()
+        self.majorVersion, self.minorVersion = data.readUI8(), data.readUI8()
+        self.build = data.readUI64()
+        self.compileTime = data.readUI64()
+
+    def __str__(self):
+        s = super(TagProductInfo, self).__str__()
+        s += " product: %s" % ProductKind.tostring(self.product)
+        s += " edition: %s" % ProductEdition.tostring(self.edition)
+        s += " major.minor.build: %d.%d.%d" % (self.majorVersion, self.minorVersion, self.build)
+        s += " compileTime: %d" % (self.compileTime)
+        return s
+
+class TagScriptLimits(Tag):
+    """
+    The ScriptLimits tag includes two fields that can be used to override the default settings for
+    maximum recursion depth and ActionScript time-out: MaxRecursionDepth and
+    ScriptTimeoutSeconds.
+    """
+    TYPE = 65
+
+    def __init__(self):
+        super(TagScriptLimits, self).__init__()
+
+    @property
+    def name(self):
+        return "TagScriptLimits"
+
+    @property
+    def type(self):
+        return TagScriptLimits.TYPE
+
+    def parse(self, data, length, version=1):
+        self.maxRecursionDepth = data.readUI16()
+        self.scriptTimeoutSeconds = data.readUI16()
+
+    def __str__(self):
+        s = super(TagScriptLimits, self).__str__()
+        s += " maxRecursionDepth: %s" % self.maxRecursionDepth
+        s += " scriptTimeoutSeconds: %s" % self.scriptTimeoutSeconds
+        return s
+
+class TagDebugID(Tag):
+    """
+    Undocumented in SWF10.  Some kind of GUID.
+    """
+    TYPE = 63
+
+    def __init__(self):
+        super(TagDebugID, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDebugID"
+
+    @property
+    def type(self):
+        return TagDebugID.TYPE
+
+    def parse(self, data, length, version=1):
+        self.guid = data.read(16)
+
+class TagExportAssets(Tag):
+    """
+    The ExportAssets tag makes portions of a SWF file available for import by other SWF files
+    """
+    TYPE = 56
+
+    def __init__(self):
+        super(TagExportAssets, self).__init__()
+
+    @property
+    def name(self):
+        return "TagExportAssets"
+
+    @property
+    def version(self):
+        return 5
+
+    @property
+    def type(self):
+        return TagExportAssets.TYPE
+
+    def parse(self, data, length, version=1):
+        self.count = data.readUI16()
+        self.exports = [data.readEXPORT() for i in range(self.count)]
+
+    def __str__(self):
+        s = super(TagExportAssets, self).__str__()
+        s += " exports: %s" % self.exports
+        return s
+
+class TagProtect(Tag):
+    """
+    The Protect tag marks a file as not importable for editing in an authoring environment. If the
+    Protect tag contains no data (tag length = 0), the SWF file cannot be imported. If this tag is
+    present in the file, any authoring tool should prevent the file from loading for editing.
+    """
+    TYPE = 24
+
+    def __init__(self):
+        super(TagProtect, self).__init__()
+        self.password = None
+
+    @property
+    def name(self):
+        return "TagProtect"
+
+    @property
+    def version(self):
+        return 2 if self.password is None else 5
+
+    @property
+    def type(self):
+        return TagProtect.TYPE
+
+    def parse(self, data, length, version=1):
+        if length:
+            self.password = data.readString()
+        else:
+            self.password = None
+
+    def __str__(self):
+        s = super(TagProtect, self).__str__()
+        s += " password: %r" % self.password
+        return s
+
+class TagEnableDebugger(Tag):
+    """
+    The EnableDebugger tag enables debugging. The password in the EnableDebugger tag is
+    encrypted by using the MD5 algorithm, in the same way as the Protect tag.
+    """
+    TYPE = 58
+
+    def __init__(self):
+        super(TagEnableDebugger, self).__init__()
+
+    @property
+    def name(self):
+        return "TagEnableDebugger"
+
+    @property
+    def version(self):
+        return 5
+
+    @property
+    def type(self):
+        return TagEnableDebugger.TYPE
+
+    def parse(self, data, length, version=1):
+        self.password = data.readString()
+
+    def __str__(self):
+        s = super(TagEnableDebugger, self).__str__()
+        s += " password: %r" % self.password
+        return s
+
+class TagEnableDebugger2(Tag):
+    """
+    The EnableDebugger2 tag enables debugging. The Password field is encrypted by using the
+    MD5 algorithm, in the same way as the Protect tag.
+    """
+    TYPE = 64
+
+    def __init__(self):
+        super(TagEnableDebugger2, self).__init__()
+
+    @property
+    def name(self):
+        return "TagEnableDebugger2"
+
+    @property
+    def version(self):
+        return 6
+
+    @property
+    def type(self):
+        return TagEnableDebugger2.TYPE
+
+    def parse(self, data, length, version=1):
+        self.reserved0 = data.readUI16()
+        self.password = data.readString()
+
+    def __str__(self):
+        s = super(TagEnableDebugger2, self).__str__()
+        s += " password: %r" % self.password
+        return s
+
+class TagDoInitAction(Tag):
+    """
+    The DoInitAction tag is similar to the DoAction tag: it defines a series of bytecodes to be
+    executed. However, the actions defined with DoInitAction are executed earlier than the usual
+    DoAction actions, and are executed only once.
+    """
+    TYPE = 59
+
+    def __init__(self):
+        super(TagDoInitAction, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDoInitAction"
+
+    @property
+    def version(self):
+        return 6
+
+    @property
+    def type(self):
+        return TagDoInitAction.TYPE
+
+    def get_dependencies(self):
+        s = super(TagDoInitAction, self).get_dependencies()
+        s.add(self.spriteId)
+        return s
+
+    def parse(self, data, length, version=1):
+        self.spriteId = data.readUI16()
+        self.actions = data.readACTIONRECORDs()
+
+class TagDefineEditText(DefinitionTag):
+    """
+    The DefineEditText tag defines a dynamic text object, or text field.
+
+    A text field is associated with an ActionScript variable name where the contents of the text
+    field are stored. The SWF file can read and write the contents of the variable, which is always
+    kept in sync with the text being displayed. If the ReadOnly flag is not set, users may change
+    the value of a text field interactively
+    """
+    TYPE = 37
+
+    def __init__(self):
+        super(TagDefineEditText, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineEditText"
+
+    @property
+    def type(self):
+        return TagDefineEditText.TYPE
+
+    def get_dependencies(self):
+        s = super(TagDefineEditText, self).get_dependencies()
+        s.add(self.fontId) if self.hasFont else None
+        return s
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self.bounds = data.readRECT()
+
+        # flags
+        self.hasText = data.readUB(1) == 1
+        self.wordWrap = data.readUB(1) == 1
+        self.multiline = data.readUB(1) == 1
+        self.password = data.readUB(1) == 1
+
+        self.readOnly = data.readUB(1) == 1
+        self.hasTextColor = data.readUB(1) == 1
+        self.hasMaxLength = data.readUB(1) == 1
+        self.hasFont = data.readUB(1) == 1
+
+        self.hasFontClass = data.readUB(1) == 1
+        self.autoSize = data.readUB(1) == 1
+        self.hasLayout = data.readUB(1) == 1
+        self.noSelect = data.readUB(1) == 1
+
+        self.border = data.readUB(1) == 1
+        self.wasStatic = data.readUB(1) == 1
+        self.html = data.readUB(1) == 1
+        self.useOutlines = data.readUB(1) == 1
+
+        # values
+        self.fontId = data.readUI16() if self.hasFont else None
+        self.fontClass = data.readString() if self.hasFontClass else None
+        self.fontHeight = data.readUI16() if self.hasFont else None
+        self.textColor = data.readRGBA() if self.hasTextColor else None
+        self.maxLength = data.readUI16() if self.hasMaxLength else None
+
+        self.align = data.readUI8() if self.hasLayout else None
+        self.leftMargin = data.readUI16() if self.hasLayout else None
+        self.rightMargin = data.readUI16() if self.hasLayout else None
+        self.indent = data.readUI16() if self.hasLayout else None
+        self.leading = data.readUI16() if self.hasLayout else None
+
+        # backend info
+        self.variableName = data.readString()
+        self.initialText = data.readString() if self.hasText else None
+
+class TagDefineButton(DefinitionTag):
+    """
+    The DefineButton tag defines a button character for later use by control tags such as
+    PlaceObject.
+    """
+    TYPE = 7
+
+    def __init__(self):
+        super(TagDefineButton, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineButton"
+
+    @property
+    def type(self):
+        return TagDefineButton.TYPE
+
+    def get_dependencies(self):
+        s = super(TagDefineButton, self).get_dependencies()
+        for b in self.buttonCharacters:
+            s.update(b.get_dependencies())
+        return s
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self.buttonCharacters = data.readBUTTONRECORDs(version = 1)
+        self.buttonActions = data.readACTIONRECORDs()
+
+class TagDefineButton2(DefinitionTag):
+    """
+    DefineButton2 extends the capabilities of DefineButton by allowing any state transition to
+    trigger actions.
+    """
+    TYPE = 34
+
+    def __init__(self):
+        super(TagDefineButton2, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineButton2"
+
+    @property
+    def type(self):
+        return TagDefineButton2.TYPE
+
+    def get_dependencies(self):
+        s = super(TagDefineButton2, self).get_dependencies()
+        for b in self.buttonCharacters:
+            s.update(b.get_dependencies())
+        return s
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self.reservedFlags = data.readUB(7)
+        self.trackAsMenu = data.readUB(1) == 1
+        offs = data.tell()
+        self.actionOffset = data.readUI16()
+        self.buttonCharacters = data.readBUTTONRECORDs(version = 2)
+
+        if self.actionOffset:
+            # if we have actions, seek to the first one
+            data.seek(offs + self.actionOffset)
+            self.buttonActions = data.readBUTTONCONDACTIONSs()
+
+class TagDefineButtonSound(Tag):
+    """
+    The DefineButtonSound tag defines which sounds (if any) are played on state transitions.
+    """
+    TYPE = 17
+
+    def __init__(self):
+        super(TagDefineButtonSound, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineButtonSound"
+
+    @property
+    def type(self):
+        return TagDefineButtonSound.TYPE
+
+    @property
+    def version(self):
+        return 2
+
+    def parse(self, data, length, version=1):
+        self.buttonId = data.readUI16()
+
+        for event in 'OverUpToIdle IdleToOverUp OverUpToOverDown OverDownToOverUp'.split():
+            soundId = data.readUI16()
+            setattr(self, 'soundOn' + event, soundId)
+            soundInfo = data.readSOUNDINFO() if soundId else None
+            setattr(self, 'soundInfoOn' + event, soundInfo)
+
+class TagDefineScalingGrid(Tag):
+    """
+    The DefineScalingGrid tag introduces the concept of 9-slice scaling, which allows
+    component-style scaling to be applied to a sprite or button character.
+    """
+    TYPE = 78
+
+    def __init__(self):
+        super(TagDefineScalingGrid, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineScalingGrid"
+
+    @property
+    def type(self):
+        return TagDefineScalingGrid.TYPE
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self.splitter = data.readRECT()
+
+class TagDefineVideoStream(DefinitionTag):
+    """
+    DefineVideoStream defines a video character that can later be placed on the display list.
+    """
+    TYPE = 60
+
+    def __init__(self):
+        super(TagDefineVideoStream, self).__init__()
+
+    @property
+    def name(self):
+        return "TagDefineVideoStream"
+
+    @property
+    def type(self):
+        return TagDefineVideoStream.TYPE
+
+    def parse(self, data, length, version=1):
+        self.characterId = data.readUI16()
+        self.numFrames = data.readUI16()
+        self.width = data.readUI16()
+        self.height = data.readUI16()
+        reserved0 = data.readUB(4)
+        self.videoDeblocking = data.readUB(3)
+        self.videoSmoothing = data.readUB(1)
+        self.codec = data.readUI8()
+
+class TagVideoFrame(Tag):
+    """
+    VideoFrame provides a single frame of video data for a video character that is already defined
+    with DefineVideoStream.
+    """
+    TYPE = 61
+
+    def __init__(self):
+        super(TagVideoFrame, self).__init__()
+
+    @property
+    def name(self):
+        return "TagVideoFrame"
+
+    @property
+    def type(self):
+        return TagVideoFrame.TYPE
+
+    def parse(self, data, length, version=1):
+        self.streamId = data.readUI16()
+        self.frameNumber = data.readUI16()
+        self.videoData = data.read(length - 4)
+
+class TagDefineMorphShape2(TagDefineMorphShape):
+    """
+    The DefineMorphShape2 tag extends the capabilities of DefineMorphShape by using a new
+    morph line style record in the morph shape. MORPHLINESTYLE2 allows the use of new
+    types of joins and caps as well as scaling options and the ability to fill the strokes of the morph
+    shape.
+    """
+    TYPE = 84
+
+    @property
+    def name(self):
+        return "TagDefineMorphShape2"
+
+    @property
+    def type(self):
+        return TagDefineMorphShape2.TYPE
+
+    @property
+    def version(self):
+        return 8
+
+    def get_dependencies(self):
+        s = super(TagDefineMorphShape2, self).get_dependencies()
+        s.update(self.startEdges.get_dependencies())
+        s.update(self.endEdges.get_dependencies())
+        return s
+
+    def parse(self, data, length, version=1):
+        self._morphFillStyles = []
+        self._morphLineStyles = []
+        self.characterId = data.readUI16()
+
+        self.startBounds = data.readRECT()
+        self.endBounds = data.readRECT()
+        self.startEdgeBounds = data.readRECT()
+        self.endEdgeBounds = data.readRECT()
+
+        self.reserved0 = data.readUB(6)
+        self.usesNonScalingStrokes = data.readUB(1) == 1
+        self.usesScalingStrokes = data.readUB(1) == 1
+
+        offset = data.readUI32()
+        self._morphFillStyles = data.readMORPHFILLSTYLEARRAY()
+        self._morphLineStyles = data.readMORPHLINESTYLEARRAY(version = 2)
+
+        self.startEdges = data.readSHAPE();
+        self.endEdges = data.readSHAPE();
+
+if __name__ == '__main__':
+    # some table checks
+    for x in range(256):
+        y = TagFactory.create(x)
+        if y:
+            assert y.type == x, y.name + ' is misnamed'
+
+    for k, v in globals().items():
+        if k.startswith('Tag') and hasattr(v, 'TYPE'):
+            y = TagFactory.create(v.TYPE)
+            if y == None:
+                #print v.__name__, 'missing', 'for', v.TYPE
+                pass

+ 55 - 0
format_convert/swf/utils.py

@@ -0,0 +1,55 @@
+from .consts import BitmapType
+import math
+
+class NumberUtils(object):
+    @classmethod
+    def round_pixels_20(cls, pixels):
+        return round(pixels * 100) / 100
+    @classmethod
+    def round_pixels_400(cls, pixels):
+        return round(pixels * 10000) / 10000
+ 
+class ColorUtils(object):
+    @classmethod
+    def alpha(cls, color):
+        return int(color >> 24) / 255.0
+    
+    @classmethod
+    def rgb(cls, color):
+        return (color & 0xffffff)
+    
+    @classmethod
+    def to_rgb_string(cls, color):
+        c = "%x" % color
+        while len(c) < 6: c = "0" + c
+        return "#"+c
+        
+class ImageUtils(object):
+    @classmethod
+    def get_image_size(cls, data):
+        pass
+        
+    @classmethod
+    def get_image_type(cls, data):
+        pos = data.tell()
+        image_type = 0
+        data.seek(0, 2) # moves file pointer to final position
+        if data.tell() > 8:
+            data.seek(0)
+            b0 = ord(data.read(1))
+            b1 = ord(data.read(1))
+            b2 = ord(data.read(1))
+            b3 = ord(data.read(1))
+            b4 = ord(data.read(1))
+            b5 = ord(data.read(1))
+            b6 = ord(data.read(1))
+            b7 = ord(data.read(1))
+            if b0 == 0xff and (b1 == 0xd8 or 1 == 0xd9):
+                image_type = BitmapType.JPEG
+            elif b0 == 0x89 and b1 == 0x50 and b2 == 0x4e and b3 == 0x47 and \
+                b4 == 0x0d and b5 == 0x0a and b6 == 0x1a and b7 == 0x0a:
+                image_type = BitmapType.PNG
+            elif b0 == 0x47 and b1 == 0x49 and b2 == 0x46 and b3 == 0x38 and b4 == 0x39 and b5 == 0x61:
+                image_type = BitmapType.GIF89A
+        data.seek(pos)
+        return image_type