]> git.parisson.com Git - timeside.git/commitdiff
begin regression to get back nframes as "totalframes"
authoryomguy <yomguy@parisson.com>
Mon, 17 Sep 2012 15:52:43 +0000 (17:52 +0200)
committeryomguy <yomguy@parisson.com>
Mon, 17 Sep 2012 15:52:43 +0000 (17:52 +0200)
timeside/api.py
timeside/core.py
timeside/decoder/core.py
timeside/grapher/core.py
timeside/grapher/waveform.py

index 509d6bea435c6a33f52f9ee4dcf51631482d2eea..f3fb08f7d6c6077d319afd7a3d32df34a3c77e50 100644 (file)
@@ -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():
index a617164da413be1dbee2af6c0b397ac695fe18d7..f87dc34153a8dfa56b3d2e2ade2f5bd8fe74073f 100644 (file)
@@ -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
index 75f584fd4c6866a94e99b16ac4aa8c7dbcf41429..c8f2604229cd1ce4b802131e01ccca714816adc0 100644 (file)
@@ -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
index 5fbca9351946ee9445885ceee8b7a2b542f527b2..8082f725adc0945454fca2b0fc533538a1b392f4 100644 (file)
@@ -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
index 7249f46f6165f40e0bcbaf4a5bda8d956ebd02f4..ad8db862fb7a1efa6db46fc9b7d189743a24b072 100644 (file)
@@ -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)