From: yomguy Date: Thu, 1 Apr 2010 21:04:31 +0000 (+0000) Subject: change processor module title, remove obsolete processors, cleanup X-Git-Tag: 0.3.2~162 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=9189c8e4c3bf013fe9f10efebea8193ed92689f7;p=timeside.git change processor module title, remove obsolete processors, cleanup --- diff --git a/__init__.py b/__init__.py index f021203..81ad004 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ from core import * from metadata import Metadata -import decode -import encode -import analyze +import decoder +import encoder +import analyzer import grapher diff --git a/analyze/__init__.py b/analyze/__init__.py deleted file mode 100644 index 307016e..0000000 --- a/analyze/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -from timeside.analyze.core import * -from timeside.analyze.channels import * -from timeside.analyze.format import * -from timeside.analyze.encoding import * -from timeside.analyze.resolution import * -from timeside.analyze.samplerate import * -from timeside.analyze.duration import * -from timeside.analyze.max_level import * -from timeside.analyze.mean_level import * -from timeside.analyze.dc import * - - diff --git a/analyze/channels.py b/analyze/channels.py deleted file mode 100644 index 6ad202d..0000000 --- a/analyze/channels.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IAnalyzer -import numpy - -class ChannelAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IAnalyzer) - - @staticmethod - def id(): - return "nchannels" - - def name(self): - return "Channels" - - def unit(self): - return "" - - def render(self, media_item, options=None): - self.pre_process(media_item) - if self.channels == 1: - return 'mono' - if self.channels == 2: - return 'stereo' - else: - return self.channels diff --git a/analyze/core.py b/analyze/core.py deleted file mode 100644 index c2371d2..0000000 --- a/analyze/core.py +++ /dev/null @@ -1,200 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Authors: -# Bram de Jong -# Guillaume Pellerin - -from timeside.core import * -import optparse, math, sys -import numpy -import scikits.audiolab as audiolab - -# FIXME: AudioProcessor: wrong name, should be Analyzer or AnalyzerCore -class AudioProcessor(Processor): - - def __init__(self): - self.fft_size = 2048 - self.window_function = numpy.ones - self.window = self.window_function(self.fft_size) - self.spectrum_range = None - self.lower = 100 - self.higher = 22050 - self.lower_log = math.log10(self.lower) - self.higher_log = math.log10(self.higher) - self.clip = lambda val, low, high: min(high, max(low, val)) - - def pre_process(self, media_item): - wav_file = media_item - self.audio_file = audiolab.sndfile(wav_file, 'read') - self.frames = self.audio_file.get_nframes() - self.samplerate = self.audio_file.get_samplerate() - self.channels = self.audio_file.get_channels() - self.format = self.audio_file.get_file_format() - self.encoding = self.audio_file.get_encoding() - - def get_samples(self): - samples = self.audio_file.read_frames(self.frames) - return samples - - def get_mono_samples(self): - # convert to mono by selecting left channel only - samples = self.get_samples() - if self.channels > 1: - return samples[:,0] - else: - return samples - - def read(self, start, size, resize_if_less=False): - """ read size samples starting at start, if resize_if_less is True and less than size - samples are read, resize the array to size and fill with zeros """ - - # number of zeros to add to start and end of the buffer - add_to_start = 0 - add_to_end = 0 - - if start < 0: - # the first FFT window starts centered around zero - if size + start <= 0: - if resize_if_less: - return numpy.zeros(size) - else: - return numpy.array([]) - else: - self.audio_file.seek(0) - - add_to_start = -start # remember: start is negative! - to_read = size + start - - if to_read > self.frames: - add_to_end = to_read - self.frames - to_read = self.frames - else: - self.audio_file.seek(start) - - to_read = size - if start + to_read >= self.frames: - to_read = self.frames - start - add_to_end = size - to_read - - try: - samples = self.audio_file.read_frames(to_read) - except IOError: - # this can happen for wave files with broken headers... - if resize_if_less: - return numpy.zeros(size) - else: - return numpy.zeros(2) - - # convert to mono by selecting left channel only - if self.channels > 1: - samples = samples[:,0] - - if resize_if_less and (add_to_start > 0 or add_to_end > 0): - if add_to_start > 0: - samples = numpy.concatenate((numpy.zeros(add_to_start), samples), axis=1) - - if add_to_end > 0: - samples = numpy.resize(samples, size) - samples[size - add_to_end:] = 0 - - return samples - - - def spectral_centroid(self, seek_point, spec_range=120.0): - """ starting at seek_point read fft_size samples, and calculate the spectral centroid """ - - samples = self.read(seek_point - self.fft_size/2, self.fft_size, True) - - samples *= self.window - fft = numpy.fft.fft(samples) - spectrum = numpy.abs(fft[:fft.shape[0] / 2 + 1]) / float(self.fft_size) # normalized abs(FFT) between 0 and 1 - length = numpy.float64(spectrum.shape[0]) - - # scale the db spectrum from [- spec_range db ... 0 db] > [0..1] - db_spectrum = ((20*(numpy.log10(spectrum + 1e-30))).clip(-spec_range, 0.0) + spec_range)/spec_range - - energy = spectrum.sum() - spectral_centroid = 0 - - if energy > 1e-20: - # calculate the spectral centroid - - if self.spectrum_range == None: - self.spectrum_range = numpy.arange(length) - - spectral_centroid = (spectrum * self.spectrum_range).sum() / (energy * (length - 1)) * self.samplerate * 0.5 - - # clip > log10 > scale between 0 and 1 - spectral_centroid = (math.log10(self.clip(spectral_centroid, self.lower, self.higher)) - self.lower_log) / (self.higher_log - self.lower_log) - - return (spectral_centroid, db_spectrum) - - - def peaks(self, start_seek, end_seek): - """ read all samples between start_seek and end_seek, then find the minimum and maximum peak - in that range. Returns that pair in the order they were found. So if min was found first, - it returns (min, max) else the other way around. """ - - # larger blocksizes are faster but take more mem... - # Aha, Watson, a clue, a tradeof! - block_size = 4096 - - max_index = -1 - max_value = -1 - min_index = -1 - min_value = 1 - - if end_seek > self.frames: - end_seek = self.frames - - if block_size > end_seek - start_seek: - block_size = end_seek - start_seek - - if block_size <= 1: - samples = self.read(start_seek, 1) - return samples[0], samples[0] - elif block_size == 2: - samples = self.read(start_seek, True) - return samples[0], samples[1] - - for i in range(start_seek, end_seek, block_size): - samples = self.read(i, block_size) - - local_max_index = numpy.argmax(samples) - local_max_value = samples[local_max_index] - - if local_max_value > max_value: - max_value = local_max_value - max_index = local_max_index - - local_min_index = numpy.argmin(samples) - local_min_value = samples[local_min_index] - - if local_min_value < min_value: - min_value = local_min_value - min_index = local_min_index - - if min_index < max_index: - return (min_value, max_value) - else: - return (max_value, min_value) - - - diff --git a/analyze/dc.py b/analyze/dc.py deleted file mode 100644 index ac31e6f..0000000 --- a/analyze/dc.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IValueAnalyzer -import numpy - -class MeanDCShiftAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IValueAnalyzer) - - @staticmethod - def id(): - return "dc" - - def name(self): - return "Mean DC shift" - - def unit(self): - return "%" - - def render(self, media_item, options=None): - self.pre_process(media_item) - samples = self.get_mono_samples() - return numpy.round(100*numpy.mean(samples),4) diff --git a/analyze/duration.py b/analyze/duration.py deleted file mode 100644 index 64546d0..0000000 --- a/analyze/duration.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IAnalyzer -import numpy -import datetime - -class DurationAnalyzer(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IAnalyzer) - - @staticmethod - def id(): - return "duration" - - def name(self): - return "Duration" - - def unit(self): - return "h:m:s" - - def render(self, media_item, options=None): - self.pre_process(media_item) - media_time = numpy.round(float(self.frames)/float(self.samplerate),0) - return datetime.timedelta(0,media_time) diff --git a/analyze/encoding.py b/analyze/encoding.py deleted file mode 100644 index 0448f47..0000000 --- a/analyze/encoding.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IAnalyzer -import numpy - -class EncodingAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IAnalyzer) - - @staticmethod - def id(): - return "encoding" - - def name(self): - return "Encoding format" - - def unit(self): - return "" - - def render(self, media_item, options=None): - self.pre_process(media_item) - return self.encoding diff --git a/analyze/format.py b/analyze/format.py deleted file mode 100644 index 2c7fdf3..0000000 --- a/analyze/format.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IAnalyzer -import numpy - -class FormatAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IAnalyzer) - - @staticmethod - def id(): - return "format" - - def name(self): - return "File format" - - def unit(self): - return "" - - def render(self, media_item, options=None): - self.pre_process(media_item) - return self.format diff --git a/analyze/max_level.py b/analyze/max_level.py deleted file mode 100644 index af6c4a0..0000000 --- a/analyze/max_level.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IValueAnalyzer -import numpy - -class MaxLevelAnalyzer(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IValueAnalyzer) - - @staticmethod - def id(): - return "max_level" - - @staticmethod - def name(): - return "Maximum peak level" - - def unit(self): - return "dB" - - def render(self, media_item, options=None): - self.pre_process(media_item) - samples = self.get_samples() - return numpy.round(20*numpy.log10(numpy.max(samples)),2) diff --git a/analyze/mean_level.py b/analyze/mean_level.py deleted file mode 100644 index 0ab38ff..0000000 --- a/analyze/mean_level.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IValueAnalyzer -import numpy - -class MeanLevelAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IValueAnalyzer) - - @staticmethod - def id(): - return "meanlevel" - - def name(self): - return "Mean RMS level" - - def unit(self): - return "dB" - - def render(self, media_item, options=None): - self.pre_process(media_item) - samples = self.get_mono_samples() - return numpy.round(20*numpy.log10(numpy.mean(numpy.sqrt(numpy.square(samples)))),2) diff --git a/analyze/resolution.py b/analyze/resolution.py deleted file mode 100644 index 6040fec..0000000 --- a/analyze/resolution.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IAnalyzer -import numpy - -class ResolutionAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IAnalyzer) - - @staticmethod - def id(): - return "resolution" - - def name(self): - return "Resolution" - - def unit(self): - return "bits" - - def render(self, media_item, options=None): - self.pre_process(media_item) - if '8' in self.encoding: - return 8 - if '16' in self.encoding: - return 16 - if '24' in self.encoding: - return 24 - if '32' in self.encoding: - return 32 - else: - return '' diff --git a/analyze/samplerate.py b/analyze/samplerate.py deleted file mode 100644 index d235ebd..0000000 --- a/analyze/samplerate.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.analyze.core import * -from timeside.api import IAnalyzer -import numpy - -class SampleRateAnalyzer(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IAnalyzer) - - @staticmethod - def id(): - return "samplerate" - - def name(self): - return "Samplerate" - - def unit(self): - return "Hz" - - def render(self, media_item, options=None): - self.pre_process(media_item) - return self.samplerate diff --git a/analyze/vamp/__init__.py b/analyze/vamp/__init__.py deleted file mode 100644 index d889d34..0000000 --- a/analyze/vamp/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from timeside.analyze.vamp.core import * diff --git a/analyze/vamp/core.py b/analyze/vamp/core.py deleted file mode 100644 index 6248508..0000000 --- a/analyze/vamp/core.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.core import * -from tempfile import NamedTemporaryFile -from timeside.exceptions import SubProcessError -import os -import random -import subprocess -import signal -import time - -class VampCoreAnalyzer: - """Parent class for Vamp plugin drivers""" - - def __init__(self): - self.vamp_path = '/usr/lib/vamp/' - # needs vamp-examples package - self.host = 'vamp-simple-host' - self.buffer_size = 0xFFFF - - def id(self): - return "vamp_plugins" - - def name(self): - return "Vamp plugins" - - def unit(self): - return "" - - def get_plugins_list(self): - if os.path.exists(self.vamp_path): - args = ' --list-outputs' - command = self.host + args - #tmp_file = NamedTemporaryFile() - data = self.core_process(command, self.buffer_size) - text = '' - plugins = [] - for chunk in data: - text = text + chunk - lines = text.split('\n') - for line in lines: - if line != '': - struct = line.split(':') - struct = struct[1:] - plugins.append(struct) - return plugins - else: - return [] - - def get_wav_path(self, media_item): - return settings.MEDIA_ROOT + '/' + media_item.file - #return media_item - - def render(self, plugin, media_item): - self.wavFile = self.get_wav_path(media_item) - args = ' -s ' + ':'.join(plugin) + ' ' + str(self.wavFile) - command = command = self.host + args - data = self.core_process(command, self.buffer_size) - string = '' - values = {} - for chunk in data: - string = string + chunk - lines = string.split('\n') - for line in lines: - if line != '': - struct = line.split(':') - values[struct[0]] = struct[1] - return values - - def core_process(self, command, buffer_size): - """Encode and stream audio data through a generator""" - - __chunk = 0 - - try: - proc = subprocess.Popen(command, - shell = True, - bufsize = buffer_size, - stdin = subprocess.PIPE, - stdout = subprocess.PIPE, - close_fds = True) - except: - raise SubProcessError('Command failure:', command, proc) - - # Core processing - while True: - __chunk = proc.stdout.read(buffer_size) - status = proc.poll() - if status != None and status != 0: - raise SubProcessError('Command failure:', command, proc) - if len(__chunk) == 0: - break - yield __chunk - - diff --git a/analyzer/__init__.py b/analyzer/__init__.py new file mode 100644 index 0000000..f0af279 --- /dev/null +++ b/analyzer/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +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 * diff --git a/analyzer/core.py b/analyzer/core.py new file mode 100644 index 0000000..17cbfa1 --- /dev/null +++ b/analyzer/core.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Authors: +# Guillaume Pellerin + +from timeside.core import * +from timeside.grapher.core import * +import numpy + diff --git a/analyzer/dc.py b/analyzer/dc.py new file mode 100644 index 0000000..e92cfd5 --- /dev/null +++ b/analyzer/dc.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +from timeside.analyzer.core import * +from timeside.api import IValueAnalyzer +import numpy + + +class MeanDCShift(Processor): + implements(IValueAnalyzer) + + @interfacedoc + def setup(self, channels=None, samplerate=None, nframes=None): + super(MeanDCShift, self).setup(channels, samplerate, nframes) + self.value = 0 + + @staticmethod + @interfacedoc + def id(): + return "dc" + + @staticmethod + @interfacedoc + def name(): + return "Mean DC shift" + + @staticmethod + @interfacedoc + def unit(): + return "%" + + def __str__(self): + return "%s %s" % (str(self.value), unit()) + + def process(self, frames, eod=False): + self.value = numpy.round(100*numpy.mean(samples),4) + return frames, eod + + def result(self): + return self.value + diff --git a/analyzer/duration.py b/analyzer/duration.py new file mode 100644 index 0000000..456a527 --- /dev/null +++ b/analyzer/duration.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +from timeside.analyzer.core import * +from timeside.api import IValueAnalyzer +import datetime + + +class Duration(Processor): + implements(IValueAnalyzer) + + @interfacedoc + def setup(self, channels=None, samplerate=None, nframes=None): + super(Duration, self).setup(channels, samplerate, nframes) + self.value = 0 + + @staticmethod + @interfacedoc + def id(): + return "duration" + + @staticmethod + @interfacedoc + def name(): + return "Duration" + + @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 + + def result(self): + return datetime.timedelta(0,numpy.round(self.nframes / float(self.samplerate), 0)) + diff --git a/analyzer/max_level.py b/analyzer/max_level.py new file mode 100644 index 0000000..93ff3ec --- /dev/null +++ b/analyzer/max_level.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin +# Copyright (c) 2009 Olivier Guilyardi + +# 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 . + +# Author: Guillaume Pellerin + +from timeside.analyzer.core import * +from timeside.api import IValueAnalyzer +import numpy + + +class MaxLevel(Processor): + implements(IValueAnalyzer) + + @interfacedoc + def setup(self, channels=None, samplerate=None, nframes=None): + super(MaxLevel, self).setup(channels, samplerate, nframes) + self.value = -140 + + @staticmethod + @interfacedoc + def id(): + return "maxlevel" + + @staticmethod + @interfacedoc + def name(): + return "Max level" + + @staticmethod + @interfacedoc + def unit(): + return "dB" + + def __str__(self): + return "%s %s" % (str(self.value), unit()) + + def process(self, frames, eod=False): + max = numpy.round(20*numpy.log10(frames.max()), 2) + if max > self.value: + self.value = max + + return frames, eod + + def result(self): + return self.value + diff --git a/analyzer/mean_level.py b/analyzer/mean_level.py new file mode 100644 index 0000000..8b68324 --- /dev/null +++ b/analyzer/mean_level.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +from timeside.analyzer.core import * +from timeside.api import IValueAnalyzer +import numpy + + +class MeanLevel(Processor): + implements(IValueAnalyzer) + + @interfacedoc + def setup(self, channels=None, samplerate=None, nframes=None): + super(MeanLevel, self).setup(channels, samplerate, nframes) + self.value = -140 + + @staticmethod + @interfacedoc + def id(): + return "meanlevel" + + @staticmethod + @interfacedoc + def name(): + return "Mean RMS level" + + @staticmethod + @interfacedoc + def unit(): + return "dB" + + def __str__(self): + return "%s %s" % (str(self.value), unit()) + + def process(self, frames, eod=False): + max = numpy.round(20*numpy.log10(numpy.mean(numpy.sqrt(numpy.square(frames.max())))), 2) + if max > self.value: + self.value = max + + return frames, eod + + def result(self): + return self.value + diff --git a/analyzer/vamp/__init__.py b/analyzer/vamp/__init__.py new file mode 100644 index 0000000..1ded83d --- /dev/null +++ b/analyzer/vamp/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from timeside.analyzer.vamp.core import * diff --git a/analyzer/vamp/core.py b/analyzer/vamp/core.py new file mode 100644 index 0000000..49ae9a0 --- /dev/null +++ b/analyzer/vamp/core.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +from timeside.core import * +from tempfile import NamedTemporaryFile +from timeside.exceptions import SubProcessError +import os +import random +import subprocess +import signal +import time + +class VampCoreAnalyzer: + """Parent class for Vamp plugin drivers""" + + def __init__(self): + self.vamp_path = '/usr/lib/vamp/' + # needs vamp-examples package + self.host = 'vamp-simple-host' + self.buffer_size = 0xFFFF + + def id(self): + return "vamp_plugins" + + def name(self): + return "Vamp plugins" + + def unit(self): + return "" + + def get_plugins_list(self): + if os.path.exists(self.vamp_path): + args = ' --list-outputs' + command = self.host + args + #tmp_file = NamedTemporaryFile() + data = self.core_process(command, self.buffer_size) + text = '' + plugins = [] + for chunk in data: + text = text + chunk + lines = text.split('\n') + for line in lines: + if line != '': + struct = line.split(':') + struct = struct[1:] + plugins.append(struct) + return plugins + else: + return [] + + def get_wav_path(self, media_item): + return settings.MEDIA_ROOT + '/' + media_item.file + #return media_item + + def render(self, plugin, media_item): + self.wavFile = self.get_wav_path(media_item) + args = ' -s ' + ':'.join(plugin) + ' ' + str(self.wavFile) + command = command = self.host + args + data = self.core_process(command, self.buffer_size) + string = '' + values = {} + for chunk in data: + string = string + chunk + lines = string.split('\n') + for line in lines: + if line != '': + struct = line.split(':') + values[struct[0]] = struct[1] + return values + + def core_process(self, command, buffer_size): + """Encode and stream audio data through a generator""" + + __chunk = 0 + + try: + proc = subprocess.Popen(command, + shell = True, + bufsize = buffer_size, + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + close_fds = True) + except: + raise SubProcessError('Command failure:', command, proc) + + # Core processing + while True: + __chunk = proc.stdout.read(buffer_size) + status = proc.poll() + if status != None and status != 0: + raise SubProcessError('Command failure:', command, proc) + if len(__chunk) == 0: + break + yield __chunk + + diff --git a/decode/__init__.py b/decode/__init__.py deleted file mode 100644 index d04cfce..0000000 --- a/decode/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- - -from timeside.decode.core import * -from timeside.decode.ogg import * -from timeside.decode.flac import * -from timeside.decode.wav import * -from timeside.decode.mp3 import * diff --git a/decode/core.py b/decode/core.py deleted file mode 100644 index b3c5b15..0000000 --- a/decode/core.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -from timeside.decode import * -from timeside.core import * -import subprocess - -class SubProcessPipe: - - def __init__(self, command, stdin=None): - """Read media and stream data through a generator. - Taken from Telemeta (see http://telemeta.org)""" - - self.buffer_size = 0xFFFF - - if not stdin: - stdin = subprocess.PIPE - - self.proc = subprocess.Popen(command.encode('utf-8'), - shell = True, - bufsize = self.buffer_size, - stdin = stdin, - stdout = subprocess.PIPE, - close_fds = True) - - self.input = self.proc.stdin - self.output = self.proc.stdout - - -class DecoderCore(Processor): - """Defines the main parts of the decoding tools : - paths, metadata parsing, data streaming thru system command""" - - def __init__(self): - self.command = 'ffmpeg -i "%s" -f wav - ' - - def process(self, source, options=None): - """Encode and stream audio data through a generator""" - - command = self.command % source - proc = SubProcessPipe(command) - return proc.output - - #while True: - #__chunk = proc.output.read(self.proc.buffer_size) - #status = proc.poll() - #if status != None and status != 0: - #raise ExportProcessError('Command failure:', command, proc) - #if len(__chunk) == 0: - #break - #yield __chunk - - - diff --git a/decode/flac.py b/decode/flac.py deleted file mode 100644 index 5289713..0000000 --- a/decode/flac.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from timeside.decode.core import * -from timeside.api import IDecoder -from mutagen.flac import FLAC -from tempfile import NamedTemporaryFile - -class FlacDecoder(DecoderCore): - """Defines methods to decode from FLAC""" - - implements(IDecoder) - - @staticmethod - def id(): - return "flacdec" - - def format(self): - return 'FLAC' - - def file_extension(self): - return 'flac' - - def mime_type(self): - return 'audio/x-flac' - - def description(self): - return """ - Free Lossless Audio Codec (FLAC) is a file format for lossless audio - data compression. During compression, FLAC does not lose quality from - the audio stream, as lossy compression formats such as MP3, AAC, and - Vorbis do. Josh Coalson is the primary author of FLAC. - (source Wikipedia) - """ - - def get_file_info(self): - try: - file1, file2 = os.popen4('metaflac --list "'+self.dest+'"') - info = [] - for line in file2.readlines(): - info.append(clean_word(line[:-1])) - self.info = info - return self.info - except: - raise IOError('DecoderError: metaflac is not installed or ' + \ - 'file does not exist.') - diff --git a/decode/mp3.py b/decode/mp3.py deleted file mode 100644 index f4f1973..0000000 --- a/decode/mp3.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Parisson SARL -# Copyright (c) 2006-2007 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from timeside.decode.core import * -from timeside.api import IDecoder - - -class Mp3Decoder(DecoderCore): - """Defines methods to decode from MP3""" - - implements(IDecoder) - - def __init__(self): - self.command = 'sox -t mp3 "%s" -q -b 16 -r 44100 -t wav -c2 - ' - - @staticmethod - def id(): - return "mp3dec" - - def format(self): - return 'MP3' - - def file_extension(self): - return 'mp3' - - def mime_type(self): - return 'audio/mpeg' - - def description(self): - return """ - MPEG-1 Audio Layer 3, more commonly referred to as MP3, is a patented - digital audio encoding format using a form of lossy data compression. - It is a common audio format for consumer audio storage, as well as a - de facto standard of digital audio compression for the transfer and - playback of music on digital audio players. MP3 is an audio-specific - format that was designed by the Moving Picture Experts Group as part - of its MPEG-1 standard. (source Wikipedia) - """ - - def get_file_info(self): - try: - file_out1, file_out2 = os.popen4('mp3info "'+self.dest+'"') - info = [] - for line in file_out2.readlines(): - info.append(clean_word(line[:-1])) - self.info = info - return self.info - except: - raise IOError('DecoderError: file does not exist.') - diff --git a/decode/ogg.py b/decode/ogg.py deleted file mode 100644 index ffd6903..0000000 --- a/decode/ogg.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from timeside.decode.core import * -from timeside.api import IDecoder -from mutagen.oggvorbis import OggVorbis - -class OggDecoder(DecoderCore): - """Defines methods to decode from OGG Vorbis""" - - implements(IDecoder) - - @staticmethod - def id(): - return "oggdec" - - def format(self): - return 'OggVorbis' - - def file_extension(self): - return 'ogg' - - def mime_type(self): - return 'application/ogg' - - def description(self): - return """ - Vorbis is a free software / open source project headed by the Xiph.Org - Foundation (formerly Xiphophorus company). The project produces an audio - format specification and software implementation (codec) for lossy audio - compression. Vorbis is most commonly used in conjunction with the Ogg - container format and it is therefore often referred to as Ogg Vorbis. - (source Wikipedia) - """ - - def get_file_info(self): - try: - file_out1, file_out2 = os.popen4('ogginfo "'+self.dest+'"') - info = [] - for line in file_out2.readlines(): - info.append(clean_word(line[:-1])) - self.info = info - return self.info - except: - raise IOError('DecoderError: file does not exist.') - diff --git a/decode/wav.py b/decode/wav.py deleted file mode 100644 index 08bca88..0000000 --- a/decode/wav.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from timeside.decode.core import * -from timeside.api import IDecoder - -class WavDecoder(DecoderCore): - """Defines methods to decode from WAV""" - - implements(IDecoder) - - @staticmethod - def id(): - return "wavdec" - - 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('DecoderError: wavinfo id not installed or file does not exist.') - diff --git a/decoder/__init__.py b/decoder/__init__.py new file mode 100644 index 0000000..9f37f75 --- /dev/null +++ b/decoder/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from timeside.decoder.core import * diff --git a/decoder/core.py b/decoder/core.py new file mode 100644 index 0000000..85d818e --- /dev/null +++ b/decoder/core.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +from timeside.core import * +import subprocess + +class SubProcessPipe: + + def __init__(self, command, stdin=None): + """Read media and stream data through a generator. + Taken from Telemeta (see http://telemeta.org)""" + + self.buffer_size = 0xFFFF + + if not stdin: + stdin = subprocess.PIPE + + self.proc = subprocess.Popen(command.encode('utf-8'), + shell = True, + bufsize = self.buffer_size, + stdin = stdin, + stdout = subprocess.PIPE, + close_fds = True) + + self.input = self.proc.stdin + self.output = self.proc.stdout + + +class DecoderCore(Processor): + """Defines the main parts of the decoding tools : + paths, metadata parsing, data streaming thru system command""" + + def __init__(self): + self.command = 'ffmpeg -i "%s" -f wav - ' + + def process(self, source, options=None): + """Encode and stream audio data through a generator""" + + command = self.command % source + proc = SubProcessPipe(command) + return proc.output + + #while True: + #__chunk = proc.output.read(self.proc.buffer_size) + #status = proc.poll() + #if status != None and status != 0: + #raise ExportProcessError('Command failure:', command, proc) + #if len(__chunk) == 0: + #break + #yield __chunk + + + diff --git a/encode/__init__.py b/encode/__init__.py deleted file mode 100644 index a3dd62f..0000000 --- a/encode/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- - -from timeside.encode.core import * -from timeside.encode.ogg import * -from timeside.encode.wav import * -from timeside.encode.mp3 import * -from timeside.encode.flac import * diff --git a/encode/core.py b/encode/core.py deleted file mode 100644 index f7284b6..0000000 --- a/encode/core.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - - -from timeside.encode import * -from timeside.core import * - -import subprocess - -class SubProcessPipe: - """Read media and stream data through a generator. - Taken from Telemeta (see http://telemeta.org)""" - - def __init__(self, command, stdin=None): - self.buffer_size = 0xFFFF - if not stdin: - stdin = subprocess.PIPE - - self.proc = subprocess.Popen(command.encode('utf-8'), - shell = True, - bufsize = self.buffer_size, - stdin = stdin, - stdout = subprocess.PIPE, - close_fds = True) - - self.input = self.proc.stdin - self.output = self.proc.stdout - -class EncoderCore(Processor): - """Defines the main parts of the encoding tools : - paths, metadata parsing, data streaming thru system command""" - - def core_process(self, command, stdin): - """Encode and stream audio data through a generator""" - - proc = SubProcessPipe(command, stdin) - - while True: - __chunk = proc.output.read(proc.buffer_size) - #status = proc.poll() - #if status != None and status != 0: - #raise EncodeProcessError('Command failure:', command, proc) - if len(__chunk) == 0: - break - yield __chunk - - - diff --git a/encode/flac.py b/encode/flac.py deleted file mode 100644 index 256c7a7..0000000 --- a/encode/flac.py +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from timeside.encode.core import * -from timeside.api import IEncoder -from tempfile import NamedTemporaryFile - -class FlacEncoder(EncoderCore): - """Defines methods to encode to FLAC""" - - implements(IEncoder) - - def __init__(self): - self.quality_default = '-5' - self.dub2args_dict = {'creator': 'artist', - 'relation': 'album' - } - - @staticmethod - def id(): - return "flacenc" - - def format(self): - return 'FLAC' - - def file_extension(self): - return 'flac' - - def mime_type(self): - return 'audio/x-flac' - - def description(self): - return """ - Free Lossless Audio Codec (FLAC) is a file format for lossless audio - data compression. - """ - - def get_file_info(self): - try: - file1, file2 = os.popen4('metaflac --list "'+self.dest+'"') - info = [] - for line in file2.readlines(): - info.append(clean_word(line[:-1])) - self.info = info - return self.info - except: - raise IOError('EncoderError: metaflac is not installed or ' + \ - 'file does not exist.') - - def write_tags(self, file): - from mutagen.flac import FLAC - media = FLAC(file) - for tag in self.metadata: - name = tag[0] - value = clean_word(tag[1]) - if name in self.dub2args_dict.keys(): - name = self.dub2args_dict[name] - if name == 'comment': - media['DESCRIPTION'] = unicode(value) - else: - media[name] = unicode(value) - try: - media.save() - except: - raise IOError('EncoderError: cannot write tags.') - - - def get_args(self,options=None): - """Get process options and return arguments for the encoder""" - args = [] - if not options is None: - self.options = options - if not ('verbose' in self.options and self.options['verbose'] != '0'): - args.append('-s') - if 'flac_quality' in self.options: - args.append('-f ' + self.options['flac_quality']) - else: - args.append('-f ' + self.quality_default) - else: - args.append('-s -f ' + self.quality_default) - - #for tag in self.metadata.keys(): - #value = clean_word(self.metadata[tag]) - #args.append('-c %s="%s"' % (tag, value)) - #if tag in self.dub2args_dict.keys(): - #arg = self.dub2args_dict[tag] - #args.append('-c %s="%s"' % (arg, value)) - - return args - - def process(self, source, metadata, options=None): - buffer_size = 0xFFFF - self.metadata= metadata - self.options = options - args = self.get_args() - args = ' '.join(args) - ext = self.file_extension() - temp_file = NamedTemporaryFile() - command = 'flac %s - -o %s ' % (args, temp_file.name) - - stream = self.core_process(command, source) - - for __chunk in stream: - #temp_file.write(__chunk) - #temp_file.flush() - pass - - #self.write_tags(temp_file.name) - - while True: - __chunk = temp_file.read(buffer_size) - if len(__chunk) == 0: - break - yield __chunk - - temp_file.close() - - diff --git a/encode/mp3.py b/encode/mp3.py deleted file mode 100644 index a29d369..0000000 --- a/encode/mp3.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Parisson SARL -# Copyright (c) 2006-2007 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from timeside.encode.core import * -from timeside.api import IEncoder - - -class Mp3Encoder(EncoderCore): - """Defines methods to encode to MP3""" - - implements(IEncoder) - - def __init__(self): - self.bitrate_default = '192' - self.dub2id3_dict = {'title': 'TIT2', #title2 - 'creator': 'TCOM', #composer - 'creator': 'TPE1', #lead - 'identifier': 'UFID', #Unique ID... - 'identifier': 'TALB', #album - 'type': 'TCON', #genre - 'publisher': 'TPUB', #comment - #'date': 'TYER', #year - } - self.dub2args_dict = {'title': 'tt', #title2 - 'creator': 'ta', #composerS - 'relation': 'tl', #album - #'type': 'tg', #genre - 'publisher': 'tc', #comment - 'date': 'ty', #year - } - - @staticmethod - def id(): - return "mp3enc" - - def format(self): - return 'MP3' - - def file_extension(self): - return 'mp3' - - def mime_type(self): - return 'audio/mpeg' - - def description(self): - return """ - MPEG-1 Audio Layer 3, more commonly referred to as MP3, is a patented - digital audio encoding format using a form of lossy data compression. - """ - - def get_file_info(self): - try: - file_out1, file_out2 = os.popen4('mp3info "'+self.dest+'"') - info = [] - for line in file_out2.readlines(): - info.append(clean_word(line[:-1])) - self.info = info - return self.info - except: - raise IOError('EncoderError: file does not exist.') - - def write_tags(self): - """Write all ID3v2.4 tags by mapping dub2id3_dict dictionnary with the - respect of mutagen classes and methods""" - from mutagen import id3 - id3 = id3.ID3(self.dest) - for tag in self.metadata.keys(): - if tag in self.dub2id3_dict.keys(): - frame_text = self.dub2id3_dict[tag] - value = self.metadata[tag] - frame = mutagen.id3.Frames[frame_text](3,value) - try: - id3.add(frame) - except: - raise IOError('EncoderError: cannot tag "'+tag+'"') - try: - id3.save() - except: - raise IOError('EncoderError: cannot write tags') - - def get_args(self): - """Get process options and return arguments for the encoder""" - args = [] - if not self.options is None: - if not ( 'verbose' in self.options and self.options['verbose'] != '0' ): - args.append('-S') - if 'mp3_bitrate' in self.options: - args.append('-b ' + self.options['mp3_bitrate']) - else: - args.append('-b '+self.bitrate_default) - #Copyrights, etc.. - args.append('-c -o') - else: - args.append('-S -c --tt "unknown" -o') - - for tag in self.metadata: - name = tag[0] - value = clean_word(tag[1]) - if name in self.dub2args_dict.keys(): - arg = self.dub2args_dict[name] - args.append('--' + arg + ' "' + value + '"') - return args - - def process(self, source, metadata, options=None): - self.metadata = metadata - self.options = options - args = self.get_args() - args = ' '.join(args) - command = 'lame %s - -' % args - - stream = self.core_process(command, source) - for __chunk in stream: - yield __chunk - diff --git a/encode/ogg.py b/encode/ogg.py deleted file mode 100644 index c0855f9..0000000 --- a/encode/ogg.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from timeside.encode.core import * -from timeside.api import IEncoder - -class OggVorbisEncoder(EncoderCore): - """Defines methods to encode to OGG Vorbis""" - - implements(IEncoder) - - def __init__(self): - self.bitrate_default = '192' - self.dub2args_dict = {'creator': 'artist', - 'relation': 'album' - } - - @staticmethod - def id(): - return "oggenc" - - def format(self): - return 'OggVorbis' - - def file_extension(self): - return 'ogg' - - def mime_type(self): - return 'application/ogg' - - def description(self): - return """ - Vorbis is a free software / open source project headed by the Xiph.Org - Foundation (formerly Xiphophorus company). The project produces an audio - format specification and software implementation (codec) for lossy audio - compression. Vorbis is most commonly used in conjunction with the Ogg - container format and it is therefore often referred to as Ogg Vorbis. - (source Wikipedia) - """ - - def get_file_info(self, file): - try: - file_out1, file_out2 = os.popen4('ogginfo "' + file + '"') - info = [] - for line in file_out2.readlines(): - info.append(clean_word(line[:-1])) - self.info = info - return self.info - except: - raise IOError('EncoderError: file does not exist.') - - def write_tags(self, file): - from mutagen.oggvorbis import OggVorbis - media = OggVorbis(file) - for tag in self.metadata.keys(): - media[tag] = str(self.metadata[tag]) - media.save() - - def get_args(self): - """Get process options and return arguments for the encoder""" - args = [] - if not self.options is None: - if not ('verbose' in self.options and self.options['verbose'] != '0'): - args.append('-Q ') - if 'ogg_bitrate' in self.options: - args.append('-b '+self.options['ogg_bitrate']) - elif 'ogg_quality' in self.options: - args.append('-q '+self.options['ogg_quality']) - else: - args.append('-b '+self.bitrate_default) - else: - args.append('-Q -b '+self.bitrate_default) - - for tag in self.metadata: - name = tag[0] - value = clean_word(tag[1]) - args.append('-c %s="%s"' % (name, value)) - if name in self.dub2args_dict.keys(): - arg = self.dub2args_dict[name] - args.append('-c %s="%s"' % (arg, value)) - return args - - def process(self, source, metadata, options=None): - self.metadata = metadata - self.options = options - args = self.get_args() - args = ' '.join(args) - command = 'oggenc %s -' % args - - stream = self.core_process(command, source) - for __chunk in stream: - yield __chunk - - diff --git a/encode/wav.py b/encode/wav.py deleted file mode 100644 index 1903050..0000000 --- a/encode/wav.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# 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 . - -# Author: Guillaume Pellerin - -import os -import string - -from timeside.encode.core import * -from timeside.api import IEncoder - -class WavEncoder(EncoderCore): - """Defines methods to encode to WAV""" - - implements(IEncoder) - - def __init__(self): - pass - - @staticmethod - 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 diff --git a/encoder/__init__.py b/encoder/__init__.py new file mode 100644 index 0000000..2818f2d --- /dev/null +++ b/encoder/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from timeside.encoder.core import * +from timeside.encoder.ogg import * +from timeside.encoder.wav import * +from timeside.encoder.mp3 import * +from timeside.encoder.flac import * diff --git a/encoder/core.py b/encoder/core.py new file mode 100644 index 0000000..3053165 --- /dev/null +++ b/encoder/core.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +from timeside.core import * + +import subprocess + +class SubProcessPipe: + """Read media and stream data through a generator. + Taken from Telemeta (see http://telemeta.org)""" + + def __init__(self, command, stdin=None): + self.buffer_size = 0xFFFF + if not stdin: + stdin = subprocess.PIPE + + self.proc = subprocess.Popen(command.encode('utf-8'), + shell = True, + bufsize = self.buffer_size, + stdin = stdin, + stdout = subprocess.PIPE, + close_fds = True) + + self.input = self.proc.stdin + self.output = self.proc.stdout + +class EncoderCore(Processor): + """Defines the main parts of the encoding tools : + paths, metadata parsing, data streaming thru system command""" + + def core_process(self, command, stdin): + """Encode and stream audio data through a generator""" + + proc = SubProcessPipe(command, stdin) + + while True: + __chunk = proc.output.read(proc.buffer_size) + #status = proc.poll() + #if status != None and status != 0: + #raise EncodeProcessError('Command failure:', command, proc) + if len(__chunk) == 0: + break + yield __chunk + + + diff --git a/encoder/flac.py b/encoder/flac.py new file mode 100644 index 0000000..0753441 --- /dev/null +++ b/encoder/flac.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +import os +import string +import subprocess + +from timeside.encoder.core import * +from timeside.api import IEncoder +from tempfile import NamedTemporaryFile + +class FlacEncoder(EncoderCore): + """Defines methods to encode to FLAC""" + + implements(IEncoder) + + def __init__(self): + self.quality_default = '-5' + self.dub2args_dict = {'creator': 'artist', + 'relation': 'album' + } + + @staticmethod + def id(): + return "flacenc" + + def format(self): + return 'FLAC' + + def file_extension(self): + return 'flac' + + def mime_type(self): + return 'audio/x-flac' + + def description(self): + return """ + Free Lossless Audio Codec (FLAC) is a file format for lossless audio + data compression. + """ + + def get_file_info(self): + try: + file1, file2 = os.popen4('metaflac --list "'+self.dest+'"') + info = [] + for line in file2.readlines(): + info.append(clean_word(line[:-1])) + self.info = info + return self.info + except: + raise IOError('EncoderError: metaflac is not installed or ' + \ + 'file does not exist.') + + def write_tags(self, file): + from mutagen.flac import FLAC + media = FLAC(file) + for tag in self.metadata: + name = tag[0] + value = clean_word(tag[1]) + if name in self.dub2args_dict.keys(): + name = self.dub2args_dict[name] + if name == 'comment': + media['DESCRIPTION'] = unicode(value) + else: + media[name] = unicode(value) + try: + media.save() + except: + raise IOError('EncoderError: cannot write tags.') + + + def get_args(self,options=None): + """Get process options and return arguments for the encoder""" + args = [] + if not options is None: + self.options = options + if not ('verbose' in self.options and self.options['verbose'] != '0'): + args.append('-s') + if 'flac_quality' in self.options: + args.append('-f ' + self.options['flac_quality']) + else: + args.append('-f ' + self.quality_default) + else: + args.append('-s -f ' + self.quality_default) + + #for tag in self.metadata.keys(): + #value = clean_word(self.metadata[tag]) + #args.append('-c %s="%s"' % (tag, value)) + #if tag in self.dub2args_dict.keys(): + #arg = self.dub2args_dict[tag] + #args.append('-c %s="%s"' % (arg, value)) + + return args + + def process(self, source, metadata, options=None): + buffer_size = 0xFFFF + self.metadata= metadata + self.options = options + args = self.get_args() + args = ' '.join(args) + ext = self.file_extension() + temp_file = NamedTemporaryFile() + command = 'flac %s - -o %s ' % (args, temp_file.name) + + stream = self.core_process(command, source) + + for __chunk in stream: + #temp_file.write(__chunk) + #temp_file.flush() + pass + + #self.write_tags(temp_file.name) + + while True: + __chunk = temp_file.read(buffer_size) + if len(__chunk) == 0: + break + yield __chunk + + temp_file.close() + + diff --git a/encoder/mp3.py b/encoder/mp3.py new file mode 100644 index 0000000..70f6dfb --- /dev/null +++ b/encoder/mp3.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Parisson SARL +# Copyright (c) 2006-2007 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +import os +import string +import subprocess + +from timeside.encoder.core import * +from timeside.api import IEncoder + + +class Mp3Encoder(EncoderCore): + """Defines methods to encode to MP3""" + + implements(IEncoder) + + def __init__(self): + self.bitrate_default = '192' + self.dub2id3_dict = {'title': 'TIT2', #title2 + 'creator': 'TCOM', #composer + 'creator': 'TPE1', #lead + 'identifier': 'UFID', #Unique ID... + 'identifier': 'TALB', #album + 'type': 'TCON', #genre + 'publisher': 'TPUB', #comment + #'date': 'TYER', #year + } + self.dub2args_dict = {'title': 'tt', #title2 + 'creator': 'ta', #composerS + 'relation': 'tl', #album + #'type': 'tg', #genre + 'publisher': 'tc', #comment + 'date': 'ty', #year + } + + @staticmethod + def id(): + return "mp3enc" + + def format(self): + return 'MP3' + + def file_extension(self): + return 'mp3' + + def mime_type(self): + return 'audio/mpeg' + + def description(self): + return """ + MPEG-1 Audio Layer 3, more commonly referred to as MP3, is a patented + digital audio encoding format using a form of lossy data compression. + """ + + def get_file_info(self): + try: + file_out1, file_out2 = os.popen4('mp3info "'+self.dest+'"') + info = [] + for line in file_out2.readlines(): + info.append(clean_word(line[:-1])) + self.info = info + return self.info + except: + raise IOError('EncoderError: file does not exist.') + + def write_tags(self): + """Write all ID3v2.4 tags by mapping dub2id3_dict dictionnary with the + respect of mutagen classes and methods""" + from mutagen import id3 + id3 = id3.ID3(self.dest) + for tag in self.metadata.keys(): + if tag in self.dub2id3_dict.keys(): + frame_text = self.dub2id3_dict[tag] + value = self.metadata[tag] + frame = mutagen.id3.Frames[frame_text](3,value) + try: + id3.add(frame) + except: + raise IOError('EncoderError: cannot tag "'+tag+'"') + try: + id3.save() + except: + raise IOError('EncoderError: cannot write tags') + + def get_args(self): + """Get process options and return arguments for the encoder""" + args = [] + if not self.options is None: + if not ( 'verbose' in self.options and self.options['verbose'] != '0' ): + args.append('-S') + if 'mp3_bitrate' in self.options: + args.append('-b ' + self.options['mp3_bitrate']) + else: + args.append('-b '+self.bitrate_default) + #Copyrights, etc.. + args.append('-c -o') + else: + args.append('-S -c --tt "unknown" -o') + + for tag in self.metadata: + name = tag[0] + value = clean_word(tag[1]) + if name in self.dub2args_dict.keys(): + arg = self.dub2args_dict[name] + args.append('--' + arg + ' "' + value + '"') + return args + + def process(self, source, metadata, options=None): + self.metadata = metadata + self.options = options + args = self.get_args() + args = ' '.join(args) + command = 'lame %s - -' % args + + stream = self.core_process(command, source) + for __chunk in stream: + yield __chunk + diff --git a/encoder/ogg.py b/encoder/ogg.py new file mode 100644 index 0000000..566c529 --- /dev/null +++ b/encoder/ogg.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +import os +import string +import subprocess + +from timeside.encoder.core import * +from timeside.api import IEncoder + +class OggVorbisEncoder(EncoderCore): + """Defines methods to encode to OGG Vorbis""" + + implements(IEncoder) + + def __init__(self): + self.bitrate_default = '192' + self.dub2args_dict = {'creator': 'artist', + 'relation': 'album' + } + + @staticmethod + def id(): + return "oggenc" + + def format(self): + return 'OggVorbis' + + def file_extension(self): + return 'ogg' + + def mime_type(self): + return 'application/ogg' + + def description(self): + return """ + Vorbis is a free software / open source project headed by the Xiph.Org + Foundation (formerly Xiphophorus company). The project produces an audio + format specification and software implementation (codec) for lossy audio + compression. Vorbis is most commonly used in conjunction with the Ogg + container format and it is therefore often referred to as Ogg Vorbis. + (source Wikipedia) + """ + + def get_file_info(self, file): + try: + file_out1, file_out2 = os.popen4('ogginfo "' + file + '"') + info = [] + for line in file_out2.readlines(): + info.append(clean_word(line[:-1])) + self.info = info + return self.info + except: + raise IOError('EncoderError: file does not exist.') + + def write_tags(self, file): + from mutagen.oggvorbis import OggVorbis + media = OggVorbis(file) + for tag in self.metadata.keys(): + media[tag] = str(self.metadata[tag]) + media.save() + + def get_args(self): + """Get process options and return arguments for the encoder""" + args = [] + if not self.options is None: + if not ('verbose' in self.options and self.options['verbose'] != '0'): + args.append('-Q ') + if 'ogg_bitrate' in self.options: + args.append('-b '+self.options['ogg_bitrate']) + elif 'ogg_quality' in self.options: + args.append('-q '+self.options['ogg_quality']) + else: + args.append('-b '+self.bitrate_default) + else: + args.append('-Q -b '+self.bitrate_default) + + for tag in self.metadata: + name = tag[0] + value = clean_word(tag[1]) + args.append('-c %s="%s"' % (name, value)) + if name in self.dub2args_dict.keys(): + arg = self.dub2args_dict[name] + args.append('-c %s="%s"' % (arg, value)) + return args + + def process(self, source, metadata, options=None): + self.metadata = metadata + self.options = options + args = self.get_args() + args = ' '.join(args) + command = 'oggenc %s -' % args + + stream = self.core_process(command, source) + for __chunk in stream: + yield __chunk + + diff --git a/encoder/wav.py b/encoder/wav.py new file mode 100644 index 0000000..4a7a90c --- /dev/null +++ b/encoder/wav.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin + +# 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 . + +# Author: Guillaume Pellerin + +import os +import string + +from timeside.encoder.core import * +from timeside.api import IEncoder + +class WavEncoder(EncoderCore): + """Defines methods to encode to WAV""" + + implements(IEncoder) + + def __init__(self): + pass + + @staticmethod + 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 diff --git a/grapher/core.py b/grapher/core.py index f90d71e..0c3ff70 100644 --- a/grapher/core.py +++ b/grapher/core.py @@ -48,7 +48,8 @@ color_schemes = { } -class SpectralCentroid(object): +class Spectrum(object): + """ FFT based frequency analysis of audio frames.""" def __init__(self, fft_size, nframes, samplerate, lower, higher, window_function=numpy.ones): self.fft_size = fft_size @@ -64,6 +65,8 @@ class SpectralCentroid(object): self.spectrum_adapter = FixedSizeInputAdapter(self.fft_size, 1, pad=True) def process(self, frames, eod, spec_range=120.0): + """ Returns a tuple containing the spectral centroid and the spectrum (dB scales) of the input audio frames. + An adapter is used to fix the buffer length and then provide fixed FFT window sizes.""" for buffer, end in self.spectrum_adapter.process(frames, True): samples = buffer[:,0].copy() @@ -92,7 +95,7 @@ class SpectralCentroid(object): def interpolate_colors(colors, flat=False, num_colors=256): - """ given a list of colors, create a larger list of colors interpolating + """ Given a list of colors, create a larger list of colors interpolating the first one. If flatten is True a list of numers will be returned. If False, a list of (r,g,b) tuples. num_colors is the number of colors wanted in the final list """ @@ -122,9 +125,11 @@ def interpolate_colors(colors, flat=False, num_colors=256): class WaveformImage(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, fft_size, bg_color=None, color_scheme=None, filename=None): - self.image_width = image_width self.image_height = image_height self.nframes = nframes @@ -148,7 +153,7 @@ class WaveformImage(object): self.lower = 500 self.higher = 16000 - self.spectral_centroid = SpectralCentroid(self.fft_size, self.nframes, self.samplerate, self.lower, self.higher, numpy.hanning) + self.spectrum = Spectrum(self.fft_size, self.nframes, self.samplerate, self.lower, self.higher, numpy.hanning) self.image = Image.new("RGB", (self.image_width, self.image_height), self.bg_color) self.pixel = self.image.load() @@ -158,9 +163,9 @@ class WaveformImage(object): self.pixel_cursor = 0 def peaks(self, samples): - """ read all samples between start_seek and end_seek, then find the minimum and maximum peak - in that range. Returns that pair in the order they were found. So if min was found first, - it returns (min, max) else the other way around. """ + """ 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] @@ -232,7 +237,7 @@ class WaveformImage(object): buffer = frames[:,0].copy() buffer.shape = (len(frames),1) - (spectral_centroid, db_spectrum) = self.spectral_centroid.process(buffer, True) + (spectral_centroid, db_spectrum) = self.spectrum.process(buffer, True) for samples, end in self.pixels_adapter.process(buffer, eod): if self.pixel_cursor < self.image_width: peaks = self.peaks(samples) @@ -240,6 +245,7 @@ class WaveformImage(object): self.pixel_cursor += 1 def save(self): + """ Apply last 2D transforms and write all pixels to the file. """ a = 25 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])) @@ -247,6 +253,8 @@ class WaveformImage(object): 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.""" def __init__(self, image_width, image_height, nframes, samplerate, fft_size, bg_color=None, color_scheme='default', filename=None): self.image_width = image_width @@ -268,7 +276,7 @@ class SpectrogramImage(object): self.lower = 20 self.higher = 16000 - self.spectral_centroid = SpectralCentroid(self.fft_size, self.nframes, self.samplerate, self.lower, self.higher, numpy.hanning) + self.spectrum = Spectrum(self.fft_size, self.nframes, self.samplerate, self.lower, self.higher, numpy.hanning) # generate the lookup which translates y-coordinate to fft-bin self.y_to_bin = [] @@ -309,11 +317,12 @@ class SpectrogramImage(object): # FIXME : breaks spectrum linearity for samples, end in self.pixels_adapter.process(buffer, eod): if self.pixel_cursor < self.image_width: - (spectral_centroid, db_spectrum) = self.spectral_centroid.process(samples, False) + (spectral_centroid, db_spectrum) = self.spectrum.process(samples, False) self.draw_spectrum(self.pixel_cursor, db_spectrum) self.pixel_cursor += 1 def save(self): + """ Apply last 2D transforms and write all pixels to the file. """ self.image.putdata(self.pixels) self.image.transpose(Image.ROTATE_90).save(self.filename) @@ -322,6 +331,7 @@ class Noise(object): """A class that mimics audiolab.sndfile but generates noise instead of reading a wave file. Additionally it can be told to have a "broken" header and thus crashing in the middle of the file. Also useful for testing ultra-short files of 20 samples.""" + def __init__(self, num_frames, has_broken_header=False): self.seekpoint = 0 self.num_frames = num_frames diff --git a/tests/test.py b/tests/test.py index 6e762ed..c2b007b 100755 --- a/tests/test.py +++ b/tests/test.py @@ -12,13 +12,7 @@ class TestAnalyzers: def list(self): analyzers = [] - for analyzer_class in self.analyzers: - # FIXME: should access the name, id and unit member statically - # there should be no need to instantiate analyzer_class - # eg: access directly analyzer_class.name(), etc... - # - # This remark is true at many places in this file - analyzer = analyzer_class() + for analyzer in self.analyzers: analyzers.append({'name':analyzer.name(), 'id':analyzer.id(), 'unit':analyzer.unit(), @@ -27,8 +21,7 @@ class TestAnalyzers: def run(self, media): print '\n=== Analyzer testing ===\n' - for analyzer_class in self.analyzers: - analyzer = analyzer_class() + for analyzer in self.analyzers: id = analyzer.id() value = analyzer.render(media) print id + ' = ' + str(value) + ' ' + analyzer.unit() @@ -39,8 +32,7 @@ class TestDecoders: def list(self): decoders_list = [] - for decoder_class in self.decoders: - decoder = decoder_class() + for decoder in self.decoders: decoders_list.append({'format': decoder.format(), 'mime_type': decoder.mime_type(), 'file_extension': decoder.file_extension(), @@ -48,8 +40,7 @@ class TestDecoders: print decoders_list def get_decoder(self, mime_type): - for decoder_class in self.decoders: - decoder = decoder_class() + for decoder in self.decoders: if decoder.mime_type() == mime_type: return decoder @@ -76,24 +67,21 @@ class TestEncoders: def list(self): encoders = [] - for encoder_class in self.encoders: - encoder = encoder_class() + for encoder in self.encoders: encoders.append({'format': encoder.format(), 'mime_type': encoder.mime_type(), }) print encoders def get_encoder(self, mime_type): - for encoder_class in self.encoders: - encoder = encoder_class() + for encoder in self.encoders: if encoder.mime_type() == mime_type: return encoder def run(self, source, metadata): print '\n=== Encoder testing ===\n' - for encoder_class in self.encoders: + for encoder in self.encoders: print '==================================' - encoder = encoder_class() mime = mimetype(source) format = encoder.format() decoders = TestDecoders() @@ -115,8 +103,7 @@ class TestGraphers: def list(self): graphers = [] - for grapher_class in self.graphers: - grapher = grapher_class() + for grapher in self.graphers: graphers.append({'id':grapher.id(), 'name':grapher.name(), }) @@ -124,8 +111,7 @@ class TestGraphers: def run(self, media): print '\n=== Grapher testing ===\n' - for grapher_class in self.graphers: - grapher = grapher_class() + for grapher in self.graphers: id = grapher.id() image = grapher.render(media) file_path = 'results/'+id+'.png' @@ -135,6 +121,7 @@ class TestGraphers: print 'Image exported to :' + file_path file.close() + def mimetype(path): if hasattr(magic, "Magic"): if not hasattr(mimetype, "magic"): @@ -155,13 +142,13 @@ if __name__ == '__main__': a = TestAnalyzers() d = TestDecoders() e = TestEncoders() - #g = TestGraphers() + g = TestGraphers() a.list() d.list() e.list() - #g.list() + g.list() a.run(sample) - #g.run(sample) + g.run(sample) e.run(sample, metadata) d.export('samples/')