From: yomguy Date: Wed, 7 Oct 2009 13:17:34 +0000 (+0000) Subject: rename analysis X-Git-Tag: 0.3.2~249 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=40b35b684fd677ede354c833d1ae82ce2c946a1b;p=timeside.git rename analysis --- diff --git a/analysis/__init__.py b/analysis/__init__.py deleted file mode 100644 index 0c2c597..0000000 --- a/analysis/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -from timeside.analysis.api import * -from timeside.analysis.core import * -from timeside.analysis.channels import * -from timeside.analysis.format import * -from timeside.analysis.encoding import * -from timeside.analysis.resolution import * -from timeside.analysis.samplerate import * -from timeside.analysis.duration import * -from timeside.analysis.max_level import * -from timeside.analysis.mean_level import * -from timeside.analysis.dc import * - - diff --git a/analysis/api.py b/analysis/api.py deleted file mode 100644 index 634ef2a..0000000 --- a/analysis/api.py +++ /dev/null @@ -1,41 +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 * - -class IMediaItemAnalyzer(Interface): - """Media item analyzer driver interface""" - - def get_id(): - """Return a short id alphanumeric, lower-case string.""" - - def get_name(): - """Return the analysis name, such as "Mean Level", "Max level", - "Total length, etc.. - """ - - def get_unit(): - """Return the unit of the data such as "dB", "seconds", etc... - """ - - def render(media_item, options=None): - """Return the result data of the process""" - diff --git a/analysis/channels.py b/analysis/channels.py deleted file mode 100644 index 5f1c2ee..0000000 --- a/analysis/channels.py +++ /dev/null @@ -1,47 +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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class ChannelAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "nb_channels" - - def get_name(self): - return "Channels" - - def get_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/analysis/core.py b/analysis/core.py deleted file mode 100644 index 9c2a2ce..0000000 --- a/analysis/core.py +++ /dev/null @@ -1,199 +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 - -class AudioProcessor(Component): - - 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.file.path - 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/analysis/dc.py b/analysis/dc.py deleted file mode 100644 index c15a6f8..0000000 --- a/analysis/dc.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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class MeanDCShiftAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "dc" - - def get_name(self): - return "Mean DC shift" - - def get_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/analysis/duration.py b/analysis/duration.py deleted file mode 100644 index 9428021..0000000 --- a/analysis/duration.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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy -import datetime - -class DurationAnalyzer(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "duration" - - def get_name(self): - return "Duration" - - def get_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/analysis/encoding.py b/analysis/encoding.py deleted file mode 100644 index 897ff04..0000000 --- a/analysis/encoding.py +++ /dev/null @@ -1,42 +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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class EncodingAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "encoding" - - def get_name(self): - return "Encoding format" - - def get_unit(self): - return "" - - def render(self, media_item, options=None): - self.pre_process(media_item) - return self.encoding diff --git a/analysis/format.py b/analysis/format.py deleted file mode 100644 index 480ce00..0000000 --- a/analysis/format.py +++ /dev/null @@ -1,42 +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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class FormatAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "format" - - def get_name(self): - return "File format" - - def get_unit(self): - return "" - - def render(self, media_item, options=None): - self.pre_process(media_item) - return self.format diff --git a/analysis/max_level.py b/analysis/max_level.py deleted file mode 100644 index a54e460..0000000 --- a/analysis/max_level.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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class MaxLevelAnalyzer(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "max_level" - - def get_name(self): - return "Maximum peak level" - - def get_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) \ No newline at end of file diff --git a/analysis/mean_level.py b/analysis/mean_level.py deleted file mode 100644 index b7696ed..0000000 --- a/analysis/mean_level.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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class MeanLevelAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "mean_level" - - def get_name(self): - return "Mean RMS level" - - def get_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/analysis/resolution.py b/analysis/resolution.py deleted file mode 100644 index 98c2901..0000000 --- a/analysis/resolution.py +++ /dev/null @@ -1,51 +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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class ResolutionAnalyser(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "resolution" - - def get_name(self): - return "Resolution" - - def get_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 '' \ No newline at end of file diff --git a/analysis/samplerate.py b/analysis/samplerate.py deleted file mode 100644 index 0970fcb..0000000 --- a/analysis/samplerate.py +++ /dev/null @@ -1,42 +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.analysis.core import * -from timeside.analysis.api import IMediaItemAnalyzer -import numpy - -class SampleRateAnalyzer(AudioProcessor): - """Media item analyzer driver interface""" - - implements(IMediaItemAnalyzer) - - def get_id(self): - return "samplerate" - - def get_name(self): - return "Samplerate" - - def get_unit(self): - return "Hz" - - def render(self, media_item, options=None): - self.pre_process(media_item) - return self.samplerate diff --git a/analysis/vamp/__init__.py b/analysis/vamp/__init__.py deleted file mode 100644 index 1dc0d67..0000000 --- a/analysis/vamp/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from timeside.analysis.vamp.core import * diff --git a/analysis/vamp/core.py b/analysis/vamp/core.py deleted file mode 100644 index bc32def..0000000 --- a/analysis/vamp/core.py +++ /dev/null @@ -1,129 +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.analysis.api import IMediaItemAnalyzer -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 get_id(self): - return "vamp_plugins" - - def get_name(self): - return "Vamp plugins" - - def get_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 VampProcessError('Command failure:', command, proc) - - # Core processing - while True: - __chunk = proc.stdout.read(buffer_size) - status = proc.poll() - if status != None and status != 0: - raise VampProcessError('Command failure:', command, proc) - if len(__chunk) == 0: - break - yield __chunk - - -class VampProcessError(TimeSideError): - - def __init__(self, message, command, subprocess): - self.message = message - self.command = str(command) - self.subprocess = subprocess - - def __str__(self): - if self.subprocess.stderr != None: - error = self.subprocess.stderr.read() - else: - error = '' - return "%s ; command: %s; error: %s" % (self.message, - self.command, - error) diff --git a/analyze/__init__.py b/analyze/__init__.py new file mode 100644 index 0000000..0c2c597 --- /dev/null +++ b/analyze/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from timeside.analysis.api import * +from timeside.analysis.core import * +from timeside.analysis.channels import * +from timeside.analysis.format import * +from timeside.analysis.encoding import * +from timeside.analysis.resolution import * +from timeside.analysis.samplerate import * +from timeside.analysis.duration import * +from timeside.analysis.max_level import * +from timeside.analysis.mean_level import * +from timeside.analysis.dc import * + + diff --git a/analyze/api.py b/analyze/api.py new file mode 100644 index 0000000..634ef2a --- /dev/null +++ b/analyze/api.py @@ -0,0 +1,41 @@ +# -*- 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 * + +class IMediaItemAnalyzer(Interface): + """Media item analyzer driver interface""" + + def get_id(): + """Return a short id alphanumeric, lower-case string.""" + + def get_name(): + """Return the analysis name, such as "Mean Level", "Max level", + "Total length, etc.. + """ + + def get_unit(): + """Return the unit of the data such as "dB", "seconds", etc... + """ + + def render(media_item, options=None): + """Return the result data of the process""" + diff --git a/analyze/channels.py b/analyze/channels.py new file mode 100644 index 0000000..5f1c2ee --- /dev/null +++ b/analyze/channels.py @@ -0,0 +1,47 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class ChannelAnalyser(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "nb_channels" + + def get_name(self): + return "Channels" + + def get_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 new file mode 100644 index 0000000..9c2a2ce --- /dev/null +++ b/analyze/core.py @@ -0,0 +1,199 @@ +# -*- 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 + +class AudioProcessor(Component): + + 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.file.path + 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 new file mode 100644 index 0000000..c15a6f8 --- /dev/null +++ b/analyze/dc.py @@ -0,0 +1,43 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class MeanDCShiftAnalyser(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "dc" + + def get_name(self): + return "Mean DC shift" + + def get_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 new file mode 100644 index 0000000..9428021 --- /dev/null +++ b/analyze/duration.py @@ -0,0 +1,44 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy +import datetime + +class DurationAnalyzer(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "duration" + + def get_name(self): + return "Duration" + + def get_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 new file mode 100644 index 0000000..897ff04 --- /dev/null +++ b/analyze/encoding.py @@ -0,0 +1,42 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class EncodingAnalyser(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "encoding" + + def get_name(self): + return "Encoding format" + + def get_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 new file mode 100644 index 0000000..480ce00 --- /dev/null +++ b/analyze/format.py @@ -0,0 +1,42 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class FormatAnalyser(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "format" + + def get_name(self): + return "File format" + + def get_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 new file mode 100644 index 0000000..a54e460 --- /dev/null +++ b/analyze/max_level.py @@ -0,0 +1,43 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class MaxLevelAnalyzer(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "max_level" + + def get_name(self): + return "Maximum peak level" + + def get_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) \ No newline at end of file diff --git a/analyze/mean_level.py b/analyze/mean_level.py new file mode 100644 index 0000000..b7696ed --- /dev/null +++ b/analyze/mean_level.py @@ -0,0 +1,43 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class MeanLevelAnalyser(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "mean_level" + + def get_name(self): + return "Mean RMS level" + + def get_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 new file mode 100644 index 0000000..98c2901 --- /dev/null +++ b/analyze/resolution.py @@ -0,0 +1,51 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class ResolutionAnalyser(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "resolution" + + def get_name(self): + return "Resolution" + + def get_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 '' \ No newline at end of file diff --git a/analyze/samplerate.py b/analyze/samplerate.py new file mode 100644 index 0000000..0970fcb --- /dev/null +++ b/analyze/samplerate.py @@ -0,0 +1,42 @@ +# -*- 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.analysis.core import * +from timeside.analysis.api import IMediaItemAnalyzer +import numpy + +class SampleRateAnalyzer(AudioProcessor): + """Media item analyzer driver interface""" + + implements(IMediaItemAnalyzer) + + def get_id(self): + return "samplerate" + + def get_name(self): + return "Samplerate" + + def get_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 new file mode 100644 index 0000000..1dc0d67 --- /dev/null +++ b/analyze/vamp/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from timeside.analysis.vamp.core import * diff --git a/analyze/vamp/core.py b/analyze/vamp/core.py new file mode 100644 index 0000000..bc32def --- /dev/null +++ b/analyze/vamp/core.py @@ -0,0 +1,129 @@ +# -*- 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.analysis.api import IMediaItemAnalyzer +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 get_id(self): + return "vamp_plugins" + + def get_name(self): + return "Vamp plugins" + + def get_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 VampProcessError('Command failure:', command, proc) + + # Core processing + while True: + __chunk = proc.stdout.read(buffer_size) + status = proc.poll() + if status != None and status != 0: + raise VampProcessError('Command failure:', command, proc) + if len(__chunk) == 0: + break + yield __chunk + + +class VampProcessError(TimeSideError): + + def __init__(self, message, command, subprocess): + self.message = message + self.command = str(command) + self.subprocess = subprocess + + def __str__(self): + if self.subprocess.stderr != None: + error = self.subprocess.stderr.read() + else: + error = '' + return "%s ; command: %s; error: %s" % (self.message, + self.command, + error)