-==============
-README (draft)
-==============
+======
+README
+======
-# Dependencies
-# ============
+TimeSide is a set of client and server side components for audio-enabling web sites and applications. It includes a powerful DHTML-based interactive player, with support for time-marking. The server side components provide generic APIs for easy transcoding, metadata embedding, sound visualization and audio analysis.
-python (>= 2.3.5-7), python-xml, python-mutagen, python-imaging (>= 1.1.6),
-python-numpy, python-scipy, python-scikits-audiolab (>= 0.10),
-python-setuptools (>= 0.6b3), python-support (>= 0.3), python-ctypes (>= 1.0.1),
-libsndfile1 (>= 1.0.17), sox (>= 14.2), vorbis-tools, flac, libgd2-xpm,
-lame (>= 3.98.2)
+Platforms
+=========
+TimeSide is intended to work on all Unix / Linux platforms.
+MacOS X and Windows versions will soon be explorated.
+
+Dependencies
+============
+
+python (>= 2.4), python-xml, python-mutagen, python-imaging (>= 1.1.6),
+python-numpy, python-setuptools (>= 0.6b3), libsndfile1 (>= 1.0.17),
+python-gst0.10, gstreamer0.10-plugins-base
+
+License
+=======
+
+TimeSide 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.
+
+TimeSide 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.
+
+
+Contact and Informations
+========================
+
+See http://code.google.com/p/timeside/
from timeside.analyzer.core import *
from timeside.analyzer.duration import *
from timeside.analyzer.max_level import *
-from timeside.analyzer.mean_level import *
-from timeside.analyzer.dc import *
+#from timeside.analyzer.mean_level import *
+#from timeside.analyzer.dc import *
# Authors:
# Guillaume Pellerin <yomguy at parisson.com>
-from timeside.core import *
-from timeside.grapher.core import *
-import numpy
+pass
+
# Author: Guillaume Pellerin <yomguy@parisson.com>
+from timeside.core import Processor, implements, interfacedoc, FixedSizeInputAdapter
from timeside.analyzer.core import *
from timeside.api import IValueAnalyzer
-import datetime
class Duration(Processor):
+ """A rather useless duration analyzer. Its only purpose is to test the
+ nframes characteristic."""
implements(IValueAnalyzer)
@interfacedoc
- def setup(self, channels=None, samplerate=None, nframes=None):
+ def setup(self, channels, samplerate, nframes):
+ if not nframes:
+ raise Exception("nframes argument required")
super(Duration, self).setup(channels, samplerate, nframes)
- self.value = 0
@staticmethod
@interfacedoc
@staticmethod
@interfacedoc
def unit():
- return "h:m:s"
-
- def __str__(self):
- return "%s %s" % (str(self.value), unit())
-
- def process(self, frames, eod=False):
- return frames, eod
+ return "seconds"
def result(self):
- return datetime.timedelta(0,numpy.round(self.nframes / float(self.samplerate), 0))
-
+ return self.input_nframes / float(self.input_samplerate)
+
\ No newline at end of file
# Author: Guillaume Pellerin <yomguy@parisson.com>
+from timeside.core import Processor, implements, interfacedoc, FixedSizeInputAdapter
from timeside.analyzer.core import *
from timeside.api import IValueAnalyzer
-import numpy
class MaxLevel(Processor):
@interfacedoc
def setup(self, channels=None, samplerate=None, nframes=None):
super(MaxLevel, self).setup(channels, samplerate, nframes)
- self.value = -140
+ self.max_value = 0
@staticmethod
@interfacedoc
@staticmethod
@interfacedoc
def unit():
- return "dB"
-
- def __str__(self):
- return "%s %s" % (str(self.value), unit())
+ # power? amplitude?
+ return ""
def process(self, frames, eod=False):
- max = numpy.round(20*numpy.log10(frames.max()), 2)
- if max > self.value:
- self.value = max
+ max = frames.max()
+ if max > self.max_value:
+ self.max_value = max
return frames, eod
def result(self):
- return self.value
+ return self.max_value
+
from timeside.core import *
import subprocess
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Parisson
+# Copyright (c) 2007 Olivier Guilyardi <olivier@samalyse.com>
+# Copyright (c) 2007-2009 Guillaume Pellerin <pellerin@parisson.com>
+#
+# This file is part of TimeSide.
+
+# TimeSide 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.
+
+# TimeSide 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 TimeSide. If not, see <http://www.gnu.org/licenses/>.
+
+# Author: Paul Brossier <piem@piem.org>
+
+from timeside.core import Processor, implements, interfacedoc
+from timeside.api import IDecoder
+from numpy import array, frombuffer, getbuffer, float32
+
+import pygst
+pygst.require('0.10')
+import gst
+import gobject
+gobject.threads_init ()
+
+
+class FileDecoder(Processor):
+ """ gstreamer-based decoder """
+ implements(IDecoder)
+
+ # duration ms, before discovery process times out
+ MAX_DISCOVERY_TIME = 3000
+
+ audioformat = None
+ audiochannels = None
+ audiorate = None
+ audionframes = None
+ mimetype = ''
+
+ # IProcessor methods
+
+ @staticmethod
+ @interfacedoc
+ def id():
+ return "gstreamerdec"
+
+ def setup(self, channels = None, samplerate = None, nframes = None):
+ # the output data format we want
+ caps = "audio/x-raw-float, width=32"
+ pipeline = gst.parse_launch('''uridecodebin uri=%s
+ ! audioconvert
+ ! %s
+ ! appsink name=sink sync=False ''' % (self.uri, caps))
+ # store a pointer to appsink in our decoder object
+ self.sink = pipeline.get_by_name('sink')
+ # adjust length of emitted buffers
+ # self.sink.set_property('blocksize', 0x10000)
+ # start pipeline
+ pipeline.set_state(gst.STATE_PLAYING)
+
+ @interfacedoc
+ def channels(self):
+ return self.audiochannels
+
+ @interfacedoc
+ def samplerate(self):
+ return self.audiorate
+
+ @interfacedoc
+ def nframes(self):
+ return self.audionframes
+
+ @interfacedoc
+ def process(self, frames = None, eod = False):
+ try:
+ buf = self.sink.emit('pull-buffer')
+ except SystemError, e:
+ # should never happen
+ print 'SystemError', e
+ return array([0.]), True
+ if buf == None:
+ return array([0.]), True
+ return self.gst_buffer_to_numpy_array(buf), False
+
+ @interfacedoc
+ def release(self):
+ # nothing to do for now
+ pass
+
+ ## IDecoder methods
+
+ @interfacedoc
+ def __init__(self, uri):
+
+ # is this a file?
+ import os.path
+ if os.path.exists(uri):
+ # get the absolute path
+ uri = os.path.abspath(uri)
+ # first run the file/uri through the discover pipeline
+ self.discover(uri)
+ # and make a uri of it
+ from urllib import quote
+ self.uri = 'file://'+quote(uri)
+
+ @interfacedoc
+ def format(self):
+ # TODO check
+ return self.mimetype
+
+ @interfacedoc
+ def encoding(self):
+ # TODO check
+ return self.mimetype.split('/')[-1]
+
+ @interfacedoc
+ def resolution(self):
+ # TODO check: width or depth?
+ return self.audiowidth
+
+ @interfacedoc
+ def metadata(self):
+ # TODO check
+ return self.tags
+
+ ## gst.extend discoverer
+
+ def discover(self, path):
+ """ gstreamer based helper function to get file attributes """
+ from gst.extend import discoverer
+ d = discoverer.Discoverer(path, timeout = self.MAX_DISCOVERY_TIME)
+ d.connect('discovered', self.discovered)
+ self.mainloop = gobject.MainLoop()
+ d.discover()
+ self.mainloop.run()
+
+ def discovered(self, d, is_media):
+ """ gstreamer based helper executed upon discover() completion """
+ if is_media and d.is_audio:
+ # copy the discoverer attributes to self
+ self.audiorate = d.audiorate
+ self.mimetype= d.mimetype
+ self.audiochannels = d.audiochannels
+ self.audiowidth = d.audiowidth
+ # conversion from time in nanoseconds to frames
+ from math import ceil
+ duration = d.audiorate * d.audiolength * 1.e-9
+ self.audionframes = int (ceil ( duration ) )
+ self.tags = d.tags
+ elif not d.is_audio:
+ print "error, no audio found!"
+ else:
+ print "fail", path
+ self.mainloop.quit()
+
+ def gst_buffer_to_numpy_array(self, buf):
+ """ gstreamer buffer to numpy array conversion """
+ chan = self.audiochannels
+ samples = frombuffer(buf.data, dtype=float32)
+ samples.resize([len(samples)/chan, chan])
+ return samples
+
+
class SubProcessPipe:
def __init__(self, command, stdin=None):
self.output = self.proc.stdout
-class DecoderCore(Processor):
+class DecoderSubProcessCore(Processor):
"""Defines the main parts of the decoding tools :
paths, metadata parsing, data streaming thru system command"""
# -*- coding: utf-8 -*-
from timeside.encoder.core import *
-from timeside.encoder.ogg import *
+#from timeside.encoder.ogg import *
from timeside.encoder.wav import *
-from timeside.encoder.mp3 import *
-from timeside.encoder.flac import *
+#from timeside.encoder.mp3 import *
+#from timeside.encoder.flac import *
# Author: Guillaume Pellerin <yomguy@parisson.com>
from timeside.core import *
-
import subprocess
class SubProcessPipe:
self.input = self.proc.stdin
self.output = self.proc.stdout
-class EncoderCore(Processor):
+class EncoderSubProcessCore(Processor):
"""Defines the main parts of the encoding tools :
paths, metadata parsing, data streaming thru system command"""
# Author: Guillaume Pellerin <yomguy@parisson.com>
-import os
-import string
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Parisson
+# Copyright (c) 2007 Olivier Guilyardi <olivier@samalyse.com>
+# Copyright (c) 2007-2009 Guillaume Pellerin <pellerin@parisson.com>
+#
+# This file is part of TimeSide.
-from timeside.encoder.core import *
+# TimeSide 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.
+
+# TimeSide 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 TimeSide. If not, see <http://www.gnu.org/licenses/>.
+
+# Author: Paul Brossier <piem@piem.org>
+
+from timeside.core import Processor, implements, interfacedoc
from timeside.api import IEncoder
+from numpy import array, frombuffer, getbuffer, float32
+
+import pygst
+pygst.require('0.10')
+import gst
+import gobject
+gobject.threads_init ()
-class WavEncoder(EncoderCore):
- """Defines methods to encode to WAV"""
+class WavEncoder(Processor):
+ """ gstreamer-based encoder """
implements(IEncoder)
- def __init__(self):
- pass
+ def __init__(self, output):
+ self.file = None
+ if isinstance(output, basestring):
+ self.filename = output
+ else:
+ raise Exception("Streaming not supported")
+
+ @interfacedoc
+ def setup(self, channels=None, samplerate=None, nframes=None):
+ super(WavEncoder, self).setup(channels, samplerate, nframes)
+ # TODO open file for writing
+ # the output data format we want
+ pipeline = gst.parse_launch(''' appsrc name=src
+ ! audioconvert
+ ! wavenc
+ ! filesink location=%s ''' % self.filename)
+ # store a pointer to appsink in our encoder object
+ self.src = pipeline.get_by_name('src')
+ srccaps = gst.Caps("""audio/x-raw-float,
+ endianness=(int)1234,
+ channels=(int)%s,
+ width=(int)32,
+ rate=(int)%d""" % (int(channels), int(samplerate)))
+ self.src.set_property("caps", srccaps)
+
+ # start pipeline
+ pipeline.set_state(gst.STATE_PLAYING)
+ self.pipeline = pipeline
@staticmethod
+ @interfacedoc
def id():
- return "wavenc"
-
- def format(self):
- return 'WAV'
-
- def file_extension(self):
- return 'wav'
-
- def mime_type(self):
- return 'audio/x-wav'
-
- def description(self):
- return """
- WAV (or WAVE), short for Waveform audio format, also known as Audio for
- Windows, is a Microsoft and IBM audio file format standard for storing
- an audio bitstream on PCs. It is an application of the RIFF bitstream
- format method for storing data in “chunks”, and thus is also close to
- the 8SVX and the AIFF format used on Amiga and Macintosh computers,
- respectively. It is the main format used on Windows systems for raw and
- typically uncompressed audio. The usual bitstream encoding is the Pulse
- Code Modulation (PCM) format.
- """
-
- def get_file_info(self):
- try:
- file1, file2 = os.popen4('wavinfo "'+self.dest+'"')
- info = []
- for line in file2.readlines():
- info.append(clean_word(line[:-1]))
- self.info = info
- return self.info
- except:
- raise IOError('EncoderError: wavinfo id not installed or file does not exist.')
-
- def process(self, source, metadata, options=None):
- self.metadata = metadata
- self.options = options
- command = 'sox -t wav - -s -q -b 16 -r 44100 -t wav -c2 -'
-
- stream = self.core_process(command, source)
- for __chunk in stream:
- yield __chunk
+ return "gstreamerenc"
+
+ @staticmethod
+ @interfacedoc
+ def description():
+ return "Gstreamer based encoder"
+
+ @staticmethod
+ @interfacedoc
+ def file_extension():
+ return "wav"
+
+ @staticmethod
+ @interfacedoc
+ def mime_type():
+ return "audio/x-wav"
+
+ @interfacedoc
+ def set_metadata(self, metadata):
+ #TODO
+ pass
+
+ @interfacedoc
+ def process(self, frames, eod=False):
+ buf = self.numpy_array_to_gst_buffer(frames)
+ self.src.emit('push-buffer', buf)
+ if eod: self.src.emit('end-of-stream')
+ return frames, eod
+
+ def numpy_array_to_gst_buffer(self, frames):
+ """ gstreamer buffer to numpy array conversion """
+ buf = gst.Buffer(getbuffer(frames))
+ return buf
# -*- coding: utf-8 -*-
from timeside.grapher.core import *
-from timeside.grapher.waveform_audiolab import *
-from timeside.grapher.spectrogram_audiolab import *
+from timeside.grapher.waveform import *
+from timeside.grapher.spectrogram import *
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2007-2010 Guillaume Pellerin <yomguy@parisson.com>
+# Copyright (c) 2010 Olivier Guilyardi <olivier@samalyse.com>
+
+# This file is part of TimeSide.
+
+# TimeSide 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.
+
+# TimeSide 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 TimeSide. If not, see <http://www.gnu.org/licenses/>.
+
+
+from timeside.core import Processor, implements, interfacedoc, FixedSizeInputAdapter
+from timeside.api import IGrapher
+from timeside.grapher import *
+
+
+class Spectrogram(Processor):
+ implements(IGrapher)
+
+ FFT_SIZE = 0x400
+
+ @interfacedoc
+ def __init__(self, width=None, height=None, output=None, bg_color=None, color_scheme=None):
+ if width:
+ self.width = width
+ else:
+ self.width = 1500
+ if height:
+ self.height = height
+ else:
+ self.height = 200
+ self.bg_color = bg_color
+ self.color_scheme = color_scheme
+ self.filename = output
+ self.graph = None
+
+ @staticmethod
+ @interfacedoc
+ def id():
+ return "spectrogram"
+
+ @staticmethod
+ @interfacedoc
+ def name():
+ return "Spectrogram"
+
+ @interfacedoc
+ def set_colors(self, background, scheme):
+ self.bg_color = background
+ self.color_scheme = scheme
+
+ @interfacedoc
+ def setup(self, channels=None, samplerate=None, nframes=None):
+ super(Spectrogram, self).setup(channels, samplerate, nframes)
+ if self.graph:
+ self.graph = None
+ self.graph = SpectrogramImage(self.width, self.height, self.nframes(), self.samplerate(), self.FFT_SIZE,
+ bg_color=self.bg_color, color_scheme=self.color_scheme, filename=self.filename)
+
+ @interfacedoc
+ def process(self, frames, eod=False):
+ self.graph.process(frames, eod)
+ return frames, eod
+
+ @interfacedoc
+ def render(self):
+ if self.filename:
+ self.graph.save()
+ return self.graph.image
+
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2007-2010 Guillaume Pellerin <yomguy@parisson.com>
+# Copyright (c) 2010 Olivier Guilyardi <olivier@samalyse.com>
+
+# This file is part of TimeSide.
+
+# TimeSide 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.
+
+# TimeSide 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 TimeSide. If not, see <http://www.gnu.org/licenses/>.
+
+
+from timeside.core import Processor, implements, interfacedoc, FixedSizeInputAdapter
+from timeside.api import IGrapher
+from timeside.grapher import *
+
+
+class Waveform(Processor):
+ implements(IGrapher)
+
+ FFT_SIZE = 0x400
+
+ @interfacedoc
+ def __init__(self, width=None, height=None, output=None, bg_color=None, color_scheme=None):
+ if width:
+ self.width = width
+ else:
+ self.width = 1500
+ if height:
+ self.height = height
+ else:
+ self.height = 200
+ self.bg_color = bg_color
+ self.color_scheme = color_scheme
+ self.filename = output
+ self.graph = None
+
+ @staticmethod
+ @interfacedoc
+ def id():
+ return "waveform"
+
+ @staticmethod
+ @interfacedoc
+ def name():
+ return "Waveform"
+
+ @interfacedoc
+ def set_colors(self, background, scheme):
+ self.bg_color = background
+ self.color_scheme = scheme
+
+ @interfacedoc
+ def setup(self, channels=None, samplerate=None, nframes=None):
+ super(Waveform, self).setup(channels, samplerate, nframes)
+ if self.graph:
+ self.graph = None
+ self.graph = WaveformImage(self.width, self.height, self.nframes(), self.samplerate(), self.FFT_SIZE,
+ bg_color=self.bg_color, color_scheme=self.color_scheme, filename=self.filename)
+
+ @interfacedoc
+ def process(self, frames, eod=False):
+ self.graph.process(frames, eod)
+ return frames, eod
+
+ @interfacedoc
+ def render(self):
+ if self.filename:
+ self.graph.save()
+ return self.graph.image
# -*- coding: utf-8 -*-
-from timeside.tests.api import examples
+from timeside.tests.api.examples import Gain
from timeside.core import *
+from timeside.decoder import *
+from timeside.analyzer import *
+from timeside.encoder import *
from timeside.api import *
from sys import stdout
-use_gst = 1
-if use_gst:
- from timeside.tests.api.gstreamer import FileDecoder, WavEncoder
-else:
- from timeside.tests.api.examples import FileDecoder, WavEncoder
-
import os.path
source = os.path.join(os.path.dirname(__file__), "../samples/guitar.wav")
print "Normalizing %s" % source
decoder = FileDecoder(source)
-maxlevel = examples.MaxLevel()
-duration = examples.Duration()
+maxlevel = MaxLevel()
+duration = Duration()
(decoder | maxlevel | duration).run()
print "gain: %f" % gain
print "duration: %f %s" % (duration.result(), duration.unit())
-gain = examples.Gain(gain)
+gain = Gain(gain)
encoder = WavEncoder("normalized.wav")
-fixed = examples.FixedInputProcessor()
-subpipe = gain | fixed | maxlevel
+subpipe = gain | maxlevel
(decoder | subpipe | encoder).run()
# -*- coding: utf-8 -*-
-from timeside.tests.api import examples
+
+import os
from timeside.core import *
from timeside.api import *
-import os.path
-
-use_gst = 1
-if use_gst:
- from timeside.tests.api.gstreamer import FileDecoder, WavEncoder
-else:
- from timeside.tests.api.examples import FileDecoder, WavEncoder
+from timeside.decoder import *
+from timeside.grapher import *
image_file = '../results/img/spectrogram.png'
source = os.path.join(os.path.dirname(__file__), "../samples/sweep.wav")
decoder = FileDecoder(source)
-spectrogram = examples.Spectrogram(width=1024, height=256, output=image_file, bg_color=(0,0,0), color_scheme='default')
+spectrogram = Spectrogram(width=1024, height=256, output=image_file, bg_color=(0,0,0), color_scheme='default')
(decoder | spectrogram).run()
# -*- coding: utf-8 -*-
-from timeside.tests.api import examples
+
+import os
from timeside.core import *
from timeside.api import *
-import os
-
-use_gst = 1
-if use_gst:
- from timeside.tests.api.gstreamer import FileDecoder
-else:
- from timeside.tests.api.examples import FileDecoder
+from timeside.decoder import *
+from timeside.grapher import *
sample_dir = '../samples'
img_dir = '../results/img'
image = img_dir + os.sep + image
print 'Test : decoder(%s) | waveform (%s)' % (source, image)
decoder = FileDecoder(audio)
- waveform = examples.Waveform(width=1024, height=256, output=image, bg_color=(0,0,0), color_scheme='default')
+ waveform = Waveform(width=1024, height=256, output=image, bg_color=(0,0,0), color_scheme='default')
(decoder | waveform).run()
print 'frames per pixel = ', waveform.graph.samples_per_pixel
print "render waveform to: %s" % image
(0, 0, 0), (58/4,68/4,65/4), (80/2,100/2,153/2), (90,180,100), (224,224,44), (255,60,30), (255,255,255)
]}
- self.width = 512
+ self.width = 2048
self.height = 128
self.bg_color = (255,255,255)
self.force = True
# Author: Guillaume Pellerin <yomguy@parisson.com>
-version = '0.1.beta'
+version = '0.1-beta'
import os
import sys
-from timeside.tests.api import examples
from timeside.core import *
-from timeside.api import *
+from timeside.decoder import *
+from timeside.grapher import *
from grapher_scheme import *
-from timeside.tests.api.gstreamer import FileDecoder
class Media2Waveform(object):
print 'Rendering ', source, ' to ', image, '...'
audio = os.path.join(os.path.dirname(__file__), source)
decoder = FileDecoder(audio)
- waveform = examples.Waveform(width=self.width, height=self.height, output=image,
+ waveform = Waveform(width=self.width, height=self.height, output=image,
bg_color=self.bg_color, color_scheme=self.color_scheme)
(decoder | waveform).run()
print 'frames per pixel = ', waveform.graph.samples_per_pixel