Remove submodule, just put Dependencies in ./libs
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user