From: Paul Brossier Date: Sun, 2 Sep 2012 23:13:53 +0000 (-0600) Subject: timeside/api.py: rename nframes to blocksize, use totalframes for file duration X-Git-Tag: 0.3.4-noleekmp3~5 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=1f7e6f8f588ab5f60671f6a0986a7d40a761bbd2;p=timeside.git timeside/api.py: rename nframes to blocksize, use totalframes for file duration --- diff --git a/tests/testdecoding.py b/tests/testdecoding.py index 3589f41..0205197 100644 --- a/tests/testdecoding.py +++ b/tests/testdecoding.py @@ -7,7 +7,7 @@ class TestDecoding(TestCase): "Test the low level streaming features" def setUp(self): - self.samplerate, self.channels, self.nframes = None, None, None + self.samplerate, self.channels, self.blocksize = None, None, None def testWav(self): "Test wav decoding" @@ -28,7 +28,7 @@ class TestDecoding(TestCase): def tearDown(self): decoder = FileDecoder(self.source) - decoder.setup(samplerate = self.samplerate, channels = self.channels, nframes = self.nframes) + decoder.setup(samplerate = self.samplerate, channels = self.channels, blocksize = self.blocksize) totalframes = 0. @@ -36,16 +36,16 @@ class TestDecoding(TestCase): frames, eod = decoder.process() totalframes += frames.shape[0] if eod or decoder.eod: break - self.assertEquals(frames.shape[0], decoder.output_nframes ) + self.assertEquals(frames.shape[0], decoder.output_blocksize ) self.assertEquals(frames.shape[1], decoder.channels() ) ratio = decoder.output_samplerate / float(decoder.input_samplerate) if 0: - print "input / output_samplerate:", decoder.input_samplerate, '/', decoder.output_samplerate, - print "ratio:", ratio - print "input / output_channels:", decoder.input_channels, decoder.output_channels - print "input_duration:", decoder.input_duration - print "input_total_frames:", decoder.input_total_frames + print "input / output_samplerate:", decoder.input_samplerate, '/', decoder.output_samplerate, + print "ratio:", ratio + print "input / output_channels:", decoder.input_channels, decoder.output_channels + print "input_duration:", decoder.input_duration + print "input_totalframes:", decoder.input_totalframes # FIXME compute actual number of frames from file if ratio == 1: @@ -59,37 +59,37 @@ class TestDecoding(TestCase): class TestDecodingStereo(TestDecoding): def setUp(self): - self.samplerate, self.channels, self.nframes = None, 2, None + self.samplerate, self.channels, self.blocksize = None, 2, None class TestDecodingMonoUpsampling(TestDecoding): def setUp(self): - self.samplerate, self.channels, self.nframes = 48000, None, None + self.samplerate, self.channels, self.blocksize = 48000, None, None class TestDecodingMonoDownsampling(TestDecoding): def setUp(self): - self.samplerate, self.channels, self.nframes = 16000, None, None + self.samplerate, self.channels, self.blocksize = 16000, None, None class TestDecodingStereoDownsampling(TestDecoding): def setUp(self): - self.samplerate, self.channels, self.nframes = 32000, 2, None + self.samplerate, self.channels, self.blocksize = 32000, 2, None class TestDecodingStereoUpsampling(TestDecoding): def setUp(self): - self.samplerate, self.channels, self.nframes = 96000, 2, None + self.samplerate, self.channels, self.blocksize = 96000, 2, None -class TestDecodingShortframes(TestDecoding): +class TestDecodingShortBlock(TestDecoding): def setUp(self): - self.samplerate, self.channels, self.nframes = None, None, 256 + self.samplerate, self.channels, self.blocksize = None, None, 256 -class TestDecodingLongframes(TestDecoding): +class TestDecodingLongBlock(TestDecoding): def setUp(self): - self.samplerate, self.channels, self.nframes = None, None, 1024*8*2 + self.samplerate, self.channels, self.blocksize = None, None, 1024*8*2 if __name__ == '__main__': unittest.main(testRunner=TestRunner()) diff --git a/timeside/api.py b/timeside/api.py index edd68b5..509d6be 100644 --- a/timeside/api.py +++ b/timeside/api.py @@ -34,11 +34,11 @@ class IProcessor(Interface): # be raised by MetaProcessor if the id is malformed or not unique amongst # registered processors. - def setup(self, channels=None, samplerate=None, nframes=None): + def setup(self, channels=None, samplerate=None, blocksize=None): """Allocate internal resources and reset state, so that this processor is ready for a new run. - The channels, samplerate and/or nframes arguments may be required by + The channels, samplerate and/or blocksize arguments may be required by processors which accept input. An error will occur if any of these arguments is passed to an output-only processor such as a decoder. """ @@ -53,7 +53,7 @@ class IProcessor(Interface): """Samplerate of the data returned by process(). May be different from the samplerate passed to setup()""" - def nframes(): + def blocksize(): """The total number of frames that this processor can output, or None if the duration is unknown.""" @@ -147,7 +147,7 @@ class IGrapher(IProcessor): """Media item visualizer driver interface""" # implementation: graphers which need to know the total number of frames - # should raise an exception in setup() if the nframes argument is None + # should raise an exception in setup() if the totalframes argument is None def __init__(self, width, height): """Create a new grapher. width and height are generally diff --git a/timeside/core.py b/timeside/core.py index f5b3c31..a617164 100644 --- a/timeside/core.py +++ b/timeside/core.py @@ -23,7 +23,7 @@ from timeside.exceptions import Error, ApiError import re import numpy -__all__ = ['Processor', 'MetaProcessor', 'implements', 'abstract', +__all__ = ['Processor', 'MetaProcessor', 'implements', 'abstract', 'interfacedoc', 'processors', 'get_processor', 'ProcessPipe', 'FixedSizeInputAdapter'] @@ -58,13 +58,13 @@ class Processor(Component): implements(IProcessor) @interfacedoc - def setup(self, channels=None, samplerate=None, nframes=None): + def setup(self, channels=None, samplerate=None, blocksize=None): self.input_channels = channels self.input_samplerate = samplerate - self.input_nframes = nframes + self.input_blocksize = blocksize - # default channels(), samplerate() and nframes() implementations returns - # the input characteristics, but processors may change this behaviour by + # default channels(), samplerate() and blocksize() implementations returns + # the input characteristics, but processors may change this behaviour by # overloading those methods @interfacedoc def channels(self): @@ -75,8 +75,8 @@ class Processor(Component): return self.input_samplerate @interfacedoc - def nframes(self): - return self.input_nframes + def blocksize(self): + return self.input_blocksize @interfacedoc def process(self, frames, eod): @@ -98,7 +98,7 @@ class FixedSizeInputAdapter(object): def __init__(self, buffer_size, channels, pad=False): """Construct a new adapter: buffer_size is the desired buffer size in frames, - channels the number of channels, and pad indicates whether the last block should + channels the number of channels, and pad indicates whether the last block should be padded with zeros.""" self.buffer = numpy.empty((buffer_size, channels)) @@ -106,21 +106,21 @@ class FixedSizeInputAdapter(object): self.len = 0 self.pad = pad - def nframes(self, input_nframes): + def blocksize(self, input_blocksize): """Return the total number of frames that this adapter will output according to the - input_nframes argument""" + input_blocksize argument""" - nframes = input_nframes + blocksize = input_blocksize if self.pad: - mod = input_nframes % self.buffer_size + mod = input_blocksize % self.buffer_size if mod: - nframes += self.buffer_size - mod + blocksize += self.buffer_size - mod - return nframes + return blocksize def process(self, frames, eod): - """Returns an iterator over tuples of the form (buffer, eod) where buffer is a + """Returns an iterator over tuples of the form (buffer, eod) where buffer is a fixed-sized block of data, and eod indicates whether this is the last block. In case padding is deactivated the last block may be smaller than the buffer size. """ @@ -155,17 +155,16 @@ class FixedSizeInputAdapter(object): yield block, True self.len = 0 - + def processors(interface=IProcessor, recurse=True): """Returns the processors implementing a given interface and, if recurse, any of the descendants of this interface.""" return implementations(interface, recurse) - def get_processor(processor_id): """Return a processor by its id""" if not _processors.has_key(processor_id): - raise Error("No processor registered with id: '%s'" + raise Error("No processor registered with id: '%s'" % processor_id) return _processors[processor_id] @@ -194,7 +193,7 @@ class ProcessPipe(object): for item in other: self |= item - return self + return self def run(self): """Setup/reset all processors in cascade and stream audio data along @@ -207,7 +206,7 @@ class ProcessPipe(object): source.setup() last = source for item in items: - item.setup(last.channels(), last.samplerate(), last.nframes()) + item.setup(last.channels(), last.samplerate(), last.blocksize()) last = item # now stream audio data along the pipe @@ -217,5 +216,4 @@ class ProcessPipe(object): for item in items: frames, eod = item.process(frames, eod) - return self - + return self diff --git a/timeside/decoder/core.py b/timeside/decoder/core.py index 5fee050..75f584f 100644 --- a/timeside/decoder/core.py +++ b/timeside/decoder/core.py @@ -38,7 +38,7 @@ class FileDecoder(Processor): implements(IDecoder) mimetype = '' - output_nframes = 8*1024 + output_blocksize = 8*1024 output_samplerate = 44100 output_channels = 1 @@ -65,9 +65,9 @@ class FileDecoder(Processor): else: self.uri = uri - def setup(self, channels = None, samplerate = None, nframes = None): + def setup(self, channels = None, samplerate = None, blocksize = None): # the output data format we want - if nframes: self.output_nframes = nframes + if blocksize: self.output_blocksize = blocksize if samplerate: self.output_samplerate = int(samplerate) if channels: self.output_channels = int(channels) uri = self.uri @@ -175,7 +175,7 @@ class FileDecoder(Processor): self.input_samplerate = caps[0]["rate"] self.input_channels = caps[0]["channels"] self.input_duration = length / 1.e9 - self.input_total_frames = int(self.input_duration * self.input_samplerate) + self.input_totalframes = int(self.input_duration * self.input_samplerate) if "x-raw-float" in caps.to_string(): self.input_width = caps[0]["width"] else: @@ -206,9 +206,9 @@ class FileDecoder(Processor): self.last_buffer = new_array else: self.last_buffer = concatenate((self.last_buffer, new_array), axis=0) - while self.last_buffer.shape[0] >= self.output_nframes: - new_block = self.last_buffer[:self.output_nframes] - self.last_buffer = self.last_buffer[self.output_nframes:] + while self.last_buffer.shape[0] >= self.output_blocksize: + new_block = self.last_buffer[:self.output_blocksize] + self.last_buffer = self.last_buffer[self.output_blocksize:] #print 'queueing', new_block.shape, 'remaining', self.last_buffer.shape self.queue.put( [new_block, False ] ) @@ -229,8 +229,15 @@ class FileDecoder(Processor): return self.output_samplerate @interfacedoc - def nframes(self): - return self.input_total_frames + def blocksize(self): + return self.output_blocksize + + def totalframes(self): + if self.input_samplerate == self.output_samplerate: + return self.input_totalframes + else: + ratio = self.input_totalframes / self.output_samplerate + return self.input_totalframes * ratio @interfacedoc def release(self): @@ -242,7 +249,6 @@ class FileDecoder(Processor): ## IDecoder methods - @interfacedoc @interfacedoc def format(self): # TODO check @@ -266,4 +272,8 @@ class FileDecoder(Processor): return self.tags def duration(self): - return self.duration + if self.input_samplerate == self.output_samplerate: + return self.input_duration + else: + ratio = self.input_totalframes / self.output_samplerate + return self.input_duration * ratio