$ apt-get install -t lenny-backports vim subversion python python-setuptools python-xml python-mutagen \
python-imaging python-numpy python-gst0.10 gstreamer0.10-plugins-base \
- gstreamer0.10-fluendo-mp3 gstreamer0.10-plugins-good
+ gstreamer0.10-fluendo-mp3 gstreamer0.10-plugins-good gstreamer0.10-plugins-bad
2. Install TimeSide
from waveform import *
from spectrogram import *
from waveform_joydiv import *
+from waveform_awdio import *
self.image.save(filename)
+class WaveformImageSimple(object):
+ """ Builds a PIL image representing a waveform of the audio stream.
+ 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, bg_color, color_scheme):
+ self.image_width = image_width
+ self.image_height = image_height
+ self.nframes = nframes
+ self.samplerate = samplerate
+ self.bg_color = bg_color
+ self.color_scheme = color_scheme
+
+ if isinstance(color_scheme, dict):
+ colors = color_scheme['waveform']
+ else:
+ colors = default_color_schemes[color_scheme]['waveform']
+
+ self.line_color = colors[0]
+
+ 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.image = Image.new("RGBA", (self.image_width, self.image_height), self.bg_color)
+ self.pixel = self.image.load()
+ self.draw = ImageDraw.Draw(self.image)
+ self.previous_x, self.previous_y = None, None
+ self.frame_cursor = 0
+ self.pixel_cursor = 0
+
+ def peaks(self, samples):
+ """ Find the minimum and maximum peak of the samples.
+ Returns that pair in the order they were found.
+ So if min was found first, it returns (min, max) else the other way around. """
+
+ max_index = numpy.argmax(samples)
+ max_value = samples[max_index]
+
+ min_index = numpy.argmin(samples)
+ min_value = samples[min_index]
+
+ if min_index < max_index:
+ return (min_value, max_value)
+ else:
+ return (max_value, min_value)
+
+ def draw_peaks(self, x, peaks):
+ """ draw 2 peaks at x using the spectral_centroid for color """
+
+ y1 = self.image_height * 0.5 - peaks[0] * (self.image_height - 4) * 0.5
+ y2 = self.image_height * 0.5 - peaks[1] * (self.image_height - 4) * 0.5
+
+ if self.previous_y and x < self.image_width-2 and self.pixel_cursor % 2:
+ if y1 < y2:
+ self.draw.line((x, 0, x, y1), self.line_color)
+ self.draw.line((x, self.image_height , x, y2), self.line_color)
+ else:
+ self.draw.line((x, 0, x, y2), self.line_color)
+ self.draw.line((x, self.image_height , x, y1), self.line_color)
+ else:
+ self.draw.line((x, 0, x, self.image_height), self.line_color)
+
+ self.previous_x, self.previous_y = x, y1
+
+# self.draw_anti_aliased_pixels(x, y1, y2, self.line_color)
+
+ def draw_anti_aliased_pixels(self, x, y1, y2, color):
+ """ vertical anti-aliasing at y1 and y2 """
+
+ y_max = max(y1, y2)
+ y_max_int = int(y_max)
+ alpha = y_max - y_max_int
+
+ if alpha > 0.0 and alpha < 1.0 and y_max_int + 1 < self.image_height:
+ current_pix = self.pixel[int(x), y_max_int + 1]
+
+ r = int((1-alpha)*current_pix[0] + alpha*color[0])
+ g = int((1-alpha)*current_pix[1] + alpha*color[1])
+ b = int((1-alpha)*current_pix[2] + alpha*color[2])
+
+ self.pixel[x, y_max_int + 1] = (r,g,b)
+
+ y_min = min(y1, y2)
+ y_min_int = int(y_min)
+ alpha = 1.0 - (y_min - y_min_int)
+
+ if alpha > 0.0 and alpha < 1.0 and y_min_int - 1 >= 0:
+ current_pix = self.pixel[x, y_min_int - 1]
+
+ r = int((1-alpha)*current_pix[0] + alpha*color[0])
+ g = int((1-alpha)*current_pix[1] + alpha*color[1])
+ b = int((1-alpha)*current_pix[2] + alpha*color[2])
+
+ self.pixel[x, y_min_int - 1] = (r,g,b)
+
+ def process(self, frames, eod):
+ if len(frames) != 1:
+ buffer = frames[:,0].copy()
+ buffer.shape = (len(buffer),1)
+ for samples, end in self.pixels_adapter.process(buffer, eod):
+ if self.pixel_cursor < self.image_width:
+ self.draw_peaks(self.pixel_cursor, self.peaks(samples))
+ self.pixel_cursor += 1
+ if self.pixel_cursor == self.image_width-2:
+ self.draw_peaks(self.pixel_cursor, (0, 0))
+ self.pixel_cursor += 1
+ else:
+ pass
+
+ def save(self, filename):
+ """ Apply last 2D transforms and write all pixels to the file. """
+
+ # middle line (0 for none)
+ a = 1
+ for x in range(self.image_width):
+ self.pixel[x, self.image_height/2] = tuple(map(lambda p: p+a, self.pixel[x, self.image_height/2]))
+ self.image.save(filename)
+
+
class SpectrogramImage(object):
""" Builds a PIL image representing a spectrogram of the audio stream (level vs. frequency vs. time).
Adds pixels iteratively thanks to the adapter providing fixed size frame buffers."""
--- /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.core import *
+
+
+class WaveformAwdio(Processor):
+ implements(IGrapher)
+
+ @interfacedoc
+ def __init__(self, width=572, height=74, bg_color=None, color_scheme='iso'):
+ self.width = width
+ self.height = height
+ self.bg_color = bg_color
+ self.color_scheme = color_scheme
+ self.graph = None
+
+ @staticmethod
+ @interfacedoc
+ def id():
+ return "waveform_awdio"
+
+ @staticmethod
+ @interfacedoc
+ def name():
+ return "Waveform Awdio"
+
+ @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(WaveformAwdio, self).setup(channels, samplerate, nframes)
+ if self.graph:
+ self.graph = None
+ self.graph = WaveformImageSimple(self.width, self.height, self.nframes(), self.samplerate(),
+ 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):
+ if output:
+ self.graph.save(output)
+ return self.graph.image
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2009-2010 Guillaume Pellerin <yomguy@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: Guillaume Pellerin <yomguy@parisson.com>
+
+version = '0.2'
+
+import os
+import sys
+import timeside
+
+class GrapherScheme:
+
+ def __init__(self):
+ self.color = 255
+ self.color_scheme = {
+ 'waveform': [ # Four (R,G,B) tuples for three main color channels for the spectral centroid method
+ (self.color,self.color,self.color)
+# (0, 0, 0), (0, 0, 0), (0, 0, 0), (0,0,0)
+ ],
+ 'spectrogram': [
+ (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)
+ ]}
+
+ # Width of the image
+ self.width = 572
+
+ # Height of the image
+ self.height = 74
+
+ # Background color
+ self.bg_color = None
+# self.bg_color = None
+
+ # Force computation. By default, the class doesn't overwrite existing image files.
+ self.force = True
+
+
+class Media2Waveform(object):
+
+ def __init__(self, media_dir, img_dir):
+ self.root_dir = media_dir
+ self.img_dir = img_dir
+ self.scheme = GrapherScheme()
+ self.width = self.scheme.width
+ self.height = self.scheme.height
+ self.bg_color = self.scheme.bg_color
+ self.color_scheme = self.scheme.color_scheme
+ self.force = self.scheme.force
+
+ self.media_list = self.get_media_list()
+ if not os.path.exists(self.img_dir):
+ os.makedirs(self.img_dir)
+ self.path_dict = self.get_path_dict()
+
+ def get_media_list(self):
+ media_list = []
+ for root, dirs, files in os.walk(self.root_dir):
+ if root:
+ for file in files:
+ ext = file.split('.')[-1]
+ media_list.append(root+os.sep+file)
+ return media_list
+
+ def get_path_dict(self):
+ path_dict = {}
+ for media in self.media_list:
+ filename = media.split(os.sep)[-1]
+ name, ext = os.path.splitext(filename)
+ path_dict[media] = self.img_dir + os.sep + filename.replace('.', '_') + '.png'
+ return path_dict
+
+ def process(self):
+ for source, image in self.path_dict.iteritems():
+ if not os.path.exists(image) or self.force:
+ print 'Processing ', source
+ audio = os.path.join(os.path.dirname(__file__), source)
+ decoder = timeside.decoder.FileDecoder(audio)
+ analyzer = timeside.analyzer.Duration()
+ waveform = timeside.grapher.WaveformAwdio(width=self.width, height=self.height,
+ bg_color=self.bg_color, color_scheme=self.color_scheme)
+ (decoder | analyzer | waveform).run()
+ duration = analyzer.result()
+ img_name = os.path.split(image)[1]
+ image = os.path.split(image)[0]+os.sep+os.path.splitext(img_name)[0] + '_' +\
+ '_'.join([str(self.width), str(self.height), str(int(duration))])+os.path.splitext(img_name)[1]
+ waveform.graph.filename = image
+ print 'Rendering ', source, ' to ', waveform.graph.filename, '...'
+ print 'frames per pixel = ', waveform.graph.samples_per_pixel
+ if os.path.exists(image):
+ os.remove(image)
+ waveform.render(output=image)
+
+
+if __name__ == '__main__':
+ if len(sys.argv) <= 2:
+ print """
+ Usage : python waveform_batch /path/to/media_dir /path/to/img_dir
+
+ Dependencies : timeside, python, python-numpy, python-gst0.10, gstreamer0.10-plugins-base
+ See http://code.google.com/p/timeside/ for more information.
+ """
+ else:
+ media_dir = sys.argv[-2]
+ img_dir = sys.argv[-1]
+ m = Media2Waveform(media_dir, img_dir)
+ m.process()