From: yomguy Date: Mon, 17 Sep 2012 15:52:43 +0000 (+0200) Subject: begin regression to get back nframes as "totalframes" X-Git-Tag: 0.4.0-a^2~11 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=bad75c416d5bb034ac38bf68cb119e9ca050631a;p=timeside.git begin regression to get back nframes as "totalframes" --- diff --git a/timeside/api.py b/timeside/api.py index 509d6be..f3fb08f 100644 --- a/timeside/api.py +++ b/timeside/api.py @@ -26,20 +26,20 @@ class IProcessor(Interface): @staticmethod def id(): - """Short alphanumeric, lower-case string which uniquely identify this - processor, suitable for use as an HTTP/GET argument value, in filenames, + """Short alphanumeric, lower-case string which uniquely identify this + processor, suitable for use as an HTTP/GET argument value, in filenames, etc...""" # implementation: only letters and digits are allowed. An exception will # be raised by MetaProcessor if the id is malformed or not unique amongst # registered processors. - def setup(self, channels=None, samplerate=None, blocksize=None): + def setup(self, channels=None, samplerate=None, blocksize=None, totalframes=None): """Allocate internal resources and reset state, so that this processor is - ready for a new run. - - The channels, samplerate and/or blocksize arguments may be required by - processors which accept input. An error will occur if any of + ready for a new run. + + The channels, samplerate and/or blocksize and/or totalframes 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. """ @@ -54,19 +54,23 @@ class IProcessor(Interface): the samplerate passed to setup()""" def blocksize(): + """The total number of frames that this processor can output for each step + in the pipeline, or None if the duration is unknown.""" + + def totalframes(): """The total number of frames that this processor can output, or None if the duration is unknown.""" def process(self, frames=None, eod=False): """Process input frames and return a (output_frames, eod) tuple. - Both input and output frames are 2D numpy arrays, where columns are - channels, and containing an undetermined number of frames. eod=True + Both input and output frames are 2D numpy arrays, where columns are + channels, and containing an undetermined number of frames. eod=True means that the end-of-data has been reached. - + Output-only processors (such as decoders) will raise an exception if the frames argument is not None. All processors (even encoders) return data, even if that means returning the input unchanged. - + Warning: it is required to call setup() before this method.""" def release(self): @@ -80,15 +84,15 @@ class IEncoder(IProcessor): format.""" def __init__(self, output): - """Create a new encoder. output can either be a filename or a python callback + """Create a new encoder. output can either be a filename or a python callback function/method for streaming mode. The streaming callback prototype is: callback(data, eod) Where data is a block of binary data of an undetermined size, and eod True when end-of-data is reached.""" - # implementation: the constructor must always accept the output argument. It may - # accept extra arguments such as bitrate, depth, etc.., but these must be optionnal + # implementation: the constructor must always accept the output argument. It may + # accept extra arguments such as bitrate, depth, etc.., but these must be optionnal @staticmethod def format(): @@ -113,12 +117,12 @@ class IEncoder(IProcessor): def set_metadata(self, metadata): """Set the metadata to be embedded in the encoded output. - - In non-streaming mode, this method updates the metadata directly into the - output file, without re-encoding the audio data, provided this file already + + In non-streaming mode, this method updates the metadata directly into the + output file, without re-encoding the audio data, provided this file already exists. - - It isn't required to call this method, but if called, it must be before + + It isn't required to call this method, but if called, it must be before process().""" class IDecoder(IProcessor): @@ -128,11 +132,11 @@ class IDecoder(IProcessor): def __init__(self, filename): """Create a new decoder for filename.""" - # implementation: additional optionnal arguments are allowed + # implementation: additional optionnal arguments are allowed def format(): """Return a user-friendly file format string""" - + def encoding(): """Return a user-friendly encoding string""" @@ -153,7 +157,7 @@ class IGrapher(IProcessor): """Create a new grapher. width and height are generally in pixels but could be something else for eg. svg rendering, etc.. """ - # implementation: additional optionnal arguments are allowed + # implementation: additional optionnal arguments are allowed @staticmethod def name(): @@ -179,7 +183,7 @@ class IAnalyzer(IProcessor): def __init__(self): """Create a new analyzer.""" - # implementation: additional optionnal arguments are allowed + # implementation: additional optionnal arguments are allowed @staticmethod def name(): @@ -198,7 +202,7 @@ class IValueAnalyzer(IAnalyzer): repeatedly calling process()""" def __str__(self): - """Return a human readable string containing both result and unit + """Return a human readable string containing both result and unit ('5.30dB', '4.2s', etc...)""" class IEffect(IProcessor): @@ -206,7 +210,7 @@ class IEffect(IProcessor): def __init__(self): """Create a new effect.""" - # implementation: additional optionnal arguments are allowed + # implementation: additional optionnal arguments are allowed @staticmethod def name(): diff --git a/timeside/core.py b/timeside/core.py index a617164..f87dc34 100644 --- a/timeside/core.py +++ b/timeside/core.py @@ -58,10 +58,11 @@ class Processor(Component): implements(IProcessor) @interfacedoc - def setup(self, channels=None, samplerate=None, blocksize=None): - self.input_channels = channels - self.input_samplerate = samplerate - self.input_blocksize = blocksize + def setup(self, channels=None, samplerate=None, blocksize=None, totalframes=None): + self.input_channels = channels + self.input_samplerate = samplerate + self.input_blocksize = blocksize + self.input_totalframes = totalframes # default channels(), samplerate() and blocksize() implementations returns # the input characteristics, but processors may change this behaviour by @@ -78,6 +79,10 @@ class Processor(Component): def blocksize(self): return self.input_blocksize + @interfacedoc + def totalframes(self): + return self.input_totalframes + @interfacedoc def process(self, frames, eod): return frames, eod @@ -119,6 +124,18 @@ class FixedSizeInputAdapter(object): return blocksize + def totalframes(self, input_totalframes): + """Return the total number of frames that this adapter will output according to the + input_blocksize argument""" + + totalframes = input_totalframes + if self.pad: + mod = input_totalframes % self.buffer_size + if mod: + totalframes += self.buffer_size - mod + + return totalframes + def process(self, frames, eod): """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. @@ -206,7 +223,7 @@ class ProcessPipe(object): source.setup() last = source for item in items: - item.setup(last.channels(), last.samplerate(), last.blocksize()) + item.setup(last.channels(), last.samplerate(), last.blocksize(), last.totalframes()) last = item # now stream audio data along the pipe diff --git a/timeside/decoder/core.py b/timeside/decoder/core.py index 75f584f..c8f2604 100644 --- a/timeside/decoder/core.py +++ b/timeside/decoder/core.py @@ -67,9 +67,10 @@ class FileDecoder(Processor): def setup(self, channels = None, samplerate = None, blocksize = None): # the output data format we want - if blocksize: self.output_blocksize = blocksize - if samplerate: self.output_samplerate = int(samplerate) - if channels: self.output_channels = int(channels) + if blocksize: self.output_blocksize = blocksize + if samplerate: self.output_samplerate = int(samplerate) + if channels: self.output_channels = int(channels) + uri = self.uri self.pipe = ''' uridecodebin name=uridecodebin uri=%(uri)s @@ -180,6 +181,7 @@ class FileDecoder(Processor): self.input_width = caps[0]["width"] else: self.input_width = caps[0]["depth"] + self.output_totalframes = self.totalframes() def _on_message_cb(self, bus, message): t = message.type @@ -232,6 +234,7 @@ class FileDecoder(Processor): def blocksize(self): return self.output_blocksize + @interfacedoc def totalframes(self): if self.input_samplerate == self.output_samplerate: return self.input_totalframes diff --git a/timeside/grapher/core.py b/timeside/grapher/core.py index 5fbca93..8082f72 100644 --- a/timeside/grapher/core.py +++ b/timeside/grapher/core.py @@ -135,10 +135,10 @@ class WaveformImage(object): Adds pixels iteratively thanks to the adapter providing fixed size frame buffers. Peaks are colored relative to the spectral centroids of each frame packet. """ - def __init__(self, image_width, image_height, nframes, samplerate, fft_size, bg_color, color_scheme): + def __init__(self, image_width, image_height, totalframes, samplerate, fft_size, bg_color, color_scheme): self.image_width = image_width self.image_height = image_height - self.nframes = nframes + self.nframes = totalframes self.samplerate = samplerate self.fft_size = fft_size self.bg_color = bg_color @@ -154,7 +154,7 @@ class WaveformImage(object): self.samples_per_pixel = self.nframes / float(self.image_width) self.buffer_size = int(round(self.samples_per_pixel, 0)) self.pixels_adapter = FixedSizeInputAdapter(self.buffer_size, 1, pad=False) - self.pixels_adapter_nframes = self.pixels_adapter.nframes(self.nframes) + self.pixels_adapter_nframes = self.pixels_adapter.totalframes(self.nframes) self.lower = 800 self.higher = 12000 diff --git a/timeside/grapher/waveform.py b/timeside/grapher/waveform.py index 7249f46..ad8db86 100644 --- a/timeside/grapher/waveform.py +++ b/timeside/grapher/waveform.py @@ -53,21 +53,23 @@ class Waveform(Processor): self.color_scheme = scheme @interfacedoc - def setup(self, channels=None, samplerate=None, nframes=None): - super(Waveform, self).setup(channels, samplerate, nframes) - self.graph = WaveformImage(self.width, self.height, self.nframes(), self.samplerate(), self.FFT_SIZE, - bg_color=self.bg_color, color_scheme=self.color_scheme) + def setup(self, channels=None, samplerate=None, blocksize=None, totalframes=None): + super(Waveform, self).setup(channels, samplerate, blocksize, totalframes) + self.graph = WaveformImage(self.width, self.height, self.totalframes(), + self.samplerate(), self.FFT_SIZE, + bg_color=self.bg_color, + color_scheme=self.color_scheme) @interfacedoc def process(self, frames, eod=False): self.graph.process(frames, eod) return frames, eod - + @interfacedoc def render(self, output=None): if output: self.graph.save(output) return self.graph.image - + def watermark(self, text, font=None, color=(255, 255, 255), opacity=.6, margin=(5,5)): self.graph.watermark(text, color=color, opacity=opacity, margin=margin)