Remove submodule, just put Dependencies in ./libs

This commit is contained in:
Ruud
2011-02-28 15:25:42 +01:00
parent 2fb578d4de
commit 6888798ef6
541 changed files with 173394 additions and 4 deletions
+12
View File
@@ -0,0 +1,12 @@
from hachoir_parser.audio.aiff import AiffFile
from hachoir_parser.audio.au import AuFile
from hachoir_parser.audio.itunesdb import ITunesDBFile
from hachoir_parser.audio.midi import MidiFile
from hachoir_parser.audio.mpeg_audio import MpegAudioFile
from hachoir_parser.audio.real_audio import RealAudioFile
from hachoir_parser.audio.xm import XMModule
from hachoir_parser.audio.s3m import S3MModule
from hachoir_parser.audio.s3m import PTMModule
from hachoir_parser.audio.mod import AmigaModule
from hachoir_parser.audio.flac import FlacParser
+127
View File
@@ -0,0 +1,127 @@
"""
Audio Interchange File Format (AIFF) parser.
Author: Victor Stinner
Creation: 27 december 2006
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet,
UInt16, UInt32, Float80, TimestampMac32,
RawBytes, NullBytes,
String, Enum, PascalString32)
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.text_handler import filesizeHandler
from hachoir_core.tools import alignValue
from hachoir_parser.audio.id3 import ID3v2
CODEC_NAME = {
'ACE2': u"ACE 2-to-1",
'ACE8': u"ACE 8-to-3",
'MAC3': u"MAC 3-to-1",
'MAC6': u"MAC 6-to-1",
'NONE': u"None",
'sowt': u"Little-endian, no compression",
}
class Comment(FieldSet):
def createFields(self):
yield TimestampMac32(self, "timestamp")
yield PascalString32(self, "text")
def parseText(self):
yield String(self, "text", self["size"].value)
def parseID3(self):
yield ID3v2(self, "id3v2", size=self["size"].value*8)
def parseComment(self):
yield UInt16(self, "nb_comment")
for index in xrange(self["nb_comment"].value):
yield Comment(self, "comment[]")
def parseCommon(self):
yield UInt16(self, "nb_channel")
yield UInt32(self, "nb_sample")
yield UInt16(self, "sample_size")
yield Float80(self, "sample_rate")
yield Enum(String(self, "codec", 4, strip="\0", charset="ASCII"), CODEC_NAME)
def parseVersion(self):
yield TimestampMac32(self, "timestamp")
def parseSound(self):
yield UInt32(self, "offset")
yield UInt32(self, "block_size")
size = (self.size - self.current_size) // 8
if size:
yield RawBytes(self, "data", size)
class Chunk(FieldSet):
TAG_INFO = {
'COMM': ('common', "Common chunk", parseCommon),
'COMT': ('comment', "Comment", parseComment),
'NAME': ('name', "Name", parseText),
'AUTH': ('author', "Author", parseText),
'FVER': ('version', "Version", parseVersion),
'SSND': ('sound', "Sound data", parseSound),
'ID3 ': ('id3', "ID3", parseID3),
}
def __init__(self, *args):
FieldSet.__init__(self, *args)
self._size = (8 + alignValue(self["size"].value, 2)) * 8
tag = self["type"].value
if tag in self.TAG_INFO:
self._name, self._description, self._parser = self.TAG_INFO[tag]
else:
self._parser = None
def createFields(self):
yield String(self, "type", 4, "Signature (FORM)", charset="ASCII")
yield filesizeHandler(UInt32(self, "size"))
size = self["size"].value
if size:
if self._parser:
for field in self._parser(self):
yield field
if size % 2:
yield NullBytes(self, "padding", 1)
else:
yield RawBytes(self, "data", size)
class AiffFile(Parser):
PARSER_TAGS = {
"id": "aiff",
"category": "audio",
"file_ext": ("aif", "aiff", "aifc"),
"mime": (u"audio/x-aiff",),
"magic_regex": (("FORM.{4}AIF[CF]", 0),),
"min_size": 12*8,
"description": "Audio Interchange File Format (AIFF)"
}
endian = BIG_ENDIAN
def validate(self):
if self.stream.readBytes(0, 4) != "FORM":
return "Invalid signature"
if self.stream.readBytes(8*8, 4) not in ("AIFF", "AIFC"):
return "Invalid type"
return True
def createFields(self):
yield String(self, "signature", 4, "Signature (FORM)", charset="ASCII")
yield filesizeHandler(UInt32(self, "filesize"))
yield String(self, "type", 4, "Form type (AIFF or AIFC)", charset="ASCII")
while not self.eof:
yield Chunk(self, "chunk[]")
def createDescription(self):
if self["type"].value == "AIFC":
return "Audio Interchange File Format Compressed (AIFC)"
else:
return "Audio Interchange File Format (AIFF)"
def createContentSize(self):
return self["filesize"].value * 8
+88
View File
@@ -0,0 +1,88 @@
"""
AU audio file parser
Author: Victor Stinner
Creation: 12 july 2006
"""
from hachoir_parser import Parser
from hachoir_core.field import UInt32, Enum, String, RawBytes
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.text_handler import displayHandler, filesizeHandler
from hachoir_core.tools import createDict, humanFrequency
class AuFile(Parser):
PARSER_TAGS = {
"id": "sun_next_snd",
"category": "audio",
"file_ext": ("au", "snd"),
"mime": (u"audio/basic",),
"min_size": 24*8,
"magic": ((".snd", 0),),
"description": "Sun/NeXT audio"
}
endian = BIG_ENDIAN
CODEC_INFO = {
1: (8, u"8-bit ISDN u-law"),
2: (8, u"8-bit linear PCM"),
3: (16, u"16-bit linear PCM"),
4: (24, u"24-bit linear PCM"),
5: (32, u"32-bit linear PCM"),
6: (32, u"32-bit IEEE floating point"),
7: (64, u"64-bit IEEE floating point"),
8: (None, u"Fragmented sample data"),
9: (None, u"DSP program"),
10: (8, u"8-bit fixed point"),
11: (16, u"16-bit fixed point"),
12: (24, u"24-bit fixed point"),
13: (32, u"32-bit fixed point"),
18: (16, u"16-bit linear with emphasis"),
19: (16, u"16-bit linear compressed"),
20: (16, u"16-bit linear with emphasis and compression"),
21: (None, u"Music kit DSP commands"),
23: (None, u"4-bit ISDN u-law compressed (CCITT G.721 ADPCM)"),
24: (None, u"ITU-T G.722 ADPCM"),
25: (None, u"ITU-T G.723 3-bit ADPCM"),
26: (None, u"ITU-T G.723 5-bit ADPCM"),
27: (8, u"8-bit ISDN A-law"),
}
# Create bit rate and codec name dictionnaries
BITS_PER_SAMPLE = createDict(CODEC_INFO, 0)
CODEC_NAME = createDict(CODEC_INFO, 1)
VALID_NB_CHANNEL = set((1,2)) # FIXME: 4, 5, 7, 8 channels are supported?
def validate(self):
if self.stream.readBytes(0, 4) != ".snd":
return "Wrong file signature"
if self["channels"].value not in self.VALID_NB_CHANNEL:
return "Invalid number of channel"
return True
def getBitsPerSample(self):
"""
Get bit rate (number of bit per sample per channel),
may returns None if you unable to compute it.
"""
return self.BITS_PER_SAMPLE.get(self["codec"].value)
def createFields(self):
yield String(self, "signature", 4, 'Format signature (".snd")', charset="ASCII")
yield UInt32(self, "data_ofs", "Data offset")
yield filesizeHandler(UInt32(self, "data_size", "Data size"))
yield Enum(UInt32(self, "codec", "Audio codec"), self.CODEC_NAME)
yield displayHandler(UInt32(self, "sample_rate", "Number of samples/second"), humanFrequency)
yield UInt32(self, "channels", "Number of interleaved channels")
size = self["data_ofs"].value - self.current_size // 8
if 0 < size:
yield String(self, "info", size, "Information", strip=" \0", charset="ISO-8859-1")
size = min(self["data_size"].value, (self.size - self.current_size) // 8)
yield RawBytes(self, "audio_data", size, "Audio data")
def createContentSize(self):
return (self["data_ofs"].value + self["data_size"].value) * 8
+157
View File
@@ -0,0 +1,157 @@
"""
FLAC (audio) parser
Documentation:
* http://flac.sourceforge.net/format.html
Author: Esteban Loiseau <baal AT tuxfamily.org>
Creation date: 2008-04-09
"""
from hachoir_parser import Parser
from hachoir_core.field import FieldSet, String, Bit, Bits, UInt16, UInt24, RawBytes, Enum, NullBytes
from hachoir_core.stream import BIG_ENDIAN, LITTLE_ENDIAN
from hachoir_core.tools import createDict
from hachoir_parser.container.ogg import parseVorbisComment
class VorbisComment(FieldSet):
endian = LITTLE_ENDIAN
createFields = parseVorbisComment
class StreamInfo(FieldSet):
static_size = 34*8
def createFields(self):
yield UInt16(self, "min_block_size", "The minimum block size (in samples) used in the stream")
yield UInt16(self, "max_block_size", "The maximum block size (in samples) used in the stream")
yield UInt24(self, "min_frame_size", "The minimum frame size (in bytes) used in the stream")
yield UInt24(self, "max_frame_size", "The maximum frame size (in bytes) used in the stream")
yield Bits(self, "sample_hertz", 20, "Sample rate in Hertz")
yield Bits(self, "nb_channel", 3, "Number of channels minus one")
yield Bits(self, "bits_per_sample", 5, "Bits per sample minus one")
yield Bits(self, "total_samples", 36, "Total samples in stream")
yield RawBytes(self, "md5sum", 16, "MD5 signature of the unencoded audio data")
class SeekPoint(FieldSet):
def createFields(self):
yield Bits(self, "sample_number", 64, "Sample number")
yield Bits(self, "offset", 64, "Offset in bytes")
yield Bits(self, "nb_sample", 16)
class SeekTable(FieldSet):
def createFields(self):
while not self.eof:
yield SeekPoint(self, "point[]")
class MetadataBlock(FieldSet):
"Metadata block field: http://flac.sourceforge.net/format.html#metadata_block"
BLOCK_TYPES = {
0: ("stream_info", u"Stream info", StreamInfo),
1: ("padding[]", u"Padding", None),
2: ("application[]", u"Application", None),
3: ("seek_table", u"Seek table", SeekTable),
4: ("comment", u"Vorbis comment", VorbisComment),
5: ("cue_sheet[]", u"Cue sheet", None),
6: ("picture[]", u"Picture", None),
}
BLOCK_TYPE_DESC = createDict(BLOCK_TYPES, 1)
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = 32 + self["metadata_length"].value * 8
try:
key = self["block_type"].value
self._name, self._description, self.handler = self.BLOCK_TYPES[key]
except KeyError:
self.handler = None
def createFields(self):
yield Bit(self, "last_metadata_block", "True if this is the last metadata block")
yield Enum(Bits(self, "block_type", 7, "Metadata block header type"), self.BLOCK_TYPE_DESC)
yield UInt24(self, "metadata_length", "Length of following metadata in bytes (doesn't include this header)")
block_type = self["block_type"].value
size = self["metadata_length"].value
if not size:
return
try:
handler = self.BLOCK_TYPES[block_type][2]
except KeyError:
handler = None
if handler:
yield handler(self, "content", size=size*8)
elif self["block_type"].value == 1:
yield NullBytes(self, "padding", size)
else:
yield RawBytes(self, "rawdata", size)
class Metadata(FieldSet):
def createFields(self):
while not self.eof:
field = MetadataBlock(self,"metadata_block[]")
yield field
if field["last_metadata_block"].value:
break
class Frame(FieldSet):
SAMPLE_RATES = {
0: "get from STREAMINFO metadata block",
1: "88.2kHz",
2: "176.4kHz",
3: "192kHz",
4: "8kHz",
5: "16kHz",
6: "22.05kHz",
7: "24kHz",
8: "32kHz",
9: "44.1kHz",
10: "48kHz",
11: "96kHz",
12: "get 8 bit sample rate (in kHz) from end of header",
13: "get 16 bit sample rate (in Hz) from end of header",
14: "get 16 bit sample rate (in tens of Hz) from end of header",
}
def createFields(self):
yield Bits(self, "sync", 14, "Sync code: 11111111111110")
yield Bit(self, "reserved[]")
yield Bit(self, "blocking_strategy")
yield Bits(self, "block_size", 4)
yield Enum(Bits(self, "sample_rate", 4), self.SAMPLE_RATES)
yield Bits(self, "channel_assign", 4)
yield Bits(self, "sample_size", 3)
yield Bit(self, "reserved[]")
# FIXME: Finish frame header parser
class Frames(FieldSet):
def createFields(self):
while not self.eof:
yield Frame(self, "frame[]")
# FIXME: Parse all frames
return
class FlacParser(Parser):
"Parse FLAC audio files: FLAC is a lossless audio codec"
MAGIC = "fLaC\x00"
PARSER_TAGS = {
"id": "flac",
"category": "audio",
"file_ext": ("flac",),
"mime": (u"audio/x-flac",),
"magic": ((MAGIC, 0),),
"min_size": 4*8,
"description": "FLAC audio",
}
endian = BIG_ENDIAN
def validate(self):
if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC:
return u"Invalid magic string"
return True
def createFields(self):
yield String(self, "signature", 4,charset="ASCII", description="FLAC signature: fLaC string")
yield Metadata(self,"metadata")
yield Frames(self,"frames")
+507
View File
@@ -0,0 +1,507 @@
"""
ID3 metadata parser, supported versions: 1.O, 2.2, 2.3 and 2.4
Informations: http://www.id3.org/
Author: Victor Stinner
"""
from hachoir_core.field import (FieldSet, MatchError, ParserError,
Enum, UInt8, UInt24, UInt32,
CString, String, RawBytes,
Bit, Bits, NullBytes, NullBits)
from hachoir_core.text_handler import textHandler
from hachoir_core.tools import humanDuration
from hachoir_core.endian import NETWORK_ENDIAN
class ID3v1(FieldSet):
static_size = 128 * 8
GENRE_NAME = {
0: u"Blues",
1: u"Classic Rock",
2: u"Country",
3: u"Dance",
4: u"Disco",
5: u"Funk",
6: u"Grunge",
7: u"Hip-Hop",
8: u"Jazz",
9: u"Metal",
10: u"New Age",
11: u"Oldies",
12: u"Other",
13: u"Pop",
14: u"R&B",
15: u"Rap",
16: u"Reggae",
17: u"Rock",
18: u"Techno",
19: u"Industrial",
20: u"Alternative",
21: u"Ska",
22: u"Death Metal",
23: u"Pranks",
24: u"Soundtrack",
25: u"Euro-Techno",
26: u"Ambient",
27: u"Trip-Hop",
28: u"Vocal",
29: u"Jazz+Funk",
30: u"Fusion",
31: u"Trance",
32: u"Classical",
33: u"Instrumental",
34: u"Acid",
35: u"House",
36: u"Game",
37: u"Sound Clip",
38: u"Gospel",
39: u"Noise",
40: u"AlternRock",
41: u"Bass",
42: u"Soul",
43: u"Punk",
44: u"Space",
45: u"Meditative",
46: u"Instrumental Pop",
47: u"Instrumental Rock",
48: u"Ethnic",
49: u"Gothic",
50: u"Darkwave",
51: u"Techno-Industrial",
52: u"Electronic",
53: u"Pop-Folk",
54: u"Eurodance",
55: u"Dream",
56: u"Southern Rock",
57: u"Comedy",
58: u"Cult",
59: u"Gangsta",
60: u"Top 40",
61: u"Christian Rap",
62: u"Pop/Funk",
63: u"Jungle",
64: u"Native American",
65: u"Cabaret",
66: u"New Wave",
67: u"Psychadelic",
68: u"Rave",
69: u"Showtunes",
70: u"Trailer",
71: u"Lo-Fi",
72: u"Tribal",
73: u"Acid Punk",
74: u"Acid Jazz",
75: u"Polka",
76: u"Retro",
77: u"Musical",
78: u"Rock & Roll",
79: u"Hard Rock",
# Following are winamp extentions
80: u"Folk",
81: u"Folk-Rock",
82: u"National Folk",
83: u"Swing",
84: u"Fast Fusion",
85: u"Bebob",
86: u"Latin",
87: u"Revival",
88: u"Celtic",
89: u"Bluegrass",
90: u"Avantgarde",
91: u"Gothic Rock",
92: u"Progressive Rock",
93: u"Psychedelic Rock",
94: u"Symphonic Rock",
95: u"Slow Rock",
96: u"Big Band",
97: u"Chorus",
98: u"Easy Listening",
99: u"Acoustic",
100: u"Humour",
101: u"Speech",
102: u"Chanson",
103: u"Opera",
104: u"Chamber Music",
105: u"Sonata",
106: u"Symphony",
107: u"Booty Bass",
108: u"Primus",
109: u"Porn Groove",
110: u"Satire",
111: u"Slow Jam",
112: u"Club",
113: u"Tango",
114: u"Samba",
115: u"Folklore",
116: u"Ballad",
117: u"Power Ballad",
118: u"Rhythmic Soul",
119: u"Freestyle",
120: u"Duet",
121: u"Punk Rock",
122: u"Drum Solo",
123: u"A capella",
124: u"Euro-House",
125: u"Dance Hall",
126: u"Goa",
127: u"Drum & Bass",
128: u"Club-House",
129: u"Hardcore",
130: u"Terror",
131: u"Indie",
132: u"Britpop",
133: u"Negerpunk",
134: u"Polsk Punk",
135: u"Beat",
136: u"Christian Gangsta Rap",
137: u"Heavy Metal",
138: u"Black Metal",
139: u"Crossover",
140: u"Contemporary Christian",
141: u"Christian Rock ",
142: u"Merengue",
143: u"Salsa",
144: u"Trash Metal",
145: u"Anime",
146: u"JPop",
147: u"Synthpop"
}
def createFields(self):
yield String(self, "signature", 3, "IDv1 signature (\"TAG\")", charset="ASCII")
if self["signature"].value != "TAG":
raise MatchError("Stream doesn't look like ID3v1 (wrong signature)!")
# TODO: Charset of below strings?
yield String(self, "song", 30, "Song title", strip=" \0", charset="ISO-8859-1")
yield String(self, "author", 30, "Author", strip=" \0", charset="ISO-8859-1")
yield String(self, "album", 30, "Album title", strip=" \0", charset="ISO-8859-1")
yield String(self, "year", 4, "Year", strip=" \0", charset="ISO-8859-1")
# TODO: Write better algorithm to guess ID3v1 version
version = self.getVersion()
if version in ("v1.1", "v1.1b"):
if version == "v1.1b":
# ID3 v1.1b
yield String(self, "comment", 29, "Comment", strip=" \0", charset="ISO-8859-1")
yield UInt8(self, "track_nb", "Track number")
else:
# ID3 v1.1
yield String(self, "comment", 30, "Comment", strip=" \0", charset="ISO-8859-1")
yield Enum(UInt8(self, "genre", "Genre"), self.GENRE_NAME)
else:
# ID3 v1.0
yield String(self, "comment", 31, "Comment", strip=" \0", charset="ISO-8859-1")
def getVersion(self):
addr = self.absolute_address + 126*8
bytes = self.stream.readBytes(addr, 2)
# last byte (127) is not space?
if bytes[1] != ' ':
# byte 126 is nul?
if bytes[0] == 0x00:
return "v1.1"
else:
return "v1.1b"
else:
return "1.0"
def createDescription(self):
version = self.getVersion()
return "ID3 %s: author=%s, song=%s" % (
version, self["author"].value, self["song"].value)
def getCharset(field):
try:
key = field.value
return ID3_StringCharset.charset_name[key]
except KeyError:
raise ParserError("ID3v2: Invalid charset (%s)." % key)
class ID3_String(FieldSet):
STRIP = " \0"
def createFields(self):
yield String(self, "text", self._size/8, "Text", charset="ISO-8859-1", strip=self.STRIP)
class ID3_StringCharset(ID3_String):
STRIP = " \0"
charset_desc = {
0: "ISO-8859-1",
1: "UTF-16 with BOM",
2: "UTF-16 (big endian)",
3: "UTF-8"
}
charset_name = {
0: "ISO-8859-1",
1: "UTF-16",
2: "UTF-16-BE",
3: "UTF-8"
}
def createFields(self):
yield Enum(UInt8(self, "charset"), self.charset_desc)
size = (self.size - self.current_size)/8
if not size:
return
charset = getCharset(self["charset"])
yield String(self, "text", size, "Text", charset=charset, strip=self.STRIP)
class ID3_GEOB(ID3_StringCharset):
def createFields(self):
yield Enum(UInt8(self, "charset"), self.charset_desc)
charset = getCharset(self["charset"])
yield CString(self, "mime", "MIME type", charset=charset)
yield CString(self, "filename", "File name", charset=charset)
yield CString(self, "description", "Content description", charset=charset)
size = (self.size - self.current_size) // 8
if not size:
return
yield String(self, "text", size, "Text", charset=charset)
class ID3_Comment(ID3_StringCharset):
def createFields(self):
yield Enum(UInt8(self, "charset"), self.charset_desc)
yield String(self, "lang", 3, "Language", charset="ASCII")
charset = getCharset(self["charset"])
yield CString(self, "title", "Title", charset=charset, strip=self.STRIP)
size = (self.size - self.current_size) // 8
if not size:
return
yield String(self, "text", size, "Text", charset=charset, strip=self.STRIP)
class ID3_StringTitle(ID3_StringCharset):
def createFields(self):
yield Enum(UInt8(self, "charset"), self.charset_desc)
if self.current_size == self.size:
return
charset = getCharset(self["charset"])
yield CString(self, "title", "Title", charset=charset, strip=self.STRIP)
size = (self.size - self.current_size)/8
if not size:
return
yield String(self, "text", size, "Text", charset=charset, strip=self.STRIP)
class ID3_Private(FieldSet):
def createFields(self):
size = self._size/8
# TODO: Strings charset?
if self.stream.readBytes(self.absolute_address, 9) == "PeakValue":
yield String(self, "text", 9, "Text")
size -= 9
yield String(self, "content", size, "Content")
class ID3_TrackLength(FieldSet):
def createFields(self):
yield NullBytes(self, "zero", 1)
yield textHandler(String(self, "length", self._size/8 - 1,
"Length in ms", charset="ASCII"), self.computeLength)
def computeLength(self, field):
try:
ms = int(field.value)
return humanDuration(ms)
except:
return field.value
class ID3_Picture23(FieldSet):
pict_type_name = {
0x00: "Other",
0x01: "32x32 pixels 'file icon' (PNG only)",
0x02: "Other file icon",
0x03: "Cover (front)",
0x04: "Cover (back)",
0x05: "Leaflet page",
0x06: "Media (e.g. lable side of CD)",
0x07: "Lead artist/lead performer/soloist",
0x08: "Artist/performer",
0x09: "Conductor",
0x0A: "Band/Orchestra",
0x0B: "Composer",
0x0C: "Lyricist/text writer",
0x0D: "Recording Location",
0x0E: "During recording",
0x0F: "During performance",
0x10: "Movie/video screen capture",
0x11: "A bright coloured fish",
0x12: "Illustration",
0x13: "Band/artist logotype",
0x14: "Publisher/Studio logotype"
}
def createFields(self):
yield Enum(UInt8(self, "charset"), ID3_StringCharset.charset_desc)
charset = getCharset(self["charset"])
yield String(self, "img_fmt", 3, charset="ASCII")
yield Enum(UInt8(self, "pict_type"), self.pict_type_name)
yield CString(self, "text", "Text", charset=charset, strip=" \0")
size = (self._size - self._current_size) / 8
if size:
yield RawBytes(self, "img_data", size)
class ID3_Picture24(FieldSet):
def createFields(self):
yield Enum(UInt8(self, "charset"), ID3_StringCharset.charset_desc)
charset = getCharset(self["charset"])
yield CString(self, "mime", "MIME type", charset=charset)
yield Enum(UInt8(self, "pict_type"), ID3_Picture23.pict_type_name)
yield CString(self, "description", charset=charset)
size = (self._size - self._current_size) / 8
if size:
yield RawBytes(self, "img_data", size)
class ID3_Chunk(FieldSet):
endian = NETWORK_ENDIAN
tag22_name = {
"TT2": "Track title",
"TP1": "Artist",
"TRK": "Track number",
"COM": "Comment",
"TCM": "Composer",
"TAL": "Album",
"TYE": "Year",
"TEN": "Encoder",
"TCO": "Content type",
"PIC": "Picture"
}
tag23_name = {
"COMM": "Comment",
"GEOB": "Encapsulated object",
"PRIV": "Private",
"TPE1": "Artist",
"TCOP": "Copyright",
"TALB": "Album",
"TENC": "Encoder",
"TYER": "Year",
"TSSE": "Encoder settings",
"TCOM": "Composer",
"TRCK": "Track number",
"PCNT": "Play counter",
"TCON": "Content type",
"TLEN": "Track length",
"TIT2": "Track title",
"WXXX": "User defined URL"
}
handler = {
"COMM": ID3_Comment,
"COM": ID3_Comment,
"GEOB": ID3_GEOB,
"PIC": ID3_Picture23,
"APIC": ID3_Picture24,
"PRIV": ID3_Private,
"TXXX": ID3_StringTitle,
"WOAR": ID3_String,
"WXXX": ID3_StringTitle,
}
def __init__(self, *args):
FieldSet.__init__(self, *args)
if 3 <= self["../ver_major"].value:
self._size = (10 + self["size"].value) * 8
else:
self._size = (self["size"].value + 6) * 8
def createFields(self):
if 3 <= self["../ver_major"].value:
# ID3 v2.3 and 2.4
yield Enum(String(self, "tag", 4, "Tag", charset="ASCII", strip="\0"), ID3_Chunk.tag23_name)
if 4 <= self["../ver_major"].value:
yield ID3_Size(self, "size") # ID3 v2.4
else:
yield UInt32(self, "size") # ID3 v2.3
yield Bit(self, "tag_alter", "Tag alter preservation")
yield Bit(self, "file_alter", "Tag alter preservation")
yield Bit(self, "rd_only", "Read only?")
yield NullBits(self, "padding[]", 5)
yield Bit(self, "compressed", "Frame is compressed?")
yield Bit(self, "encrypted", "Frame is encrypted?")
yield Bit(self, "group", "Grouping identity")
yield NullBits(self, "padding[]", 5)
size = self["size"].value
is_compressed = self["compressed"].value
else:
# ID3 v2.2
yield Enum(String(self, "tag", 3, "Tag", charset="ASCII", strip="\0"), ID3_Chunk.tag22_name)
yield UInt24(self, "size")
size = self["size"].value - self.current_size/8 + 6
is_compressed = False
if size:
cls = None
if not(is_compressed):
tag = self["tag"].value
if tag in ID3_Chunk.handler:
cls = ID3_Chunk.handler[tag]
elif tag[0] == "T":
cls = ID3_StringCharset
if cls:
yield cls(self, "content", "Content", size=size*8)
else:
yield RawBytes(self, "content", size, "Raw data content")
def createDescription(self):
if self["size"].value != 0:
return "ID3 Chunk: %s" % self["tag"].display
else:
return "ID3 Chunk: (terminator)"
class ID3_Size(Bits):
static_size = 32
def __init__(self, parent, name, description=None):
Bits.__init__(self, parent, name, 32, description)
def createValue(self):
data = self.parent.stream.readBytes(self.absolute_address, 4)
# TODO: Check that bit #7 of each byte is nul: not(ord(data[i]) & 127)
return reduce(lambda x, y: x*128 + y, (ord(item) for item in data ))
class ID3v2(FieldSet):
endian = NETWORK_ENDIAN
VALID_MAJOR_VERSIONS = (2, 3, 4)
def __init__(self, parent, name, size=None):
FieldSet.__init__(self, parent, name, size=size)
if not self._size:
self._size = (self["size"].value + 10) * 8
def createDescription(self):
return "ID3 v2.%s.%s" % \
(self["ver_major"].value, self["ver_minor"].value)
def createFields(self):
# Signature + version
yield String(self, "header", 3, "Header (ID3)", charset="ASCII")
yield UInt8(self, "ver_major", "Version (major)")
yield UInt8(self, "ver_minor", "Version (minor)")
# Check format
if self["header"].value != "ID3":
raise MatchError("Signature error, should be \"ID3\".")
if self["ver_major"].value not in self.VALID_MAJOR_VERSIONS \
or self["ver_minor"].value != 0:
raise MatchError(
"Unknown ID3 metadata version (2.%u.%u)"
% (self["ver_major"].value, self["ver_minor"].value))
# Flags
yield Bit(self, "unsync", "Unsynchronisation is used?")
yield Bit(self, "ext", "Extended header is used?")
yield Bit(self, "exp", "Experimental indicator")
yield NullBits(self, "padding[]", 5)
# Size
yield ID3_Size(self, "size")
# All tags
while self.current_size < self._size:
field = ID3_Chunk(self, "field[]")
yield field
if field["size"].value == 0:
break
# Search first byte of the MPEG file
padding = self.seekBit(self._size)
if padding:
yield padding
+433
View File
@@ -0,0 +1,433 @@
"""
iPod iTunesDB parser.
Documentation:
- http://ipodlinux.org/ITunesDB
Author: Romain HERAULT
Creation date: 19 august 2006
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet,
UInt8, UInt16, UInt32, UInt64, TimestampMac32,
String, Float32, NullBytes, Enum)
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.tools import humanDuration
from hachoir_core.text_handler import displayHandler, filesizeHandler
list_order={
1 : "playlist order (manual sort order)",
2 : "???",
3 : "songtitle",
4 : "album",
5 : "artist",
6 : "bitrate",
7 : "genre",
8 : "kind",
9 : "date modified",
10 : "track number",
11 : "size",
12 : "time",
13 : "year",
14 : "sample rate",
15 : "comment",
16 : "date added",
17 : "equalizer",
18 : "composer",
19 : "???",
20 : "play count",
21 : "last played",
22 : "disc number",
23 : "my rating",
24 : "release date",
25 : "BPM",
26 : "grouping",
27 : "category",
28 : "description",
29 : "show",
30 : "season",
31 : "episode number"
}
class DataObject(FieldSet):
type_name={
1:"Title",
2:"Location",
3:"Album",
4:"Artist",
5:"Genre",
6:"Filetype",
7:"EQ Setting",
8:"Comment",
9:"Category",
12:"Composer",
13:"Grouping",
14:"Description text",
15:"Podcast Enclosure URL",
16:"Podcast RSS URL",
17:"Chapter data",
18:"Subtitle",
19:"Show (for TV Shows only)",
20:"Episode",
21:"TV Network",
50:"Smart Playlist Data",
51:"Smart Playlist Rules",
52:"Library Playlist Index",
100:"Column info",
}
mhod52_sort_index_type_name={
3:"Title",
4:"Album, then Disk/Tracknumber, then Title",
5:"Artist, then Album, then Disc/Tracknumber, then Title",
7:"Genre, then Artist, then Album, then Disc/Tracknumber, then Title",
8:"Composer, then Title"
}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = self["entry_length"].value *8
def createFields(self):
yield String(self, "header_id", 4, "Data Object Header Markup (\"mhod\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "entry_length", "Entry Length")
yield Enum(UInt32(self, "type", "type"),self.type_name)
if(self["type"].value<15):
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "position", "Position")
yield UInt32(self, "length", "String Length in bytes")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield String(self, "string", self["length"].value, "String Data", charset="UTF-16-LE")
elif (self["type"].value<17):
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield String(self, "string", self._size/8-self["header_length"].value, "String Data", charset="UTF-8")
elif (self["type"].value == 52):
yield UInt32(self, "unknown[]", "unk1")
yield UInt32(self, "unknown[]", "unk2")
yield Enum(UInt32(self, "sort_index_type", "Sort Index Type"),self.mhod52_sort_index_type_name)
yield UInt32(self, "entry_count", "Entry Count")
indexes_size = self["entry_count"].value*4
padding_offset = self["entry_length"].value - indexes_size
padding = self.seekByte(padding_offset, "header padding")
if padding:
yield padding
for i in xrange(self["entry_count"].value):
yield UInt32(self, "index["+str(i)+"]", "Index of the "+str(i)+"nth mhit")
else:
padding = self.seekByte(self["header_length"].value, "header padding")
if padding:
yield padding
padding = self.seekBit(self._size, "entry padding")
if padding:
yield padding
class TrackItem(FieldSet):
x1_type_name={
0:"AAC or CBR MP3",
1:"VBR MP3"
}
x2_type_name={
0:"AAC",
1:"MP3"
}
media_type_name={
0x00:"Audio/Video",
0x01:"Audio",
0x02:"Video",
0x04:"Podcast",
0x06:"Video Podcast",
0x08:"Audiobook",
0x20:"Music Video",
0x40:"TV Show",
0X60:"TV Show (Music lists)",
}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = self["entry_length"].value *8
def createFields(self):
yield String(self, "header_id", 4, "Track Item Header Markup (\"mhit\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "entry_length", "Entry Length")
yield UInt32(self, "string_number", "Number of Strings")
yield UInt32(self, "unique_id", "Unique ID")
yield UInt32(self, "visible_tag", "Visible Tag")
yield String(self, "file_type", 4, "File Type")
yield Enum(UInt8(self, "x1_type", "Extended Type 1"),self.x1_type_name)
yield Enum(UInt8(self, "x2_type", "Extended type 2"),self.x2_type_name)
yield UInt8(self, "compilation_flag", "Compilation Flag")
yield UInt8(self, "rating", "Rating")
yield TimestampMac32(self, "added_date", "Date when the item was added")
yield filesizeHandler(UInt32(self, "size", "Track size in bytes"))
yield displayHandler(UInt32(self, "length", "Track length in milliseconds"), humanDuration)
yield UInt32(self, "track_number", "Number of this track")
yield UInt32(self, "total_track", "Total number of tracks")
yield UInt32(self, "year", "Year of the track")
yield UInt32(self, "bitrate", "Bitrate")
yield UInt32(self, "samplerate", "Sample Rate")
yield UInt32(self, "volume", "volume")
yield UInt32(self, "start_time", "Start playing at, in milliseconds")
yield UInt32(self, "stop_time", "Stop playing at, in milliseconds")
yield UInt32(self, "soundcheck", "SoundCheck preamp")
yield UInt32(self, "playcount_1", "Play count of the track")
yield UInt32(self, "playcount_2", "Play count of the track (identical to playcount_1)")
yield UInt32(self, "last_played_time", "Time the song was last played")
yield UInt32(self, "disc_number", "disc number in multi disc sets")
yield UInt32(self, "total_discs", "Total number of discs in the disc set")
yield UInt32(self, "userid", "User ID in the DRM scheme")
yield TimestampMac32(self, "last_modified", "Time of the last modification of the track")
yield UInt32(self, "bookmark_time", "Bookmark time for AudioBook")
yield UInt64(self, "dbid", "Unique DataBase ID for the song (identical in mhit and in mhii)")
yield UInt8(self, "checked", "song is checked")
yield UInt8(self, "application_rating", "Last Rating before change")
yield UInt16(self, "BPM", "BPM of the track")
yield UInt16(self, "artwork_count", "number of artworks fo this item")
yield UInt16(self, "unknown[]")
yield UInt32(self, "artwork_size", "Total size of artworks in bytes")
yield UInt32(self, "unknown[]")
yield Float32(self, "sample_rate_2", "Sample Rate express in float")
yield UInt32(self, "released_date", "Date of release in Music Store or in Podcast")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt8(self, "has_artwork", "0x01 for track with artwork, 0x02 otherwise")
yield UInt8(self, "skip_wen_shuffling", "Skip that track when shuffling")
yield UInt8(self, "remember_playback_position", "Remember playback position")
yield UInt8(self, "flag4", "Flag 4")
yield UInt64(self, "dbid2", "Unique DataBase ID for the song (identical as above)")
yield UInt8(self, "lyrics_flag", "Lyrics Flag")
yield UInt8(self, "movie_file_flag", "Movie File Flag")
yield UInt8(self, "played_mark", "Track has been played")
yield UInt8(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "sample_count", "Number of samples in the song (only for WAV and AAC files)")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield Enum(UInt32(self, "media_type", "Media Type for video iPod"),self.media_type_name)
yield UInt32(self, "season_number", "Season Number")
yield UInt32(self, "episode_number", "Episode Number")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
yield UInt32(self, "unknown[]")
padding = self.seekByte(self["header_length"].value, "header padding")
if padding:
yield padding
#while ((self.stream.readBytes(0, 4) == 'mhod') and ((self.current_size/8) < self["entry_length"].value)):
for i in xrange(self["string_number"].value):
yield DataObject(self, "data[]")
padding = self.seekBit(self._size, "entry padding")
if padding:
yield padding
class TrackList(FieldSet):
def createFields(self):
yield String(self, "header_id", 4, "Track List Header Markup (\"mhlt\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "track_number", "Number of Tracks")
padding = self.seekByte(self["header_length"].value, "header padding")
if padding:
yield padding
for i in xrange(self["track_number"].value):
yield TrackItem(self, "track[]")
class PlaylistItem(FieldSet):
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = self["entry_length"].value *8
def createFields(self):
yield String(self, "header_id", 4, "Playlist Item Header Markup (\"mhip\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "entry_length", "Entry Length")
yield UInt32(self, "data_object_child_count", "Number of Child Data Objects")
yield UInt32(self, "podcast_grouping_flag", "Podcast Grouping Flag")
yield UInt32(self, "group_id", "Group ID")
yield UInt32(self, "track_id", "Track ID")
yield TimestampMac32(self, "timestamp", "Song Timestamp")
yield UInt32(self, "podcast_grouping_ref", "Podcast Grouping Reference")
padding = self.seekByte(self["header_length"].value, "header padding")
if padding:
yield padding
for i in xrange(self["data_object_child_count"].value):
yield DataObject(self, "mhod[]")
class Playlist(FieldSet):
is_master_pl_name={
0:"Regular playlist",
1:"Master playlist"
}
is_podcast_name={
0:"Normal Playlist List",
1:"Podcast Playlist List"
}
list_sort_order_name={
1:"Manual Sort Order",
2:"???",
3:"Song Title",
4:"Album",
5:"Artist",
6:"Bitrate",
7:"Genre",
8:"Kind",
9:"Date Modified",
10:"Track Number",
11:"Size",
12:"Time",
13:"Year",
14:"Sample Rate",
15:"Comment",
16:"Date Added",
17:"Equalizer",
18:"Composer",
19:"???",
20:"Play Count",
21:"Last Played",
22:"Disc Number",
23:"My Rating",
24:"Release Date",
25:"BPM",
26:"Grouping",
27:"Category",
28:"Description",
29:"Show",
30:"Season",
31:"Episode Number"
}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = self["entry_length"].value *8
def createFields(self):
yield String(self, "header_id", 4, "Playlist List Header Markup (\"mhyp\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "entry_length", "Entry Length")
yield UInt32(self, "data_object_child_count", "Number of Child Data Objects")
yield UInt32(self, "playlist_count", "Number of Playlist Items")
yield Enum(UInt8(self, "type", "Normal or master playlist?"), self.is_master_pl_name)
yield UInt8(self, "XXX1", "XXX1")
yield UInt8(self, "XXX2", "XXX2")
yield UInt8(self, "XXX3", "XXX3")
yield TimestampMac32(self, "creation_date", "Date when the playlist was created")
yield UInt64(self, "playlistid", "Persistent Playlist ID")
yield UInt32(self, "unk3", "unk3")
yield UInt16(self, "string_mhod_count", "Number of string MHODs for this playlist")
yield Enum(UInt16(self, "is_podcast", "Playlist or Podcast List?"), self.is_podcast_name)
yield Enum(UInt32(self, "sort_order", "Playlist Sort Order"), self.list_sort_order_name)
padding = self.seekByte(self["header_length"].value, "entry padding")
if padding:
yield padding
for i in xrange(self["data_object_child_count"].value):
yield DataObject(self, "mhod[]")
for i in xrange(self["playlist_count"].value):
yield PlaylistItem(self, "playlist_item[]")
class PlaylistList(FieldSet):
def createFields(self):
yield String(self, "header_id", 4, "Playlist List Header Markup (\"mhlp\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "playlist_number", "Number of Playlists")
padding = self.seekByte(self["header_length"].value, "header padding")
if padding:
yield padding
for i in xrange(self["playlist_number"].value):
yield Playlist(self, "playlist[]")
class DataSet(FieldSet):
type_name={
1:"Track List",
2:"Play List",
3:"Podcast List"
}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = self["entry_length"].value *8
def createFields(self):
yield String(self, "header_id", 4, "DataSet Header Markup (\"mhsd\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "entry_length", "Entry Length")
yield Enum(UInt32(self, "type", "type"),self.type_name)
padding = self.seekByte(self["header_length"].value, "header_raw")
if padding:
yield padding
if self["type"].value == 1:
yield TrackList(self, "tracklist[]")
if self["type"].value == 2:
yield PlaylistList(self, "playlist_list[]");
if self["type"].value == 3:
yield PlaylistList(self, "podcast_list[]");
padding = self.seekBit(self._size, "entry padding")
if padding:
yield padding
class DataBase(FieldSet):
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
self._size = self["entry_length"].value *8
# def createFields(self):
class ITunesDBFile(Parser):
PARSER_TAGS = {
"id": "itunesdb",
"category": "audio",
"min_size": 44*8,
"magic": (('mhbd',0),),
"description": "iPod iTunesDB file"
}
endian = LITTLE_ENDIAN
def validate(self):
return self.stream.readBytes(0, 4) == 'mhbd'
def createFields(self):
yield String(self, "header_id", 4, "DataBase Header Markup (\"mhbd\")", charset="ISO-8859-1")
yield UInt32(self, "header_length", "Header Length")
yield UInt32(self, "entry_length", "Entry Length")
yield UInt32(self, "unknown[]")
yield UInt32(self, "version_number", "Version Number")
yield UInt32(self, "child_number", "Number of Children")
yield UInt64(self, "id", "ID for this database")
yield UInt32(self, "unknown[]")
yield UInt64(self, "initial_dbid", "Initial DBID")
size = self["header_length"].value-self.current_size/ 8
if size>0:
yield NullBytes(self, "padding", size)
for i in xrange(self["child_number"].value):
yield DataSet(self, "dataset[]")
padding = self.seekByte(self["entry_length"].value, "entry padding")
if padding:
yield padding
def createContentSize(self):
return self["entry_length"].value * 8
+246
View File
@@ -0,0 +1,246 @@
"""
Musical Instrument Digital Interface (MIDI) audio file parser.
Documentation:
- Standard MIDI File Format, Dustin Caldwell (downloaded on wotsit.org)
Author: Victor Stinner
Creation: 27 december 2006
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, Bits, ParserError,
String, UInt32, UInt24, UInt16, UInt8, Enum, RawBits, RawBytes)
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.text_handler import textHandler, hexadecimal
from hachoir_core.tools import createDict, humanDurationNanosec
from hachoir_parser.common.tracker import NOTE_NAME
MAX_FILESIZE = 10 * 1024 * 1024
class Integer(Bits):
def __init__(self, parent, name, description=None):
Bits.__init__(self, parent, name, 8, description)
stream = parent.stream
addr = self.absolute_address
value = 0
while True:
bits = stream.readBits(addr, 8, parent.endian)
value = (value << 7) + (bits & 127)
if not(bits & 128):
break
addr += 8
self._size += 8
if 32 < self._size:
raise ParserError("Integer size is bigger than 32-bit")
self.createValue = lambda: value
def parseNote(parser):
yield Enum(UInt8(parser, "note", "Note number"), NOTE_NAME)
yield UInt8(parser, "velocity")
def parseControl(parser):
yield UInt8(parser, "control", "Controller number")
yield UInt8(parser, "value", "New value")
def parsePatch(parser):
yield UInt8(parser, "program", "New program number")
def parseChannel(parser, size=1):
yield UInt8(parser, "channel", "Channel number")
def parsePitch(parser):
yield UInt8(parser, "bottom", "(least sig) 7 bits of value")
yield UInt8(parser, "top", "(most sig) 7 bits of value")
def parseText(parser, size):
yield String(parser, "text", size)
def parseSMPTEOffset(parser, size):
yield RawBits(parser, "padding", 1)
yield Enum(Bits(parser, "frame_rate", 2),
{0:"24 fps", 1:"25 fps", 2:"30 fps (drop frame)", 3:"30 fps"})
yield Bits(parser, "hour", 5)
yield UInt8(parser, "minute")
yield UInt8(parser, "second")
yield UInt8(parser, "frame")
yield UInt8(parser, "subframe", "100 subframes per frame")
def formatTempo(field):
return humanDurationNanosec(field.value*1000)
def parseTempo(parser, size):
yield textHandler(UInt24(parser, "microsec_quarter", "Microseconds per quarter note"), formatTempo)
def parseTimeSignature(parser, size):
yield UInt8(parser, "numerator", "Numerator of time signature")
yield UInt8(parser, "denominator", "denominator of time signature 2=quarter 3=eighth, etc.")
yield UInt8(parser, "nb_tick", "Number of ticks in metronome click")
yield UInt8(parser, "nb_32nd_note", "Number of 32nd notes to the quarter note")
class Command(FieldSet):
COMMAND = {}
for channel in xrange(16):
COMMAND[0x80+channel] = ("Note off (channel %u)" % channel, parseNote)
COMMAND[0x90+channel] = ("Note on (channel %u)" % channel, parseNote)
COMMAND[0xA0+channel] = ("Key after-touch (channel %u)" % channel, parseNote)
COMMAND[0xB0+channel] = ("Control change (channel %u)" % channel, parseControl)
COMMAND[0xC0+channel] = ("Program (patch) change (channel %u)" % channel, parsePatch)
COMMAND[0xD0+channel] = ("Channel after-touch (channel %u)" % channel, parseChannel)
COMMAND[0xE0+channel] = ("Pitch wheel change (channel %u)" % channel, parsePitch)
COMMAND_DESC = createDict(COMMAND, 0)
COMMAND_PARSER = createDict(COMMAND, 1)
META_COMMAND_TEXT = 1
META_COMMAND_NAME = 3
META_COMMAND = {
0x00: ("Sets the track's sequence number", None),
0x01: ("Text event", parseText),
0x02: ("Copyright info", parseText),
0x03: ("Sequence or Track name", parseText),
0x04: ("Track instrument name", parseText),
0x05: ("Lyric", parseText),
0x06: ("Marker", parseText),
0x07: ("Cue point", parseText),
0x20: ("MIDI Channel Prefix", parseChannel),
0x2F: ("End of the track", None),
0x51: ("Set tempo", parseTempo),
0x54: ("SMPTE offset", parseSMPTEOffset),
0x58: ("Time Signature", parseTimeSignature),
0x59: ("Key signature", None),
0x7F: ("Sequencer specific information", None),
}
META_COMMAND_DESC = createDict(META_COMMAND, 0)
META_COMMAND_PARSER = createDict(META_COMMAND, 1)
def __init__(self, *args, **kwargs):
if 'prev_command' in kwargs:
self.prev_command = kwargs['prev_command']
del kwargs['prev_command']
else:
self.prev_command = None
self.command = None
FieldSet.__init__(self, *args, **kwargs)
def createFields(self):
yield Integer(self, "time", "Delta time in ticks")
next = self.stream.readBits(self.absolute_address+self.current_size, 8, self.root.endian)
if next & 0x80 == 0:
# "Running Status" command
if self.prev_command is None:
raise ParserError("Running Status command not preceded by another command.")
self.command = self.prev_command.command
else:
yield Enum(textHandler(UInt8(self, "command"), hexadecimal), self.COMMAND_DESC)
self.command = self["command"].value
if self.command == 0xFF:
yield Enum(textHandler(UInt8(self, "meta_command"), hexadecimal), self.META_COMMAND_DESC)
yield UInt8(self, "data_len")
size = self["data_len"].value
if size:
command = self["meta_command"].value
if command in self.META_COMMAND_PARSER:
parser = self.META_COMMAND_PARSER[command]
else:
parser = None
if parser:
for field in parser(self, size):
yield field
else:
yield RawBytes(self, "data", size)
else:
if self.command not in self.COMMAND_PARSER:
raise ParserError("Unknown command: %s" % self["command"].display)
parser = self.COMMAND_PARSER[self.command]
for field in parser(self):
yield field
def createDescription(self):
if "meta_command" in self:
return self["meta_command"].display
else:
return self.COMMAND_DESC[self.command]
class Track(FieldSet):
def __init__(self, *args):
FieldSet.__init__(self, *args)
self._size = (8 + self["size"].value) * 8
def createFields(self):
yield String(self, "marker", 4, "Track marker (MTrk)", charset="ASCII")
yield UInt32(self, "size")
cur = None
if True:
while not self.eof:
cur = Command(self, "command[]", prev_command=cur)
yield cur
else:
size = self["size"].value
if size:
yield RawBytes(self, "raw", size)
def createDescription(self):
command = self["command[0]"]
if "meta_command" in command \
and command["meta_command"].value in (Command.META_COMMAND_TEXT, Command.META_COMMAND_NAME) \
and "text" in command:
return command["text"].value.strip("\r\n")
else:
return ""
class Header(FieldSet):
static_size = 10*8
FILE_FORMAT = {
0: "Single track",
1: "Multiple tracks, synchronous",
2: "Multiple tracks, asynchronous",
}
def createFields(self):
yield UInt32(self, "size")
yield Enum(UInt16(self, "file_format"), self.FILE_FORMAT)
yield UInt16(self, "nb_track")
yield UInt16(self, "delta_time", "Delta-time ticks per quarter note")
def createDescription(self):
return "%s; %s tracks" % (
self["file_format"].display, self["nb_track"].value)
class MidiFile(Parser):
MAGIC = "MThd"
PARSER_TAGS = {
"id": "midi",
"category": "audio",
"file_ext": ["mid", "midi"],
"mime": (u"audio/mime", ),
"magic": ((MAGIC, 0),),
"min_size": 64,
"description": "MIDI audio"
}
endian = BIG_ENDIAN
def validate(self):
if self.stream.readBytes(0, 4) != self.MAGIC:
return "Invalid signature"
if self["header/size"].value != 6:
return "Invalid header size"
return True
def createFields(self):
yield String(self, "signature", 4, r"MIDI signature (MThd)", charset="ASCII")
yield Header(self, "header")
while not self.eof:
yield Track(self, "track[]")
def createDescription(self):
return "MIDI audio: %s" % self["header"].description
def createContentSize(self):
count = self["/header/nb_track"].value - 1
start = self["track[%u]" % count].absolute_address
# Search "End of track" of last track
end = self.stream.searchBytes("\xff\x2f\x00", start, MAX_FILESIZE*8)
if end is not None:
return end + 3*8
return None
+149
View File
@@ -0,0 +1,149 @@
"""
Parser of FastTrackerII Extended Module (XM) version 1.4
Documents:
- Modplug source code (file modplug/soundlib/Load_mod.cpp)
http://sourceforge.net/projects/modplug
- Dumb source code (files include/dumb.h and src/it/readmod.c
http://dumb.sf.net/
- Documents on "MOD" format on Wotsit
http://www.wotsit.org
Compressed formats (i.e. starting with "PP20" or having "PACK" as type
are not handled. Also NoiseTracker's NST modules aren't handled, although
it might be possible: no file format and 15 samples
Author: Christophe GISQUET <christophe.gisquet@free.fr>
Creation: 18th February 2007
"""
from math import log10
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet,
Bits, UInt16, UInt8,
RawBytes, String, GenericVector)
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.text_handler import textHandler
# Old NoiseTracker 15-samples modules can have anything here.
MODULE_TYPE = {
"M.K.": ("Noise/Pro-Tracker", 4),
"M!K!": ("Noise/Pro-Tracker", 4),
"M&K&": ("Noise/Pro-Tracker", 4),
"RASP": ("StarTrekker", 4),
"FLT4": ("StarTrekker", 4),
"FLT8": ("StarTrekker", 8),
"6CHN": ("FastTracker", 6),
"8CHN": ("FastTracker", 8),
"CD81": ("Octalyser", 8),
"OCTA": ("Octalyser", 8),
"FA04": ("Digital Tracker", 4),
"FA06": ("Digital Tracker", 6),
"FA08": ("Digital Tracker", 8),
}
def getFineTune(val):
return ("0", "1", "2", "3", "4", "5", "6", "7", "8",
"-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1")[val.value]
def getVolume(val):
return "%.1f dB" % (20.0*log10(val.value/64.0))
class SampleInfo(FieldSet):
static_size = 30*8
def createFields(self):
yield String(self, "name", 22, strip='\0')
yield UInt16(self, "sample_count")
yield textHandler(UInt8(self, "fine_tune"), getFineTune)
yield textHandler(UInt8(self, "volume"), getVolume)
yield UInt16(self, "loop_start", "Loop start offset in samples")
yield UInt16(self, "loop_len", "Loop length in samples")
def createValue(self):
return self["name"].value
class Header(FieldSet):
static_size = 1084*8
def createFields(self):
yield String(self, "name", 20, strip='\0')
yield GenericVector(self, "samples", 31, SampleInfo, "info")
yield UInt8(self, "length")
yield UInt8(self, "played_patterns_count")
yield GenericVector(self, "patterns", 128, UInt8, "position")
yield String(self, "type", 4)
def getNumChannels(self):
return MODULE_TYPE[self["type"].value][1]
class Note(FieldSet):
static_size = 8*4
def createFields(self):
yield Bits(self, 4, "note_hi_nibble")
yield Bits(self, 12, "period")
yield Bits(self, 4, "note_low_nibble")
yield Bits(self, 4, "effect")
yield UInt8(self, "parameter")
class Row(FieldSet):
def __init__(self, parent, name, channels, desc=None):
FieldSet.__init__(self, parent, name, description=desc)
self.channels = channels
self._size = 8*self.channels*4
def createFields(self):
for index in xrange(self.channels):
yield Note(self, "note[]")
class Pattern(FieldSet):
def __init__(self, parent, name, channels, desc=None):
FieldSet.__init__(self, parent, name, description=desc)
self.channels = channels
self._size = 64*8*self.channels*4
def createFields(self):
for index in xrange(64):
yield Row(self, "row[]", self.channels)
class AmigaModule(Parser):
PARSER_TAGS = {
"id": "mod",
"category": "audio",
"file_ext": ("mod", "nst", "wow", "oct", "sd0" ),
"mime": (u'audio/mod', u'audio/x-mod', u'audio/mod', u'audio/x-mod'),
"min_size": 1084*8,
"description": "Uncompressed amiga module"
}
endian = BIG_ENDIAN
def validate(self):
t = self.stream.readBytes(1080*8, 4)
if t not in MODULE_TYPE:
return "Invalid module type '%s'" % t
self.createValue = lambda t: "%s module, %u channels" % MODULE_TYPE[t]
return True
def createFields(self):
header = Header(self, "header")
yield header
channels = header.getNumChannels()
# Number of patterns
patterns = 0
for index in xrange(128):
patterns = max(patterns,
header["patterns/position[%u]" % index].value)
patterns += 1
# Yield patterns
for index in xrange(patterns):
yield Pattern(self, "pattern[]", channels)
# Yield samples
for index in xrange(31):
count = header["samples/info[%u]/sample_count" % index].value
if count:
self.info("Yielding sample %u: %u samples" % (index, count))
yield RawBytes(self, "sample_data[]", 2*count, \
"Sample %u" % index)
+291
View File
@@ -0,0 +1,291 @@
"""
Modplug metadata inserted into module files.
Doc:
- http://modplug.svn.sourceforge.net/viewvc/modplug/trunk/modplug/soundlib/
Author: Christophe GISQUET <christophe.gisquet@free.fr>
Creation: 10th February 2007
"""
from hachoir_core.field import (FieldSet,
UInt32, UInt16, UInt8, Int8, Float32,
RawBytes, String, GenericVector, ParserError)
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.text_handler import textHandler, hexadecimal
MAX_ENVPOINTS = 32
def parseComments(parser):
size = parser["block_size"].value
if size > 0:
yield String(parser, "comment", size)
class MidiOut(FieldSet):
static_size = 9*32*8
def createFields(self):
for name in ("start", "stop", "tick", "noteon", "noteoff",
"volume", "pan", "banksel", "program"):
yield String(self, name, 32, strip='\0')
class Command(FieldSet):
static_size = 32*8
def createFields(self):
start = self.absolute_address
size = self.stream.searchBytesLength("\0", False, start)
if size > 0:
self.info("Command: %s" % self.stream.readBytes(start, size))
yield String(self, "command", size, strip='\0')
yield RawBytes(self, "parameter", (self._size//8)-size)
class MidiSFXExt(FieldSet):
static_size = 16*32*8
def createFields(self):
for index in xrange(16):
yield Command(self, "command[]")
class MidiZXXExt(FieldSet):
static_size = 128*32*8
def createFields(self):
for index in xrange(128):
yield Command(self, "command[]")
def parseMidiConfig(parser):
yield MidiOut(parser, "midi_out")
yield MidiSFXExt(parser, "sfx_ext")
yield MidiZXXExt(parser, "zxx_ext")
def parseChannelSettings(parser):
size = parser["block_size"].value//4
if size > 0:
yield GenericVector(parser, "settings", size, UInt32, "mix_plugin")
def parseEQBands(parser):
size = parser["block_size"].value//4
if size > 0:
yield GenericVector(parser, "gains", size, UInt32, "band")
class SoundMixPluginInfo(FieldSet):
static_size = 128*8
def createFields(self):
yield textHandler(UInt32(self, "plugin_id1"), hexadecimal)
yield textHandler(UInt32(self, "plugin_id2"), hexadecimal)
yield UInt32(self, "input_routing")
yield UInt32(self, "output_routing")
yield GenericVector(self, "routing_info", 4, UInt32, "reserved")
yield String(self, "name", 32, strip='\0')
yield String(self, "dll_name", 64, desc="Original DLL name", strip='\0')
class ExtraData(FieldSet):
def __init__(self, parent, name, desc=None):
FieldSet.__init__(self, parent, name, desc)
self._size = (4+self["size"].value)*8
def createFields(self):
yield UInt32(self, "size")
size = self["size"].value
if size:
yield RawBytes(self, "data", size)
class XPlugData(FieldSet):
def __init__(self, parent, name, desc=None):
FieldSet.__init__(self, parent, name, desc)
self._size = (4+self["size"].value)*8
def createFields(self):
yield UInt32(self, "size")
while not self.eof:
yield UInt32(self, "marker")
if self["marker"].value == 'DWRT':
yield Float32(self, "dry_ratio")
elif self["marker"].value == 'PORG':
yield UInt32(self, "default_program")
def parsePlugin(parser):
yield SoundMixPluginInfo(parser, "info")
# Check if VST setchunk present
size = parser.stream.readBits(parser.absolute_address+parser.current_size, 32, LITTLE_ENDIAN)
if 0 < size < parser.current_size + parser._size:
yield ExtraData(parser, "extra_data")
# Check if XPlugData is present
size = parser.stream.readBits(parser.absolute_address+parser.current_size, 32, LITTLE_ENDIAN)
if 0 < size < parser.current_size + parser._size:
yield XPlugData(parser, "xplug_data")
# Format: "XXXX": (type, count, name)
EXTENSIONS = {
# WriteInstrumentHeaderStruct@Sndfile.cpp
"XTPM": {
"..Fd": (UInt32, 1, "Flags"),
"..OF": (UInt32, 1, "Fade out"),
"..VG": (UInt32, 1, "Global Volume"),
"...P": (UInt32, 1, "Panning"),
"..EV": (UInt32, 1, "Volume Envelope"),
"..EP": (UInt32, 1, "Panning Envelope"),
".EiP": (UInt32, 1, "Pitch Envelope"),
".SLV": (UInt8, 1, "Volume Loop Start"),
".ELV": (UInt8, 1, "Volume Loop End"),
".BSV": (UInt8, 1, "Volume Sustain Begin"),
".ESV": (UInt8, 1, "Volume Sustain End"),
".SLP": (UInt8, 1, "Panning Loop Start"),
".ELP": (UInt8, 1, "Panning Loop End"),
".BSP": (UInt8, 1, "Panning Substain Begin"),
".ESP": (UInt8, 1, "Padding Substain End"),
"SLiP": (UInt8, 1, "Pitch Loop Start"),
"ELiP": (UInt8, 1, "Pitch Loop End"),
"BSiP": (UInt8, 1, "Pitch Substain Begin"),
"ESiP": (UInt8, 1, "Pitch Substain End"),
".ANN": (UInt8, 1, "NNA"),
".TCD": (UInt8, 1, "DCT"),
".AND": (UInt8, 1, "DNA"),
"..SP": (UInt8, 1, "Panning Swing"),
"..SV": (UInt8, 1, "Volume Swing"),
".CFI": (UInt8, 1, "IFC"),
".RFI": (UInt8, 1, "IFR"),
"..BM": (UInt32, 1, "Midi Bank"),
"..PM": (UInt8, 1, "Midi Program"),
"..CM": (UInt8, 1, "Midi Channel"),
".KDM": (UInt8, 1, "Midi Drum Key"),
".SPP": (Int8, 1, "PPS"),
".CPP": (UInt8, 1, "PPC"),
".[PV": (UInt32, MAX_ENVPOINTS, "Volume Points"),
".[PP": (UInt32, MAX_ENVPOINTS, "Panning Points"),
"[PiP": (UInt32, MAX_ENVPOINTS, "Pitch Points"),
".[EV": (UInt8, MAX_ENVPOINTS, "Volume Enveloppe"),
".[EP": (UInt8, MAX_ENVPOINTS, "Panning Enveloppe"),
"[EiP": (UInt8, MAX_ENVPOINTS, "Pitch Enveloppe"),
".[MN": (UInt8, 128, "Note Mapping"),
"..[K": (UInt32, 128, "Keyboard"),
"..[n": (String, 32, "Name"),
".[nf": (String, 12, "Filename"),
".PiM": (UInt8, 1, "MixPlug"),
"..RV": (UInt16, 1, "Volume Ramping"),
"...R": (UInt16, 1, "Resampling"),
"..SC": (UInt8, 1, "Cut Swing"),
"..SR": (UInt8, 1, "Res Swing"),
"..MF": (UInt8, 1, "Filter Mode"),
},
# See after "CODE tag dictionary", same place, elements with [EXT]
"STPM": {
"...C": (UInt32, 1, "Channels"),
".VWC": (None, 0, "CreatedWith version"),
".VGD": (None, 0, "Default global volume"),
"..TD": (None, 0, "Default tempo"),
"HIBE": (None, 0, "Embedded instrument header"),
"VWSL": (None, 0, "LastSavedWith version"),
".MMP": (None, 0, "Plugin Mix mode"),
".BPR": (None, 0, "Rows per beat"),
".MPR": (None, 0, "Rows per measure"),
"@PES": (None, 0, "Chunk separator"),
".APS": (None, 0, "Song Pre-amplification"),
"..MT": (None, 0, "Tempo mode"),
"VTSV": (None, 0, "VSTi volume"),
}
}
class MPField(FieldSet):
def __init__(self, parent, name, ext, desc=None):
FieldSet.__init__(self, parent, name, desc)
self.ext = ext
self.info(self.createDescription())
self._size = (6+self["data_size"].value)*8
def createFields(self):
# Identify tag
code = self.stream.readBytes(self.absolute_address, 4)
if code in self.ext:
cls, count, comment = self.ext[code]
else:
cls, count, comment = RawBytes, 1, "Unknown tag"
# Header
yield String(self, "code", 4, comment)
yield UInt16(self, "data_size")
# Data
if not cls:
size = self["data_size"].value
if size > 0:
yield RawBytes(self, "data", size)
elif cls in (String, RawBytes):
yield cls(self, "value", count)
else:
if count > 1:
yield GenericVector(self, "values", count, cls, "item")
else:
yield cls(self, "value")
def createDescription(self):
return "Element '%s', size %i" % \
(self["code"]._description, self["data_size"].value)
def parseFields(parser):
# Determine field names
ext = EXTENSIONS[parser["block_type"].value]
if ext == None:
raise ParserError("Unknown parent '%s'" % parser["block_type"].value)
# Parse fields
addr = parser.absolute_address + parser.current_size
while not parser.eof and parser.stream.readBytes(addr, 4) in ext:
field = MPField(parser, "field[]", ext)
yield field
addr += field._size
# Abort on unknown codes
parser.info("End of extension '%s' when finding '%s'" %
(parser["block_type"].value, parser.stream.readBytes(addr, 4)))
class ModplugBlock(FieldSet):
BLOCK_INFO = {
"TEXT": ("comment", True, "Comment", parseComments),
"MIDI": ("midi_config", True, "Midi configuration", parseMidiConfig),
"XFHC": ("channel_settings", True, "Channel settings", parseChannelSettings),
"XTPM": ("instrument_ext", False, "Instrument extensions", parseFields),
"STPM": ("song_ext", False, "Song extensions", parseFields),
}
def __init__(self, parent, name, desc=None):
FieldSet.__init__(self, parent, name, desc)
self.parseBlock = parsePlugin
t = self["block_type"].value
self.has_size = False
if t in self.BLOCK_INFO:
self._name, self.has_size, desc, parseBlock = self.BLOCK_INFO[t]
if callable(desc):
self.createDescription = lambda: desc(self)
if parseBlock:
self.parseBlock = lambda: parseBlock(self)
if self.has_size:
self._size = 8*(self["block_size"].value + 8)
def createFields(self):
yield String(self, "block_type", 4)
if self.has_size:
yield UInt32(self, "block_size")
if self.parseBlock:
for field in self.parseBlock():
yield field
if self.has_size:
size = self["block_size"].value - (self.current_size//8)
if size > 0:
yield RawBytes(self, "data", size, "Unknown data")
def ParseModplugMetadata(parser):
while not parser.eof:
block = ModplugBlock(parser, "block[]")
yield block
if block["block_type"].value == "STPM":
break
# More undocumented stuff: date ?
size = (parser._size - parser.absolute_address - parser.current_size)//8
if size > 0:
yield RawBytes(parser, "info", size)
+408
View File
@@ -0,0 +1,408 @@
"""
MPEG audio file parser.
Creation: 12 decembre 2005
Author: Victor Stinner
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet,
MissingField, ParserError, createOrphanField,
Bit, Bits, Enum,
PaddingBits, PaddingBytes,
RawBytes)
from hachoir_parser.audio.id3 import ID3v1, ID3v2
from hachoir_core.endian import BIG_ENDIAN
from hachoir_core.tools import humanFrequency, humanBitSize
from hachoir_core.bits import long2raw
from hachoir_core.error import HACHOIR_ERRORS
from hachoir_core.stream import InputStreamError
# Max MP3 filesize: 200 MB
MAX_FILESIZE = 200*1024*1024*8
class Frame(FieldSet):
VERSION_NAME = { 0: "2.5", 2: "2", 3: "1" }
MPEG_I = 3
MPEG_II = 2
MPEG_II_5 = 0
LAYER_NAME = { 1: "III", 2: "II", 3: "I" }
LAYER_I = 3
LAYER_II = 2
LAYER_III = 1
# Bit rates (bit_rate * 1000 = bits/sec)
# key 15 is always invalid
BIT_RATES = {
1: ( # MPEG1
( 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 ), # layer I
( 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 ), # layer II
( 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 ), # layer III
# - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -
),
2: ( # MPEG2 / MPEG2.5
( 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 ), # layer I
( 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ), # layer II
( 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ), # layer III
# - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -
)
}
SAMPLING_RATES = {
3: {0: 44100, 1: 48000, 2: 32000}, # MPEG1
2: {0: 22050, 1: 24000, 2: 16000}, # MPEG2
0: {0: 11025, 1: 12000, 2: 8000} # MPEG2.5
}
EMPHASIS_NAME = {0: "none", 1: "50/15 ms", 3: "CCIT J.17"}
CHANNEL_MODE_NAME = {
0: "Stereo",
1: "Joint stereo",
2: "Dual channel",
3: "Single channel"
}
# Channel mode => number of channels
NB_CHANNEL = {
0: 2,
1: 2,
2: 2,
3: 1,
}
def __init__(self, *args, **kw):
FieldSet.__init__(self, *args, **kw)
if not self._size:
frame_size = self.getFrameSize()
if not frame_size:
raise ParserError("MPEG audio: Invalid frame %s" % self.path)
self._size = min(frame_size * 8, self.parent.size - self.address)
def createFields(self):
# Header
yield PaddingBits(self, "sync", 11, "Synchronize bits (set to 1)", pattern=1)
yield Enum(Bits(self, "version", 2, "MPEG audio version"), self.VERSION_NAME)
yield Enum(Bits(self, "layer", 2, "MPEG audio layer"), self.LAYER_NAME)
yield Bit(self, "crc16", "No CRC16 protection?")
# Rates and padding
yield Bits(self, "bit_rate", 4, "Bit rate")
yield Bits(self, "sampling_rate", 2, "Sampling rate")
yield Bit(self, "use_padding", "Stream field use padding?")
yield Bit(self, "extension", "Extension")
# Channel mode, mode extension, copyright, ...
yield Enum(Bits(self, "channel_mode", 2, "Channel mode"), self.CHANNEL_MODE_NAME)
yield Bits(self, "mode_ext", 2, "Mode extension")
yield Bit(self, "copyright", "Is copyrighted?")
yield Bit(self, "original", "Is original?")
yield Enum(Bits(self, "emphasis", 2, "Emphasis"), self.EMPHASIS_NAME)
size = (self.size - self.current_size) / 8
if size:
yield RawBytes(self, "data", size)
def isValid(self):
return (self["layer"].value != 0
and self["sync"].value == 2047
and self["version"].value != 1
and self["sampling_rate"].value != 3
and self["bit_rate"].value not in (0, 15)
and self["emphasis"].value != 2)
def getSampleRate(self):
"""
Read sampling rate. Returns None on error.
"""
version = self["version"].value
rate = self["sampling_rate"].value
try:
return self.SAMPLING_RATES[version][rate]
except (KeyError, IndexError):
return None
def getBitRate(self):
"""
Read bit rate in bit/sec. Returns None on error.
"""
layer = 3 - self["layer"].value
bit_rate = self["bit_rate"].value
if bit_rate in (0, 15):
return None
if self["version"].value == 3:
dataset = self.BIT_RATES[1] # MPEG1
else:
dataset = self.BIT_RATES[2] # MPEG2 / MPEG2.5
try:
return dataset[layer][bit_rate] * 1000
except (KeyError, IndexError):
return None
def getFrameSize(self):
"""
Read frame size in bytes. Returns None on error.
"""
frame_size = self.getBitRate()
if not frame_size:
return None
sample_rate = self.getSampleRate()
if not sample_rate:
return None
padding = int(self["use_padding"].value)
if self["layer"].value == self.LAYER_III:
if self["version"].value == self.MPEG_I:
return (frame_size * 144) // sample_rate + padding
else:
return (frame_size * 72) // sample_rate + padding
elif self["layer"].value == self.LAYER_II:
return (frame_size * 144) / sample_rate + padding
else: # self.LAYER_I:
frame_size = (frame_size * 12) / sample_rate
return (frame_size + padding) * 4
def getNbChannel(self):
return self.NB_CHANNEL[ self["channel_mode"].value ]
def createDescription(self):
info = ["layer %s" % self["layer"].display]
bit_rate = self.getBitRate()
if bit_rate:
info.append("%s/sec" % humanBitSize(bit_rate))
sampling_rate = self.getSampleRate()
if sampling_rate:
info.append(humanFrequency(sampling_rate))
return "MPEG-%s %s" % (self["version"].display, ", ".join(info))
def findSynchronizeBits(parser, start, max_size):
"""
Find synchronisation bits (11 bits set to 1)
Returns None on error, or number of bytes before the synchronization.
"""
address0 = parser.absolute_address
end = start + max_size
size = 0
while start < end:
# Fast search: search 0xFF (first byte of sync frame field)
length = parser.stream.searchBytesLength("\xff", False, start, end)
if length is None:
return None
size += length
start += length * 8
# Strong validation of frame: create the frame
# and call method isValid()
try:
frame = createOrphanField(parser, start-address0, Frame, "frame")
valid = frame.isValid()
except HACHOIR_ERRORS:
valid = False
if valid:
return size
# Invalid frame: continue
start += 8
size += 1
return None
class Frames(FieldSet):
# Padding bytes allowed before a frame
MAX_PADDING = 256
def synchronize(self):
addr = self.absolute_address
start = addr + self.current_size
end = min(start + self.MAX_PADDING*8, addr + self.size)
padding = findSynchronizeBits(self, start, end)
if padding is None:
raise ParserError("MPEG audio: Unable to find synchronization bits")
if padding:
return PaddingBytes(self, "padding[]", padding, "Padding before synchronization")
else:
return None
def looksConstantBitRate(self, count=10):
"""
Guess if frames are constant bit rate. If it returns False, you can
be sure that frames are variable bit rate. Otherwise, it looks like
constant bit rate (on first count fields).
"""
check_keys = ("version", "layer", "bit_rate")
last_field = None
for index, field in enumerate(self.array("frame")):
if last_field:
for key in check_keys:
if field[key].value != last_field[key].value:
return False
last_field = field
if index == count:
break
return True
def createFields(self):
# Find synchronisation bytes
padding = self.synchronize()
if padding:
yield padding
while self.current_size < self.size:
yield Frame(self, "frame[]")
# padding = self.synchronize()
# if padding:
# yield padding
# Read raw bytes at the end (if any)
size = (self.size - self.current_size) / 8
if size:
yield RawBytes(self, "raw", size)
def createDescription(self):
if self.looksConstantBitRate():
text = "(looks like) Constant bit rate (CBR)"
else:
text = "Variable bit rate (VBR)"
return "Frames: %s" % text
def createMpegAudioMagic():
# ID3v1 magic
magics = [("TAG", 0)]
# ID3v2 magics
for ver_major in ID3v2.VALID_MAJOR_VERSIONS:
magic = "ID3%c\x00" % ver_major
magics.append( (magic,0) )
# MPEG frame magic
# TODO: Use longer magic: 32 bits instead of 16 bits
SYNC_BITS = 2047
for version in Frame.VERSION_NAME.iterkeys():
for layer in Frame.LAYER_NAME.iterkeys():
for crc16 in (0, 1):
magic = (SYNC_BITS << 5) | (version << 3) | (layer << 1) | crc16
magic = long2raw(magic, BIG_ENDIAN, 2)
magics.append( (magic, 0) )
return magics
class MpegAudioFile(Parser):
PARSER_TAGS = {
"id": "mpeg_audio",
"category": "audio",
"file_ext": ("mpa", "mp1", "mp2", "mp3"),
"mime": (u"audio/mpeg",),
"min_size": 4*8,
# "magic": createMpegAudioMagic(),
"description": "MPEG audio version 1, 2, 2.5",
"subfile": "skip",
}
endian = BIG_ENDIAN
def validate(self):
if self[0].name in ("id3v2", "id3v1"):
return True
if not self.stream.checked: # TODO: is it possible to handle piped input?
return False
# Validate first 5 frames
for index in xrange(5):
try:
frame = self["frames/frame[%u]" % index]
except MissingField:
# Require a least one valid frame
if (1 <= index) \
and self["frames"].done:
return True
return "Unable to get frame #%u" % index
except (InputStreamError, ParserError):
return "Unable to create frame #%u" % index
# Check first frame values
if not frame.isValid():
return "Frame #%u is invalid" % index
# Check that all frames are similar
if not index:
frame0 = frame
else:
if frame0["channel_mode"].value != frame["channel_mode"].value:
return "Frame #%u channel mode is different" % index
return True
def createFields(self):
# Read ID3v2 (if any)
if self.stream.readBytes(0, 3) == "ID3":
yield ID3v2(self, "id3v2")
if self._size is None: # TODO: is it possible to handle piped input?
raise NotImplementedError
# Check if file is ending with ID3v1 or not and compute frames size
frames_size = self.size - self.current_size
addr = self.size - 128*8
if 0 <= addr:
has_id3 = (self.stream.readBytes(addr, 3) == "TAG")
if has_id3:
frames_size -= 128*8
else:
has_id3 = False
# Read frames (if any)
if frames_size:
yield Frames(self, "frames", size=frames_size)
# Read ID3v1 (if any)
if has_id3:
yield ID3v1(self, "id3v1")
def createDescription(self):
if "frames" in self:
frame = self["frames/frame[0]"]
return "%s, %s" % (frame.description, frame["channel_mode"].display)
elif "id3v2" in self:
return self["id3v2"].description
elif "id3v1" in self:
return self["id3v1"].description
else:
return "MPEG audio"
def createContentSize(self):
# Get "frames" field
field = self[0]
if field.name != "frames":
try:
field = self[1]
except MissingField:
# File only contains ID3v1 or ID3v2
return field.size
# Error: second field are not the frames"?
if field.name != "frames":
return None
# Go to last frame
frames = field
frame = frames["frame[0]"]
address0 = field.absolute_address
size = address0 + frame.size
while True:
try:
# Parse one MPEG audio frame
frame = createOrphanField(frames, size - address0, Frame, "frame")
# Check frame 32 bits header
if not frame.isValid():
break
except HACHOIR_ERRORS:
break
if MAX_FILESIZE < (size + frame.size):
break
size += frame.size
# ID3v1 at the end?
try:
if self.stream.readBytes(size, 3) == "TAG":
size += ID3v1.static_size
except InputStreamError:
pass
return size
+90
View File
@@ -0,0 +1,90 @@
"""
RealAudio (.ra) parser
Author: Mike Melanson
References:
http://wiki.multimedia.cx/index.php?title=RealMedia
Samples:
http://samples.mplayerhq.hu/real/RA/
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet,
UInt8, UInt16, UInt32,
Bytes, RawBytes, String,
PascalString8)
from hachoir_core.tools import humanFrequency
from hachoir_core.text_handler import displayHandler
from hachoir_core.endian import BIG_ENDIAN
class Metadata(FieldSet):
def createFields(self):
yield PascalString8(self, "title", charset="ISO-8859-1")
yield PascalString8(self, "author", charset="ISO-8859-1")
yield PascalString8(self, "copyright", charset="ISO-8859-1")
yield PascalString8(self, "comment", charset="ISO-8859-1")
class RealAudioFile(Parser):
MAGIC = ".ra\xFD"
PARSER_TAGS = {
"id": "real_audio",
"category": "audio",
"file_ext": ["ra"],
"mime": (u"audio/x-realaudio", u"audio/x-pn-realaudio"),
"min_size": 6*8,
"magic": ((MAGIC, 0),),
"description": u"Real audio (.ra)",
}
endian = BIG_ENDIAN
def validate(self):
if self["signature"].value != self.MAGIC:
return "Invalid signature"
if self["version"].value not in (3, 4):
return "Unknown version"
return True
def createFields(self):
yield Bytes(self, "signature", 4, r"RealAudio identifier ('.ra\xFD')")
yield UInt16(self, "version", "Version")
if self["version"].value == 3:
yield UInt16(self, "header_size", "Header size")
yield RawBytes(self, "Unknown1", 10)
yield UInt32(self, "data_size", "Data size")
yield Metadata(self, "metadata")
yield UInt8(self, "Unknown2")
yield PascalString8(self, "FourCC")
audio_size = self["data_size"].value
else: # version = 4
yield UInt16(self, "reserved1", "Reserved, should be 0")
yield String(self, "ra4sig", 4, "'.ra4' signature")
yield UInt32(self, "filesize", "File size (minus 40 bytes)")
yield UInt16(self, "version2", "Version 2 (always equal to version)")
yield UInt32(self, "headersize", "Header size (minus 16)")
yield UInt16(self, "codec_flavor", "Codec flavor")
yield UInt32(self, "coded_frame_size", "Coded frame size")
yield RawBytes(self, "unknown1", 12)
yield UInt16(self, "subpacketh", "Subpacket h (?)")
yield UInt16(self, "frame_size", "Frame size")
yield UInt16(self, "sub_packet_size", "Subpacket size")
yield UInt16(self, "unknown2", "Unknown")
yield displayHandler(UInt16(self, "sample_rate", "Sample rate"), humanFrequency)
yield UInt16(self, "unknown3", "Unknown")
yield UInt16(self, "sample_size", "Sample size")
yield UInt16(self, "channels", "Channels")
yield PascalString8(self, "Interleaving ID String")
yield PascalString8(self, "FourCC")
yield RawBytes(self, "unknown4", 3)
yield Metadata(self, "metadata")
audio_size = (self["filesize"].value + 40) - (self["headersize"].value + 16)
if 0 < audio_size:
yield RawBytes(self, "audio_data", audio_size)
def createDescription(self):
if (self["version"].value == 3):
return "RealAudio v3 file, '%s' codec" % self["FourCC"].value
elif (self["version"].value == 4):
return "RealAudio v4 file, '%s' codec, %s, %u channels" % (
self["FourCC"].value, self["sample_rate"].display, self["channels"].value)
else:
return "Real audio"
+668
View File
@@ -0,0 +1,668 @@
"""
The ScreamTracker 3.0x module format description for .s3m files.
Documents:
- Search s3m on Wotsit
http://www.wotsit.org/
Author: Christophe GISQUET <christophe.gisquet@free.fr>
Creation: 11th February 2007
"""
from hachoir_parser import Parser
from hachoir_core.field import (StaticFieldSet, FieldSet, Field,
Bit, Bits,
UInt32, UInt16, UInt8, Enum,
PaddingBytes, RawBytes, NullBytes,
String, GenericVector, ParserError)
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.text_handler import textHandler, hexadecimal
from hachoir_core.tools import alignValue
class Chunk:
def __init__(self, cls, name, offset, size, *args):
# Todo: swap and have None=unknown instead of now: 0=unknown
assert size != None and size>=0
self.cls = cls
self.name = name
self.offset = offset
self.size = size
self.args = args
class ChunkIndexer:
def __init__(self):
self.chunks = [ ]
# Check if a chunk fits
def canHouse(self, chunk, index):
if index > 1:
if chunk.offset + chunk.size > self.chunks[index-1].offset:
return False
# We could test now that it fits in the memory
return True
# Farthest element is last
def addChunk(self, new_chunk):
index = 0
# Find first chunk whose value is bigger
while index < len(self.chunks):
offset = self.chunks[index].offset
if offset < new_chunk.offset:
if not self.canHouse(new_chunk, index):
raise ParserError("Chunk '%s' doesn't fit!" % new_chunk.name)
self.chunks.insert(index, new_chunk)
return
index += 1
# Not found or empty
# We could at least check that it fits in the memory
self.chunks.append(new_chunk)
def yieldChunks(self, obj):
while len(self.chunks) > 0:
chunk = self.chunks.pop()
current_pos = obj.current_size//8
# Check if padding needed
size = chunk.offset - current_pos
if size > 0:
obj.info("Padding of %u bytes needed: curr=%u offset=%u" % \
(size, current_pos, chunk.offset))
yield PaddingBytes(obj, "padding[]", size)
current_pos = obj.current_size//8
# Find resynch point if needed
count = 0
old_off = chunk.offset
while chunk.offset < current_pos:
count += 1
chunk = self.chunks.pop()
# Unfortunaly, we also pass the underlying chunks
if chunk == None:
obj.info("Couldn't resynch: %u object skipped to reach %u" % \
(count, current_pos))
return
# Resynch
size = chunk.offset-current_pos
if size > 0:
obj.info("Skipped %u objects to resynch to %u; chunk offset: %u->%u" % \
(count, current_pos, old_off, chunk.offset))
yield RawBytes(obj, "resynch[]", size)
# Yield
obj.info("Yielding element of size %u at offset %u" % \
(chunk.size, chunk.offset))
field = chunk.cls(obj, chunk.name, chunk.size, *chunk.args)
# Not tested, probably wrong:
#if chunk.size: field.static_size = 8*chunk.size
yield field
if hasattr(field, "getSubChunks"):
for sub_chunk in field.getSubChunks():
obj.info("Adding sub chunk: position=%u size=%u name='%s'" % \
(sub_chunk.offset, sub_chunk.size, sub_chunk.name))
self.addChunk(sub_chunk)
# Let missing padding be done by next chunk
class S3MFlags(StaticFieldSet):
format = (
(Bit, "st2_vibrato", "Vibrato (File version 1/ScreamTrack 2)"),
(Bit, "st2_tempo", "Tempo (File version 1/ScreamTrack 2)"),
(Bit, "amiga_slides", "Amiga slides (File version 1/ScreamTrack 2)"),
(Bit, "zero_vol_opt", "Automatically turn off looping notes whose volume is zero for >2 note rows"),
(Bit, "amiga_limits", "Disallow notes beyond Amiga hardware specs"),
(Bit, "sb_processing", "Enable filter/SFX with SoundBlaster"),
(Bit, "vol_slide", "Volume slide also performed on first row"),
(Bit, "extended", "Special custom data in file"),
(Bits, "unused[]", 8)
)
def parseChannelType(val):
val = val.value
if val<8:
return "Left Sample Channel %u" % val
if val<16:
return "Right Sample Channel %u" % (val-8)
if val<32:
return "Adlib channel %u" % (val-16)
return "Value %u unknown" % val
class ChannelSettings(FieldSet):
static_size = 8
def createFields(self):
yield textHandler(Bits(self, "type", 7), parseChannelType)
yield Bit(self, "enabled")
class ChannelPanning(FieldSet):
static_size = 8
def createFields(self):
yield Bits(self, "default_position", 4, "Default pan position")
yield Bit(self, "reserved[]")
yield Bit(self, "use_default", "Bits 0:3 specify default position")
yield Bits(self, "reserved[]", 2)
# Provide an automatic constructor
class SizeFieldSet(FieldSet):
"""
Provide an automatic constructor for a sized field that can be aligned
on byte positions according to ALIGN.
Size is ignored if static_size is set. Real size is stored
for convenience, but beware, it is not in bits, but in bytes.
Field can be automatically padded, unless:
- size is 0 (unknown, so padding doesn't make sense)
- it shouldn't be aligned
If it shouldn't be aligned, two solutions:
- change _size to another value than the one found through aligment.
- derive a class with ALIGN = 0.
"""
ALIGN = 16
def __init__(self, parent, name, size, desc=None):
FieldSet.__init__(self, parent, name, desc)
if size:
self.real_size = size
if self.static_size == None:
self.setCheckedSizes(size)
def setCheckedSizes(self, size):
# First set size so that end is aligned, if needed
self.real_size = size
size *= 8
if self.ALIGN:
size = alignValue(self.absolute_address+size, 8*self.ALIGN) \
- self.absolute_address
if self._parent._size:
if self._parent.current_size + size > self._parent._size:
size = self._parent._size - self._parent.current_size
self._size = size
def createFields(self):
for field in self.createUnpaddedFields():
yield field
size = (self._size - self.current_size)//8
if size > 0:
yield PaddingBytes(self, "padding", size)
class Header(SizeFieldSet):
def createDescription(self):
return "%s (%u patterns, %u instruments)" % \
(self["title"].value, self["num_patterns"].value,
self["num_instruments"].value)
def createValue(self):
return self["title"].value
# Header fields may have to be padded - specify static_size
# or modify _size in a derived class if never.
def createUnpaddedFields(self):
yield String(self, "title", 28, strip='\0')
yield textHandler(UInt8(self, "marker[]"), hexadecimal)
for field in self.getFileVersionField():
yield field
yield UInt16(self, "num_orders")
yield UInt16(self, "num_instruments")
yield UInt16(self, "num_patterns")
for field in self.getFirstProperties():
yield field
yield String(self, "marker[]", 4)
for field in self.getLastProperties():
yield field
yield GenericVector(self, "channel_settings", 32,
ChannelSettings, "channel")
# Orders
yield GenericVector(self, "orders", self.getNumOrders(), UInt8, "order")
for field in self.getHeaderEndFields():
yield field
class S3MHeader(Header):
"""
0 1 2 3 4 5 6 7 8 9 A B C D E F
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0000: | Song name, max 28 chars (end with NUL (0)) |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0010: | |1Ah|Typ| x | x |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0020: |OrdNum |InsNum |PatNum | Flags | Cwt/v | Ffi |'S'|'C'|'R'|'M'|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0030: |g.v|i.s|i.t|m.v|u.c|d.p| x | x | x | x | x | x | x | x |Special|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0040: |Channel settings for 32 channels, 255=unused,+128=disabled |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0050: | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0060: |Orders; length=OrdNum (should be even) |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
xxx1: |Parapointers to instruments; length=InsNum*2 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
xxx2: |Parapointers to patterns; length=PatNum*2 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
xxx3: |Channel default pan positions |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
xxx1=70h+orders
xxx2=70h+orders+instruments*2
xxx3=70h+orders+instruments*2+patterns*2
"""
def __init__(self, parent, name, size, desc=None):
Header.__init__(self, parent, name, size, desc)
# Overwrite real_size
size = 0x60 + self["num_orders"].value + \
2*(self["num_instruments"].value + self["num_patterns"].value)
if self["panning_info"].value == 252:
size += 32
# Deduce size for SizeFieldSet
self.setCheckedSizes(size)
def getFileVersionField(self):
yield UInt8(self, "type")
yield RawBytes(self, "reserved[]", 2)
def getFirstProperties(self):
yield S3MFlags(self, "flags")
yield UInt8(self, "creation_version_minor")
yield Bits(self, "creation_version_major", 4)
yield Bits(self, "creation_version_unknown", 4, "(=1)")
yield UInt16(self, "format_version")
def getLastProperties(self):
yield UInt8(self, "glob_vol", "Global volume")
yield UInt8(self, "init_speed", "Initial speed (command A)")
yield UInt8(self, "init_tempo", "Initial tempo (command T)")
yield Bits(self, "volume", 7)
yield Bit(self, "stereo")
yield UInt8(self, "click_removal", "Number of GUS channels to run to prevent clicks")
yield UInt8(self, "panning_info")
yield RawBytes(self, "reserved[]", 8)
yield UInt16(self, "custom_data_parapointer",
"Parapointer to special custom data (not used by ST3.01)")
def getNumOrders(self): return self["num_orders"].value
def getHeaderEndFields(self):
instr = self["num_instruments"].value
patterns = self["num_patterns"].value
# File pointers
if instr > 0:
yield GenericVector(self, "instr_pptr", instr, UInt16, "offset")
if patterns > 0:
yield GenericVector(self, "pattern_pptr", patterns, UInt16, "offset")
# S3M 3.20 extension
if self["creation_version_major"].value >= 3 \
and self["creation_version_minor"].value >= 0x20 \
and self["panning_info"].value == 252:
yield GenericVector(self, "channel_panning", 32, ChannelPanning, "channel")
# Padding required for 16B alignment
size = self._size - self.current_size
if size > 0:
yield PaddingBytes(self, "padding", size//8)
def getSubChunks(self):
# Instruments - no warranty that they are concatenated
for index in xrange(self["num_instruments"].value):
yield Chunk(S3MInstrument, "instrument[]",
16*self["instr_pptr/offset[%u]" % index].value,
S3MInstrument.static_size//8)
# Patterns - size unknown but listed in their headers
for index in xrange(self["num_patterns"].value):
yield Chunk(S3MPattern, "pattern[]",
16*self["pattern_pptr/offset[%u]" % index].value, 0)
class PTMHeader(Header):
# static_size should prime over _size, right?
static_size = 8*608
def getTrackerVersion(val):
val = val.value
return "ProTracker x%04X" % val
def getFileVersionField(self):
yield UInt16(self, "type")
yield RawBytes(self, "reserved[]", 1)
def getFirstProperties(self):
yield UInt16(self, "channels")
yield UInt16(self, "flags") # 0 => NullBytes
yield UInt16(self, "reserved[]")
def getLastProperties(self):
yield RawBytes(self, "reserved[]", 16)
def getNumOrders(self): return 256
def getHeaderEndFields(self):
yield GenericVector(self, "pattern_pptr", 128, UInt16, "offset")
def getSubChunks(self):
# It goes like this in the BS: patterns->instruments->instr. samples
if self._parent._size:
min_off = self.absolute_address+self._parent._size
else:
min_off = 99999999999
# Instruments and minimal end position for last pattern
count = self["num_instruments"].value
addr = self.absolute_address
for index in xrange(count):
offset = (self.static_size+index*PTMInstrument.static_size)//8
yield Chunk(PTMInstrument, "instrument[]", offset,
PTMInstrument.static_size//8)
offset = self.stream.readBits(addr+8*(offset+18), 32, LITTLE_ENDIAN)
min_off = min(min_off, offset)
# Patterns
count = self["num_patterns"].value
prev_off = 16*self["pattern_pptr/offset[0]"].value
for index in range(1, count):
offset = 16*self["pattern_pptr/offset[%u]" % index].value
yield Chunk(PTMPattern, "pattern[]", prev_off, offset-prev_off)
prev_off = offset
# Difficult to account for
yield Chunk(PTMPattern, "pattern[]", prev_off, min_off-prev_off)
class SampleFlags(StaticFieldSet):
format = (
(Bit, "loop_on"),
(Bit, "stereo", "Sample size will be 2*length"),
(Bit, "16bits", "16b sample, Intel LO-HI byteorder"),
(Bits, "unused", 5)
)
class S3MUInt24(Field):
static_size = 24
def __init__(self, parent, name, desc=None):
Field.__init__(self, parent, name, size=24, description=desc)
addr = self.absolute_address
val = parent.stream.readBits(addr, 8, LITTLE_ENDIAN) << 20
val += parent.stream.readBits(addr+8, 16, LITTLE_ENDIAN) << 4
self.createValue = lambda: val
class SampleData(SizeFieldSet):
def createUnpaddedFields(self):
yield RawBytes(self, "data", self.real_size)
class PTMSampleData(SampleData):
ALIGN = 0
class Instrument(SizeFieldSet):
static_size = 8*0x50
def createDescription(self):
info = [self["c4_speed"].display]
if "flags/stereo" in self:
if self["flags/stereo"].value:
info.append("stereo")
else:
info.append("mono")
info.append("%u bits" % self.getSampleBits())
return ", ".join(info)
# Structure knows its size and doesn't need padding anyway, so
# overwrite base member: no need to go through it.
def createFields(self):
yield self.getType()
yield String(self, "filename", 12, strip='\0')
for field in self.getInstrumentFields():
yield field
yield String(self, "name", 28, strip='\0')
yield String(self, "marker", 4, "Either 'SCRS' or '(empty)'", strip='\0')
def createValue(self):
return self["name"].value
class S3MInstrument(Instrument):
"""
In fact a sample. Description follows:
0 1 2 3 4 5 6 7 8 9 A B C D E F
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0000: |[T]| Dos filename (12345678.ABC) | MemSeg |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0010: |Length |HI:leng|LoopBeg|HI:LBeg|LoopEnd|HI:Lend|Vol| x |[P]|[F]|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0020: |C2Spd |HI:C2sp| x | x | x | x |Int:Gp |Int:512|Int:lastused |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0030: | Sample name, 28 characters max... (incl. NUL) |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0040: | ...sample name... |'S'|'C'|'R'|'S'|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
xxxx: sampledata
"""
MAGIC = "SCRS"
PACKING = {0: "Unpacked", 1: "DP30ADPCM" }
TYPE = {0: "Unknown", 1: "Sample", 2: "adlib melody", 3: "adlib drum2" }
def getType(self):
return Enum(UInt8(self, "type"), self.TYPE)
def getSampleBits(self):
return 8*(1+self["flags/16bits"].value)
def getInstrumentFields(self):
yield S3MUInt24(self, "sample_offset")
yield UInt32(self, "sample_size")
yield UInt32(self, "loop_begin")
yield UInt32(self, "loop_end")
yield UInt8(self, "volume")
yield UInt8(self, "reserved[]")
yield Enum(UInt8(self, "packing"), self.PACKING)
yield SampleFlags(self, "flags")
yield UInt32(self, "c4_speed", "Frequency for middle C note")
yield UInt32(self, "reserved[]", 4)
yield UInt16(self, "internal[]", "Sample address in GUS memory")
yield UInt16(self, "internal[]", "Flags for SoundBlaster loop expansion")
yield UInt32(self, "internal[]", "Last used position (SB)")
def getSubChunks(self):
size = self["sample_size"].value
if self["flags/stereo"].value: size *= 2
if self["flags/16bits"].value: size *= 2
yield Chunk(SampleData, "sample_data[]",
self["sample_offset"].value, size)
class PTMType(FieldSet):
TYPES = {0: "No sample", 1: "Regular", 2: "OPL2/OPL2 instrument", 3: "MIDI instrument" }
static_size = 8
def createFields(self):
yield Bits(self, "unused", 2)
yield Bit(self, "is_tonable")
yield Bit(self, "16bits")
yield Bit(self, "loop_bidir")
yield Bit(self, "loop")
yield Enum(Bits(self, "origin", 2), self.TYPES)
##class PTMType(StaticFieldSet):
## format = (
## (Bits, "unused", 2),
## (Bit, "is_tonable"),
## (Bit, "16bits"),
## (Bit, "loop_bidir"),
## (Bit, "loop"),
## (Bits, "origin", 2),
## )
class PTMInstrument(Instrument):
MAGIC = "PTMI"
ALIGN = 0
def getType(self):
return PTMType(self, "flags") # Hack to have more common code
# PTM doesn't pretend to manage 16bits
def getSampleBits(self):
return 8
def getInstrumentFields(self):
yield UInt8(self, "volume")
yield UInt16(self, "c4_speed")
yield UInt16(self, "sample_segment")
yield UInt32(self, "sample_offset")
yield UInt32(self, "sample_size")
yield UInt32(self, "loop_begin")
yield UInt32(self, "loop_end")
yield UInt32(self, "gus_begin")
yield UInt32(self, "gus_loop_start")
yield UInt32(self, "gus_loop_end")
yield textHandler(UInt8(self, "gus_loop_flags"), hexadecimal)
yield UInt8(self, "reserved[]") # Should be 0
def getSubChunks(self):
# Samples are NOT padded, and the size is already the correct one
size = self["sample_size"].value
if size:
yield Chunk(PTMSampleData, "sample_data[]", self["sample_offset"].value, size)
class S3MNoteInfo(StaticFieldSet):
"""
0=end of row
&31=channel
&32=follows; BYTE:note, BYTE:instrument
&64=follows; BYTE:volume
&128=follows; BYTE:command, BYTE:info
"""
format = (
(Bits, "channel", 5),
(Bit, "has_note"),
(Bit, "has_volume"),
(Bit, "has_effect")
)
class PTMNoteInfo(StaticFieldSet):
format = (
(Bits, "channel", 5),
(Bit, "has_note"),
(Bit, "has_effect"),
(Bit, "has_volume")
)
class Note(FieldSet):
def createFields(self):
# Used by Row to check if end of Row
info = self.NOTE_INFO(self, "info")
yield info
if info["has_note"].value:
yield UInt8(self, "note")
yield UInt8(self, "instrument")
if info["has_volume"].value:
yield UInt8(self, "volume")
if info["has_effect"].value:
yield UInt8(self, "effect")
yield UInt8(self, "param")
class S3MNote(Note):
NOTE_INFO = S3MNoteInfo
class PTMNote(Note):
NOTE_INFO = PTMNoteInfo
class Row(FieldSet):
def createFields(self):
addr = self.absolute_address
while True:
# Check empty note
byte = self.stream.readBits(addr, 8, self.endian)
if not byte:
yield NullBytes(self, "terminator", 1)
return
note = self.NOTE(self, "note[]")
yield note
addr += note.size
class S3MRow(Row):
NOTE = S3MNote
class PTMRow(Row):
NOTE = PTMNote
class Pattern(SizeFieldSet):
def createUnpaddedFields(self):
count = 0
while count < 64 and not self.eof:
yield self.ROW(self, "row[]")
count += 1
class S3MPattern(Pattern):
ROW = S3MRow
def __init__(self, parent, name, size, desc=None):
Pattern.__init__(self, parent, name, size, desc)
# Get real_size from header
addr = self.absolute_address
size = self.stream.readBits(addr, 16, LITTLE_ENDIAN)
self.setCheckedSizes(size)
class PTMPattern(Pattern):
ROW = PTMRow
class Module(Parser):
# MARKER / HEADER are defined in derived classes
endian = LITTLE_ENDIAN
def validate(self):
marker = self.stream.readBits(0x1C*8, 8, LITTLE_ENDIAN)
if marker != 0x1A:
return "Invalid start marker %u" % marker
marker = self.stream.readBytes(0x2C*8, 4)
if marker != self.MARKER:
return "Invalid marker %s!=%s" % (marker, self.MARKER)
return True
def createFields(self):
# Index chunks
indexer = ChunkIndexer()
# Add header - at least 0x50 bytes
indexer.addChunk(Chunk(self.HEADER, "header", 0, 0x50))
for field in indexer.yieldChunks(self):
yield field
class S3MModule(Module):
PARSER_TAGS = {
"id": "s3m",
"category": "audio",
"file_ext": ("s3m",),
"mime": (u'audio/s3m', u'audio/x-s3m'),
"min_size": 64*8,
"description": "ScreamTracker3 module"
}
MARKER = "SCRM"
HEADER = S3MHeader
## def createContentSize(self):
## hdr = Header(self, "header")
## max_offset = hdr._size//8
## instr_size = Instrument._size//8
## for index in xrange(self["header/num_instruments"].value):
## offset = 16*hdr["instr_pptr/offset[%u]" % index].value
## max_offset = max(offset+instr_size, max_offset)
## addr = self.absolute_address + 8*offset
class PTMModule(Module):
PARSER_TAGS = {
"id": "ptm",
"category": "audio",
"file_ext": ("ptm",),
"min_size": 64*8,
"description": "PolyTracker module (v1.17)"
}
MARKER = "PTMF"
HEADER = PTMHeader
+390
View File
@@ -0,0 +1,390 @@
"""
Parser of FastTrackerII Extended Module (XM) version 1.4
Documents:
- Modplug source code (file modplug/soundlib/Load_xm.cpp)
http://sourceforge.net/projects/modplug
- Dumb source code (files include/dumb.h and src/it/readxm.c
http://dumb.sf.net/
- Documents of "XM" format on Wotsit
http://www.wotsit.org
Author: Christophe GISQUET <christophe.gisquet@free.fr>
Creation: 8th February 2007
"""
from hachoir_parser import Parser
from hachoir_core.field import (StaticFieldSet, FieldSet,
Bit, RawBits, Bits,
UInt32, UInt16, UInt8, Int8, Enum,
RawBytes, String, GenericVector)
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
from hachoir_core.text_handler import textHandler, filesizeHandler, hexadecimal
from hachoir_parser.audio.modplug import ParseModplugMetadata
from hachoir_parser.common.tracker import NOTE_NAME
def parseSigned(val):
return "%i" % (val.value-128)
# From dumb
SEMITONE_BASE = 1.059463094359295309843105314939748495817
PITCH_BASE = 1.000225659305069791926712241547647863626
SAMPLE_LOOP_MODE = ("No loop", "Forward loop", "Ping-pong loop", "Undef")
class SampleType(FieldSet):
static_size = 8
def createFields(self):
yield Bits(self, "unused[]", 4)
yield Bit(self, "16bits")
yield Bits(self, "unused[]", 1)
yield Enum(Bits(self, "loop_mode", 2), SAMPLE_LOOP_MODE)
class SampleHeader(FieldSet):
static_size = 40*8
def createFields(self):
yield UInt32(self, "length")
yield UInt32(self, "loop_start")
yield UInt32(self, "loop_end")
yield UInt8(self, "volume")
yield Int8(self, "fine_tune")
yield SampleType(self, "type")
yield UInt8(self, "panning")
yield Int8(self, "relative_note")
yield UInt8(self, "reserved")
yield String(self, "name", 22, charset="ASCII", strip=' \0')
def createValue(self):
bytes = 1+self["type/16bits"].value
C5_speed = int(16726.0*pow(SEMITONE_BASE, self["relative_note"].value)
*pow(PITCH_BASE, self["fine_tune"].value*2))
return "%s, %ubits, %u samples, %uHz" % \
(self["name"].display, 8*bytes, self["length"].value/bytes, C5_speed)
class StuffType(StaticFieldSet):
format = (
(Bits, "unused", 5),
(Bit, "loop"),
(Bit, "sustain"),
(Bit, "on")
)
class InstrumentSecondHeader(FieldSet):
static_size = 234*8
def createFields(self):
yield UInt32(self, "sample_header_size")
yield GenericVector(self, "notes", 96, UInt8, "sample")
yield GenericVector(self, "volume_envelope", 24, UInt16, "point")
yield GenericVector(self, "panning_envelope", 24, UInt16, "point")
yield UInt8(self, "volume_points", r"Number of volume points")
yield UInt8(self, "panning_points", r"Number of panning points")
yield UInt8(self, "volume_sustain_point")
yield UInt8(self, "volume_loop_start_point")
yield UInt8(self, "volume_loop_end_point")
yield UInt8(self, "panning_sustain_point")
yield UInt8(self, "panning_loop_start_point")
yield UInt8(self, "panning_loop_end_point")
yield StuffType(self, "volume_type")
yield StuffType(self, "panning_type")
yield UInt8(self, "vibrato_type")
yield UInt8(self, "vibrato_sweep")
yield UInt8(self, "vibrato_depth")
yield UInt8(self, "vibrato_rate")
yield UInt16(self, "volume_fadeout")
yield GenericVector(self, "reserved", 11, UInt16, "word")
def createInstrumentContentSize(s, addr):
start = addr
samples = s.stream.readBits(addr+27*8, 16, LITTLE_ENDIAN)
# Seek to end of header (1st + 2nd part)
addr += 8*s.stream.readBits(addr, 32, LITTLE_ENDIAN)
sample_size = 0
if samples:
for index in xrange(samples):
# Read the sample size from the header
sample_size += s.stream.readBits(addr, 32, LITTLE_ENDIAN)
# Seek to next sample header
addr += SampleHeader.static_size
return addr - start + 8*sample_size
class Instrument(FieldSet):
def __init__(self, parent, name):
FieldSet.__init__(self, parent, name)
self._size = createInstrumentContentSize(self, self.absolute_address)
self.info(self.createDescription())
# Seems to fix things...
def fixInstrumentHeader(self):
size = self["size"].value - self.current_size//8
if size:
yield RawBytes(self, "unknown_data", size)
def createFields(self):
yield UInt32(self, "size")
yield String(self, "name", 22, charset="ASCII", strip=" \0")
# Doc says type is always 0, but I've found values of 24 and 96 for
# the _same_ song here, just different download sources for the file
yield UInt8(self, "type")
yield UInt16(self, "samples")
num = self["samples"].value
self.info(self.createDescription())
if num:
yield InstrumentSecondHeader(self, "second_header")
for field in self.fixInstrumentHeader():
yield field
# This part probably wrong
sample_size = [ ]
for index in xrange(num):
sample = SampleHeader(self, "sample_header[]")
yield sample
sample_size.append(sample["length"].value)
for size in sample_size:
if size:
yield RawBytes(self, "sample_data[]", size, "Deltas")
else:
for field in self.fixInstrumentHeader():
yield field
def createDescription(self):
return "Instrument '%s': %i samples, header %i bytes" % \
(self["name"].value, self["samples"].value, self["size"].value)
VOLUME_NAME = (
"Volume slide down", "Volume slide up", "Fine volume slide down",
"Fine volume slide up", "Set vibrato speed", "Vibrato",
"Set panning", "Panning slide left", "Panning slide right",
"Tone porta", "Unhandled")
def parseVolume(val):
val = val.value
if 0x10<=val<=0x50:
return "Volume %i" % val-16
else:
return VOLUME_NAME[val/16 - 6]
class RealBit(RawBits):
static_size = 1
def __init__(self, parent, name, description=None):
RawBits.__init__(self, parent, name, 1, description=description)
def createValue(self):
return self._parent.stream.readBits(self.absolute_address, 1, BIG_ENDIAN)
class NoteInfo(StaticFieldSet):
format = (
(RawBits, "unused", 2),
(RealBit, "has_parameter"),
(RealBit, "has_type"),
(RealBit, "has_volume"),
(RealBit, "has_instrument"),
(RealBit, "has_note")
)
EFFECT_NAME = (
"Arppegio", "Porta up", "Porta down", "Tone porta", "Vibrato",
"Tone porta+Volume slide", "Vibrato+Volume slide", "Tremolo",
"Set panning", "Sample offset", "Volume slide", "Position jump",
"Set volume", "Pattern break", None, "Set tempo/BPM",
"Set global volume", "Global volume slide", "Unused", "Unused",
"Unused", "Set envelope position", "Unused", "Unused",
"Panning slide", "Unused", "Multi retrig note", "Unused",
"Tremor", "Unused", "Unused", "Unused", None)
EFFECT_E_NAME = (
"Unknown", "Fine porta up", "Fine porta down",
"Set gliss control", "Set vibrato control", "Set finetune",
"Set loop begin/loop", "Set tremolo control", "Retrig note",
"Fine volume slide up", "Fine volume slide down", "Note cut",
"Note delay", "Pattern delay")
class Effect(RawBits):
def __init__(self, parent, name):
RawBits.__init__(self, parent, name, 8)
def createValue(self):
t = self.parent.stream.readBits(self.absolute_address, 8, LITTLE_ENDIAN)
param = self.parent.stream.readBits(self.absolute_address+8, 8, LITTLE_ENDIAN)
if t == 0x0E:
return EFFECT_E_NAME[param>>4] + " %i" % (param&0x07)
elif t == 0x21:
return ("Extra fine porta up", "Extra fine porta down")[param>>4]
else:
return EFFECT_NAME[t]
class Note(FieldSet):
def __init__(self, parent, name, desc=None):
FieldSet.__init__(self, parent, name, desc)
self.flags = self.stream.readBits(self.absolute_address, 8, LITTLE_ENDIAN)
if self.flags&0x80:
# TODO: optimize bitcounting with a table:
# http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable
self._size = 8
if self.flags&0x01: self._size += 8
if self.flags&0x02: self._size += 8
if self.flags&0x04: self._size += 8
if self.flags&0x08: self._size += 8
if self.flags&0x10: self._size += 8
else:
self._size = 5*8
def createFields(self):
# This stupid shit gets the LSB, not the MSB...
self.info("Note info: 0x%02X" %
self.stream.readBits(self.absolute_address, 8, LITTLE_ENDIAN))
yield RealBit(self, "is_extended")
if self["is_extended"].value:
info = NoteInfo(self, "info")
yield info
if info["has_note"].value:
yield Enum(UInt8(self, "note"), NOTE_NAME)
if info["has_instrument"].value:
yield UInt8(self, "instrument")
if info["has_volume"].value:
yield textHandler(UInt8(self, "volume"), parseVolume)
if info["has_type"].value:
yield Effect(self, "effect_type")
if info["has_parameter"].value:
yield textHandler(UInt8(self, "effect_parameter"), hexadecimal)
else:
yield Enum(Bits(self, "note", 7), NOTE_NAME)
yield UInt8(self, "instrument")
yield textHandler(UInt8(self, "volume"), parseVolume)
yield Effect(self, "effect_type")
yield textHandler(UInt8(self, "effect_parameter"), hexadecimal)
def createDescription(self):
if "info" in self:
info = self["info"]
desc = []
if info["has_note"].value:
desc.append(self["note"].display)
if info["has_instrument"].value:
desc.append("instrument %i" % self["instrument"].value)
if info["has_volume"].value:
desc.append(self["has_volume"].display)
if info["has_type"].value:
desc.append("effect %s" % self["effect_type"].value)
if info["has_parameter"].value:
desc.append("parameter %i" % self["effect_parameter"].value)
else:
desc = (self["note"].display, "instrument %i" % self["instrument"].value,
self["has_volume"].display, "effect %s" % self["effect_type"].value,
"parameter %i" % self["effect_parameter"].value)
if desc:
return "Note %s" % ", ".join(desc)
else:
return "Note"
class Row(FieldSet):
def createFields(self):
for index in xrange(self["/header/channels"].value):
yield Note(self, "note[]")
def createPatternContentSize(s, addr):
return 8*(s.stream.readBits(addr, 32, LITTLE_ENDIAN) +
s.stream.readBits(addr+7*8, 16, LITTLE_ENDIAN))
class Pattern(FieldSet):
def __init__(self, parent, name, desc=None):
FieldSet.__init__(self, parent, name, desc)
self._size = createPatternContentSize(self, self.absolute_address)
def createFields(self):
yield UInt32(self, "header_size", r"Header length (9)")
yield UInt8(self, "packing_type", r"Packing type (always 0)")
yield UInt16(self, "rows", r"Number of rows in pattern (1..256)")
yield UInt16(self, "data_size", r"Packed patterndata size")
rows = self["rows"].value
self.info("Pattern: %i rows" % rows)
for index in xrange(rows):
yield Row(self, "row[]")
def createDescription(self):
return "Pattern with %i rows" % self["rows"].value
class Header(FieldSet):
MAGIC = "Extended Module: "
static_size = 336*8
def createFields(self):
yield String(self, "signature", 17, "XM signature", charset="ASCII")
yield String(self, "title", 20, "XM title", charset="ASCII", strip=' ')
yield UInt8(self, "marker", "Marker (0x1A)")
yield String(self, "tracker_name", 20, "XM tracker name", charset="ASCII", strip=' ')
yield UInt8(self, "format_minor")
yield UInt8(self, "format_major")
yield filesizeHandler(UInt32(self, "header_size", "Header size (276)"))
yield UInt16(self, "song_length", "Length in patten order table")
yield UInt16(self, "restart", "Restart position")
yield UInt16(self, "channels", "Number of channels (2,4,6,8,10,...,32)")
yield UInt16(self, "patterns", "Number of patterns (max 256)")
yield UInt16(self, "instruments", "Number of instruments (max 128)")
yield Bit(self, "amiga_ftable", "Amiga frequency table")
yield Bit(self, "linear_ftable", "Linear frequency table")
yield Bits(self, "unused", 14)
yield UInt16(self, "tempo", "Default tempo")
yield UInt16(self, "bpm", "Default BPM")
yield GenericVector(self, "pattern_order", 256, UInt8, "order")
def createDescription(self):
return "'%s' by '%s'" % (
self["title"].value, self["tracker_name"].value)
class XMModule(Parser):
PARSER_TAGS = {
"id": "fasttracker2",
"category": "audio",
"file_ext": ("xm",),
"mime": (
u'audio/xm', u'audio/x-xm',
u'audio/module-xm', u'audio/mod', u'audio/x-mod'),
"magic": ((Header.MAGIC, 0),),
"min_size": Header.static_size +29*8, # Header + 1 empty instrument
"description": "FastTracker2 module"
}
endian = LITTLE_ENDIAN
def validate(self):
header = self.stream.readBytes(0, 17)
if header != Header.MAGIC:
return "Invalid signature '%s'" % header
if self["/header/header_size"].value != 276:
return "Unknown header size (%u)" % self["/header/header_size"].value
return True
def createFields(self):
yield Header(self, "header")
for index in xrange(self["/header/patterns"].value):
yield Pattern(self, "pattern[]")
for index in xrange(self["/header/instruments"].value):
yield Instrument(self, "instrument[]")
# Metadata added by ModPlug - can be discarded
for field in ParseModplugMetadata(self):
yield field
def createContentSize(self):
# Header size
size = Header.static_size
# Add patterns size
for index in xrange(self["/header/patterns"].value):
size += createPatternContentSize(self, size)
# Add instruments size
for index in xrange(self["/header/instruments"].value):
size += createInstrumentContentSize(self, size)
# Not reporting Modplug metadata
return size
def createDescription(self):
return self["header"].description