Remove submodule, just put Dependencies in ./libs

This commit is contained in:
Ruud
2011-02-28 15:25:42 +01:00
parent 2fb578d4de
commit 6888798ef6
541 changed files with 173394 additions and 4 deletions
@@ -0,0 +1,7 @@
from hachoir_parser.container.asn1 import ASN1File
from hachoir_parser.container.mkv import MkvFile
from hachoir_parser.container.ogg import OggFile, OggStream
from hachoir_parser.container.riff import RiffFile
from hachoir_parser.container.swf import SwfFile
from hachoir_parser.container.realmedia import RealMediaFile
@@ -0,0 +1,661 @@
"""
SWF (Macromedia/Adobe Flash) file parser.
Documentation:
- Alexis' SWF Reference:
http://www.m2osw.com/swf_alexref.html
- Tamarin ABC format:
http://www.m2osw.com/abc_format.html
Authors: Sebastien Ponce, Robert Xiao
Creation date: 26 April 2008
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, ParserError,
Bit, Bits, UInt8, UInt32, Int16, UInt16, Float32, Float64, CString, Enum,
Bytes, RawBytes, NullBits, String, SubFile, Field)
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
from hachoir_core.field.float import FloatExponent
from struct import unpack
class FlashPackedInteger(Bits):
def __init__(self, parent, name, signed=False, nbits=30, description=None):
Bits.__init__(self, parent, name, 8, description)
stream = self._parent.stream
addr = self.absolute_address
size = 0
value = 0
mult = 1
while True:
byte = stream.readBits(addr+size, 8, LITTLE_ENDIAN)
value += mult * (byte & 0x7f)
size += 8
mult <<= 7
if byte < 128:
break
self._size = size
if signed and (1 << (nbits-1)) <= value:
value -= (1 << nbits)
self.createValue = lambda: value
class FlashU30(FlashPackedInteger):
def __init__(self, parent, name, description=None):
FlashPackedInteger.__init__(self, parent, name, signed=False, nbits=30, description=description)
class FlashS32(FlashPackedInteger):
def __init__(self, parent, name, description=None):
FlashPackedInteger.__init__(self, parent, name, signed=True, nbits=32, description=description)
class FlashU32(FlashPackedInteger):
def __init__(self, parent, name, description=None):
FlashPackedInteger.__init__(self, parent, name, signed=False, nbits=32, description=description)
class FlashFloat64(FieldSet):
def createFields(self):
yield Bits(self, "mantissa_high", 20)
yield FloatExponent(self, "exponent", 11)
yield Bit(self, "negative")
yield Bits(self, "mantissa_low", 32)
def createValue(self):
# Manual computation:
# mantissa = mantissa_high * 2^32 + mantissa_low
# float = 2^exponent + (1 + mantissa / 2^52)
# (and float is negative if negative=True)
bytes = self.parent.stream.readBytes(
self.absolute_address, self.size//8)
# Mix bytes: xxxxyyyy <=> yyyyxxxx
bytes = bytes[4:8] + bytes[0:4]
return unpack('<d', bytes)[0]
TYPE_INFO = {
0x00: (CString, "Cstring[]"),
0x01: (Float32, "Float[]"),
0x02: (None, "Null[]"),
0x03: (None, "Undefined[]"),
0x04: (UInt8, "Register[]"),
0x05: (UInt8, "Boolean[]"),
0x06: (FlashFloat64, "Double[]"),
0x07: (UInt32, "Integer[]"),
0x08: (UInt8, "Dictionary_Lookup_Index[]"),
0x09: (UInt16, "Large_Dictionary_Lookup_Index[]"),
}
def parseBranch(parent, size):
yield Int16(parent, "offset")
def parseDeclareFunction(parent, size):
yield CString(parent, "name")
argCount = UInt16(parent, "arg_count")
yield argCount
for i in range(argCount.value):
yield CString(parent, "arg[]")
yield UInt16(parent, "function_length")
def parseDeclareFunctionV7(parent, size):
yield CString(parent, "name")
argCount = UInt16(parent, "arg_count")
yield argCount
yield UInt8(parent, "reg_count")
yield Bits(parent, "reserved", 7)
yield Bit(parent, "preload_global")
yield Bit(parent, "preload_parent")
yield Bit(parent, "preload_root")
yield Bit(parent, "suppress_super")
yield Bit(parent, "preload_super")
yield Bit(parent, "suppress_arguments")
yield Bit(parent, "preload_arguments")
yield Bit(parent, "suppress_this")
yield Bit(parent, "preload_this")
for i in range(argCount.value):
yield UInt8(parent, "register[]")
yield CString(parent, "arg[]")
yield UInt16(parent, "function_length")
def parseTry(parent, size):
yield Bits(parent, "reserved", 5)
catchInReg = Bit(parent, "catch_in_register")
yield catchInReg
yield Bit(parent, "finally")
yield Bit(parent, "catch")
yield UInt8(parent, "try_size")
yield UInt8(parent, "catch_size")
yield UInt8(parent, "finally_size")
if catchInReg.value:
yield CString(parent, "name")
else:
yield UInt8(parent, "register")
def parsePushData(parent, size):
while not parent.eof:
codeobj = UInt8(parent, "data_type[]")
yield codeobj
code = codeobj.value
if code not in TYPE_INFO:
raise ParserError("Unknown type in Push_Data : " + hex(code))
parser, name = TYPE_INFO[code]
if parser:
yield parser(parent, name)
# else:
# yield Field(parent, name, 0)
def parseSetTarget(parent, size):
yield CString(parent, "target")
def parseWith(parent, size):
yield UInt16(parent, "size")
def parseGetURL(parent, size):
yield CString(parent, "url")
yield CString(parent, "target")
def parseGetURL2(parent, size):
yield UInt8(parent, "method")
def parseGotoExpression(parent, size):
yield UInt8(parent, "play")
def parseGotoFrame(parent, size):
yield UInt16(parent, "frame_no")
def parseGotoLabel(parent, size):
yield CString(parent, "label")
def parseWaitForFrame(parent, size):
yield UInt16(parent, "frame")
yield UInt8(parent, "skip")
def parseWaitForFrameDyn(parent, size):
yield UInt8(parent, "skip")
def parseDeclareDictionary(parent, size):
count = UInt16(parent, "count")
yield count
for i in range(count.value):
yield CString(parent, "dictionnary[]")
def parseStoreRegister(parent, size):
yield UInt8(parent, "register")
def parseStrictMode(parent, size):
yield UInt8(parent, "strict")
class Instruction(FieldSet):
ACTION_INFO = {
0x00: ("end[]", "End", None),
0x99: ("Branch_Always[]", "Branch Always", parseBranch),
0x9D: ("Branch_If_True[]", "Branch If True", parseBranch),
0x3D: ("Call_Function[]", "Call Function", None),
0x52: ("Call_Method[]", "Call Method", None),
0x9B: ("Declare_Function[]", "Declare Function", parseDeclareFunction),
0x8E: ("Declare_Function_V7[]", "Declare Function (V7)", parseDeclareFunctionV7),
0x3E: ("Return[]", "Return", None),
0x2A: ("Throw[]", "Throw", None),
0x8F: ("Try[]", "Try", parseTry),
# Stack Control
0x4C: ("Duplicate[]", "Duplicate", None),
0x96: ("Push_Data[]", "Push Data", parsePushData),
0x4D: ("Swap[]", "Swap", None),
# Action Script Context
0x8B: ("Set_Target[]", "Set Target", parseSetTarget),
0x20: ("Set_Target_dynamic[]", "Set Target (dynamic)", None),
0x94: ("With[]", "With", parseWith),
# Movie Control
0x9E: ("Call_Frame[]", "Call Frame", None),
0x83: ("Get_URL[]", "Get URL", parseGetURL),
0x9A: ("Get_URL2[]", "Get URL2", parseGetURL2),
0x9F: ("Goto_Expression[]", "Goto Expression", parseGotoExpression),
0x81: ("Goto_Frame[]", "Goto Frame", parseGotoFrame),
0x8C: ("Goto_Label[]", "Goto Label", parseGotoLabel),
0x04: ("Next_Frame[]", "Next Frame", None),
0x06: ("Play[]", "Play", None),
0x05: ("Previous_Frame[]", "Previous Frame", None),
0x07: ("Stop[]", "Stop", None),
0x08: ("Toggle_Quality[]", "Toggle Quality", None),
0x8A: ("Wait_For_Frame[]", "Wait For Frame", parseWaitForFrame),
0x8D: ("Wait_For_Frame_dynamic[]", "Wait For Frame (dynamic)", parseWaitForFrameDyn),
# Sound
0x09: ("Stop_Sound[]", "Stop Sound", None),
# Arithmetic
0x0A: ("Add[]", "Add", None),
0x47: ("Add_typed[]", "Add (typed)", None),
0x51: ("Decrement[]", "Decrement", None),
0x0D: ("Divide[]", "Divide", None),
0x50: ("Increment[]", "Increment", None),
0x18: ("Integral_Part[]", "Integral Part", None),
0x3F: ("Modulo[]", "Modulo", None),
0x0C: ("Multiply[]", "Multiply", None),
0x4A: ("Number[]", "Number", None),
0x0B: ("Subtract[]", "Subtract", None),
# Comparisons
0x0E: ("Equal[]", "Equal", None),
0x49: ("Equal_typed[]", "Equal (typed)", None),
0x66: ("Strict_Equal[]", "Strict Equal", None),
0x67: ("Greater_Than_typed[]", "Greater Than (typed)", None),
0x0F: ("Less_Than[]", "Less Than", None),
0x48: ("Less_Than_typed[]", "Less Than (typed)", None),
0x13: ("String_Equal[]", "String Equal", None),
0x68: ("String_Greater_Than[]", "String Greater Than", None),
0x29: ("String_Less_Than[]", "String Less Than", None),
# Logical and Bit Wise
0x60: ("And[]", "And", None),
0x10: ("Logical_And[]", "Logical And", None),
0x12: ("Logical_Not[]", "Logical Not", None),
0x11: ("Logical_Or[]", "Logical Or", None),
0x61: ("Or[]", "Or", None),
0x63: ("Shift_Left[]", "Shift Left", None),
0x64: ("Shift_Right[]", "Shift Right", None),
0x65: ("Shift_Right_Unsigned[]", "Shift Right Unsigned", None),
0x62: ("Xor[]", "Xor", None),
# Strings & Characters (See the String Object also)
0x33: ("Chr[]", "Chr", None),
0x37: ("Chr_multi-bytes[]", "Chr (multi-bytes)", None),
0x21: ("Concatenate_Strings[]", "Concatenate Strings", None),
0x32: ("Ord[]", "Ord", None),
0x36: ("Ord_multi-bytes[]", "Ord (multi-bytes)", None),
0x4B: ("String[]", "String", None),
0x14: ("String_Length[]", "String Length", None),
0x31: ("String_Length_multi-bytes[]", "String Length (multi-bytes)", None),
0x15: ("SubString[]", "SubString", None),
0x35: ("SubString_multi-bytes[]", "SubString (multi-bytes)", None),
# Properties
0x22: ("Get_Property[]", "Get Property", None),
0x23: ("Set_Property[]", "Set Property", None),
# Objects
0x2B: ("Cast_Object[]", "Cast Object", None),
0x42: ("Declare_Array[]", "Declare Array", None),
0x88: ("Declare_Dictionary[]", "Declare Dictionary", parseDeclareDictionary),
0x43: ("Declare_Object[]", "Declare Object", None),
0x3A: ("Delete[]", "Delete", None),
0x3B: ("Delete_All[]", "Delete All", None),
0x24: ("Duplicate_Sprite[]", "Duplicate Sprite", None),
0x46: ("Enumerate[]", "Enumerate", None),
0x55: ("Enumerate_Object[]", "Enumerate Object", None),
0x69: ("Extends[]", "Extends", None),
0x4E: ("Get_Member[]", "Get Member", None),
0x45: ("Get_Target[]", "Get Target", None),
0x2C: ("Implements[]", "Implements", None),
0x54: ("Instance_Of[]", "Instance Of", None),
0x40: ("New[]", "New", None),
0x53: ("New_Method[]", "New Method", None),
0x25: ("Remove_Sprite[]", "Remove Sprite", None),
0x4F: ("Set_Member[]", "Set Member", None),
0x44: ("Type_Of[]", "Type Of", None),
# Variables
0x41: ("Declare_Local_Variable[]", "Declare Local Variable", None),
0x1C: ("Get_Variable[]", "Get Variable", None),
0x3C: ("Set_Local_Variable[]", "Set Local Variable", None),
0x1D: ("Set_Variable[]", "Set Variable", None),
# Miscellaneous
0x2D: ("FSCommand2[]", "FSCommand2", None),
0x34: ("Get_Timer[]", "Get Timer", None),
0x30: ("Random[]", "Random", None),
0x27: ("Start_Drag[]", "Start Drag", None),
0x28: ("Stop_Drag[]", "Stop Drag", None),
0x87: ("Store_Register[]", "Store Register", parseStoreRegister),
0x89: ("Strict_Mode[]", "Strict Mode", parseStrictMode),
0x26: ("Trace[]", "Trace", None),
}
def __init__(self, *args):
FieldSet.__init__(self, *args)
code = self["action_id"].value
if code & 128:
self._size = (3 + self["action_length"].value) * 8
else:
self._size = 8
if code in self.ACTION_INFO:
self._name, self._description, self.parser = self.ACTION_INFO[code]
else:
self.parser = None
def createFields(self):
yield Bits(self, "action_id", 8)
if not (self["action_id"].value & 128):
return
yield UInt16(self, "action_length")
size = self["action_length"].value
if not size:
return
if self.parser:
for field in self.parser(self, size):
yield field
else:
yield RawBytes(self, "action_data", size)
def createDescription(self):
return self._description
def __str__(self):
r = str(self._description)
for f in self:
if f.name not in ("action_id", "action_length", "count") and not f.name.startswith("data_type") :
r = r + "\n " + str((self.address+f.address)/8) + " " + str(f.name) + "=" + str(f.value)
return r
class ActionScript(FieldSet):
def createFields(self):
while not self.eof:
yield Instruction(self, "instr[]")
def __str__(self):
r = ""
for f in self:
r = r + str(f.address/8) + " " + str(f) + "\n"
return r
def parseActionScript(parent, size):
yield ActionScript(parent, "action", size=size*8)
def FindABC(field):
while not getattr(field, "isABC", False):
field = field.parent
if field is None:
return None
return field
def GetConstant(field, pool, index):
if index == 0:
return None
return FindABC(field)["constant_%s_pool/constant[%i]"%(pool, index)]
def GetMultiname(field, index):
fld = GetConstant(field, "multiname", index)
if fld is None:
return "*"
if "name_index" not in fld:
return "?"
fld2 = GetConstant(fld, "string", fld["name_index"].value)
if fld2 is None:
return "*"
return fld2.value
class ABCStringIndex(FlashU30):
def createDisplay(self):
fld = GetConstant(self, "string", self.value)
if fld is None:
return "*"
return fld.value
class ABCNSIndex(FlashU30):
def createDisplay(self):
fld = GetConstant(self, "namespace", self.value)
if fld is None:
return "*"
return fld.display
class ABCMethodIndex(FlashU30):
def createDisplay(self):
fld = FindABC(self)["method_array/method[%i]"%self.value]
if fld is None:
return "*"
return fld.description
class ABCMultinameIndex(FlashU30):
def createDisplay(self):
return GetMultiname(self, self.value)
class ABCConstantPool(FieldSet):
def __init__(self, parent, name, klass):
FieldSet.__init__(self, parent, 'constant_%s_pool'%name)
self.klass = klass
def createFields(self):
ctr = FlashU30(self, "count")
yield ctr
for i in xrange(ctr.value-1):
yield self.klass(self, "constant[%i]"%(i+1))
class ABCObjectArray(FieldSet):
def __init__(self, parent, name, klass):
self.arrname = name
FieldSet.__init__(self, parent, name+'_array')
self.klass = klass
def createFields(self):
ctr = FlashU30(self, "count")
yield ctr
for i in xrange(ctr.value):
yield self.klass(self, self.arrname+"[]")
class ABCClassArray(FieldSet):
def __init__(self, parent, name):
FieldSet.__init__(self, parent, name+'_array')
def createFields(self):
ctr = FlashU30(self, "count")
yield ctr
for i in xrange(ctr.value):
yield ABCInstanceInfo(self, "instance[]")
for i in xrange(ctr.value):
yield ABCClassInfo(self, "class[]")
class ABCConstantString(FieldSet):
def createFields(self):
yield FlashU30(self, "length")
size = self["length"].value
if size:
yield String(self, "data", size, charset="UTF-8")
def createDisplay(self):
if "data" in self:
return self["data"].display
else:
return "<empty>"
def createValue(self):
if "data" in self:
return self["data"].value
else:
return ""
class ABCConstantNamespace(FieldSet):
NAMESPACE_KIND = {8: "Namespace",
5: "PrivateNamespace",
22: "PackageNamespace",
23: "PacakgeInternalNamespace",
24: "ProtectedNamespace",
25: "ExplicitNamespace",
26: "MultinameL"}
def createFields(self):
yield Enum(UInt8(self, "kind"), self.NAMESPACE_KIND)
yield ABCStringIndex(self, "name_index")
def createDisplay(self):
return "%s %s"%(self["kind"].display, self["name_index"].display)
def createValue(self):
return self["name_index"].value
class ABCConstantNamespaceSet(FieldSet):
def createFields(self):
ctr = FlashU30(self, "namespace_count")
yield ctr
for i in xrange(ctr.value):
yield ABCNSIndex(self, "namespace_index[]")
def createDescription(self):
ret = [fld.display for fld in self.array("namespace_index")]
return ', '.join(ret)
class ABCConstantMultiname(FieldSet):
MULTINAME_KIND = {7: "Qname",
13: "QnameA",
9: "Multiname",
14: "MultinameA",
15: "RTQname",
16: "RTQnameA",
27: "MultinameL",
17: "RTQnameL",
18: "RTQnameLA"}
def createFields(self):
yield Enum(UInt8(self, "kind"), self.MULTINAME_KIND)
kind = self["kind"].value
if kind in (7,13): # Qname
yield FlashU30(self, "namespace_index")
yield ABCStringIndex(self, "name_index")
elif kind in (9,14): # Multiname
yield ABCStringIndex(self, "name_index")
yield FlashU30(self, "namespace_set_index")
elif kind in (15,16): # RTQname
yield ABCStringIndex(self, "name_index")
elif kind == 27: # MultinameL
yield FlashU30(self, "namespace_set_index")
elif kind in (17,18): # RTQnameL
pass
def createDisplay(self):
kind = self["kind"].display
if "name_index" in self:
return kind + " " + self["name_index"].display
return kind
def createValue(self):
return self["kind"].value
class ABCTrait(FieldSet):
TRAIT_KIND = {0: "slot",
1: "method",
2: "getter",
3: "setter",
4: "class",
5: "function",
6: "const",}
def createFields(self):
yield ABCMultinameIndex(self, "name_index")
yield Enum(Bits(self, "kind", 4), self.TRAIT_KIND)
yield Enum(Bit(self, "is_final"), {True:'final',False:'virtual'})
yield Enum(Bit(self, "is_override"), {True:'override',False:'new'})
yield Bit(self, "has_metadata")
yield Bits(self, "unused", 1)
kind = self["kind"].value
if kind in (0,6): # slot, const
yield FlashU30(self, "slot_id")
yield ABCMultinameIndex(self, "type_index")
### TODO reference appropriate constant pool using value_kind
yield FlashU30(self, "value_index")
if self['value_index'].value != 0:
yield UInt8(self, "value_kind")
elif kind in (1,2,3): # method, getter, setter
yield FlashU30(self, "disp_id")
yield ABCMethodIndex(self, "method_info")
elif kind == 4: # class
yield FlashU30(self, "disp_id")
yield FlashU30(self, "class_info")
elif kind == 5: # function
yield FlashU30(self, "disp_id")
yield ABCMethodIndex(self, "method_info")
if self['has_metadata'].value:
yield ABCObjectArray(self, "metadata", FlashU30)
class ABCValueKind(FieldSet):
def createFields(self):
yield FlashU30(self, "value_index")
yield UInt8(self, "value_kind")
class ABCMethodInfo(FieldSet):
def createFields(self):
yield FlashU30(self, "param_count")
yield ABCMultinameIndex(self, "ret_type")
for i in xrange(self["param_count"].value):
yield ABCMultinameIndex(self, "param_type[]")
yield ABCStringIndex(self, "name_index")
yield Bit(self, "need_arguments")
yield Bit(self, "need_activation")
yield Bit(self, "need_rest")
yield Bit(self, "has_optional")
yield Bit(self, "ignore_rest")
yield Bit(self, "explicit")
yield Bit(self, "setsdxns")
yield Bit(self, "has_paramnames")
if self["has_optional"].value:
yield ABCObjectArray(self, "optional", ABCValueKind)
if self["has_paramnames"].value:
for i in xrange(self["param_count"].value):
yield FlashU30(self, "param_name[]")
def createDescription(self):
ret = GetMultiname(self, self["ret_type"].value)
ret += " " + self["name_index"].display
ret += "(" + ", ".join(GetMultiname(self, fld.value) for fld in self.array("param_type")) + ")"
return ret
class ABCMetadataInfo(FieldSet):
def createFields(self):
yield ABCStringIndex(self, "name_index")
yield FlashU30(self, "values_count")
count = self["values_count"].value
for i in xrange(count):
yield FlashU30(self, "key[]")
for i in xrange(count):
yield FlashU30(self, "value[]")
class ABCInstanceInfo(FieldSet):
def createFields(self):
yield ABCMultinameIndex(self, "name_index")
yield ABCMultinameIndex(self, "super_index")
yield Bit(self, "is_sealed")
yield Bit(self, "is_final")
yield Bit(self, "is_interface")
yield Bit(self, "is_protected")
yield Bits(self, "unused", 4)
if self['is_protected'].value:
yield ABCNSIndex(self, "protectedNS")
yield FlashU30(self, "interfaces_count")
for i in xrange(self["interfaces_count"].value):
yield ABCMultinameIndex(self, "interface[]")
yield ABCMethodIndex(self, "iinit_index")
yield ABCObjectArray(self, "trait", ABCTrait)
class ABCClassInfo(FieldSet):
def createFields(self):
yield ABCMethodIndex(self, "cinit_index")
yield ABCObjectArray(self, "trait", ABCTrait)
class ABCScriptInfo(FieldSet):
def createFields(self):
yield ABCMethodIndex(self, "init_index")
yield ABCObjectArray(self, "trait", ABCTrait)
class ABCException(FieldSet):
def createFields(self):
yield FlashU30(self, "start")
yield FlashU30(self, "end")
yield FlashU30(self, "target")
yield FlashU30(self, "type_index")
yield FlashU30(self, "name_index")
class ABCMethodBody(FieldSet):
def createFields(self):
yield ABCMethodIndex(self, "method_info")
yield FlashU30(self, "max_stack")
yield FlashU30(self, "max_regs")
yield FlashU30(self, "scope_depth")
yield FlashU30(self, "max_scope")
yield FlashU30(self, "code_length")
yield RawBytes(self, "code", self['code_length'].value)
yield ABCObjectArray(self, "exception", ABCException)
yield ABCObjectArray(self, "trait", ABCTrait)
def parseABC(parent, size):
code = parent["code"].value
if code == parent.TAG_DO_ABC_DEFINE:
yield UInt32(parent, "action_flags")
yield CString(parent, "action_name")
yield UInt16(parent, "minor_version")
yield UInt16(parent, "major_version")
parent.isABC = True
yield ABCConstantPool(parent, "int", FlashS32)
yield ABCConstantPool(parent, "uint", FlashU32)
yield ABCConstantPool(parent, "double", Float64)
yield ABCConstantPool(parent, "string", ABCConstantString)
yield ABCConstantPool(parent, "namespace", ABCConstantNamespace)
yield ABCConstantPool(parent, "namespace_set", ABCConstantNamespaceSet)
yield ABCConstantPool(parent, "multiname", ABCConstantMultiname)
yield ABCObjectArray(parent, "method", ABCMethodInfo)
yield ABCObjectArray(parent, "metadata", ABCMetadataInfo)
yield ABCClassArray(parent, "class")
yield ABCObjectArray(parent, "script", ABCScriptInfo)
yield ABCObjectArray(parent, "body", ABCMethodBody)
+282
View File
@@ -0,0 +1,282 @@
"""
Abstract Syntax Notation One (ASN.1) parser.
Technical informations:
* PER standard
http://www.tu.int/ITU-T/studygroups/com17/languages/X.691-0207.pdf
* Python library
http://pyasn1.sourceforge.net/
* Specification of Abstract Syntax Notation One (ASN.1)
ISO/IEC 8824:1990 Information Technology
* Specification of Basic Encoding Rules (BER) for ASN.1
ISO/IEC 8825:1990 Information Technology
* OpenSSL asn1parser, use command:
openssl asn1parse -i -inform DER -in file.der
* ITU-U recommendations:
http://www.itu.int/rec/T-REC-X/en
(X.680, X.681, X.682, X.683, X.690, X.691, X.692, X.693, X.694)
* dumpasn1
http://www.cs.auckland.ac.nz/~pgut001/dumpasn1.c
General information:
* Wikipedia (english) article
http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
* ASN.1 information site
http://asn1.elibel.tm.fr/en/
* ASN.1 consortium
http://www.asn1.org/
Encodings:
* Basic Encoding Rules (BER)
* Canonical Encoding Rules (CER) -- DER derivative that is not widely used
* Distinguished Encoding Rules (DER) -- used for encrypted applications
* XML Encoding Rules (XER)
* Packed Encoding Rules (PER) -- result in the fewest number of bytes
* Generic String Encoding Rules (GSER)
=> Are encodings compatibles? Which encodings are supported??
Author: Victor Stinner
Creation date: 24 september 2006
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet,
FieldError, ParserError,
Bit, Bits, Bytes, UInt8, GenericInteger, String,
Field, Enum, RawBytes)
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.tools import createDict, humanDatetime
from hachoir_core.stream import InputStreamError
from hachoir_core.text_handler import textHandler
# --- Field parser ---
class ASNInteger(Field):
"""
Integer: two cases:
- first byte in 0..127: it's the value
- first byte in 128..255: byte & 127 is the number of bytes,
next bytes are the value
"""
def __init__(self, parent, name, description=None):
Field.__init__(self, parent, name, 8, description)
stream = self._parent.stream
addr = self.absolute_address
value = stream.readBits(addr, 8, BIG_ENDIAN)
if 128 <= value:
nbits = (value & 127) * 8
if not nbits:
raise ParserError("ASN.1: invalid ASN integer size (zero)")
if 64 < nbits:
# Arbitrary limit to catch errors
raise ParserError("ASN.1: ASN integer is limited to 64 bits")
self._size = 8 + nbits
value = stream.readBits(addr+8, nbits, BIG_ENDIAN)
self.createValue = lambda: value
class OID_Integer(Bits):
def __init__(self, parent, name, description=None):
Bits.__init__(self, parent, name, 8, description)
stream = self._parent.stream
addr = self.absolute_address
size = 8
value = 0
byte = stream.readBits(addr, 8, BIG_ENDIAN)
value = byte & 127
while 128 <= byte:
addr += 8
size += 8
if 64 < size:
# Arbitrary limit to catch errors
raise ParserError("ASN.1: Object identifier is limited 64 bits")
byte = stream.readBits(addr, 8, BIG_ENDIAN)
value = (value << 7) + (byte & 127)
self._size = size
self.createValue = lambda: value
def readSequence(self, content_size):
while self.current_size < self.size:
yield Object(self, "item[]")
def readSet(self, content_size):
yield Object(self, "value", size=content_size*8)
def readASCIIString(self, content_size):
yield String(self, "value", content_size, charset="ASCII")
def readUTF8String(self, content_size):
yield String(self, "value", content_size, charset="UTF-8")
def readBMPString(self, content_size):
yield String(self, "value", content_size, charset="UTF-16")
def readBitString(self, content_size):
yield UInt8(self, "padding_size", description="Number of unused bits")
if content_size > 1:
yield Bytes(self, "value", content_size-1)
def readOctetString(self, content_size):
yield Bytes(self, "value", content_size)
def formatObjectID(fieldset):
text = [ fieldset["first"].display ]
items = [ field for field in fieldset if field.name.startswith("item[") ]
text.extend( str(field.value) for field in items )
return ".".join(text)
def readObjectID(self, content_size):
yield textHandler(UInt8(self, "first"), formatFirstObjectID)
while self.current_size < self.size:
yield OID_Integer(self, "item[]")
def readBoolean(self, content_size):
if content_size != 1:
raise ParserError("Overlong boolean: got %s bytes, expected 1 byte"%content_size)
yield textHandler(UInt8(self, "value"), lambda field:str(bool(field.value)))
def readInteger(self, content_size):
# Always signed?
yield GenericInteger(self, "value", True, content_size*8)
# --- Format ---
def formatFirstObjectID(field):
value = field.value
return "%u.%u" % (value // 40, value % 40)
def formatValue(fieldset):
return fieldset["value"].display
def formatUTCTime(fieldset):
import datetime
value = fieldset["value"].value
year = int(value[0:2])
if year < 50:
year += 2000
else:
year += 1900
month = int(value[2:4])
day = int(value[4:6])
hour = int(value[6:8])
minute = int(value[8:10])
if value[-1] == "Z":
second = int(value[10:12])
dt = datetime.datetime(year, month, day, hour, minute, second)
else:
# Skip timezone...
dt = datetime.datetime(year, month, day, hour, minute)
return humanDatetime(dt)
# --- Object parser ---
class Object(FieldSet):
TYPE_INFO = {
0: ("end[]", None, "End (reserved for BER, None)", None), # TODO: Write parser
1: ("boolean[]", readBoolean, "Boolean", None),
2: ("integer[]", readInteger, "Integer", None),
3: ("bit_str[]", readBitString, "Bit string", None),
4: ("octet_str[]", readOctetString, "Octet string", None),
5: ("null[]", None, "NULL (empty, None)", None),
6: ("obj_id[]", readObjectID, "Object identifier", formatObjectID),
7: ("obj_desc[]", None, "Object descriptor", None), # TODO: Write parser
8: ("external[]", None, "External, instance of", None), # TODO: Write parser # External?
9: ("real[]", readASCIIString, "Real number", None), # TODO: Write parser
10: ("enum[]", readInteger, "Enumerated", None),
11: ("embedded[]", None, "Embedded PDV", None), # TODO: Write parser
12: ("utf8_str[]", readUTF8String, "Printable string", None),
13: ("rel_obj_id[]", None, "Relative object identifier", None), # TODO: Write parser
14: ("time[]", None, "Time", None), # TODO: Write parser
# 15: invalid??? sequence of???
16: ("seq[]", readSequence, "Sequence", None),
17: ("set[]", readSet, "Set", None),
18: ("num_str[]", readASCIIString, "Numeric string", None),
19: ("print_str[]", readASCIIString, "Printable string", formatValue),
20: ("teletex_str[]", readASCIIString, "Teletex (T61, None) string", None),
21: ("videotex_str[]", readASCIIString, "Videotex string", None),
22: ("ia5_str[]", readASCIIString, "IA5 string", formatValue),
23: ("utc_time[]", readASCIIString, "UTC time", formatUTCTime),
24: ("general_time[]", readASCIIString, "Generalized time", None),
25: ("graphic_str[]", readASCIIString, "Graphic string", None),
26: ("visible_str[]", readASCIIString, "Visible (ISO64, None) string", None),
27: ("general_str[]", readASCIIString, "General string", None),
28: ("universal_str[]", readASCIIString, "Universal string", None),
29: ("unrestricted_str[]", readASCIIString, "Unrestricted string", None),
30: ("bmp_str[]", readBMPString, "BMP string", None),
# 31: multiple octet tag number, TODO: not supported
# Extended tag values:
# 31: Date
# 32: Time of day
# 33: Date-time
# 34: Duration
}
TYPE_DESC = createDict(TYPE_INFO, 2)
CLASS_DESC = {0: "universal", 1: "application", 2: "context", 3: "private"}
FORM_DESC = {False: "primitive", True: "constructed"}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
key = self["type"].value & 31
if self['class'].value == 0:
# universal object
if key in self.TYPE_INFO:
self._name, self._handler, self._description, create_desc = self.TYPE_INFO[key]
if create_desc:
self.createDescription = lambda: "%s: %s" % (self.TYPE_INFO[key][2], create_desc(self))
self._description = None
elif key == 31:
raise ParserError("ASN.1 Object: tag bigger than 30 are not supported")
else:
self._handler = None
elif self['form'].value:
# constructed: treat as sequence
self._name = 'seq[]'
self._handler = readSequence
self._description = 'constructed object type %i' % key
else:
# primitive, context/private
self._name = 'raw[]'
self._handler = readASCIIString
self._description = '%s object type %i' % (self['class'].display, key)
field = self["size"]
self._size = field.address + field.size + field.value*8
def createFields(self):
yield Enum(Bits(self, "class", 2), self.CLASS_DESC)
yield Enum(Bit(self, "form"), self.FORM_DESC)
if self['class'].value == 0:
yield Enum(Bits(self, "type", 5), self.TYPE_DESC)
else:
yield Bits(self, "type", 5)
yield ASNInteger(self, "size", "Size in bytes")
size = self["size"].value
if size:
if self._handler:
for field in self._handler(self, size):
yield field
else:
yield RawBytes(self, "raw", size)
class ASN1File(Parser):
PARSER_TAGS = {
"id": "asn1",
"category": "container",
"file_ext": ("der",),
"min_size": 16,
"description": "Abstract Syntax Notation One (ASN.1)"
}
endian = BIG_ENDIAN
def validate(self):
try:
root = self[0]
except (InputStreamError, FieldError):
return "Unable to create root object"
if root.size != self.size:
return "Invalid root object size"
return True
def createFields(self):
yield Object(self, "root")
+598
View File
@@ -0,0 +1,598 @@
#
# Matroska parser
# Author Julien Muchembled <jm AT jm10.no-ip.com>
# Created: 8 june 2006
#
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, Link,
MissingField, ParserError,
Enum as _Enum, String as _String,
Float32, Float64,
NullBits, Bits, Bit, RawBytes, Bytes,
Int16, GenericInteger)
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.iso639 import ISO639_2
from hachoir_core.tools import humanDatetime
from hachoir_core.text_handler import textHandler, hexadecimal
from hachoir_parser.container.ogg import XiphInt
from datetime import datetime, timedelta
class RawInt(GenericInteger):
"""
Raw integer: have to be used in BIG_ENDIAN!
"""
def __init__(self, parent, name, description=None):
GenericInteger.__init__(self, parent, name, False, 8, description)
i = GenericInteger.createValue(self)
if i == 0:
raise ParserError('Invalid integer length!')
while i < 0x80:
self._size += 8
i <<= 1
class Unsigned(RawInt):
def __init__(self, parent, name, description=None):
RawInt.__init__(self, parent, name, description)
def hasValue(self):
return True
def createValue(self):
header = 1 << self._size / 8 * 7
value = RawInt.createValue(self) - header
if value + 1 == header:
return None
return value
class Signed(Unsigned):
def createValue(self):
header = 1 << self._size / 8 * 7 - 1
value = RawInt.createValue(self) - 3 * header + 1
if value == header:
return None
return value
def Enum(parent, enum):
return _Enum(GenericInteger(parent, 'enum', False, parent['size'].value*8), enum)
def Bool(parent):
return textHandler(GenericInteger(parent, 'bool', False, parent['size'].value*8),
lambda chunk: str(chunk.value != 0))
def UInt(parent):
return GenericInteger(parent, 'unsigned', False, parent['size'].value*8)
def SInt(parent):
return GenericInteger(parent, 'signed', True, parent['size'].value*8)
def String(parent):
return _String(parent, 'string', parent['size'].value, charset="ASCII")
def EnumString(parent, enum):
return _Enum(String(parent), enum)
def Binary(parent):
return RawBytes(parent, 'binary', parent['size'].value)
class AttachedFile(Bytes):
def __init__(self, parent):
Bytes.__init__(self, parent, 'file', parent['size'].value, None)
def _getFilename(self):
if not hasattr(self, "_filename"):
try:
self._filename = self["../../FileName/unicode"].value
except MissingField:
self._filename = None
return self._filename
def createDescription(self):
filename = self._getFilename()
if filename:
return 'File "%s"' % filename
return "('Filename' entry not found)"
def _createInputStream(self, **args):
tags = args.setdefault("tags",[])
try:
tags.append(("mime", self["../../FileMimeType/string"].value))
except MissingField:
pass
filename = self._getFilename()
if filename:
tags.append(("filename", filename))
return Bytes._createInputStream(self, **args)
def UTF8(parent):
return _String(parent,'unicode', parent['size'].value, charset='UTF-8')
def Float(parent):
size = parent['size'].value
if size == 4:
return Float32(parent, 'float')
elif size == 8:
return Float64(parent, 'double')
else:
return RawBytes(parent, 'INVALID_FLOAT', size)
TIMESTAMP_T0 = datetime(2001, 1, 1)
def dateToDatetime(value):
return TIMESTAMP_T0 + timedelta(microseconds=value//1000)
def dateToString(field):
return humanDatetime(dateToDatetime(field.value))
def Date(parent):
return textHandler(GenericInteger(parent, 'date', True, parent['size'].value*8),
dateToString)
def SeekID(parent):
return textHandler(GenericInteger(parent, 'binary', False, parent['size'].value*8),
lambda chunk: segment.get(chunk.value, (hexadecimal(chunk),))[0])
def CueClusterPosition(parent):
class Cluster(Link):
def createValue(self):
parent = self.parent
segment = parent['.....']
pos = parent['unsigned'].value * 8 + segment[2].address
return segment.getFieldByAddress(pos, feed=False)
return Cluster(parent, 'cluster')
def CueTrackPositions(parent):
class Block(Link):
def createValue(self):
parent = self.parent
time = parent['../CueTime/unsigned'].value
track = parent['CueTrack/unsigned'].value
cluster = parent['CueClusterPosition/cluster'].value
time -= cluster['Timecode/unsigned'].value
for field in cluster:
if field.name.startswith('BlockGroup['):
for path in 'Block/block', 'SimpleBlock':
try:
block = field[path]
if block['track'].value == track and \
block['timecode'].value == time:
return field
except MissingField:
pass
parent.error('Cue point not found')
return self
return Block(parent, 'block')
class Lace(FieldSet):
def __init__(self, parent, lacing, size):
self.n_frames = parent['n_frames'].value
self.createFields = ( self.parseXiph, self.parseFixed, self.parseEBML )[lacing]
FieldSet.__init__(self, parent, 'Lace', size=size * 8)
def parseXiph(self):
for i in xrange(self.n_frames):
yield XiphInt(self, 'size[]')
for i in xrange(self.n_frames):
yield RawBytes(self, 'frame[]', self['size['+str(i)+']'].value)
yield RawBytes(self,'frame[]', (self._size - self.current_size) / 8)
def parseEBML(self):
yield Unsigned(self, 'size')
for i in xrange(1, self.n_frames):
yield Signed(self, 'dsize[]')
size = self['size'].value
yield RawBytes(self, 'frame[]', size)
for i in xrange(self.n_frames-1):
size += self['dsize['+str(i)+']'].value
yield RawBytes(self, 'frame[]', size)
yield RawBytes(self,'frame[]', (self._size - self.current_size) / 8)
def parseFixed(self):
n = self.n_frames + 1
size = self._size / 8 / n
for i in xrange(n):
yield RawBytes(self, 'frame[]', size)
class Block(FieldSet):
def __init__(self, parent):
FieldSet.__init__(self, parent, 'block')
self._size = 8 * parent['size'].value
def lacing(self):
return _Enum(Bits(self, 'lacing', 2), [ 'none', 'Xiph', 'fixed', 'EBML' ])
def createFields(self):
yield Unsigned(self, 'track')
yield Int16(self, 'timecode')
if self.parent._name == 'Block':
yield NullBits(self, 'reserved[]', 4)
yield Bit(self, 'invisible')
yield self.lacing()
yield NullBits(self, 'reserved[]', 1)
elif self.parent._name == 'SimpleBlock[]':
yield Bit(self, 'keyframe')
yield NullBits(self, 'reserved', 3)
yield Bit(self, 'invisible')
yield self.lacing()
yield Bit(self, 'discardable')
else:
yield NullBits(self, 'reserved', 8)
return
size = (self._size - self.current_size) / 8
lacing = self['lacing'].value
if lacing:
yield textHandler(GenericInteger(self, 'n_frames', False, 8),
lambda chunk: str(chunk.value+1))
yield Lace(self, lacing - 1, size - 1)
else:
yield RawBytes(self,'frame', size)
ebml = {
0x1A45DFA3: ('EBML[]', {
0x4286: ('EBMLVersion',UInt),
0x42F7: ('EBMLReadVersion',UInt),
0x42F2: ('EBMLMaxIDLength',UInt),
0x42F3: ('EBMLMaxSizeLength',UInt),
0x4282: ('DocType',String),
0x4287: ('DocTypeVersion',UInt),
0x4285: ('DocTypeReadVersion',UInt)
})
}
signature = {
0x7E8A: ('SignatureAlgo', UInt),
0x7E9A: ('SignatureHash', UInt),
0x7EA5: ('SignaturePublicKey', Binary),
0x7EB5: ('Signature', Binary),
0x7E5B: ('SignatureElements', {
0x7E7B: ('SignatureElementList[]', {
0x6532: ('SignedElement[]', Binary)
})
})
}
chapter_atom = {
0x73C4: ('ChapterUID', UInt),
0x91: ('ChapterTimeStart', UInt),
0x92: ('ChapterTimeEnd', UInt),
0x98: ('ChapterFlagHidden', Bool),
0x4598: ('ChapterFlagEnabled', Bool),
0x6E67: ('ChapterSegmentUID', Binary),
0x6EBC: ('ChapterSegmentEditionUID', Binary),
0x63C3: ('ChapterPhysicalEquiv', UInt),
0x8F: ('ChapterTrack', {
0x89: ('ChapterTrackNumber[]', UInt)
}),
0x80: ('ChapterDisplay[]', {
0x85: ('ChapString', UTF8),
0x437C: ('ChapLanguage[]', String),
0x437E: ('ChapCountry[]', String)
}),
0x6944: ('ChapProcess[]', {
0x6955: ('ChapProcessCodecID', UInt),
0x450D: ('ChapProcessPrivate', Binary),
0x6911: ('ChapProcessCommand[]', {
0x6922: ('ChapProcessTime', UInt),
0x6933: ('ChapProcessData', Binary)
})
})
}
simple_tag = {
0x45A3: ('TagName', UTF8),
0x447A: ('TagLanguage', String),
0x44B4: ('TagDefault', Bool), # 0x4484
0x4487: ('TagString', UTF8),
0x4485: ('TagBinary', Binary)
}
segment_seek = {
0x4DBB: ('Seek[]', {
0x53AB: ('SeekID', SeekID),
0x53AC: ('SeekPosition', UInt)
})
}
segment_info = {
0x73A4: ('SegmentUID', Binary),
0x7384: ('SegmentFilename', UTF8),
0x3CB923: ('PrevUID', Binary),
0x3C83AB: ('PrevFilename', UTF8),
0x3EB923: ('NextUID', Binary),
0x3E83BB: ('NextFilename', UTF8),
0x4444: ('SegmentFamily[]', Binary),
0x6924: ('ChapterTranslate[]', {
0x69FC: ('ChapterTranslateEditionUID[]', UInt),
0x69BF: ('ChapterTranslateCodec', UInt),
0x69A5: ('ChapterTranslateID', Binary)
}),
0x2AD7B1: ('TimecodeScale', UInt),
0x4489: ('Duration', Float),
0x4461: ('DateUTC', Date),
0x7BA9: ('Title', UTF8),
0x4D80: ('MuxingApp', UTF8),
0x5741: ('WritingApp', UTF8)
}
segment_clusters = {
0xE7: ('Timecode', UInt),
0x5854: ('SilentTracks', {
0x58D7: ('SilentTrackNumber[]', UInt)
}),
0xA7: ('Position', UInt),
0xAB: ('PrevSize', UInt),
0xA0: ('BlockGroup[]', {
0xA1: ('Block', Block),
0xA2: ('BlockVirtual[]', Block),
0x75A1: ('BlockAdditions', {
0xA6: ('BlockMore[]', {
0xEE: ('BlockAddID', UInt),
0xA5: ('BlockAdditional', Binary)
})
}),
0x9B: ('BlockDuration', UInt),
0xFA: ('ReferencePriority', UInt),
0xFB: ('ReferenceBlock[]', SInt),
0xFD: ('ReferenceVirtual', SInt),
0xA4: ('CodecState', Binary),
0x8E: ('Slices[]', {
0xE8: ('TimeSlice[]', {
0xCC: ('LaceNumber', UInt),
0xCD: ('FrameNumber', UInt),
0xCB: ('BlockAdditionID', UInt),
0xCE: ('Delay', UInt),
0xCF: ('Duration', UInt)
})
})
}),
0xA3: ('SimpleBlock[]', Block)
}
tracks_video = {
0x9A: ('FlagInterlaced', Bool),
0x53B8: ('StereoMode', lambda parent: Enum(parent, \
[ 'mono', 'right eye', 'left eye', 'both eyes' ])),
0xB0: ('PixelWidth', UInt),
0xBA: ('PixelHeight', UInt),
0x54AA: ('PixelCropBottom', UInt),
0x54BB: ('PixelCropTop', UInt),
0x54CC: ('PixelCropLeft', UInt),
0x54DD: ('PixelCropRight', UInt),
0x54B0: ('DisplayWidth', UInt),
0x54BA: ('DisplayHeight', UInt),
0x54B2: ('DisplayUnit', lambda parent: Enum(parent, \
[ 'pixels', 'centimeters', 'inches' ])),
0x54B3: ('AspectRatioType', lambda parent: Enum(parent, \
[ 'free resizing', 'keep aspect ratio', 'fixed' ])),
0x2EB524: ('ColourSpace', Binary),
0x2FB523: ('GammaValue', Float)
}
tracks_audio = {
0xB5: ('SamplingFrequency', Float),
0x78B5: ('OutputSamplingFrequency', Float),
0x9F: ('Channels', UInt),
0x7D7B: ('ChannelPositions', Binary),
0x6264: ('BitDepth', UInt)
}
tracks_content_encodings = {
0x6240: ('ContentEncoding[]', {
0x5031: ('ContentEncodingOrder', UInt),
0x5032: ('ContentEncodingScope', UInt),
0x5033: ('ContentEncodingType', UInt),
0x5034: ('ContentCompression', {
0x4254: ('ContentCompAlgo', UInt),
0x4255: ('ContentCompSettings', Binary)
}),
0x5035: ('ContentEncryption', {
0x47e1: ('ContentEncAlgo', UInt),
0x47e2: ('ContentEncKeyID', Binary),
0x47e3: ('ContentSignature', Binary),
0x47e4: ('ContentSigKeyID', Binary),
0x47e5: ('ContentSigAlgo', UInt),
0x47e6: ('ContentSigHashAlgo', UInt),
})
})
}
segment_tracks = {
0xAE: ('TrackEntry[]', {
0xD7: ('TrackNumber', UInt),
0x73C5: ('TrackUID', UInt),
0x83: ('TrackType', lambda parent: Enum(parent, {
0x01: 'video',
0x02: 'audio',
0x03: 'complex',
0x10: 'logo',
0x11: 'subtitle',
0x12: 'buttons',
0x20: 'control'
})),
0xB9: ('FlagEnabled', Bool),
0x88: ('FlagDefault', Bool),
0x55AA: ('FlagForced[]', Bool),
0x9C: ('FlagLacing', Bool),
0x6DE7: ('MinCache', UInt),
0x6DF8: ('MaxCache', UInt),
0x23E383: ('DefaultDuration', UInt),
0x23314F: ('TrackTimecodeScale', Float),
0x537F: ('TrackOffset', SInt),
0x55EE: ('MaxBlockAdditionID', UInt),
0x536E: ('Name', UTF8),
0x22B59C: ('Language', lambda parent: EnumString(parent, ISO639_2)),
0x86: ('CodecID', String),
0x63A2: ('CodecPrivate', Binary),
0x258688: ('CodecName', UTF8),
0x7446: ('AttachmentLink', UInt),
0x3A9697: ('CodecSettings', UTF8),
0x3B4040: ('CodecInfoURL[]', String),
0x26B240: ('CodecDownloadURL[]', String),
0xAA: ('CodecDecodeAll', Bool),
0x6FAB: ('TrackOverlay[]', UInt),
0x6624: ('TrackTranslate[]', {
0x66FC: ('TrackTranslateEditionUID[]', UInt),
0x66BF: ('TrackTranslateCodec', UInt),
0x66A5: ('TrackTranslateTrackID', Binary)
}),
0xE0: ('Video', tracks_video),
0xE1: ('Audio', tracks_audio),
0x6d80: ('ContentEncodings', tracks_content_encodings)
})
}
segment_cues = {
0xBB: ('CuePoint[]', {
0xB3: ('CueTime', UInt),
0xB7: ('CueTrackPositions[]', CueTrackPositions, {
0xF7: ('CueTrack', UInt),
0xF1: ('CueClusterPosition', CueClusterPosition, UInt),
0x5378: ('CueBlockNumber', UInt),
0xEA: ('CueCodecState', UInt),
0xDB: ('CueReference[]', {
0x96: ('CueRefTime', UInt),
0x97: ('CueRefCluster', UInt),
0x535F: ('CueRefNumber', UInt),
0xEB: ('CueRefCodecState', UInt)
})
})
})
}
segment_attachments = {
0x61A7: ('AttachedFile[]', {
0x467E: ('FileDescription', UTF8),
0x466E: ('FileName', UTF8),
0x4660: ('FileMimeType', String),
0x465C: ('FileData', AttachedFile),
0x46AE: ('FileUID', UInt),
0x4675: ('FileReferral', Binary)
})
}
segment_chapters = {
0x45B9: ('EditionEntry[]', {
0x45BC: ('EditionUID', UInt),
0x45BD: ('EditionFlagHidden', Bool),
0x45DB: ('EditionFlagDefault', Bool),
0x45DD: ('EditionFlagOrdered', Bool),
0xB6: ('ChapterAtom[]', chapter_atom)
})
}
segment_tags = {
0x7373: ('Tag[]', {
0x63C0: ('Targets', {
0x68CA: ('TargetTypeValue', UInt),
0x63CA: ('TargetType', String),
0x63C5: ('TrackUID[]', UInt),
0x63C9: ('EditionUID[]', UInt),
0x63C4: ('ChapterUID[]', UInt),
0x63C6: ('AttachmentUID[]', UInt)
}),
0x67C8: ('SimpleTag[]', simple_tag)
})
}
segment = {
0x114D9B74: ('SeekHead[]', segment_seek),
0x1549A966: ('Info[]', segment_info),
0x1F43B675: ('Cluster[]', segment_clusters),
0x1654AE6B: ('Tracks[]', segment_tracks),
0x1C53BB6B: ('Cues', segment_cues),
0x1941A469: ('Attachments', segment_attachments),
0x1043A770: ('Chapters', segment_chapters),
0x1254C367: ('Tags[]', segment_tags)
}
class EBML(FieldSet):
def __init__(self, parent, ids):
FieldSet.__init__(self, parent, "?[]")
# Set name
id = self['id'].value
self.val = ids.get(id)
if not self.val:
if id == 0xBF:
self.val = 'CRC-32[]', Binary
elif id == 0xEC:
self.val = 'Void[]', Binary
elif id == 0x1B538667:
self.val = 'SignatureSlot[]', signature
else:
self.val = 'Unknown[]', Binary
self._name = self.val[0]
# Compute size
size = self['size']
if size.value is not None:
self._size = size.address + size.size + size.value * 8
elif self._parent._parent:
raise ParserError("Unknown length (only allowed for the last Level 0 element)")
elif self._parent._size is not None:
self._size = self._parent._size - self.address
def createFields(self):
yield RawInt(self, 'id')
yield Unsigned(self, 'size')
for val in self.val[1:]:
if callable(val):
yield val(self)
else:
while not self.eof:
yield EBML(self, val)
class MkvFile(Parser):
EBML_SIGNATURE = 0x1A45DFA3
PARSER_TAGS = {
"id": "matroska",
"category": "container",
"file_ext": ("mka", "mkv", "webm"),
"mime": (
u"video/x-matroska",
u"audio/x-matroska",
u"video/webm",
u"audio/webm"),
"min_size": 5*8,
"magic": (("\x1A\x45\xDF\xA3", 0),),
"description": "Matroska multimedia container"
}
endian = BIG_ENDIAN
def _getDoctype(self):
return self[0]['DocType/string'].value
def validate(self):
if self.stream.readBits(0, 32, self.endian) != self.EBML_SIGNATURE:
return False
try:
first = self[0]
except ParserError:
return False
if None < self._size < first._size:
return "First chunk size is invalid"
if self._getDoctype() not in ('matroska', 'webm'):
return "Stream isn't a matroska document."
return True
def createFields(self):
hdr = EBML(self, ebml)
yield hdr
while not self.eof:
yield EBML(self, { 0x18538067: ('Segment[]', segment) })
def createContentSize(self):
field = self["Segment[0]/size"]
return field.absolute_address + field.value * 8 + field.size
def createDescription(self):
if self._getDoctype() == 'webm':
return 'WebM video'
else:
return 'Matroska video'
def createMimeType(self):
if self._getDoctype() == 'webm':
return u"video/webm"
else:
return u"video/x-matroska"
+349
View File
@@ -0,0 +1,349 @@
#
# Ogg parser
# Author Julien Muchembled <jm AT jm10.no-ip.com>
# Created: 10 june 2006
#
from hachoir_parser import Parser
from hachoir_core.field import (Field, FieldSet, createOrphanField,
NullBits, Bit, Bits, Enum, Fragment, MissingField, ParserError,
UInt8, UInt16, UInt24, UInt32, UInt64,
RawBytes, String, PascalString32, NullBytes)
from hachoir_core.stream import FragmentedStream, InputStreamError
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
from hachoir_core.tools import humanDurationNanosec
from hachoir_core.text_handler import textHandler, hexadecimal
MAX_FILESIZE = 1000 * 1024 * 1024
class XiphInt(Field):
"""
Positive integer with variable size. Values bigger than 254 are stored as
(255, 255, ..., rest): value is the sum of all bytes.
Example: 1000 is stored as (255, 255, 255, 235), total = 255*3+235 = 1000
"""
def __init__(self, parent, name, max_size=None, description=None):
Field.__init__(self, parent, name, size=0, description=description)
value = 0
addr = self.absolute_address
while max_size is None or self._size < max_size:
byte = parent.stream.readBits(addr, 8, LITTLE_ENDIAN)
value += byte
self._size += 8
if byte != 0xff:
break
addr += 8
self.createValue = lambda: value
class Lacing(FieldSet):
def createFields(self):
size = self.size
while size:
field = XiphInt(self, 'size[]', size)
yield field
size -= field.size
def parseVorbisComment(parent):
yield PascalString32(parent, 'vendor', charset="UTF-8")
yield UInt32(parent, 'count')
for index in xrange(parent["count"].value):
yield PascalString32(parent, 'metadata[]', charset="UTF-8")
if parent.current_size != parent.size:
yield UInt8(parent, "framing_flag")
PIXEL_FORMATS = {
0: "4:2:0",
2: "4:2:2",
3: "4:4:4",
}
def formatTimeUnit(field):
return humanDurationNanosec(field.value * 100)
def parseVideoHeader(parent):
yield NullBytes(parent, "padding[]", 2)
yield String(parent, "fourcc", 4)
yield UInt32(parent, "size")
yield textHandler(UInt64(parent, "time_unit", "Frame duration"), formatTimeUnit)
yield UInt64(parent, "sample_per_unit")
yield UInt32(parent, "default_len")
yield UInt32(parent, "buffer_size")
yield UInt16(parent, "bits_per_sample")
yield NullBytes(parent, "padding[]", 2)
yield UInt32(parent, "width")
yield UInt32(parent, "height")
yield NullBytes(parent, "padding[]", 4)
def parseTheoraHeader(parent):
yield UInt8(parent, "version_major")
yield UInt8(parent, "version_minor")
yield UInt8(parent, "version_revision")
yield UInt16(parent, "width", "Width*16 in pixel")
yield UInt16(parent, "height", "Height*16 in pixel")
yield UInt24(parent, "frame_width")
yield UInt24(parent, "frame_height")
yield UInt8(parent, "offset_x")
yield UInt8(parent, "offset_y")
yield UInt32(parent, "fps_num", "Frame per second numerator")
yield UInt32(parent, "fps_den", "Frame per second denominator")
yield UInt24(parent, "aspect_ratio_num", "Aspect ratio numerator")
yield UInt24(parent, "aspect_ratio_den", "Aspect ratio denominator")
yield UInt8(parent, "color_space")
yield UInt24(parent, "target_bitrate")
yield Bits(parent, "quality", 6)
yield Bits(parent, "gp_shift", 5)
yield Enum(Bits(parent, "pixel_format", 2), PIXEL_FORMATS)
yield Bits(parent, "spare_config", 3)
def parseVorbisHeader(parent):
yield UInt32(parent, "vorbis_version")
yield UInt8(parent, "audio_channels")
yield UInt32(parent, "audio_sample_rate")
yield UInt32(parent, "bitrate_maximum")
yield UInt32(parent, "bitrate_nominal")
yield UInt32(parent, "bitrate_minimum")
yield Bits(parent, "blocksize_0", 4)
yield Bits(parent, "blocksize_1", 4)
yield UInt8(parent, "framing_flag")
class Chunk(FieldSet):
tag_info = {
"vorbis": {
3: ("comment", parseVorbisComment),
1: ("vorbis_hdr", parseVorbisHeader),
}, "theora": {
128: ("theora_hdr", parseTheoraHeader),
129: ("comment", parseVorbisComment),
}, "video\0": {
1: ("video_hdr", parseVideoHeader),
},
}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
if 7*8 <= self.size:
try:
self._name, self.parser = self.tag_info[self["codec"].value][self["type"].value]
if self._name == "theora_hdr":
self.endian = BIG_ENDIAN
except KeyError:
self.parser = None
else:
self.parser = None
def createFields(self):
if 7*8 <= self.size:
yield UInt8(self, 'type')
yield String(self, 'codec', 6)
if self.parser:
for field in self.parser(self):
yield field
else:
size = (self.size - self.current_size) // 8
if size:
yield RawBytes(self, "raw", size)
class Packets:
def __init__(self, first):
self.first = first
def __iter__(self):
fragment = self.first
size = None
while fragment is not None:
page = fragment.parent
continued_packet = page["continued_packet"].value
for segment_size in page.segment_size:
if continued_packet:
size += segment_size
continued_packet = False
else:
if size:
yield size * 8
size = segment_size
fragment = fragment.next
if size:
yield size * 8
class Segments(Fragment):
def __init__(self, parent, *args, **kw):
Fragment.__init__(self, parent, *args, **kw)
if parent['last_page'].value:
next = None
else:
next = self.createNext
self.setLinks(parent.parent.streams.setdefault(parent['serial'].value, self), next)
def _createInputStream(self, **args):
if self.first is self:
return FragmentedStream(self, packets=Packets(self), tags=[("id","ogg_stream")], **args)
return Fragment._createInputStream(self, **args)
def _getData(self):
return self
def createNext(self):
parent = self.parent
index = parent.index
parent = parent.parent
first = self.first
try:
while True:
index += 1
next = parent[index][self.name]
if next.first is first:
return next
except MissingField:
pass
def createFields(self):
for segment_size in self.parent.segment_size:
if segment_size:
yield Chunk(self, "chunk[]", size=segment_size*8)
class OggPage(FieldSet):
MAGIC = "OggS"
def __init__(self, *args):
FieldSet.__init__(self, *args)
size = 27
self.lacing_size = self['lacing_size'].value
if self.lacing_size:
size += self.lacing_size
lacing = self['lacing']
self.segment_size = [ field.value for field in lacing ]
size += sum(self.segment_size)
self._size = size * 8
def createFields(self):
yield String(self, 'capture_pattern', 4, charset="ASCII")
if self['capture_pattern'].value != self.MAGIC:
self.warning('Invalid signature. An Ogg page must start with "%s".' % self.MAGIC)
yield UInt8(self, 'stream_structure_version')
yield Bit(self, 'continued_packet')
yield Bit(self, 'first_page')
yield Bit(self, 'last_page')
yield NullBits(self, 'unused', 5)
yield UInt64(self, 'abs_granule_pos')
yield textHandler(UInt32(self, 'serial'), hexadecimal)
yield UInt32(self, 'page')
yield textHandler(UInt32(self, 'checksum'), hexadecimal)
yield UInt8(self, 'lacing_size')
if self.lacing_size:
yield Lacing(self, "lacing", size=self.lacing_size*8)
yield Segments(self, "segments", size=self._size-self._current_size)
def validate(self):
if self['capture_pattern'].value != self.MAGIC:
return "Wrong signature"
if self['stream_structure_version'].value != 0:
return "Unknown structure version (%s)" % self['stream_structure_version'].value
return ""
class OggFile(Parser):
PARSER_TAGS = {
"id": "ogg",
"category": "container",
"file_ext": ("ogg", "ogm"),
"mime": (
u"application/ogg", u"application/x-ogg",
u"audio/ogg", u"audio/x-ogg",
u"video/ogg", u"video/x-ogg",
u"video/theora", u"video/x-theora",
),
"magic": ((OggPage.MAGIC, 0),),
"subfile": "skip",
"min_size": 28*8,
"description": "Ogg multimedia container"
}
endian = LITTLE_ENDIAN
def validate(self):
magic = OggPage.MAGIC
if self.stream.readBytes(0, len(magic)) != magic:
return "Invalid magic string"
# Validate first 3 pages
for index in xrange(3):
try:
page = self[index]
except MissingField:
if self.done:
return True
return "Unable to get page #%u" % index
except (InputStreamError, ParserError):
return "Unable to create page #%u" % index
err = page.validate()
if err:
return "Invalid page #%s: %s" % (index, err)
return True
def createMimeType(self):
if "theora_hdr" in self["page[0]/segments"]:
return u"video/theora"
elif "vorbis_hdr" in self["page[0]/segments"]:
return u"audio/vorbis"
else:
return u"application/ogg"
def createDescription(self):
if "theora_hdr" in self["page[0]"]:
return u"Ogg/Theora video"
elif "vorbis_hdr" in self["page[0]"]:
return u"Ogg/Vorbis audio"
else:
return u"Ogg multimedia container"
def createFields(self):
self.streams = {}
while not self.eof:
yield OggPage(self, "page[]")
def createLastPage(self):
start = self[0].size
end = MAX_FILESIZE * 8
if True:
# FIXME: This doesn't work on all files (eg. some Ogg/Theora)
offset = self.stream.searchBytes("OggS\0\5", start, end)
if offset is None:
offset = self.stream.searchBytes("OggS\0\4", start, end)
if offset is None:
return None
return createOrphanField(self, offset, OggPage, "page")
else:
# Very slow version
page = None
while True:
offset = self.stream.searchBytes("OggS\0", start, end)
if offset is None:
break
page = createOrphanField(self, offset, OggPage, "page")
start += page.size
return page
def createContentSize(self):
page = self.createLastPage()
if page:
return page.absolute_address + page.size
else:
return None
class OggStream(Parser):
PARSER_TAGS = {
"id": "ogg_stream",
"category": "container",
"subfile": "skip",
"min_size": 7*8,
"description": "Ogg logical stream"
}
endian = LITTLE_ENDIAN
def validate(self):
return False
def createFields(self):
for size in self.stream.packets:
yield RawBytes(self, "packet[]", size//8)
+172
View File
@@ -0,0 +1,172 @@
"""
RealMedia (.rm) parser
Author: Mike Melanson
Creation date: 15 december 2006
References:
- http://wiki.multimedia.cx/index.php?title=RealMedia
- Appendix E: RealMedia File Format (RMFF) Reference
https://common.helixcommunity.org/nonav/2003/HCS_SDK_r5/htmfiles/rmff.htm
Samples:
- http://samples.mplayerhq.hu/real/
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet,
UInt16, UInt32, Bit, RawBits,
RawBytes, String, PascalString8, PascalString16)
from hachoir_core.text_handler import textHandler, hexadecimal
from hachoir_core.endian import BIG_ENDIAN
def parseHeader(self):
yield UInt32(self, "filever", "File version")
yield UInt32(self, "numheaders", "number of headers")
def parseFileProperties(self):
yield UInt32(self, "max_bit_rate", "Maximum bit rate")
yield UInt32(self, "avg_bit_rate", "Average bit rate")
yield UInt32(self, "max_pkt_size", "Size of largest data packet")
yield UInt32(self, "avg_pkt_size", "Size of average data packet")
yield UInt32(self, "num_pkts", "Number of data packets")
yield UInt32(self, "duration", "File duration in milliseconds")
yield UInt32(self, "preroll", "Suggested preroll in milliseconds")
yield textHandler(UInt32(self, "index_offset", "Absolute offset of first index chunk"), hexadecimal)
yield textHandler(UInt32(self, "data_offset", "Absolute offset of first data chunk"), hexadecimal)
yield UInt16(self, "stream_count", "Number of streams in the file")
yield RawBits(self, "reserved", 13)
yield Bit(self, "is_live", "Whether file is a live broadcast")
yield Bit(self, "is_perfect_play", "Whether PerfectPlay can be used")
yield Bit(self, "is_saveable", "Whether file can be saved")
def parseContentDescription(self):
yield PascalString16(self, "title", charset="ISO-8859-1", strip=" \0")
yield PascalString16(self, "author", charset="ISO-8859-1", strip=" \0")
yield PascalString16(self, "copyright", charset="ISO-8859-1", strip=" \0")
yield PascalString16(self, "comment", charset="ISO-8859-1", strip=" \0")
class NameValueProperty(FieldSet):
def __init__(self, *args):
FieldSet.__init__(self, *args)
self._size = self["size"].value * 8
def createFields(self):
yield UInt32(self, "size")
yield UInt16(self, "obj_version")
yield PascalString8(self, "name", charset="ASCII")
yield UInt32(self, "type")
yield PascalString16(self, "value", charset="ISO-8859-1", strip=" \0")
class LogicalFileInfo(FieldSet):
def createFields(self):
yield UInt32(self, "size")
yield UInt16(self, "obj_version")
yield UInt16(self, "nb_physical_stream")
for index in xrange(self["nb_physical_stream"].value):
yield UInt16(self, "physical_stream[]")
for index in xrange(self["nb_physical_stream"].value):
yield UInt16(self, "data_offset[]")
yield UInt16(self, "nb_rule")
for index in xrange(self["nb_rule"].value):
yield UInt16(self, "rule[]")
yield UInt16(self, "nb_prop")
for index in xrange(self["nb_prop"].value):
yield NameValueProperty(self, "prop[]")
def parseMediaPropertiesHeader(self):
yield UInt16(self, "stream_number", "Stream number")
yield UInt32(self, "max_bit_rate", "Maximum bit rate")
yield UInt32(self, "avg_bit_rate", "Average bit rate")
yield UInt32(self, "max_pkt_size", "Size of largest data packet")
yield UInt32(self, "avg_pkt_size", "Size of average data packet")
yield UInt32(self, "stream_start", "Stream start offset in milliseconds")
yield UInt32(self, "preroll", "Preroll in milliseconds")
yield UInt32(self, "duration", "Stream duration in milliseconds")
yield PascalString8(self, "desc", "Stream description", charset="ISO-8859-1")
yield PascalString8(self, "mime_type", "MIME type string", charset="ASCII")
yield UInt32(self, "specific_size", "Size of type-specific data")
size = self['specific_size'].value
if size:
if self["mime_type"].value == "logical-fileinfo":
yield LogicalFileInfo(self, "file_info", size=size*8)
else:
yield RawBytes(self, "specific", size, "Type-specific data")
class Chunk(FieldSet):
tag_info = {
".RMF": ("header", parseHeader),
"PROP": ("file_prop", parseFileProperties),
"CONT": ("content_desc", parseContentDescription),
"MDPR": ("stream_prop[]", parseMediaPropertiesHeader),
"DATA": ("data[]", None),
"INDX": ("file_index[]", None)
}
def createValueFunc(self):
return self.value_func(self)
def __init__(self, parent, name, description=None):
FieldSet.__init__(self, parent, name, description)
self._size = (self["size"].value) * 8
tag = self["tag"].value
if tag in self.tag_info:
self._name, self.parse_func = self.tag_info[tag]
else:
self._description = ""
self.parse_func = None
def createFields(self):
yield String(self, "tag", 4, "Chunk FourCC", charset="ASCII")
yield UInt32(self, "size", "Chunk Size")
yield UInt16(self, "version", "Chunk Version")
if self.parse_func:
for field in self.parse_func(self):
yield field
else:
size = (self.size - self.current_size) // 8
if size:
yield RawBytes(self, "raw", size)
def createDescription(self):
return "Chunk: %s" % self["tag"].display
class RealMediaFile(Parser):
MAGIC = '.RMF\0\0\0\x12\0\1' # (magic, size=18, version=1)
PARSER_TAGS = {
"id": "real_media",
"category": "container",
"file_ext": ("rm",),
"mime": (
u"video/x-pn-realvideo",
u"audio/x-pn-realaudio",
u"audio/x-pn-realaudio-plugin",
u"audio/x-real-audio",
u"application/vnd.rn-realmedia"),
"min_size": len(MAGIC)*8, # just the identifier
"magic": ((MAGIC, 0),),
"description": u"RealMedia (rm) Container File",
}
endian = BIG_ENDIAN
def validate(self):
if self.stream.readBytes(0, 4) != '.RMF':
return "Invalid magic"
if self["header/size"].value != 18:
return "Invalid header size"
if self["header/version"].value not in (0, 1):
return "Unknown file format version (%s)" % self["header/version"].value
return True
def createFields(self):
while not self.eof:
yield Chunk(self, "chunk")
def createMimeType(self):
for prop in self.array("stream_prop"):
if prop["mime_type"].value == "video/x-pn-realvideo":
return u"video/x-pn-realvideo"
return u"audio/x-pn-realaudio"
+439
View File
@@ -0,0 +1,439 @@
# -*- coding: UTF-8 -*-
"""
RIFF parser, able to parse:
* AVI video container
* WAV audio container
* CDA file
Documents:
- libavformat source code from ffmpeg library
http://ffmpeg.mplayerhq.hu/
- Video for Windows Programmer's Guide
http://www.opennet.ru/docs/formats/avi.txt
- What is an animated cursor?
http://www.gdgsoft.com/anituner/help/aniformat.htm
Authors:
* Aurélien Jacobs
* Mickaël KENIKSSI
* Victor Stinner
Changelog:
* 2007-03-30: support ACON (animated icons)
* 2006-08-08: merge AVI, WAV and CDA parsers into RIFF parser
* 2006-08-03: creation of CDA parser by Mickaël KENIKSSI
* 2005-06-21: creation of WAV parser by Victor Stinner
* 2005-06-08: creation of AVI parser by Victor Stinner and Aurélien Jacobs
Thanks to:
* Wojtek Kaniewski (wojtekka AT logonet.com.pl) for its CDA file
format information
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, ParserError,
UInt8, UInt16, UInt32, Enum,
Bit, NullBits, NullBytes,
RawBytes, String, PaddingBytes,
SubFile)
from hachoir_core.tools import alignValue, humanDuration
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.text_handler import filesizeHandler, textHandler
from hachoir_parser.video.fourcc import audio_codec_name, video_fourcc_name
from hachoir_parser.image.ico import IcoFile
from datetime import timedelta
def parseText(self):
yield String(self, "text", self["size"].value,
strip=" \0", truncate="\0",
charset="ISO-8859-1")
def parseRawFormat(self, size):
yield RawBytes(self, "raw_format", size)
def parseVideoFormat(self, size):
yield UInt32(self, "video_size", "Video format: Size")
yield UInt32(self, "width", "Video format: Width")
yield UInt32(self, "height", "Video format: Height")
yield UInt16(self, "panes", "Video format: Panes")
yield UInt16(self, "depth", "Video format: Depth")
yield UInt32(self, "tag1", "Video format: Tag1")
yield UInt32(self, "img_size", "Video format: Image size")
yield UInt32(self, "xpels_meter", "Video format: XPelsPerMeter")
yield UInt32(self, "ypels_meter", "Video format: YPelsPerMeter")
yield UInt32(self, "clr_used", "Video format: ClrUsed")
yield UInt32(self, "clr_important", "Video format: ClrImportant")
def parseAudioFormat(self, size):
yield Enum(UInt16(self, "codec", "Audio format: Codec id"), audio_codec_name)
yield UInt16(self, "channel", "Audio format: Channels")
yield UInt32(self, "sample_rate", "Audio format: Sample rate")
yield UInt32(self, "bit_rate", "Audio format: Bit rate")
yield UInt16(self, "block_align", "Audio format: Block align")
if size >= 16:
yield UInt16(self, "bits_per_sample", "Audio format: Bits per sample")
if size >= 18:
yield UInt16(self, "ext_size", "Audio format: Size of extra information")
if size >= 28: # and self["a_channel"].value > 2
yield UInt16(self, "reserved", "Audio format: ")
yield UInt32(self, "channel_mask", "Audio format: channels placement bitmask")
yield UInt32(self, "subformat", "Audio format: Subformat id")
def parseAVIStreamFormat(self):
size = self["size"].value
strtype = self["../stream_hdr/stream_type"].value
TYPE_HANDLER = {
"vids": (parseVideoFormat, 40),
"auds": (parseAudioFormat, 16)
}
handler = parseRawFormat
if strtype in TYPE_HANDLER:
info = TYPE_HANDLER[strtype]
if info[1] <= size:
handler = info[0]
for field in handler(self, size):
yield field
def parseAVIStreamHeader(self):
if self["size"].value != 56:
raise ParserError("Invalid stream header size")
yield String(self, "stream_type", 4, "Stream type four character code", charset="ASCII")
field = String(self, "fourcc", 4, "Stream four character code", strip=" \0", charset="ASCII")
if self["stream_type"].value == "vids":
yield Enum(field, video_fourcc_name, lambda text: text.upper())
else:
yield field
yield UInt32(self, "flags", "Stream flags")
yield UInt16(self, "priority", "Stream priority")
yield String(self, "language", 2, "Stream language", charset="ASCII", strip="\0")
yield UInt32(self, "init_frames", "InitialFrames")
yield UInt32(self, "scale", "Time scale")
yield UInt32(self, "rate", "Divide by scale to give frame rate")
yield UInt32(self, "start", "Stream start time (unit: rate/scale)")
yield UInt32(self, "length", "Stream length (unit: rate/scale)")
yield UInt32(self, "buf_size", "Suggested buffer size")
yield UInt32(self, "quality", "Stream quality")
yield UInt32(self, "sample_size", "Size of samples")
yield UInt16(self, "left", "Destination rectangle (left)")
yield UInt16(self, "top", "Destination rectangle (top)")
yield UInt16(self, "right", "Destination rectangle (right)")
yield UInt16(self, "bottom", "Destination rectangle (bottom)")
class RedBook(FieldSet):
"""
RedBook offset parser, used in CD audio (.cda) file
"""
def createFields(self):
yield UInt8(self, "frame")
yield UInt8(self, "second")
yield UInt8(self, "minute")
yield PaddingBytes(self, "notused", 1)
def formatSerialNumber(field):
"""
Format an disc serial number.
Eg. 0x00085C48 => "0008-5C48"
"""
sn = field.value
return "%04X-%04X" % (sn >> 16, sn & 0xFFFF)
def parseCDDA(self):
"""
HSG address format: number of 1/75 second
HSG offset = (minute*60 + second)*75 + frame + 150 (from RB offset)
HSG length = (minute*60 + second)*75 + frame (from RB length)
"""
yield UInt16(self, "cda_version", "CD file version (currently 1)")
yield UInt16(self, "track_no", "Number of track")
yield textHandler(UInt32(self, "disc_serial", "Disc serial number"),
formatSerialNumber)
yield UInt32(self, "hsg_offset", "Track offset (HSG format)")
yield UInt32(self, "hsg_length", "Track length (HSG format)")
yield RedBook(self, "rb_offset", "Track offset (Red-book format)")
yield RedBook(self, "rb_length", "Track length (Red-book format)")
def parseWAVFormat(self):
size = self["size"].value
if size not in (16, 18):
self.warning("Format with size of %s bytes is not supported!" % size)
yield Enum(UInt16(self, "codec", "Audio codec"), audio_codec_name)
yield UInt16(self, "nb_channel", "Number of audio channel")
yield UInt32(self, "sample_per_sec", "Sample per second")
yield UInt32(self, "byte_per_sec", "Average byte per second")
yield UInt16(self, "block_align", "Block align")
yield UInt16(self, "bit_per_sample", "Bits per sample")
def parseWAVFact(self):
yield UInt32(self, "nb_sample", "Number of samples in audio stream")
def parseAviHeader(self):
yield UInt32(self, "microsec_per_frame", "Microsecond per frame")
yield UInt32(self, "max_byte_per_sec", "Maximum byte per second")
yield NullBytes(self, "reserved", 4)
# Flags
yield NullBits(self, "reserved[]", 4)
yield Bit(self, "has_index")
yield Bit(self, "must_use_index")
yield NullBits(self, "reserved[]", 2)
yield Bit(self, "is_interleaved")
yield NullBits(self, "reserved[]", 2)
yield Bit(self, "trust_cktype")
yield NullBits(self, "reserved[]", 4)
yield Bit(self, "was_capture_file")
yield Bit(self, "is_copyrighted")
yield NullBits(self, "reserved[]", 14)
yield UInt32(self, "total_frame", "Total number of frames in the video")
yield UInt32(self, "init_frame", "Initial frame (used in interleaved video)")
yield UInt32(self, "nb_stream", "Number of streams")
yield UInt32(self, "sug_buf_size", "Suggested buffer size")
yield UInt32(self, "width", "Width in pixel")
yield UInt32(self, "height", "Height in pixel")
yield UInt32(self, "scale")
yield UInt32(self, "rate")
yield UInt32(self, "start")
yield UInt32(self, "length")
def parseODML(self):
yield UInt32(self, "total_frame", "Real number of frame of OpenDML video")
padding = self["size"].value - 4
if 0 < padding:
yield NullBytes(self, "padding[]", padding)
class AVIIndexEntry(FieldSet):
size = 16*8
def createFields(self):
yield String(self, "tag", 4, "Tag", charset="ASCII")
yield UInt32(self, "flags")
yield UInt32(self, "start", "Offset from start of movie data")
yield UInt32(self, "length")
def parseIndex(self):
while not self.eof:
yield AVIIndexEntry(self, "index[]")
class Chunk(FieldSet):
TAG_INFO = {
# This dictionnary is edited by RiffFile.validate()
"LIST": ("list[]", None, "Sub-field list"),
"JUNK": ("junk[]", None, "Junk (padding)"),
# Metadata
"INAM": ("title", parseText, "Document title"),
"IART": ("artist", parseText, "Artist"),
"ICMT": ("comment", parseText, "Comment"),
"ICOP": ("copyright", parseText, "Copyright"),
"IENG": ("author", parseText, "Author"),
"ICRD": ("creation_date", parseText, "Creation date"),
"ISFT": ("producer", parseText, "Producer"),
"IDIT": ("datetime", parseText, "Date time"),
# TODO: Todo: see below
# "strn": Stream description
# TWOCC code, movie/field[]/tag.value[2:4]:
# "db": "Uncompressed video frame",
# "dc": "Compressed video frame",
# "wb": "Audio data",
# "pc": "Palette change"
}
subtag_info = {
"INFO": ("info", "File informations"),
"hdrl": ("headers", "Headers"),
"strl": ("stream[]", "Stream header list"),
"movi": ("movie", "Movie stream"),
"odml": ("odml", "ODML"),
}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = (8 + alignValue(self["size"].value, 2)) * 8
tag = self["tag"].value
if tag in self.TAG_INFO:
self.tag_info = self.TAG_INFO[tag]
if tag == "LIST":
subtag = self["subtag"].value
if subtag in self.subtag_info:
info = self.subtag_info[subtag]
self.tag_info = (info[0], None, info[1])
self._name = self.tag_info[0]
self._description = self.tag_info[2]
else:
self.tag_info = ("field[]", None, None)
def createFields(self):
yield String(self, "tag", 4, "Tag", charset="ASCII")
yield filesizeHandler(UInt32(self, "size", "Size"))
if not self["size"].value:
return
if self["tag"].value == "LIST":
yield String(self, "subtag", 4, "Sub-tag", charset="ASCII")
handler = self.tag_info[1]
while 8 < (self.size - self.current_size)/8:
field = self.__class__(self, "field[]")
yield field
if (field.size/8) % 2 != 0:
yield UInt8(self, "padding[]", "Padding")
else:
handler = self.tag_info[1]
if handler:
for field in handler(self):
yield field
else:
yield RawBytes(self, "raw_content", self["size"].value)
padding = self.seekBit(self._size)
if padding:
yield padding
def createDescription(self):
tag = self["tag"].display
return u"Chunk (tag %s)" % tag
class ChunkAVI(Chunk):
TAG_INFO = Chunk.TAG_INFO.copy()
TAG_INFO.update({
"strh": ("stream_hdr", parseAVIStreamHeader, "Stream header"),
"strf": ("stream_fmt", parseAVIStreamFormat, "Stream format"),
"avih": ("avi_hdr", parseAviHeader, "AVI header"),
"idx1": ("index", parseIndex, "Stream index"),
"dmlh": ("odml_hdr", parseODML, "ODML header"),
})
class ChunkCDDA(Chunk):
TAG_INFO = Chunk.TAG_INFO.copy()
TAG_INFO.update({
'fmt ': ("cdda", parseCDDA, "CD audio informations"),
})
class ChunkWAVE(Chunk):
TAG_INFO = Chunk.TAG_INFO.copy()
TAG_INFO.update({
'fmt ': ("format", parseWAVFormat, "Audio format"),
'fact': ("nb_sample", parseWAVFact, "Number of samples"),
'data': ("audio_data", None, "Audio stream data"),
})
def parseAnimationHeader(self):
yield UInt32(self, "hdr_size", "Size of header (36 bytes)")
if self["hdr_size"].value != 36:
self.warning("Animation header with unknown size (%s)" % self["size"].value)
yield UInt32(self, "nb_frame", "Number of unique Icons in this cursor")
yield UInt32(self, "nb_step", "Number of Blits before the animation cycles")
yield UInt32(self, "cx")
yield UInt32(self, "cy")
yield UInt32(self, "bit_count")
yield UInt32(self, "planes")
yield UInt32(self, "jiffie_rate", "Default Jiffies (1/60th of a second) if rate chunk not present")
yield Bit(self, "is_icon")
yield NullBits(self, "padding", 31)
def parseAnimationSequence(self):
while not self.eof:
yield UInt32(self, "icon[]")
def formatJiffie(field):
sec = float(field.value) / 60
return humanDuration(timedelta(seconds=sec))
def parseAnimationRate(self):
while not self.eof:
yield textHandler(UInt32(self, "rate[]"), formatJiffie)
def parseIcon(self):
yield SubFile(self, "icon_file", self["size"].value, parser_class=IcoFile)
class ChunkACON(Chunk):
TAG_INFO = Chunk.TAG_INFO.copy()
TAG_INFO.update({
'anih': ("anim_hdr", parseAnimationHeader, "Animation header"),
'seq ': ("anim_seq", parseAnimationSequence, "Animation sequence"),
'rate': ("anim_rate", parseAnimationRate, "Animation sequence"),
'icon': ("icon[]", parseIcon, "Icon"),
})
class RiffFile(Parser):
PARSER_TAGS = {
"id": "riff",
"category": "container",
"file_ext": ("avi", "cda", "wav", "ani"),
"min_size": 16*8,
"mime": (u"video/x-msvideo", u"audio/x-wav", u"audio/x-cda"),
# FIXME: Use regex "RIFF.{4}(WAVE|CDDA|AVI )"
"magic": (
("AVI LIST", 8*8),
("WAVEfmt ", 8*8),
("CDDAfmt ", 8*8),
("ACONanih", 8*8),
),
"description": "Microsoft RIFF container"
}
VALID_TYPES = {
"WAVE": (ChunkWAVE, u"audio/x-wav", u"Microsoft WAVE audio", ".wav"),
"CDDA": (ChunkCDDA, u"audio/x-cda", u"Microsoft Windows audio CD file (cda)", ".cda"),
"AVI ": (ChunkAVI, u"video/x-msvideo", u"Microsoft AVI video", ".avi"),
"ACON": (ChunkACON, u"image/x-ani", u"Microsoft Windows animated cursor", ".ani"),
}
endian = LITTLE_ENDIAN
def validate(self):
if self.stream.readBytes(0, 4) != "RIFF":
return "Wrong signature"
if self["type"].value not in self.VALID_TYPES:
return "Unknown RIFF content type"
return True
def createFields(self):
yield String(self, "signature", 4, "AVI header (RIFF)", charset="ASCII")
yield filesizeHandler(UInt32(self, "filesize", "File size"))
yield String(self, "type", 4, "Content type (\"AVI \", \"WAVE\", ...)", charset="ASCII")
# Choose chunk type depending on file type
try:
chunk_cls = self.VALID_TYPES[self["type"].value][0]
except KeyError:
chunk_cls = Chunk
# Parse all chunks up to filesize
while self.current_size < self["filesize"].value*8+8:
yield chunk_cls(self, "chunk[]")
if not self.eof:
yield RawBytes(self, "padding[]", (self.size-self.current_size)/8)
def createMimeType(self):
try:
return self.VALID_TYPES[self["type"].value][1]
except KeyError:
return None
def createDescription(self):
tag = self["type"].value
if tag == "AVI ":
desc = u"Microsoft AVI video"
if "headers/avi_hdr" in self:
header = self["headers/avi_hdr"]
desc += ": %ux%u pixels" % (header["width"].value, header["height"].value)
microsec = header["microsec_per_frame"].value
if microsec:
desc += ", %.1f fps" % (1000000.0 / microsec)
if "total_frame" in header and header["total_frame"].value:
delta = timedelta(seconds=float(header["total_frame"].value) * microsec)
desc += ", " + humanDuration(delta)
return desc
else:
try:
return self.VALID_TYPES[tag][2]
except KeyError:
return u"Microsoft RIFF container"
def createContentSize(self):
size = (self["filesize"].value + 8) * 8
return min(size, self.stream.size)
def createFilenameSuffix(self):
try:
return self.VALID_TYPES[self["type"].value][3]
except KeyError:
return ".riff"
+433
View File
@@ -0,0 +1,433 @@
"""
SWF (Macromedia/Adobe Flash) file parser.
Documentation:
- Alexis' SWF Reference:
http://www.m2osw.com/swf_alexref.html
- http://www.half-serious.com/swf/format/
- http://www.anotherbigidea.com/javaswf/
- http://www.gnu.org/software/gnash/
Author: Victor Stinner
Creation date: 29 october 2006
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, ParserError,
Bit, Bits, UInt8, UInt16, Int32, UInt32, Int64, CString, Enum,
Bytes, RawBytes, NullBits, String, SubFile)
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
from hachoir_core.text_handler import textHandler, filesizeHandler
from hachoir_core.tools import paddingSize, humanFrequency
from hachoir_parser.image.common import RGB
from hachoir_parser.image.jpeg import JpegChunk, JpegFile
from hachoir_core.stream import StringInputStream, ConcatStream
from hachoir_parser.common.deflate import Deflate, has_deflate
from hachoir_parser.container.action_script import parseActionScript, parseABC
import math
# Maximum file size (50 MB)
MAX_FILE_SIZE = 50 * 1024 * 1024
TWIPS = 20
class RECT(FieldSet):
endian = BIG_ENDIAN
def createFields(self):
yield Bits(self, "nbits", 5)
nbits = self["nbits"].value
if not nbits:
raise ParserError("SWF parser: Invalid RECT field size (0)")
yield Bits(self, "xmin", nbits, "X minimum in twips")
yield Bits(self, "xmax", nbits, "X maximum in twips")
yield Bits(self, "ymin", nbits, "Y minimum in twips")
yield Bits(self, "ymax", nbits, "Y maximum in twips")
size = paddingSize(self.current_size, 8)
if size:
yield NullBits(self, "padding", size)
def getWidth(self):
return math.ceil(float(self["xmax"].value) / TWIPS)
def getHeight(self):
return math.ceil(float(self["ymax"].value) / TWIPS)
def createDescription(self):
return "Rectangle: %ux%u" % (self.getWidth(), self.getHeight())
class FixedFloat16(FieldSet):
def createFields(self):
yield UInt8(self, "float_part")
yield UInt8(self, "int_part")
def createValue(self):
return self["int_part"].value + float(self["float_part"].value) / 256
def parseBackgroundColor(parent, size):
yield RGB(parent, "color")
def bit2hertz(field):
return humanFrequency(5512.5 * (2 ** field.value))
SOUND_CODEC_MP3 = 2
SOUND_CODEC = {
0: "RAW",
1: "ADPCM",
SOUND_CODEC_MP3: "MP3",
3: "Uncompressed",
6: "Nellymoser",
}
class SoundEnvelope(FieldSet):
def createFields(self):
yield UInt8(self, "count")
for index in xrange(self["count"].value):
yield UInt32(self, "mark44[]")
yield UInt16(self, "level0[]")
yield UInt16(self, "level1[]")
def parseSoundBlock(parent, size):
# TODO: Be able to get codec... Need to know last sound "def_sound[]" field
# if not (...)sound_header:
# raise ParserError("Sound block without header")
if True: #sound_header == SOUND_CODEC_MP3:
yield UInt16(parent, "samples")
yield UInt16(parent, "left")
size = (parent.size - parent.current_size) // 8
if size:
yield RawBytes(parent, "music_data", size)
def parseStartSound(parent, size):
yield UInt16(parent, "sound_id")
yield Bit(parent, "has_in_point")
yield Bit(parent, "has_out_point")
yield Bit(parent, "has_loops")
yield Bit(parent, "has_envelope")
yield Bit(parent, "no_multiple")
yield Bit(parent, "stop_playback")
yield NullBits(parent, "reserved", 2)
if parent["has_in_point"].value:
yield UInt32(parent, "in_point")
if parent["has_out_point"].value:
yield UInt32(parent, "out_point")
if parent["has_loops"].value:
yield UInt16(parent, "loop_count")
if parent["has_envelope"].value:
yield SoundEnvelope(parent, "envelope")
def parseDefineSound(parent, size):
yield UInt16(parent, "sound_id")
yield Bit(parent, "is_stereo")
yield Bit(parent, "is_16bit")
yield textHandler(Bits(parent, "rate", 2), bit2hertz)
yield Enum(Bits(parent, "codec", 4), SOUND_CODEC)
yield UInt32(parent, "sample_count")
if parent["codec"].value == SOUND_CODEC_MP3:
yield UInt16(parent, "len")
size = (parent.size - parent.current_size) // 8
if size:
yield RawBytes(parent, "music_data", size)
def parseSoundHeader(parent, size):
yield Bit(parent, "playback_is_stereo")
yield Bit(parent, "playback_is_16bit")
yield textHandler(Bits(parent, "playback_rate", 2), bit2hertz)
yield NullBits(parent, "reserved", 4)
yield Bit(parent, "sound_is_stereo")
yield Bit(parent, "sound_is_16bit")
yield textHandler(Bits(parent, "sound_rate", 2), bit2hertz)
yield Enum(Bits(parent, "codec", 4), SOUND_CODEC)
yield UInt16(parent, "sample_count")
if parent["codec"].value == 2:
yield UInt16(parent, "latency_seek")
class JpegHeader(FieldSet):
endian = BIG_ENDIAN
def createFields(self):
count = 1
while True:
chunk = JpegChunk(self, "jpeg_chunk[]")
yield chunk
if 1 < count and chunk["type"].value in (JpegChunk.TAG_SOI, JpegChunk.TAG_EOI):
break
count += 1
def parseJpeg(parent, size):
yield UInt16(parent, "char_id", "Character identifier")
size -= 2
code = parent["code"].value
if code != Tag.TAG_BITS:
if code == Tag.TAG_BITS_JPEG3:
yield UInt32(parent, "alpha_offset", "Character identifier")
size -= 4
addr = parent.absolute_address + parent.current_size + 16
if parent.stream.readBytes(addr, 2) in ("\xff\xdb", "\xff\xd8"):
header = JpegHeader(parent, "jpeg_header")
yield header
hdr_size = header.size // 8
size -= hdr_size
else:
hdr_size = 0
if code == Tag.TAG_BITS_JPEG3:
img_size = parent["alpha_offset"].value - hdr_size
else:
img_size = size
else:
img_size = size
yield SubFile(parent, "image", img_size, "JPEG picture", parser=JpegFile)
if code == Tag.TAG_BITS_JPEG3:
size = (parent.size - parent.current_size) // 8
yield RawBytes(parent, "alpha", size, "Image data")
def parseVideoFrame(parent, size):
yield UInt16(parent, "stream_id")
yield UInt16(parent, "frame_num")
if 4 < size:
yield RawBytes(parent, "video_data", size-4)
class Export(FieldSet):
def createFields(self):
yield UInt16(self, "object_id")
yield CString(self, "name")
def parseExport(parent, size):
yield UInt16(parent, "count")
for index in xrange(parent["count"].value):
yield Export(parent, "export[]")
def parseProductInfo(parent, size):
yield Int32(parent, "product_id")
yield Int32(parent, "edition")
yield UInt8(parent, "major_version")
yield UInt8(parent, "minor_version")
yield Int64(parent, "build_number")
yield Int64(parent, "compilation_date")
def parseScriptLimits(parent, size):
yield UInt16(parent, "max_recursion_limit")
yield UInt16(parent, "timeout_seconds", "Seconds of processing until the SWF is considered 'stuck'")
def parseSymbolClass(parent, size):
yield UInt16(parent, "count")
for index in xrange(parent["count"].value):
yield UInt16(parent, "symbol_id[]")
yield CString(parent, "symbol_name[]")
def parseBinaryData(parent, size):
yield UInt16(parent, "data_id")
yield UInt32(parent, "reserved")
if size > 6:
yield RawBytes(parent, "data", size-6)
class Tag(FieldSet):
TAG_BITS = 6
TAG_BITS_JPEG2 = 32
TAG_BITS_JPEG3 = 35
TAG_DO_ABC_DEFINE = 82
TAG_INFO = {
# SWF version 1.0
0: ("end[]", "End", None),
1: ("show_frame[]", "Show frame", None),
2: ("def_shape[]", "Define shape", None),
3: ("free_char[]", "Free character", None),
4: ("place_obj[]", "Place object", None),
5: ("remove_obj[]", "Remove object", None),
6: ("def_bits[]", "Define bits", parseJpeg),
7: ("def_but[]", "Define button", None),
8: ("jpg_table", "JPEG tables", None),
9: ("bkgd_color[]", "Set background color", parseBackgroundColor),
10: ("def_font[]", "Define font", None),
11: ("def_text[]", "Define text", None),
12: ("action[]", "Action script", parseActionScript),
13: ("def_font_info[]", "Define font info", None),
# SWF version 2.0
14: ("def_sound[]", "Define sound", parseDefineSound),
15: ("start_sound[]", "Start sound", parseStartSound),
16: ("stop_sound[]", "Stop sound", None),
17: ("def_but_sound[]", "Define button sound", None),
18: ("sound_hdr", "Sound stream header", parseSoundHeader),
19: ("sound_blk[]", "Sound stream block", parseSoundBlock),
20: ("def_bits_lossless[]", "Define bits lossless", None),
21: ("def_bits_jpeg2[]", "Define bits JPEG 2", parseJpeg),
22: ("def_shape2[]", "Define shape 2", None),
23: ("def_but_cxform[]", "Define button CXFORM", None),
24: ("protect", "File is protected", None),
# SWF version 3.0
25: ("path_are_ps[]", "Paths are Postscript", None),
26: ("place_obj2[]", "Place object 2", None),
28: ("remove_obj2[]", "Remove object 2", None),
29: ("sync_frame[]", "Synchronize frame", None),
31: ("free_all[]", "Free all", None),
32: ("def_shape3[]", "Define shape 3", None),
33: ("def_text2[]", "Define text 2", None),
34: ("def_but2[]", "Define button2", None),
35: ("def_bits_jpeg3[]", "Define bits JPEG 3", parseJpeg),
36: ("def_bits_lossless2[]", "Define bits lossless 2", None),
39: ("def_sprite[]", "Define sprite", None),
40: ("name_character[]", "Name character", None),
41: ("product_info", "Generator product info", parseProductInfo),
42: ("generator_text[]", "Generator text", None),
43: ("frame_label[]", "Frame label", None),
45: ("sound_hdr2[]", "Sound stream header2", parseSoundHeader),
46: ("def_morph_shape[]", "Define morph shape", None),
47: ("gen_frame[]", "Generate frame", None),
48: ("def_font2[]", "Define font 2", None),
49: ("tpl_command[]", "Template command", None),
# SWF version 4.0
37: ("def_text_field[]", "Define text field", None),
38: ("def_quicktime_movie[]", "Define QuickTime movie", None),
# SWF version 5.0
50: ("def_cmd_obj[]", "Define command object", None),
51: ("flash_generator", "Flash generator", None),
52: ("gen_ext_font[]", "Gen external font", None),
56: ("export[]", "Export", parseExport),
57: ("import[]", "Import", None),
58: ("ebnable_debug", "Enable debug", None),
# SWF version 6.0
59: ("do_init_action[]", "Do init action", None),
60: ("video_str[]", "Video stream", None),
61: ("video_frame[]", "Video frame", parseVideoFrame),
62: ("def_font_info2[]", "Define font info 2", None),
63: ("mx4[]", "MX4", None),
64: ("enable_debug2", "Enable debugger 2", None),
# SWF version 7.0
65: ("script_limits[]", "Script limits", parseScriptLimits),
66: ("tab_index[]", "Set tab index", None),
# SWF version 8.0
69: ("file_attr[]", "File attributes", None),
70: ("place_obj3[]", "Place object 3", None),
71: ("import2[]", "Import a definition list from another movie", None),
73: ("def_font_align[]", "Define font alignment zones", None),
74: ("csm_txt_set[]", "CSM text settings", None),
75: ("def_font3[]", "Define font text 3", None),
77: ("metadata[]", "XML code describing the movie", None),
78: ("def_scale_grid[]", "Define scaling factors", None),
83: ("def_shape4[]", "Define shape 4", None),
84: ("def_morph2[]", "Define a morphing shape 2", None),
# SWF version 9.0
72: ("do_abc[]", "SWF 9 ActionScript container; actions only", parseABC),
76: ("symbol_class[]", "Instantiate objects from a set of classes", parseSymbolClass),
82: ("do_abc_define[]", "SWF 9 ActionScript container; identifier, name, actions", parseABC),
86: ("def_scene_frame[]", "Define raw data for scenes and frames", None),
87: ("def_binary_data[]", "Defines a buffer of any size with any binary user data", parseBinaryData),
88: ("def_font_name[]", "Define the legal font name and copyright", None),
}
def __init__(self, *args):
FieldSet.__init__(self, *args)
size = self["length"].value
if self[0].name == "length_ext":
self._size = (6+size) * 8
else:
self._size = (2+size) * 8
code = self["code"].value
if code in self.TAG_INFO:
self._name, self._description, self.parser = self.TAG_INFO[code]
else:
self.parser = None
def createFields(self):
if self.stream.readBits(self.absolute_address, 6, self.endian) == 63:
yield Bits(self, "length_ext", 6)
yield Bits(self, "code", 10)
yield filesizeHandler(UInt32(self, "length"))
else:
yield filesizeHandler(Bits(self, "length", 6))
yield Bits(self, "code", 10)
size = self["length"].value
if 0 < size:
if self.parser:
for field in self.parser(self, size):
yield field
else:
yield RawBytes(self, "data", size)
def createDescription(self):
return "Tag: %s (%s)" % (self["code"].display, self["length"].display)
class SwfFile(Parser):
VALID_VERSIONS = set(xrange(1, 10+1))
PARSER_TAGS = {
"id": "swf",
"category": "container",
"file_ext": ["swf"],
"mime": (u"application/x-shockwave-flash",),
"min_size": 64,
"description": u"Macromedia Flash data"
}
PARSER_TAGS["magic"] = []
for version in VALID_VERSIONS:
PARSER_TAGS["magic"].append(("FWS%c" % version, 0))
PARSER_TAGS["magic"].append(("CWS%c" % version, 0))
endian = LITTLE_ENDIAN
SWF_SCALE_FACTOR = 1.0 / 20
def validate(self):
if self.stream.readBytes(0, 3) not in ("FWS", "CWS"):
return "Wrong file signature"
if self["version"].value not in self.VALID_VERSIONS:
return "Unknown version"
if MAX_FILE_SIZE < self["filesize"].value:
return "File too big (%u)" % self["filesize"].value
if self["signature"].value == "FWS":
if self["rect/padding"].value != 0:
return "Unknown rectangle padding value"
return True
def createFields(self):
yield String(self, "signature", 3, "SWF format signature", charset="ASCII")
yield UInt8(self, "version")
yield filesizeHandler(UInt32(self, "filesize"))
if self["signature"].value != "CWS":
yield RECT(self, "rect")
yield FixedFloat16(self, "frame_rate")
yield UInt16(self, "frame_count")
while not self.eof:
yield Tag(self, "tag[]")
else:
size = (self.size - self.current_size) // 8
if has_deflate:
data = Deflate(Bytes(self, "compressed_data", size), False)
def createInputStream(cis, source=None, **args):
stream = cis(source=source)
header = StringInputStream("FWS" + self.stream.readBytes(3*8, 5))
args.setdefault("tags",[]).append(("class", SwfFile))
return ConcatStream((header, stream), source=stream.source, **args)
data.setSubIStream(createInputStream)
yield data
else:
yield Bytes(self, "compressed_data", size)
def createDescription(self):
desc = ["version %u" % self["version"].value]
if self["signature"].value == "CWS":
desc.append("compressed")
return u"Macromedia Flash data: %s" % (", ".join(desc))
def createContentSize(self):
if self["signature"].value == "FWS":
return self["filesize"].value * 8
else:
# TODO: Size of compressed Flash?
return None