Melodia/Melodia/resources/audiotools/flac.py

716 lines
25 KiB
Python

#!/usr/bin/python
#Audio Tools, a module and set of tools for manipulating audio data
#Copyright (C) 2007-2011 Brian Langenberger
#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import array
import audiotools
import sys,cStringIO
Con = audiotools.Con
class UTF8(Con.Struct):
@classmethod
def __total_utf8_bytes__(cls, header):
total = 0
for b in header:
if b == '\x01':
total += 1
else:
break
return max(1,total)
@classmethod
def __calculate_utf8_value__(cls, ctx):
import operator
return Con.lib.bin_to_int(ctx.header[ctx.header.index('\x00') + 1:] + \
reduce(operator.concat,
[s[2:] for s in ctx['sub_byte']],
''))
def __init__(self, name):
Con.Struct.__init__(
self,name,
Con.Bytes('header',8),
Con.Value('total_bytes',
lambda ctx: self.__total_utf8_bytes__(ctx['header'])),
Con.MetaRepeater(
lambda ctx: self.__total_utf8_bytes__(ctx['header']) - 1,
Con.Bytes('sub_byte',8)),
Con.Value('value',
lambda ctx: self.__calculate_utf8_value__(ctx)))
class Unary(Con.Adapter):
def __init__(self, name):
Con.Adapter.__init__(
self,
Con.RepeatUntil(lambda obj,ctx: obj == 1,
Con.Byte(name)))
def _encode(self, value, context):
if (value > 0):
return ([0] * (value)) + [1]
else:
return [1]
def _decode(self, obj, context):
return len(obj) - 1
class PlusOne(Con.Adapter):
def _encode(self, value, context):
return value - 1
def _decode(self, obj, context):
return obj + 1
class FlacStreamException(Exception): pass
class FlacReader:
FRAME_HEADER = Con.Struct('frame_header',
Con.Bits('sync',14),
Con.Bits('reserved',2),
Con.Bits('block_size',4),
Con.Bits('sample_rate',4),
Con.Bits('channel_assignment',4),
Con.Bits('bits_per_sample',3),
Con.Padding(1),
Con.IfThenElse(
'total_channels',
lambda ctx1: ctx1['channel_assignment'] <= 7,
Con.Value('c',lambda ctx2: ctx2['channel_assignment'] + 1),
Con.Value('c',lambda ctx3: 2)),
UTF8('frame_number'),
Con.IfThenElse(
'extended_block_size',
lambda ctx1: ctx1['block_size'] == 6,
Con.Bits('b',8),
Con.If(lambda ctx2: ctx2['block_size'] == 7,
Con.Bits('b',16))),
Con.IfThenElse(
'extended_sample_rate',
lambda ctx1: ctx1['sample_rate'] == 12,
Con.Bits('s',8),
Con.If(lambda ctx2: ctx2['sample_rate'] in (13,14),
Con.Bits('s',16))),
Con.Bits('crc8',8))
UNARY = Con.Struct('unary',
Con.RepeatUntil(
lambda obj,ctx: obj == '\x01',
Con.Field('bytes',1)),
Con.Value('value',
lambda ctx: len(ctx['bytes']) - 1)
)
SUBFRAME_HEADER = Con.Struct('subframe_header',
Con.Padding(1),
Con.Bits('subframe_type',6),
Con.Flag('has_wasted_bits_per_sample'),
Con.IfThenElse(
'wasted_bits_per_sample',
lambda ctx: ctx['has_wasted_bits_per_sample'],
PlusOne(Unary('value')),
Con.Value('value',lambda ctx2: 0)))
GET_BLOCKSIZE_FROM_STREAMINFO = -1
GET_8BIT_BLOCKSIZE_FROM_END_OF_HEADER = -2
GET_16BIT_BLOCKSIZE_FROM_END_OF_HEADER = -3
BLOCK_SIZE = (GET_BLOCKSIZE_FROM_STREAMINFO,
192,
576,1152,2304,4608,
GET_8BIT_BLOCKSIZE_FROM_END_OF_HEADER,
GET_16BIT_BLOCKSIZE_FROM_END_OF_HEADER,
256,512,1024,2048,4096,8192,16384,32768)
GET_SAMPLE_SIZE_FROM_STREAMINFO = -1
SAMPLE_SIZE = (GET_SAMPLE_SIZE_FROM_STREAMINFO,
8,12,None,16,20,24,None)
def FIXED0(subframe,residual,i):
subframe.insert(i,
residual[i])
def FIXED1(subframe,residual,i):
subframe.insert(i,
subframe[i - 1] + residual[i])
def FIXED2(subframe,residual,i):
subframe.insert(i,
((2 * subframe[i - 1]) - subframe[i - 2] + \
residual[i]))
def FIXED3(subframe,residual,i):
subframe.insert(i,
((3 * subframe[i - 1]) - (3 * subframe[i - 2]) + \
subframe[i - 3] + residual[i]))
def FIXED4(subframe,residual,i):
subframe.insert(i,
((4 * subframe[i - 1]) - (6 * subframe[i - 2]) + \
(4 * subframe[i - 3]) - subframe[i - 4] + residual[i]))
#iterates over all of the channels, in order
def MERGE_INDEPENDENT(channel_list):
channel_data = [iter(c) for c in channel_list]
while (True):
for channel in channel_data:
yield channel.next()
def MERGE_LEFT(channel_list):
channel_left = iter(channel_list[0])
channel_side = iter(channel_list[1])
while (True):
left = channel_left.next()
side = channel_side.next()
yield left
yield left - side
def MERGE_RIGHT(channel_list):
channel_side = iter(channel_list[0])
channel_right = iter(channel_list[1])
while (True):
side = channel_side.next()
right = channel_right.next()
yield side + right
yield right
def MERGE_MID(channel_list):
channel_mid = iter(channel_list[0])
channel_side = iter(channel_list[1])
while (True):
mid = channel_mid.next()
side = channel_side.next()
mid = mid << 1
mid |= (side & 0x1)
yield (mid + side) >> 1
yield (mid - side) >> 1
CHANNEL_FUNCTIONS = (MERGE_INDEPENDENT,
MERGE_INDEPENDENT,
MERGE_INDEPENDENT,
MERGE_INDEPENDENT,
MERGE_INDEPENDENT,
MERGE_INDEPENDENT,
MERGE_INDEPENDENT,
MERGE_INDEPENDENT,
MERGE_LEFT,
MERGE_RIGHT,
MERGE_MID)
FIXED_FUNCTIONS = (FIXED0,FIXED1,FIXED2,FIXED3,FIXED4)
def __init__(self, flac_stream):
self.stream = BufferedStream(flac_stream)
self.streaminfo = None
self.bitstream = None
#ensure the file starts with 'fLaC'
self.read_stream_marker()
#initialize self.bitstream
self.begin_bitstream()
#find self.streaminfo in case we need it
self.read_metadata_blocks()
def close(self):
if (self.bitstream != None):
self.bitstream.close()
else:
self.stream.close()
def read_stream_marker(self):
if (self.stream.read(4) != 'fLaC'):
raise FlacStreamException('invalid stream marker')
def read_metadata_blocks(self):
block = audiotools.FlacAudio.METADATA_BLOCK_HEADER.parse_stream(self.stream)
while (block.last_block == 0):
if (block.block_type == 0):
self.streaminfo = audiotools.FlacAudio.STREAMINFO.parse_stream(self.stream)
else:
self.stream.seek(block.block_length,1)
block = audiotools.FlacAudio.METADATA_BLOCK_HEADER.parse_stream(self.stream)
self.stream.seek(block.block_length,1)
def begin_bitstream(self):
import bitstream
#self.bitstream = Con.BitStreamReader(self.stream)
self.bitstream = bitstream.BitStreamReader(self.stream)
def read_frame(self):
self.stream.reset_buffer()
try:
header = FlacReader.FRAME_HEADER.parse_stream(self.bitstream)
except Con.core.FieldError:
return ""
if (header.sync != 0x3FFE):
raise FlacStreamException('invalid sync')
if (crc8(self.stream.getvalue()[0:-1]) != header.crc8):
raise FlacStreamException('crc8 checksum failed')
#block_size tells us how many samples we need from each subframe
block_size = FlacReader.BLOCK_SIZE[header.block_size]
if (block_size == self.GET_BLOCKSIZE_FROM_STREAMINFO):
block_size = self.streaminfo.maximum_blocksize
elif ((block_size == self.GET_8BIT_BLOCKSIZE_FROM_END_OF_HEADER) or
(block_size == self.GET_16BIT_BLOCKSIZE_FROM_END_OF_HEADER)):
block_size = header.extended_block_size + 1
#grab subframe data as 32-bit array objects
subframe_data = []
for channel_number in xrange(header.total_channels):
subframe_data.append(
self.read_subframe(header, block_size, channel_number))
crc16sum = crc16(self.stream.getvalue())
#try to byte-align the stream
if (len(self.bitstream.buffer) > 0):
self.bitstream.read(len(self.bitstream.buffer))
if (crc16sum != Con.Bits('crc16',16).parse_stream(self.bitstream)):
raise FlacStreamException('crc16 checksum failed')
#convert our list of subframe data arrays into
#a string of sample data
if (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == 16):
merged_frames = array.array('h',
FlacReader.CHANNEL_FUNCTIONS[
header.channel_assignment](subframe_data))
if (audiotools.BIG_ENDIAN):
merged_frames.byteswap()
return merged_frames.tostring()
elif (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == 8):
merged_frames = array.array('b',
FlacReader.CHANNEL_FUNCTIONS[
header.channel_assignment](subframe_data))
return merged_frames.tostring()
else:
if (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == \
self.GET_SAMPLE_SIZE_FROM_STREAMINFO):
bits_per_sample = self.streaminfo.bits_per_sample + 1
elif (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == None):
raise FlacStreamException('invalid bits per sample')
else:
bits_per_sample = FlacReader.SAMPLE_SIZE[header.bits_per_sample]
stream = Con.GreedyRepeater(
Con.BitStruct('bits',
Con.Bits('value',bits_per_sample,
swapped=True,signed=True)))
return stream.build(
[Con.Container(value=v) for v in
FlacReader.CHANNEL_FUNCTIONS[header.channel_assignment](
subframe_data)])
def read_subframe(self, frame_header, block_size, channel_number):
subframe_header = \
FlacReader.SUBFRAME_HEADER.parse_stream(self.bitstream)
#figure out the bits-per-sample of this subframe
if ((frame_header.channel_assignment == 8) and
(channel_number == 1)):
#if channel is stored as left+difference
#and this is the difference, add 1 bit
bits_per_sample = FlacReader.SAMPLE_SIZE[
frame_header.bits_per_sample] + 1
elif ((frame_header.channel_assignment == 9) and
(channel_number == 0)):
#if channel is stored as difference+right
#and this is the difference, add 1 bit
bits_per_sample = FlacReader.SAMPLE_SIZE[
frame_header.bits_per_sample] + 1
elif ((frame_header.channel_assignment == 10) and
(channel_number == 1)):
#if channel is stored as average+difference
#and this is the difference, add 1 bit
bits_per_sample = FlacReader.SAMPLE_SIZE[
frame_header.bits_per_sample] + 1
else:
#otherwise, use the number from the frame header
bits_per_sample = FlacReader.SAMPLE_SIZE[
frame_header.bits_per_sample]
if (subframe_header.has_wasted_bits_per_sample):
bits_per_sample -= subframe_header.wasted_bits_per_sample
if (subframe_header.subframe_type == 0):
subframe = self.read_subframe_constant(block_size, bits_per_sample)
elif (subframe_header.subframe_type == 1):
subframe = self.read_subframe_verbatim(block_size, bits_per_sample)
elif ((subframe_header.subframe_type & 0x38) == 0x08):
subframe = self.read_subframe_fixed(
subframe_header.subframe_type & 0x07,
block_size,
bits_per_sample)
elif ((subframe_header.subframe_type & 0x20) == 0x20):
subframe = self.read_subframe_lpc(
(subframe_header.subframe_type & 0x1F) + 1,
block_size,
bits_per_sample)
else:
raise FlacStreamException('invalid subframe type')
if (subframe_header.has_wasted_bits_per_sample):
return array.array(
'i',
[i << subframe_header.wasted_bits_per_sample
for i in subframe])
else:
return subframe
def read_subframe_constant(self, block_size, bits_per_sample):
sample = Con.Bits('b',bits_per_sample).parse_stream(
self.bitstream)
subframe = array.array('i',[sample] * block_size)
return subframe
def read_subframe_verbatim(self, block_size, bits_per_sample):
return array.array('i',
Con.StrictRepeater(
block_size,
Con.Bits("samples",
bits_per_sample,
signed=True)).parse_stream(self.bitstream))
def read_subframe_fixed(self, order, block_size, bits_per_sample):
samples = Con.StrictRepeater(
order,
Con.Bits("warm_up_samples",
bits_per_sample,
signed=True))
subframe = array.array('i',
samples.parse_stream(self.bitstream))
residual = self.read_residual(block_size,order)
fixed_func = self.FIXED_FUNCTIONS[order]
for i in xrange(len(subframe),block_size):
fixed_func(subframe,residual,i)
return subframe
def read_subframe_lpc(self, order, block_size, bits_per_sample):
samples = Con.StrictRepeater(
order,
Con.Bits("warm_up_samples",
bits_per_sample,
signed=True))
subframe = array.array('i',
samples.parse_stream(self.bitstream))
lpc_precision = Con.Bits('lpc_precision',
4).parse_stream(self.bitstream) + 1
lpc_shift = Con.Bits('lpc_shift',
5).parse_stream(self.bitstream)
coefficients = array.array('i',
Con.StrictRepeater(
order,
Con.Bits('coefficients',
lpc_precision,
signed=True)).parse_stream(self.bitstream))
residual = self.read_residual(block_size, order)
for i in xrange(len(subframe),block_size):
subframe.insert(i,
(sum(
[coefficients[j] * subframe[i - j - 1] for j in
xrange(0,len(coefficients))]) >> lpc_shift) + \
residual[i])
return subframe
def read_residual(self, block_size, predictor_order):
rice = array.array('i')
#add some dummy rice so that the Rice index matches
#that of the rest of the subframe
for i in xrange(predictor_order):
rice.append(0)
coding_method = self.bitstream.read(2)
if (coding_method == '\x00\x00'):
rice2 = False
elif (coding_method == '\x00\x01'):
rice2 = True
else:
raise FlacStreamException('invalid residual coding method')
partition_order = Con.Bits('partition_order',4).parse_stream(
self.bitstream)
if (partition_order > 0):
total_samples = ((block_size / 2 ** partition_order) -
predictor_order)
rice.extend(self.read_encoded_rice(total_samples,rice2))
for i in xrange(1,2 ** partition_order):
total_samples = (block_size / 2 ** partition_order)
rice.extend(self.read_encoded_rice(total_samples,rice2))
else:
rice.extend(self.read_encoded_rice(block_size - predictor_order,
rice2))
return rice
def read_encoded_rice(self, total_samples, rice2=False):
bin_to_int = Con.lib.binary.bin_to_int
samples = array.array('i')
if (not rice2):
rice_parameter = Con.Bits('rice_parameter',4).parse_stream(
self.bitstream)
else:
rice_parameter = Con.Bits('rice_parameter',5).parse_stream(
self.bitstream)
if (rice_parameter != 0xF):
#a Rice encoded residual
for x in xrange(total_samples):
#count the number of 0 bits before the next 1 bit
#(unary encoding)
#to find our most significant bits
msb = 0
s = self.bitstream.read(1)
while (s != '\x01'):
msb += 1
s = self.bitstream.read(1)
#grab the proper number of least significant bits
lsb = bin_to_int(self.bitstream.read(rice_parameter))
#combine msb and lsb to get the Rice-encoded value
value = (msb << rice_parameter) | lsb
if ((value & 0x1) == 0x1): #negative
samples.append(-(value >> 1) - 1)
else: #positive
samples.append(value >> 1)
else:
#unencoded residual
bits_per_sample = Con.Bits('escape_code',5).parse_stream(
self.bitstream)
sample = Con.Bits("sample",bits_per_sample,signed=True)
for x in xrange(total_samples):
samples.append(sample.parse_stream(self.bitstream))
return samples
###############################
#Checksum calculation functions
###############################
CRC8TABLE = (0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3)
def crc8(data, start=0):
value = start
for i in map(ord,data):
value = CRC8TABLE[value ^ i]
return value
CRC16TABLE = (0x0000,0x8005,0x800f,0x000a,0x801b,0x001e,0x0014,0x8011,
0x8033,0x0036,0x003c,0x8039,0x0028,0x802d,0x8027,0x0022,
0x8063,0x0066,0x006c,0x8069,0x0078,0x807d,0x8077,0x0072,
0x0050,0x8055,0x805f,0x005a,0x804b,0x004e,0x0044,0x8041,
0x80c3,0x00c6,0x00cc,0x80c9,0x00d8,0x80dd,0x80d7,0x00d2,
0x00f0,0x80f5,0x80ff,0x00fa,0x80eb,0x00ee,0x00e4,0x80e1,
0x00a0,0x80a5,0x80af,0x00aa,0x80bb,0x00be,0x00b4,0x80b1,
0x8093,0x0096,0x009c,0x8099,0x0088,0x808d,0x8087,0x0082,
0x8183,0x0186,0x018c,0x8189,0x0198,0x819d,0x8197,0x0192,
0x01b0,0x81b5,0x81bf,0x01ba,0x81ab,0x01ae,0x01a4,0x81a1,
0x01e0,0x81e5,0x81ef,0x01ea,0x81fb,0x01fe,0x01f4,0x81f1,
0x81d3,0x01d6,0x01dc,0x81d9,0x01c8,0x81cd,0x81c7,0x01c2,
0x0140,0x8145,0x814f,0x014a,0x815b,0x015e,0x0154,0x8151,
0x8173,0x0176,0x017c,0x8179,0x0168,0x816d,0x8167,0x0162,
0x8123,0x0126,0x012c,0x8129,0x0138,0x813d,0x8137,0x0132,
0x0110,0x8115,0x811f,0x011a,0x810b,0x010e,0x0104,0x8101,
0x8303,0x0306,0x030c,0x8309,0x0318,0x831d,0x8317,0x0312,
0x0330,0x8335,0x833f,0x033a,0x832b,0x032e,0x0324,0x8321,
0x0360,0x8365,0x836f,0x036a,0x837b,0x037e,0x0374,0x8371,
0x8353,0x0356,0x035c,0x8359,0x0348,0x834d,0x8347,0x0342,
0x03c0,0x83c5,0x83cf,0x03ca,0x83db,0x03de,0x03d4,0x83d1,
0x83f3,0x03f6,0x03fc,0x83f9,0x03e8,0x83ed,0x83e7,0x03e2,
0x83a3,0x03a6,0x03ac,0x83a9,0x03b8,0x83bd,0x83b7,0x03b2,
0x0390,0x8395,0x839f,0x039a,0x838b,0x038e,0x0384,0x8381,
0x0280,0x8285,0x828f,0x028a,0x829b,0x029e,0x0294,0x8291,
0x82b3,0x02b6,0x02bc,0x82b9,0x02a8,0x82ad,0x82a7,0x02a2,
0x82e3,0x02e6,0x02ec,0x82e9,0x02f8,0x82fd,0x82f7,0x02f2,
0x02d0,0x82d5,0x82df,0x02da,0x82cb,0x02ce,0x02c4,0x82c1,
0x8243,0x0246,0x024c,0x8249,0x0258,0x825d,0x8257,0x0252,
0x0270,0x8275,0x827f,0x027a,0x826b,0x026e,0x0264,0x8261,
0x0220,0x8225,0x822f,0x022a,0x823b,0x023e,0x0234,0x8231,
0x8213,0x0216,0x021c,0x8219,0x0208,0x820d,0x8207,0x0202)
def crc16(data, start=0):
value = start
for i in map(ord,data):
value = ((value << 8) ^ CRC16TABLE[(value >> 8) ^ i]) & 0xFFFF
return value
#BufferedStream stores the data that passes through read()
#so that checksums can be calculated from it.
#Be sure to reset the buffer as needed.
class BufferedStream:
def __init__(self, stream):
self.stream = stream
self.buffer = cStringIO.StringIO()
def read(self, count):
s = self.stream.read(count)
self.buffer.write(s)
return s
def seek(self, offset, whence=0):
self.stream.seek(offset,whence)
def tell(self):
return self.stream.tell()
def close(self):
self.stream.close()
def reset_buffer(self):
self.buffer.close()
self.buffer = cStringIO.StringIO()
def getvalue(self):
return self.buffer.getvalue()
class FlacPCMReader(audiotools.PCMReader):
#flac_file should be a file-like stream of FLAC data
def __init__(self, flac_file):
self.flacreader = FlacReader(flac_file)
self.sample_rate = self.flacreader.streaminfo.samplerate
self.channels = self.flacreader.streaminfo.channels + 1
self.bits_per_sample = self.flacreader.streaminfo.bits_per_sample + 1
self.process = None
self.buffer = []
#this won't return even close to the expected number of bytes
#(though that won't really break anything)
def read(self, bytes):
return self.flacreader.read_frame()
def close(self):
self.flacreader.close()