mirror of
https://github.com/SickGear/SickGear.git
synced 2024-11-30 00:13:38 +00:00
171 lines
5.5 KiB
Python
171 lines
5.5 KiB
Python
|
"""
|
||
|
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.field import FieldSet, String, Bit, Bits, UInt16, UInt24, RawBytes, Enum, NullBytes
|
||
|
from hachoir.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", "Stream info", StreamInfo),
|
||
|
1: ("padding[]", "Padding", None),
|
||
|
2: ("application[]", "Application", None),
|
||
|
3: ("seek_table", "Seek table", SeekTable),
|
||
|
4: ("comment", "Vorbis comment", VorbisComment),
|
||
|
5: ("cue_sheet[]", "Cue sheet", None),
|
||
|
6: ("picture[]", "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 = b"fLaC\x00"
|
||
|
PARSER_TAGS = {
|
||
|
"id": "flac",
|
||
|
"category": "audio",
|
||
|
"file_ext": ("flac",),
|
||
|
"mime": ("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 "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")
|