From 5939683961866bbe7572da8d71354260e89276b9 Mon Sep 17 00:00:00 2001 From: yomguy <> Date: Mon, 30 Aug 2010 01:13:13 +0000 Subject: [PATCH] - now depends on TimeSide for the web audio components - removed old processors based on audiolab & subprocess pipes (bye bye) - analyzers are working but removed *temporaly* from templates - improve caching for image and audio media streaming - fixed many bugs around timeside, BE CAREFUL, needed more tests for 0.5.2 --- INSTALL | 31 +- telemeta/__init__.py | 1 - telemeta/analysis/__init__.py | 13 - telemeta/analysis/api.py | 54 -- telemeta/analysis/channels.py | 60 -- telemeta/analysis/core.py | 196 ------ telemeta/analysis/dc.py | 56 -- telemeta/analysis/duration.py | 57 -- telemeta/analysis/encoding.py | 55 -- telemeta/analysis/format.py | 55 -- telemeta/analysis/max_level.py | 56 -- telemeta/analysis/mean_level.py | 56 -- telemeta/analysis/resolution.py | 64 -- telemeta/analysis/samplerate.py | 55 -- telemeta/analysis/vamp/__init__.py | 1 - telemeta/analysis/vamp/core.py | 110 ---- telemeta/export/__init__.py | 6 - telemeta/export/api.py | 110 ---- telemeta/export/core.py | 290 --------- telemeta/export/flac.py | 182 ------ telemeta/export/mp3.py | 182 ------ telemeta/export/ogg.py | 156 ----- telemeta/export/wav.py | 171 ------ telemeta/models/media.py | 10 +- .../templates/telemeta_default/index.html | 4 +- .../telemeta_default/mediaitem_detail.html | 17 +- telemeta/urls.py | 6 +- telemeta/visualization/__init__.py | 4 - telemeta/visualization/api.py | 54 -- telemeta/visualization/octave/jet.m | 29 - telemeta/visualization/octave/spectrogram.m | 198 ------ .../visualization/octave/spectrogram2img.m | 81 --- telemeta/visualization/octave/waveform2img.m | 80 --- telemeta/visualization/octave_core.py | 103 ---- telemeta/visualization/old/spectrogram.py | 50 -- telemeta/visualization/old/spectrogram3.py | 59 -- telemeta/visualization/old/waveform.py | 55 -- telemeta/visualization/old/waveform2.py | 41 -- telemeta/visualization/old/waveform4.py | 59 -- telemeta/visualization/snack_core.py | 47 -- .../visualization/spectrogram_audiolab.py | 84 --- telemeta/visualization/spectrogram_octave.py | 64 -- telemeta/visualization/tkSnack.py | 579 ------------------ telemeta/visualization/wav2png.py | 436 ------------- telemeta/visualization/waveform_audiolab.py | 86 --- telemeta/web/__init__.py | 1 + telemeta/web/base.py | 183 ++++-- 47 files changed, 149 insertions(+), 4198 deletions(-) delete mode 100644 telemeta/analysis/__init__.py delete mode 100644 telemeta/analysis/api.py delete mode 100644 telemeta/analysis/channels.py delete mode 100644 telemeta/analysis/core.py delete mode 100644 telemeta/analysis/dc.py delete mode 100644 telemeta/analysis/duration.py delete mode 100644 telemeta/analysis/encoding.py delete mode 100644 telemeta/analysis/format.py delete mode 100644 telemeta/analysis/max_level.py delete mode 100644 telemeta/analysis/mean_level.py delete mode 100644 telemeta/analysis/resolution.py delete mode 100644 telemeta/analysis/samplerate.py delete mode 100644 telemeta/analysis/vamp/__init__.py delete mode 100644 telemeta/analysis/vamp/core.py delete mode 100644 telemeta/export/__init__.py delete mode 100644 telemeta/export/api.py delete mode 100644 telemeta/export/core.py delete mode 100644 telemeta/export/flac.py delete mode 100644 telemeta/export/mp3.py delete mode 100644 telemeta/export/ogg.py delete mode 100644 telemeta/export/wav.py delete mode 100644 telemeta/visualization/__init__.py delete mode 100644 telemeta/visualization/api.py delete mode 100644 telemeta/visualization/octave/jet.m delete mode 100644 telemeta/visualization/octave/spectrogram.m delete mode 100644 telemeta/visualization/octave/spectrogram2img.m delete mode 100644 telemeta/visualization/octave/waveform2img.m delete mode 100644 telemeta/visualization/octave_core.py delete mode 100644 telemeta/visualization/old/spectrogram.py delete mode 100644 telemeta/visualization/old/spectrogram3.py delete mode 100644 telemeta/visualization/old/waveform.py delete mode 100644 telemeta/visualization/old/waveform2.py delete mode 100644 telemeta/visualization/old/waveform4.py delete mode 100644 telemeta/visualization/snack_core.py delete mode 100644 telemeta/visualization/spectrogram_audiolab.py delete mode 100644 telemeta/visualization/spectrogram_octave.py delete mode 100755 telemeta/visualization/tkSnack.py delete mode 100755 telemeta/visualization/wav2png.py delete mode 100644 telemeta/visualization/waveform_audiolab.py diff --git a/INSTALL b/INSTALL index 2e45ef4b..d327105b 100644 --- a/INSTALL +++ b/INSTALL @@ -10,11 +10,9 @@ The following third party applications or libraries are required: :dependencies: python (>= 2.3.5-7), python-xml, python-mutagen, python-django (>= 1.0-1), python-imaging (>= 1.1.6), sox (>= 14.2), vorbis-tools, flac, normalize-audio, - python-mysqldb, mysql-server, octave2.9, python-tk, libgd2-xpm, - libsndfile1 (>= 1.0.17), python-numpy, python-ctypes (>= 1.0.1), - python-scikits-audiolab (>= 0.10), python-setuptools (>= 0.6b3), - python-support (>= 0.3), python-scipy, lame (>= 3.98.2), - python-docutils (>= 0.5) + python-mysqldb, mysql-server, python-timeside (>= 0.2), python-numpy, python-scipy + python-ctypes (>= 1.0.1), python-setuptools (>= 0.6b3), + python-support (>= 0.3), python-docutils (>= 0.5) :optional: ecasound, festival, par2 @@ -65,16 +63,22 @@ Install $ sudo python setup.py install -1.2. Install audiolab +1.2. Install TimeSide --------------------- -This is ONLY needed if you did NOT install telemeta with Debian's apt-get. +In order to get all the Web Audio Components from TimeSide, +you have to download and install it from source. -In order to get the wavforms of the audio files, -python-audiolab have to be installed with the help of git:: +So, checkout the last archives at : +http://code.google.com/p/timeside/downloads/list + +Install it like in the following example:: + +$ tar xzf timeside-0.2.tar.gz +$ cd timeside-0.2 + +Read the README and INSTALL file, install dependencies and then:: -$ git clone git://github.com/cournape/audiolab.git -$ cd audiolab/ $ sudo python setup.py install @@ -123,7 +127,7 @@ Modifiy the following variables: :DATABASE_*: your database settings (don't forget to create the database if needed) :MEDIA_ROOT: absolute path to the media directory you just created :INSTALLED_APPS: add 'telemeta' - :TEMPLATE_CONTEXT_PROCESSORS: include 'django.core.context_processors.request' and + :TEMPLATE_CONTEXT_PROCESSORS: include 'django.core.context_processors.request' and 'django.core.context_processors.auth' in this tuple Add the following variables: @@ -133,13 +137,14 @@ Add the following variables: as "Ethnology", etc... :TELEMETA_CACHE_DIR: absolute path to the cache directory that you just created :TELEMETA_GMAP_KEY: your Google Map API key - :TELEMETA_DOWNLOAD_ENABLED: True to enable audio data download + :TELEMETA_DOWNLOAD_ENABLED: True to enable audio data download Just paste the lines below:: LOGIN_URL = '/login' LOGIN_REDIRECT_URL = '/' TELEMETA_EXPORT_CACHE_DIR = TELEMETA_CACHE_DIR + "/export" + TELEMETA_DATA_CACHE_DIR = TELEMETA_CACHE_DIR + "/data" CACHE_BACKEND = "file://" + TELEMETA_CACHE_DIR + "/data" diff --git a/telemeta/__init__.py b/telemeta/__init__.py index f2462bde..f95b4649 100644 --- a/telemeta/__init__.py +++ b/telemeta/__init__.py @@ -2,7 +2,6 @@ """ Telemeta -Parisson SARL U{http://telemeta.org} diff --git a/telemeta/analysis/__init__.py b/telemeta/analysis/__init__.py deleted file mode 100644 index 080b2749..00000000 --- a/telemeta/analysis/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from telemeta.analysis.api import * -from telemeta.analysis.core import * -from telemeta.analysis.channels import * -from telemeta.analysis.format import * -from telemeta.analysis.encoding import * -from telemeta.analysis.resolution import * -from telemeta.analysis.samplerate import * -from telemeta.analysis.duration import * -from telemeta.analysis.max_level import * -from telemeta.analysis.mean_level import * -from telemeta.analysis.dc import * - - diff --git a/telemeta/analysis/api.py b/telemeta/analysis/api.py deleted file mode 100644 index 302506c9..00000000 --- a/telemeta/analysis/api.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.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/telemeta/analysis/channels.py b/telemeta/analysis/channels.py deleted file mode 100644 index 2e905823..00000000 --- a/telemeta/analysis/channels.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL -# -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/core.py b/telemeta/analysis/core.py deleted file mode 100644 index 9ecf85a0..00000000 --- a/telemeta/analysis/core.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# Authors: -# Bram de Jong -# Guillaume Pellerin - -from django.conf import settings -from telemeta.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/telemeta/analysis/dc.py b/telemeta/analysis/dc.py deleted file mode 100644 index e9e9fa5c..00000000 --- a/telemeta/analysis/dc.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/duration.py b/telemeta/analysis/duration.py deleted file mode 100644 index cebdd6d4..00000000 --- a/telemeta/analysis/duration.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/encoding.py b/telemeta/analysis/encoding.py deleted file mode 100644 index b06c04df..00000000 --- a/telemeta/analysis/encoding.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/format.py b/telemeta/analysis/format.py deleted file mode 100644 index b2019c4e..00000000 --- a/telemeta/analysis/format.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/max_level.py b/telemeta/analysis/max_level.py deleted file mode 100644 index a33831de..00000000 --- a/telemeta/analysis/max_level.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/mean_level.py b/telemeta/analysis/mean_level.py deleted file mode 100644 index 8f3aba8a..00000000 --- a/telemeta/analysis/mean_level.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/resolution.py b/telemeta/analysis/resolution.py deleted file mode 100644 index 1d375fcb..00000000 --- a/telemeta/analysis/resolution.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/samplerate.py b/telemeta/analysis/samplerate.py deleted file mode 100644 index 6daae7f9..00000000 --- a/telemeta/analysis/samplerate.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.analysis.core import * -from telemeta.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/telemeta/analysis/vamp/__init__.py b/telemeta/analysis/vamp/__init__.py deleted file mode 100644 index 80988760..00000000 --- a/telemeta/analysis/vamp/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from telemeta.analysis.vamp.core import * diff --git a/telemeta/analysis/vamp/core.py b/telemeta/analysis/vamp/core.py deleted file mode 100644 index 242e49b3..00000000 --- a/telemeta/analysis/vamp/core.py +++ /dev/null @@ -1,110 +0,0 @@ - -from telemeta.core import * -from django.conf import settings -from tempfile import NamedTemporaryFile -from telemeta.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(TelemetaError): - - 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/telemeta/export/__init__.py b/telemeta/export/__init__.py deleted file mode 100644 index 6ba2ef16..00000000 --- a/telemeta/export/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from telemeta.export.api import * -from telemeta.export.core import * -from telemeta.export.ogg import * -from telemeta.export.flac import * -from telemeta.export.wav import * -from telemeta.export.mp3 import * \ No newline at end of file diff --git a/telemeta/export/api.py b/telemeta/export/api.py deleted file mode 100644 index 8e8b77e1..00000000 --- a/telemeta/export/api.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2009 Parisson -# Copyright (c) 2007 Olivier Guilyardi -# Copyright (c) 2007-2009 Guillaume Pellerin -# -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. - -from telemeta.core import Interface, TelemetaError - - -class IExporter(Interface): - """Export driver interface""" - - # Remark: the method prototypes do not include any self or cls argument - # because an interface is meant to show what methods a class must expose - # from the caller's point of view. However, when implementing the class - # you'll obviously want to include this extra argument. - - def get_format(): - """Return the export/encoding format as a short string - Example: "MP3", "OGG", "AVI", ... - """ - - def get_description(): - """Return a string describing what this export format provides, is good - for, etc... The description is meant to help the end user decide what - format is good for him/her - """ - - def get_file_extension(): - """Return the filename extension corresponding to this export format""" - - def get_mime_type(): - """Return the mime type corresponding to this export format""" - - def set_cache_dir(path): - """Set the directory where cached files should be stored. Does nothing - if the exporter doesn't support caching. - - The driver shouldn't assume that this method will always get called. A - temporary directory should be used if that's not the case. - """ - - def process(item_id, source, metadata, options=None): - """Perform the exporting process and return the absolute path - to the resulting file. - - item_id is the media item id that uniquely identifies this audio/video - resource - - source is the audio/video source file absolute path. For audio that - should be a WAV file - - metadata is a tuple containing tuples for each descriptor return by - the dc.Ressource of the item, in the model order : - ((name1, value1),(name2, value2),(name1, value3), ...) - - The returned file path is not meant to be permanent in any way, it - should be considered temporary/volatile by the caller. - - It is highly recommended that export drivers implement some sort of - cache instead of re-encoding each time process() is called. - - It should be possible to make subsequent calls to process() with - different items, using the same driver instance. - """ - -class ExportProcessError(TelemetaError): - - 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/telemeta/export/core.py b/telemeta/export/core.py deleted file mode 100644 index 2b08ff49..00000000 --- a/telemeta/export/core.py +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. - -import os -import re -import md5 -import string -import subprocess -import mutagen - -import telemeta.export -from telemeta.export import * -from telemeta.core import * -import xml.dom.minidom -import xml.dom.ext - -class ExporterCore(Component): - """Defines the main parts of the exporting tools : - paths, metadata parsing, data streaming thru system command""" - - def __init__(self): - self.source = '' - self.collection = '' - self.verbose = '' - self.dest = '' - self.metadata = [] - self.cache_dir = 'cache' - self.buffer_size = 0xFFFF - - def set_cache_dir(self,path): - self.cache_dir = path - - def normalize(self): - """ Normalize the source and return its path """ - args = '' - if self.verbose == '0': - args = '-q' - try: - os.system('normalize-audio '+args+' "'+self.source+'"') - return self.source - except: - raise IOError('ExporterError: cannot normalize, path does not exist.') - - def check_md5_key(self): - """ Check if the md5 key is OK and return a boolean """ - try: - md5_log = os.popen4('md5sum -c "'+self.dest+ \ - '" "'+self.dest+'.md5"') - return 'OK' in md5_log.split(':') - except IOError: - raise IOError('ExporterError: cannot check the md5 key.') - - def get_file_info(self): - """ Return the list of informations of the dest """ - return self.export.get_file_info() - - def get_wav_length_sec(self) : - """ Return the length of the audio source file in seconds """ - try: - file1, file2 = os.popen4('wavinfo "'+self.source+ \ - '" | grep wavDataSize') - for line in file2.readlines(): - line_split = line.split(':') - value = int(int(line_split[1])/(4*44100)) - return value - except: - raise IOError('ExporterError: cannot get the wav length.') - - def compare_md5_key(self, source, dest): - """ Compare source and dest files wih md5 method """ - f_source = open(source).read() - f_dest = open(dest).read() - return md5.new(f_source).digest() == md5.new(f_dest).digest() - - def write_metadata_xml(self,path): - doc = xml.dom.minidom.Document() - root = doc.createElement('telemeta') - doc.appendChild(root) - for tag in self.metadata.keys() : - value = self.metadata[tag] - node = doc.createElement(tag) - node.setAttribute('value', str(value)) - #node.setAttribute('type', get_type(value)) - root.appendChild(node) - xml_file = open(path, "w") - xml.dom.ext.PrettyPrint(doc, xml_file) - xml_file.close() - - def pre_process(self, item_id, source, metadata, ext, - cache_dir, options=None): - """ Pre processing : prepare the export path and return it""" - self.item_id = str(item_id) - self.source = source - file_name = get_file_name(self.source) - file_name_wo_ext, file_ext = split_file_name(file_name) - self.cache_dir = cache_dir - self.metadata = metadata - #self.collection = self.metadata['Collection'] - #self.artist = self.metadata['Artist'] - #self.title = self.metadata['Title'] - - # Normalize if demanded - if not options is None: - self.options = options - if 'normalize' in self.options and \ - self.options['normalize'] == True: - self.normalize() - - # Define the export directory - self.ext = self.get_file_extension() - export_dir = os.path.join(self.cache_dir,self.ext) - - if not os.path.exists(export_dir): - export_dir_split = export_dir.split(os.sep) - path = os.sep + export_dir_split[0] - for _dir in export_dir_split[1:]: - path = os.path.join(path,_dir) - if not os.path.exists(path): - os.mkdir(path) - else: - path = export_dir - - # Set the target file - target_file = self.item_id+'.'+self.ext - dest = os.path.join(path,target_file) - return dest - - def core_process(self, command, buffer_size, dest): - """Encode and stream audio data through a generator""" - - __chunk = 0 - file_out = open(dest,'w') - - proc = subprocess.Popen(command.encode('utf-8'), - shell = True, - bufsize = buffer_size, - stdin = subprocess.PIPE, - stdout = subprocess.PIPE, - close_fds = True) - - # Core processing - while True: - __chunk = proc.stdout.read(buffer_size) - status = proc.poll() - if status != None and status != 0: - raise ExportProcessError('Command failure:', command, proc) - if len(__chunk) == 0: - break - yield __chunk - file_out.write(__chunk) - - file_out.close() - - def post_process(self, item_id, source, metadata, ext, - cache_dir, options=None): - """ Post processing : write tags, print infos, etc...""" - #self.write_tags() - if not options is None: - if 'verbose' in self.options and self.options['verbose'] != '0': - print self.dest - print self.get_file_info() - - -# External functions - -def get_type(value): - """ Return a String with the type of value """ - types = {bool : 'bool', int : 'int', str : 'str'} - # 'bool' type must be placed *before* 'int' type, otherwise booleans are - # detected as integers - for type in types.keys(): - if isinstance(value, type) : - return types[type] - raise TypeError(str(value) + ' has an unsupported type') - -def get_cast(value, type) : - """ Return value, casted into type """ - if type == 'bool' : - if value == 'True' : - return True - return False - elif type == 'int' : - return int(value) - elif type == 'str' : - return str(value) - raise TypeError(type + ' is an unsupported type') - -def get_file_mime_type(path): - """ Return the mime type of a file """ - try: - file_out1, file_out2 = os.popen4('file -i "'+path+'"') - for line in file_out2.readlines(): - line_split = line.split(': ') - mime = line_split[len(line_split)-1] - return mime[:len(mime)-1] - except: - raise IOError('ExporterError: path does not exist.') - -def get_file_type_desc(path): - """ Return the type of a file given by the 'file' command """ - try: - file_out1, file_out2 = os.popen4('file "'+path+'"') - for line in file_out2.readlines(): - description = line.split(': ') - description = description[1].split(', ') - return description - except: - raise IOError('ExporterError: path does not exist.') - -def iswav(path): - """ Tell if path is a WAV """ - try: - mime = get_file_mime_type(path) - return mime == 'audio/x-wav' - except: - raise IOError('ExporterError: path does not exist.') - -def iswav16(path): - """ Tell if path is a 16 bit WAV """ - try: - file_type_desc = get_file_type_desc(path) - return iswav(path) and '16 bit' in file_type_desc - except: - raise IOError('ExporterError: path does not exist.') - -def get_file_name(path): - """ Return the file name targeted in the path """ - return os.path.split(path)[1] - -def split_file_name(file): - """ Return main file name and its extension """ - try: - return os.path.splitext(file) - except: - raise IOError('ExporterError: path does not exist.') - -def clean_word(word) : - """ Return the word without excessive blank spaces, underscores and - characters causing problem to exporters""" - word = re.sub("^[^\w]+","",word) #trim the beginning - word = re.sub("[^\w]+$","",word) #trim the end - word = re.sub("_+","_",word) #squeeze continuous _ to one _ - word = re.sub("^[^\w]+","",word) #trim the beginning _ - #word = string.replace(word,' ','_') - #word = string.capitalize(word) - dict = '&[];"*:,' - for letter in dict: - word = string.replace(word,letter,'_') - return word - -def recover_par_key(path): - """ Recover a file with par2 key """ - os.system('par2 r "'+path+'"') - -def verify_par_key(path): - """ Verify a par2 key """ - os.system('par2 v "'+path+'.par2"') - - diff --git a/telemeta/export/flac.py b/telemeta/export/flac.py deleted file mode 100644 index 6282d05f..00000000 --- a/telemeta/export/flac.py +++ /dev/null @@ -1,182 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. - -# Author: Guillaume Pellerin - -import os -import string -import subprocess - -from telemeta.export.core import * -from telemeta.export.api import IExporter -from mutagen.flac import FLAC -from tempfile import NamedTemporaryFile - -class FlacExporter(ExporterCore): - """Defines methods to export to FLAC""" - - implements(IExporter) - - def __init__(self): - self.item_id = '' - self.source = '' - self.metadata = {} - self.options = {} - self.description = '' - self.dest = '' - self.quality_default = '-5' - self.info = [] - self.buffer_size = 0xFFFF - - def get_format(self): - return 'FLAC' - - def get_file_extension(self): - return 'flac' - - def get_mime_type(self): - return 'application/flac' - - def get_description(self): - return 'FIXME' - - 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('ExporterError: metaflac is not installed or ' + \ - 'file does not exist.') - - def set_cache_dir(self,path): - """Set the directory where cached files should be stored. Does nothing - if the exporter doesn't support caching. - - The driver shouldn't assume that this method will always get called. A - temporary directory should be used if that's not the case. - """ - self.cache_dir = path - - def decode(self): - try: - file_name, ext = get_file_name(self.source) - dest = self.cache_dir+os.sep+file_name+'.wav' - os.system('flac -d -o "'+dest+'" "'+self.source+'"') - self.source = dest - return dest - except: - raise IOError('ExporterError: decoder is not compatible.') - - def write_tags(self, file): - media = FLAC(file) - for tag in self.metadata: - name = tag[0] - value = clean_word(tag[1]) - if name == 'COMMENT': - media['DESCRIPTION'] = unicode(value) - else: - media[name] = unicode(value) - try: - media.save() - except: - raise IOError('ExporterError: 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, item_id, source, metadata, options=None): - self.item_id = item_id - self.source = source - self.metadata = metadata - self.args = self.get_args(options) - self.ext = self.get_file_extension() - self.args = ' '.join(self.args) - self.command = 'sox "%s" --single-threaded -q -b 16 -t wav -c2 - | flac -c %s - ' % (self.source, self.args) - - # Pre-proccessing - self.dest = self.pre_process(self.item_id, - self.source, - self.metadata, - self.ext, - self.cache_dir, - self.options) - - # Processing (streaming + cache writing) - stream = self.core_process(self.command, self.buffer_size, self.dest) - - for chunk in stream: - pass - - self.write_tags(self.dest) - file = open(self.dest,'r') - - while True: - chunk = file.read(self.buffer_size) - if len(chunk) == 0: - break - yield chunk - - file.close() - - # Post-proccessing - #self.post_process(self.item_id, - #self.source, - #self.metadata, - #self.ext, - #self.cache_dir, - #self.options) - diff --git a/telemeta/export/mp3.py b/telemeta/export/mp3.py deleted file mode 100644 index e5317be7..00000000 --- a/telemeta/export/mp3.py +++ /dev/null @@ -1,182 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Parisson SARL -# Copyright (c) 2006-2007 Guillaume Pellerin - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. - -import os -import string -import subprocess - -from telemeta.export.core import * -from telemeta.export.api import IExporter -#from mutagen.id3 import * - - -class Mp3Exporter(ExporterCore): - """Defines methods to export to MP3""" - - implements(IExporter) - - def __init__(self): - self.item_id = '' - self.metadata = {} - self.description = '' - self.info = [] - self.source = '' - self.dest = '' - self.options = {} - self.bitrate_default = '192' - self.buffer_size = 0xFFFF - 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 - } - def get_format(self): - return 'MP3' - - def get_file_extension(self): - return 'mp3' - - def get_mime_type(self): - return 'audio/mpeg' - - def get_description(self): - return "FIXME" - - def set_cache_dir(self,path): - self.cache_dir = path - - 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('ExporterError: file does not exist.') - - def decode(self): - try: - os.system('sox "'+self.source+'" -s -q -r 44100 -t wav "' \ - +self.cache_dir+os.sep+self.item_id+'"') - return self.cache_dir+os.sep+self.item_id+'.wav' - except: - raise IOError('ExporterError: decoder is not compatible.') - - 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('ExporterError: cannot tag "'+tag+'"') - try: - id3.save() - except: - raise IOError('ExporterError: 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 '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 -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, item_id, source, metadata, options=None): - self.item_id = item_id - self.source = source - self.metadata = metadata - self.args = self.get_args(options) - self.ext = self.get_file_extension() - self.args = ' '.join(self.args) - self.command = 'sox "%s" --single-threaded -q -b 16 -t wav - | lame %s -' % (self.source, self.args) - #self.command = 'lame %s "%s" -' % (self.args, self.source) - - # Pre-proccessing - self.dest = self.pre_process(self.item_id, - self.source, - self.metadata, - self.ext, - self.cache_dir, - self.options) - - # Processing (streaming + cache writing) - stream = self.core_process(self.command, self.buffer_size, self.dest) - for chunk in stream: - yield chunk - - # Post-proccessing - #self.post_process(self.item_id, - #self.source, - #self.metadata, - #self.ext, - #self.cache_dir, - #self.options) - diff --git a/telemeta/export/ogg.py b/telemeta/export/ogg.py deleted file mode 100644 index d2328fc2..00000000 --- a/telemeta/export/ogg.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Parisson -# Copyright (c) 2006-2009 Guillaume Pellerin - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. - -import os -import string -import subprocess - -from telemeta.export.core import * -from telemeta.export.api import IExporter -from mutagen.oggvorbis import OggVorbis - -class OggExporter(ExporterCore): - """Defines methods to export to OGG Vorbis""" - - implements(IExporter) - - def __init__(self): - self.item_id = '' - self.metadata = {} - self.description = '' - self.info = [] - self.source = '' - self.dest = '' - self.options = {} - self.bitrate_default = '192' - self.buffer_size = 0xFFFF - self.dub2args_dict = {'creator': 'artist', - 'relation': 'album' - } - - def get_format(self): - return 'OGG' - - def get_file_extension(self): - return 'ogg' - - def get_mime_type(self): - return 'application/ogg' - - def get_description(self): - return 'FIXME' - - 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('ExporterError: file does not exist.') - - def set_cache_dir(self,path): - self.cache_dir = path - - def decode(self): - try: - os.system('oggdec -o "'+self.cache_dir+os.sep+self.item_id+ - '.wav" "'+self.source+'"') - return self.cache_dir+os.sep+self.item_id+'.wav' - except: - raise IOError('ExporterError: decoder is not compatible.') - - def write_tags(self): - media = OggVorbis(self.dest) - for tag in self.metadata.keys(): - media[tag] = str(self.metadata[tag]) - media.save() - - 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('-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, item_id, source, metadata, options=None): - self.item_id = item_id - self.source = source - self.metadata = metadata - self.args = self.get_args(options) - self.ext = self.get_file_extension() - self.args = ' '.join(self.args) - self.command = 'sox "%s" --single-threaded -q -b 16 -t wav -c2 - | oggenc %s -' % (self.source, self.args) - - # Pre-proccessing - self.dest = self.pre_process(self.item_id, - self.source, - self.metadata, - self.ext, - self.cache_dir, - self.options) - - # Processing (streaming + cache writing) - stream = self.core_process(self.command, self.buffer_size, self.dest) - for chunk in stream: - yield chunk - - # Post-proccessing - #self.post_process(self.item_id, - #self.source, - #self.metadata, - #self.ext, - #self.cache_dir, - #self.options) - diff --git a/telemeta/export/wav.py b/telemeta/export/wav.py deleted file mode 100644 index bfad553a..00000000 --- a/telemeta/export/wav.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright Guillaume Pellerin, (2006-2009) -# - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. - -import os -import string - -from telemeta.export.core import * -from telemeta.export.api import IExporter - -class WavExporter(ExporterCore): - """Defines methods to export to WAV""" - - implements(IExporter) - - def __init__(self): - self.item_id = '' - self.metadata = {} - self.description = '' - self.info = [] - self.source = '' - self.dest = '' - self.options = {} - self.buffer_size = 0xFFFF - - def get_format(self): - return 'WAV' - - def get_file_extension(self): - return 'wav' - - def get_mime_type(self): - return 'audio/x-wav' - - def get_description(self): - return 'FIXME' - - 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('ExporterError: wavinfo id not installed or file does not exist.') - - def set_cache_dir(self,path): - self.cache_dir = path - - def decode(self): - try: - file_name, ext = get_file_name(self.source) - dest = self.cache_dir+os.sep+file_name+'.wav' - os.system('sox "'+self.source+'" -s -r 44100 -t wav -c2 "'+ \ - dest+'.wav"') - self.source = dest - return dest - except: - raise IOError('ExporterError: decoder is not compatible.') - - def write_tags(self): - # Create metadata XML file ! - self.write_metadata_xml(self.dest+'.xml') - - def create_md5_key(self): - """ Create the md5 keys of the dest """ - try: - os.system('md5sum -b "'+self.dest+'" >"'+self.dest+'.md5"') - except: - raise IOError('ExporterError: cannot create the md5 key.') - - def create_par_key(self): - """ Create the par2 keys of the dest """ - args = 'c -n1 ' - if 'verbose' in self.options and self.options['verbose'] != '0': - args = args - else: - args = args + '-q -q ' - - try: - os.system('par2 '+args+' "'+self.dest+'"') - except: - raise IOError('ExporterError: cannot create the par2 key.') - - def process(self, item_id, source, metadata, options=None): - self.item_id = item_id - self.source = source - self.metadata = metadata - self.options = {} - - if not options is None: - self.options = options - - # Pre-proccessing - self.ext = self.get_file_extension() - self.dest = self.pre_process(self.item_id, - self.source, - self.metadata, - self.ext, - self.cache_dir, - self.options) - - # Initializing - file_in = open(self.source,'rb') - file_out = open(self.dest,'w') - - # Core Processing - while True: - chunk = file_in.read(self.buffer_size) - if len(chunk) == 0: - break - yield chunk - file_out.write(chunk) - - file_in.close() - file_out.close() - - # Create the md5 key - #if 'md5' in self.metadata and self.metadata['md5']: - self.create_md5_key() - - # Create the par2 key - #if 'par2' in self.metadata and self.metadata['par2']: - #self.create_par_key() - - # Pre-proccessing - self.post_process(self.item_id, - self.source, - self.metadata, - self.ext, - self.cache_dir, - self.options) - - - - #if self.compare_md5_key(): - #os.system('cp -a "'+self.source+'" "'+ self.dest+'"') - #print 'COPIED' - diff --git a/telemeta/models/media.py b/telemeta/models/media.py index a86f8782..a33c519d 100644 --- a/telemeta/models/media.py +++ b/telemeta/models/media.py @@ -282,15 +282,7 @@ class MediaItem(MediaResource): def computed_duration(self): "Tell the length in seconds of this item media data" - # FIXME: use TimeSide? - seconds = 0 - if self.file: - import wave - media = wave.open(self.file.path, "rb") - seconds = media.getnframes() / media.getframerate() - media.close() - - return Duration(seconds=seconds) + return self.approx_duration computed_duration.verbose_name = _('computed duration') diff --git a/telemeta/templates/telemeta_default/index.html b/telemeta/templates/telemeta_default/index.html index ea9f7caa..0fb65cb0 100644 --- a/telemeta/templates/telemeta_default/index.html +++ b/telemeta/templates/telemeta_default/index.html @@ -20,11 +20,11 @@
  • {{item}}
    - {{item.computed_duration}} {{item.apparent_collector|prepend:' - '}} - {{item.country_or_continent}} + {{item.apparent_collector|prepend:' - '}} - {{item.country_or_continent}}
  • {% endfor %} - + {% endblock %} diff --git a/telemeta/templates/telemeta_default/mediaitem_detail.html b/telemeta/templates/telemeta_default/mediaitem_detail.html index 2e3bb841..6249c488 100644 --- a/telemeta/templates/telemeta_default/mediaitem_detail.html +++ b/telemeta/templates/telemeta_default/mediaitem_detail.html @@ -22,7 +22,7 @@ soundManager.url = '{% url telemeta-swf "./" %}'; soundManager.flashVersion = 9; soundManager.debugMode = false; set_player_image_url('{% url telemeta-item-visualize item.public_id,visualizer_id,"WIDTH","HEIGHT" %}'); -load_player({{ item.computed_duration.as_seconds }}); +load_player({{ item.approx_duration.as_seconds }}); {% endblock %} @@ -52,10 +52,10 @@ load_player({{ item.computed_duration.as_seconds }});
    -
    + @@ -79,7 +79,7 @@ load_player({{ item.computed_duration.as_seconds }}); -
    + +
    {% if audio_export_enabled %} @@ -230,7 +230,6 @@ load_player({{ item.computed_duration.as_seconds }});
    {% trans "Media type" %}
    {% trans "Audio" %}
    {% dl_field item "approx_duration" %} - {% dl_field item "computed_duration" %}
    diff --git a/telemeta/urls.py b/telemeta/urls.py index 336205cd..3fc26b39 100644 --- a/telemeta/urls.py +++ b/telemeta/urls.py @@ -36,7 +36,7 @@ from django.conf.urls.defaults import * from telemeta.models import MediaItem, MediaCollection -from telemeta.core import ComponentManager +#from telemeta.core import ComponentManager from telemeta.web import WebView import os.path import telemeta.config @@ -44,8 +44,8 @@ import telemeta.config telemeta.config.check() # initialization -comp_mgr = ComponentManager() -web_view = WebView(comp_mgr) +#comp_mgr = ComponentManager() +web_view = WebView() # query sets for Django generic views all_items = { 'queryset': MediaItem.objects.enriched(), } diff --git a/telemeta/visualization/__init__.py b/telemeta/visualization/__init__.py deleted file mode 100644 index 7b46c19a..00000000 --- a/telemeta/visualization/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from telemeta.visualization.api import * -from telemeta.visualization.waveform_audiolab import * -from telemeta.visualization.spectrogram_audiolab import * -from telemeta.visualization.spectrogram_octave import * diff --git a/telemeta/visualization/api.py b/telemeta/visualization/api.py deleted file mode 100644 index aa7b4e9f..00000000 --- a/telemeta/visualization/api.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2007 Samalyse SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Olivier Guilyardi - -from telemeta.core import * - -class IMediaItemVisualizer(Interface): - """Media item visualizer driver interface""" - - def get_id(): - """Return a short id alphanumeric, lower-case string.""" - - def get_name(): - """Return the visualization name, such as "Waveform", "Spectral view", - etc.. - """ - - def set_colors(self, background=None, scheme=None): - """Set the colors used for image generation. background is a RGB tuple, - and scheme a a predefined color theme name""" - pass - - def render(media_item, width=None, height=None, options=None): - """Generator that streams the visualization output as a PNG image""" diff --git a/telemeta/visualization/octave/jet.m b/telemeta/visualization/octave/jet.m deleted file mode 100644 index ba9f742e..00000000 --- a/telemeta/visualization/octave/jet.m +++ /dev/null @@ -1,29 +0,0 @@ -function J = jet(m) -%JET Variant of HSV -% JET(M), a variant of HSV(M), is an M-by-3 matrix containing -% the default colormap used by CONTOUR, SURF and PCOLOR. -% The colors begin with dark blue, range through shades of -% blue, cyan, green, yellow and red, and end with dark red. -% JET, by itself, is the same length as the current figure's -% colormap. If no figure exists, MATLAB creates one. -% -% See also HSV, HOT, PINK, FLAG, COLORMAP, RGBPLOT. - -% Copyright 1984-2004 The MathWorks, Inc. -% $Revision: 5.7.4.2 $ $Date: 2005/06/21 19:31:40 $ - -if nargin < 1 - m = size(get(gcf,'colormap'),1); -end -n = ceil(m/4); -u = [(1:1:n)/n ones(1,n-1) (n:-1:1)/n]'; -g = ceil(n/2) - (mod(m,4)==1) + (1:length(u))'; -r = g + n; -b = g - n; -g(g>m) = []; -r(r>m) = []; -b(b<1) = []; -J = zeros(m,3); -J(r,1) = u(1:length(r)); -J(g,2) = u(1:length(g)); -J(b,3) = u(end-length(b)+1:end); \ No newline at end of file diff --git a/telemeta/visualization/octave/spectrogram.m b/telemeta/visualization/octave/spectrogram.m deleted file mode 100644 index 3b9e071b..00000000 --- a/telemeta/visualization/octave/spectrogram.m +++ /dev/null @@ -1,198 +0,0 @@ -## Copyright (C) 2000 Paul Kienzle -## -## This program is free software and may be used for any purpose. This -## copyright notice must be maintained. Paul Kienzle is not responsible -## for the consequences of using this software. - -## usage: [S, f, t] = spectrogram(x, Fs, window, step, maxF, shape, minE) -## -## Generate a spectrogram for the signal. This chops the signal into -## overlapping slices, windows each slice and applies a Fourier -## transform to determine the frequency components at that slice. -## -## x: signal to analyse -## Fs: sampling rate for the signal -## window: analysis window length (default 30 msec) -## step: time between windows, start to start (default 5 ms) -## maxF: maximum frequency to display (default 4000 Hz) -## Alternatively, use [maxF, nF], where nF is the minimum -## of frequency points to display. If nF is greater than -## what it would normally be for the given window size and -## maximum displayed frequency, the FFT is zero-padded until -## it at least nF points are displayed on the y axis. -## shape: window analysis function (default 'hanning') -## Shape is any function which takes an integer n and returns -## a vector of length n. If shape contains %d and ends with -## ')', as for example '(1:%d)' or 'kaiser(%d,0.5)' do, then -## %d is replaced with the desired window length, and the -## expression is evaluated. -## minE: noise floor (default -40dB) -## Any value less than the noise floor is clipped before the -## spectrogram is displayed. This limits the dynamic range -## that your spectrogram must accomodate. Alternatively, -## use [minE, maxE], where maxE is the clipping ceiling, also -## in decibels. -## -## Return values -## S is the spectrogram in S with linear magnitude normalized to 1. -## f is the frequency indices corresponding to the rows of S. -## t is the time indices corresponding to the columns of S. -## If no return value is requested, the spectrogram is displayed instead. -## -## Global variables -## spectrogram_{window,step,maxF,nF,shape,minE,maxE} can override -## the default values with your own. -## -## To make a good spectrogram, generating spectral slices is only half -## the problem. Before you generate them, you must first choose your -## window size, step size and FFT size. A wide window shows more -## harmonic detail, a narrow window shows more formant structure. This -## defines your time-frequency resolution. Step size controls the -## horizontal scale of the spectrogram. Decrease it to stretch, or -## increase it to compress. Certainly, increasing step size will reduce -## time resolution, but decreasing it will not improve it much beyond -## the limits imposed by the window size (you do gain a little bit, -## depending on the shape of your window, as the peak of the window -## slides over peaks in the signal energy). The range 1-5 msec is good -## for speech. Finally, FFT length controls the vertical scale, with -## larger values stretching the frequency range. Clearly, padding with -## zeros does not add any information to the spectrum, but it is a -## cheap, easy and good way to interpolate between frequency points, and -## can make for prettier spectrograms. -## -## After you have generated the spectral slices, there are a number of -## decisions for displaying them. Firstly, the entire frequency range -## does not need to be displayed. The frequency range of the FFT is -## determined by sampling rate. If most of your signal is below 4 kHz -## (in speech for example), there is no reason to display up to the -## Nyquist frequency of 10 kHz for a 20 kHz sampling rate. Next, there -## is the dynamic range of the signal. Since the information in speech -## is well above the noise floor, it makes sense to eliminate any -## dynamic range at the bottom end. This is done by taking the max of -## the normalized magnitude and some lower limit such as -40 dB. -## Similarly, there is not much information in the very top of the -## range, so clipping to -3 dB makes sense there. Finally, there is the -## choice of colormap. A brightness varying colormap such as copper or -## bone gives good shape to the ridges and valleys. A hue varying -## colormap such as jet or hsv gives an indication of the steepness of -## the slopes. - -## TODO: Accept vector of frequencies at which to sample the signal. -## TODO: Consider accepting maxF (values > 0), shape (value is string) -## TODO: and dynamic range (values <= 0) in any order. -## TODO: Consider defaulting step and maxF so that the spectrogram is -## TODO: an appropriate size for the screen (eg, 600x100). -## TODO: Consider drawing in frequency/time grid; -## TODO: (necessary with automatic sizing as suggested above) -## TODO: Consider using step vs. [nT, nF] rather than maxF vs [maxF, nF] -## TODO: Figure out why exist() is so slow: 50 ms vs 1 ms for lookup. - -function [S_r, f_r, t_r] = spectrogram(x, Fs, window, step, maxF, shape, minE) - global spectrogram_window=30; - global spectrogram_step=5; - global spectrogram_maxF=4000; - global spectrogram_shape="hanning"; - global spectrogram_minE=-40; - global spectrogram_maxE=0; - global spectrogram_nF=[]; - - if nargin < 2 || nargin > 7 - usage ("[S, f, t] = spectrogram(x, fs, window, step, maxF, shape, minE)"); - end - - if nargin<3 || isempty(window), - window=spectrogram_window; - endif - if nargin<4 || isempty(step), - step=spectrogram_step; - endif - if nargin<5 || isempty(maxF), - maxF=spectrogram_maxF; - endif - if nargin<6 || isempty(shape), - shape=spectrogram_shape; - endif - if nargin<7 || isempty(minE), - minE=spectrogram_minE; - endif - if any(minE>0) - error ("spectrogram clipping range must use values less than 0 dB"); - endif - if length(minE)>1, - maxE=minE(2); - minE=minE(1); - else - maxE = spectrogram_maxE; - endif - if length(maxF)>1, - min_nF=maxF(2); - maxF=maxF(1); - else - min_nF=spectrogram_nF; - endif - - ## make sure x is a column vector - if size(x,2) != 1 && size(x,1) != 1 - error ("spectrogram data must be a vector"); - end - if size(x,2) != 1, x = x'; end - - if (maxF>Fs/2) - ## warning("spectrogram: cannot display frequencies greater than Fs/2"); - maxF = Fs/2; - endif - - step_n = fix(step*Fs/1000); # one spectral slice every step ms - - ## generate window from duration and shape function name - win_n = fix(window*Fs/1000); - if shape(length(shape)) == ')' - shape = sprintf(shape, win_n); - else - shape = sprintf("%s(%d)", shape, win_n); - endif - win_vec = eval(strcat(shape,";")); - if size(win_vec,2) != 1, win_vec = win_vec'; endif - if size(win_vec,2) != 1 || size(win_vec,1) != win_n, - error("spectrogram %s did not return a window of length %d", \ - shape, win_n); - endif - - ## FFT length from size of window and number of freq. pts requested - fft_n = 2^nextpow2(win_n); # next highest power of 2 - dF = Fs/fft_n; # freq. step with current fft_n - nF = ceil(maxF(1)/dF); # freq. pts with current fft_n,maxF - if !isempty(min_nF) # make sure there are at least n freq. pts - if min_nF > nF, # if not enough - dF = maxF/min_nF; # figure out what freq. step we need - fft_n = 2^nextpow2(Fs/dF); # figure out what fft_n this requires - dF = Fs/fft_n; # freq. step with new fft_n - nF = ceil(maxF/dF); # freq. pts with new fft_n,maxF - endif - endif - - ## build matrix of windowed data slices - offset = 1:step_n:length(x)-win_n; - S = zeros (fft_n, length(offset)); - for i=1:length(offset) - S(1:win_n, i) = x(offset(i):offset(i)+win_n-1) .* win_vec; - endfor - - ## compute fourier transform - S = fft (S); - S = abs(S(1:nF,:)); # select the desired frequencies - S = S/max(S(:)); # normalize magnitude so that max is 0 dB. - S = max(S, 10^(minE/10)); # clip below minF dB. - S = min(S, 10^(maxE/10)); # clip above maxF dB. - - f = [0:nF-1]*Fs/fft_n; - t = offset/Fs; - if nargout==0 - imagesc(f,t,20*log10(flipud(S))); - else - S_r = S; - f_r = f; - t_r = t; - endif - -endfunction \ No newline at end of file diff --git a/telemeta/visualization/octave/spectrogram2img.m b/telemeta/visualization/octave/spectrogram2img.m deleted file mode 100644 index 74b13092..00000000 --- a/telemeta/visualization/octave/spectrogram2img.m +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# Author: Guillaume Pellerin -# -# SpectrogramVisualizer2.m -# -# Depends: octave2.9, octave2.9-forge, spectrogram.m, xloadimage, imagemagick - -clear all; -close all; - -dest_image = $IMGFILE; -wav_file = $WAVFILE; -octave_path = $OCTAVEPATH; - -cd(octave_path); -ncmap = 128; % number of points for colormap -step = 6; % spectral slice period (ms) -% step_length = fix(5*Fs/1000); -window = 30; % filter window (ms) -% window = fix(40*Fs/1000); -time_limit = 30; % length limit of the displayed sample (s) - -[x, Fs] = wavread(wav_file); -x = x(:,1); % mono -lx = length(x); - -% LIMITING time -lx_lim = Fs.*time_limit; -if lx > lx_lim; - x = x(1:lx_lim); -end - -%fftn = 2^nextpow2(window); % next highest power of 2 -[S, f, t] = spectrogram(x, Fs, window, step, 8000, 'hanning', -30); -S = flipud(20*log10(S)); -% -% cmap = [0:1:ncmap-1]; -% map_cos = cos(cmap*3.141/(2*ncmap)); -% map_lin = cmap./ncmap; -% map_one = ones(1,ncmap); -% -% cmap = [ [map_cos]' [map_cos]' [fliplr(map_cos)]' ]; -% colormap(jet(ncmap)); -cmap = colormap(jet(ncmap)); - -img = imagesc(t, f, S); -saveimage(dest_image, img, 'ppm', cmap); - -quit; - diff --git a/telemeta/visualization/octave/waveform2img.m b/telemeta/visualization/octave/waveform2img.m deleted file mode 100644 index 936dee34..00000000 --- a/telemeta/visualization/octave/waveform2img.m +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin -# -# SpectrogramVisualizer2.m -# -# Depends: octave2.9, octave2.9-forge - -clear all; -close all; - -dest_image = $IMGFILE; -wav_file = $WAVFILE; -octave_path = $OCTAVEPATH; - -cd(octave_path); -ncmap = 128; % number of points for colormap -step = 6; % spectral slice period (ms) -% step_length = fix(5*Fs/1000); -window = 30; % filter window (ms) -% window = fix(40*Fs/1000); -time_limit = 300; % length limit of the displayed sample (s) -% Downsampling factor -D = 100; - -% Read audio data -[x, Fs] = wavread(wav_file); -x = x(:,1); % mono -lx = length(x); - -% LIMITING time -lx_lim = Fs.*time_limit; -if lx > lx_lim; - x = x(1:lx_lim); -end -N = length(x); - -% Downsampling by D -t = 1:1:lx; -t = (t-1)./Fs; -x2(1:ceil(N/D)) = x(1:D:N); -t2(1:ceil(N/D)) = t(1:D:N); -%x(ceil(N/D)+1:N) = zeros(1,N-ceil(N/D)); - -img = plot(t2,x2); -print(dest_image, '-dpng'); - -quit; - diff --git a/telemeta/visualization/octave_core.py b/telemeta/visualization/octave_core.py deleted file mode 100644 index 788860f2..00000000 --- a/telemeta/visualization/octave_core.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2007-2009 Guillaume Pellerin - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -from telemeta.core import * -#from telemeta.visualization.api import IMediaItemVisualizer -from django.conf import settings -from tempfile import NamedTemporaryFile -import os -import random -import subprocess -import signal -import time - -class OctaveCoreVisualizer(Component): - """Parent class for Octave visualization drivers""" - - def get_mFile_line(self): - octave_path = os.path.dirname(__file__) + '/octave/' - mFile_path = os.path.dirname(__file__) + '/octave/' + self.mFile - mFile = open(mFile_path,'r') - - for line in mFile.readlines(): - if '$OCTAVEPATH' in line: - line = line.replace('$OCTAVEPATH','"'+octave_path+'"') - if '$WAVFILE' in line: - line = line.replace('$WAVFILE','"'+self.wavFile_path+'"') - if '$IMGFILE' in line: - line = line.replace('$IMGFILE','"'+self.ppmFile.name+'"') - yield line - - mFile.close() - - def set_m_file(self,mFile): - self.mFile = mFile - - def get_wav_path(self, media_item): - self.wavFile_path = media_item.file.path - - def octave_to_png_stream(self, media_item): - self.buffer_size = 0xFFFF - self.trans_type = 'ppm' - self.mat_type = 'm' - self.ppmFile = NamedTemporaryFile(suffix='.'+self.trans_type) - self.wavFile = self.get_wav_path(media_item) - mFile_tmp = NamedTemporaryFile(suffix='.'+self.mat_type) - mFile_name = mFile_tmp.name - mFile_tmp.close() - mFile_tmp = open(mFile_name,'w') - self.pngFile = NamedTemporaryFile(suffix='.png') - command = ['octave', mFile_name] - - for line in self.get_mFile_line(): - mFile_tmp.write(line) - mFile_tmp.close() - - # Compute - proc = subprocess.Popen(command, stdout = subprocess.PIPE) - proc.wait() - - # Convert - os.system('convert ' + self.ppmFile.name + \ - ' -scale x250 ' + self.pngFile.name) - - # Stream - while True: - buffer = self.pngFile.read(self.buffer_size) - if len(buffer) == 0: - break - yield buffer - - self.ppmFile.close() - self.pngFile.close() - os.remove(mFile_name) - - diff --git a/telemeta/visualization/old/spectrogram.py b/telemeta/visualization/old/spectrogram.py deleted file mode 100644 index f4f0bfc3..00000000 --- a/telemeta/visualization/old/spectrogram.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2007 Samalyse SARL -# All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://svn.parisson.org/telemeta/TelemetaLicense. -# -# Author: Olivier Guilyardi - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from telemeta.visualization.snack_core import SnackCoreVisualizer - -class SpectrogramVisualizer(SnackCoreVisualizer): - """Spectral view visualization driver""" - - implements(IMediaItemVisualizer) - - # possible alternative: - # http://jokosher.python-hosting.com/file/jokosher-extra/Waveform.py - - def get_id(self): - return "spectrogram" - - def get_name(self): - return "Spectrogram 1" - - def set_colors(self, background=None, scheme=None): - pass - - def render(self, media_item, options=None): - """Generator that streams the spectral view as a PNG image""" - - canvas = self.get_snack_canvas() - snd = self.get_snack_sound(media_item) - - canvas.create_spectrogram(0, 10, sound=snd, height=180, width=300 , - windowtype="hamming", fftlength=1024, topfrequency=5000, channel="all", winlength=64) - - stream = self.canvas_to_png_stream(canvas) - - return stream - - - - - - - - diff --git a/telemeta/visualization/old/spectrogram3.py b/telemeta/visualization/old/spectrogram3.py deleted file mode 100644 index f970e470..00000000 --- a/telemeta/visualization/old/spectrogram3.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (C) 2008 Parisson SARL -# All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://svn.parisson.org/telemeta/TelemetaLicense. -# -# Author: Guillaume Pellerin - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from django.conf import settings -from tempfile import NamedTemporaryFile -from telemeta.visualization.wav2png import * - -class SpectrogramVisualizer3(Component): - """Spectrogram visualization driver (python style)""" - - implements(IMediaItemVisualizer) - - bg_color = None - color_scheme = None - - def get_id(self): - return "spectrogram3" - - def get_name(self): - return "Spectrogram (audiolab)" - - def set_colors(self, background=None, scheme=None): - self.bg_color = background - self.color_scheme = scheme - - def render(self, media_item, width=None, height=None, options=None): - """Generator that streams the spectrogram as a PNG image with a python method""" - - wav_file = media_item.file.path - pngFile = NamedTemporaryFile(suffix='.png') - - if not width == None: - image_width = width - else: - image_width = 305 - if not height == None: - image_height = height - else: - image_height = 150 - - fft_size = 2048 - args = (wav_file, pngFile.name, image_width, image_height, fft_size, - self.bg_color, self.color_scheme) - create_spectrogram_png(*args) - - buffer = pngFile.read(0xFFFF) - while buffer: - yield buffer - buffer = pngFile.read(0xFFFF) - - pngFile.close() diff --git a/telemeta/visualization/old/waveform.py b/telemeta/visualization/old/waveform.py deleted file mode 100644 index c645c5ef..00000000 --- a/telemeta/visualization/old/waveform.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (C) 2007 Samalyse SARL -# All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://svn.parisson.org/telemeta/TelemetaLicense. -# -# Author: Olivier Guilyardi - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from django.conf import settings -from tempfile import NamedTemporaryFile -import os -import os.path - -class WaveFormVisualizer(Component): - """WaveForm visualization driver""" - - implements(IMediaItemVisualizer) - - # possible alternative: - # http://jokosher.python-hosting.com/file/jokosher-extra/Waveform.py - - def get_id(self): - return "waveform_first" - - def get_name(self): - return "Waveform (wav2png.c)" - - def set_colors(self, background=None, scheme=None): - pass - - def render(self, media_item, options=None): - """Generator that streams the waveform as a PNG image""" - - pngFile = NamedTemporaryFile(suffix='.png') - wav2png = os.path.dirname(__file__) + '/wav2png/wav2png' - args = "-i " + media_item.file.path + " " - args += "-o " + pngFile.name + " " - args += "-b ffffff " - args += "-l 000088 " - args += "-z 990000 " - args += "-w 300 " - args += "-h 151 " - - os.system(wav2png + " " + args) - - buffer = pngFile.read(0xFFFF) - while buffer: - yield buffer - buffer = pngFile.read(0xFFFF) - - pngFile.close() - diff --git a/telemeta/visualization/old/waveform2.py b/telemeta/visualization/old/waveform2.py deleted file mode 100644 index 83a67aa6..00000000 --- a/telemeta/visualization/old/waveform2.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2007 Samalyse SARL -# All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://svn.parisson.org/telemeta/TelemetaLicense. -# -# Authors: Olivier Guilyardi -# Guillaume Pellerin - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from telemeta.visualization.octave_core import OctaveCoreVisualizer - -class WaveformVisualizer2(OctaveCoreVisualizer): - """Octave temporal view visualization driver""" - - implements(IMediaItemVisualizer) - - def __init__(self): - self.set_m_file('waveform2img.m') - self.buffer_size = 0xFFFF - self.trans_type = 'png' - - def get_id(self): - return "waveform_octave" - - def get_name(self): - return "Waveform (octave)" - - def set_colors(self, background=None, scheme=None): - pass - - def render(self, media_item, options=None): - """Generator that streams the temporal view as a PNG image""" - - stream = self.octave_to_png_stream(media_item) - for chunk in stream: - yield chunk - - diff --git a/telemeta/visualization/old/waveform4.py b/telemeta/visualization/old/waveform4.py deleted file mode 100644 index 93d9b57f..00000000 --- a/telemeta/visualization/old/waveform4.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (C) 2008 Parisson SARL -# All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://svn.parisson.org/telemeta/TelemetaLicense. -# -# Author: Guillaume Pellerin - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from django.conf import settings -from tempfile import NamedTemporaryFile -from telemeta.visualization.wav2png import * - -class WaveFormVisualizer(Component): - """WaveForm visualization driver (python style)""" - - implements(IMediaItemVisualizer) - - bg_color = None - color_scheme = None - - def get_id(self): - return "waveform4" - - def get_name(self): - return "Waveform (audiolab large)" - - def set_colors(self, background=None, scheme=None): - self.bg_color = background - self.color_scheme = scheme - - def render(self, media_item, width=None, height=None, options=None): - """Generator that streams the waveform as a PNG image with a python method""" - - wav_file = media_item.file.path - pngFile = NamedTemporaryFile(suffix='.png') - - if not width == None: - image_width = width - else: - image_width = 1800 - if not height == None: - image_height = height - else: - image_height = 300 - - fft_size = 2048 - args = (wav_file, pngFile.name, image_width, image_height, fft_size, self.bg_color, self.color_scheme) - create_wavform_png(*args) - - buffer = pngFile.read(0xFFFF) - while buffer: - yield buffer - buffer = pngFile.read(0xFFFF) - - pngFile.close() - diff --git a/telemeta/visualization/snack_core.py b/telemeta/visualization/snack_core.py deleted file mode 100644 index 1232d880..00000000 --- a/telemeta/visualization/snack_core.py +++ /dev/null @@ -1,47 +0,0 @@ - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from django.conf import settings -from Tkinter import Tk -import tkSnack -from tempfile import NamedTemporaryFile -import os -import random - -class SnackCoreVisualizer(Component): - """Parent class for tkSnack-based visualization drivers""" - - def get_snack_canvas(self): - #id = "telemeta" + str(random.randrange(0,1000000)) - #self.tk_root = Tk(baseName=id) - self.tk_root = Tk() - tkSnack.initializeSnack(self.tk_root) - canvas = tkSnack.SnackCanvas(height=200) - canvas.pack() - return canvas - - def get_snack_sound(self, media_item): - self.snd = tkSnack.Sound() - self.snd.read(media_item.file.path) - return self.snd - - def canvas_to_png_stream(self, canvas): - - psFile = NamedTemporaryFile(suffix='.ps') - canvas.postscript({'file': psFile.name, 'height': 200, 'width': 300}) - pngFile = NamedTemporaryFile(suffix='.png') - os.system('convert -resize 300x200 ' + psFile.name + ' ' + pngFile.name) - psFile.close() - - buffer = pngFile.read(0xFFFF) - while buffer: - yield buffer - buffer = pngFile.read(0xFFFF) - - pngFile.close() - self.cleanup() - - def cleanup(self): - self.snd.destroy() - self.tk_root.destroy() - diff --git a/telemeta/visualization/spectrogram_audiolab.py b/telemeta/visualization/spectrogram_audiolab.py deleted file mode 100644 index 0b07b473..00000000 --- a/telemeta/visualization/spectrogram_audiolab.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL - -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from django.conf import settings -from tempfile import NamedTemporaryFile -from telemeta.visualization.wav2png import * - -class SpectrogramVisualizerAudiolab(Component): - """Spectrogram visualization driver (python style thanks to wav2png.py and scikits.audiolab)""" - - implements(IMediaItemVisualizer) - - bg_color = None - color_scheme = None - - def get_id(self): - return "spectrogram_audiolab" - - def get_name(self): - return "Spectrogram (audiolab)" - - def set_colors(self, background=None, scheme=None): - self.bg_color = background - self.color_scheme = scheme - - def render(self, media_item, width=None, height=None, options=None): - """Generator that streams the spectrogram as a PNG image with a python method""" - - wav_file = media_item.file.path - pngFile = NamedTemporaryFile(suffix='.png') - - if not width == None: - image_width = width - else: - image_width = 1500 - if not height == None: - image_height = height - else: - image_height = 200 - - fft_size = 2048 - args = (wav_file, pngFile.name, image_width, image_height, fft_size, - self.bg_color, self.color_scheme) - create_spectrogram_png(*args) - - buffer = pngFile.read(0xFFFF) - while buffer: - yield buffer - buffer = pngFile.read(0xFFFF) - - pngFile.close() diff --git a/telemeta/visualization/spectrogram_octave.py b/telemeta/visualization/spectrogram_octave.py deleted file mode 100644 index 8a5c24b9..00000000 --- a/telemeta/visualization/spectrogram_octave.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2007 Samalyse SARL -# Copyright (c) 2007-2009 Guillaume Pellerin -# -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Authors: Olivier Guilyardi -# Guillaume Pellerin - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from telemeta.visualization.octave_core import OctaveCoreVisualizer - -class SpectrogramVisualizerOctave(OctaveCoreVisualizer): - """Octave spectral view visualization driver""" - - implements(IMediaItemVisualizer) - - def __init__(self): - self.set_m_file('spectrogram2img.m') - - def get_id(self): - return "spectrogram_octave" - - def get_name(self): - return "Spectrogram (octave)" - - def set_colors(self, background=None, scheme=None): - pass - - def render(self, media_item, width=None, height=None, options=None): - """Generator that streams the spectral view as a PNG image""" - - stream = self.octave_to_png_stream(media_item) - for chunk in stream: - yield chunk - diff --git a/telemeta/visualization/tkSnack.py b/telemeta/visualization/tkSnack.py deleted file mode 100755 index 5fde35a6..00000000 --- a/telemeta/visualization/tkSnack.py +++ /dev/null @@ -1,579 +0,0 @@ -""" -tkSnack -An interface to Kare Sjolander's Snack Tcl extension -http://www.speech.kth.se/snack/index.html - -by Kevin Russell and Kare Sjolander -last modified: Mar 28, 2003 -""" - -import Tkinter -import types -import string - -Tkroot = None -audio = None -mixer = None - -def initializeSnack(newroot): - global Tkroot, audio, mixer - Tkroot = newroot - Tkroot.tk.call('eval', 'package require snack') - Tkroot.tk.call('snack::createIcons') - Tkroot.tk.call('snack::setUseOldObjAPI') - audio = AudioControllerSingleton() - mixer = MixerControllerSingleton() - - -def _cast(astring): - """This function tries to convert a string returned by a Tcl call - to a Python integer or float, if possible, and otherwise passes on the - raw string (or None instead of an empty string).""" - try: - return int(astring) - except ValueError: - try: - return float(astring) - except ValueError: - if astring: - return astring - else: - return None - - -class NotImplementedException(Exception): - pass - - -class TkObject: - """A mixin class for various Python/Tk communication functions, - such as reading and setting the object's configuration options. - We put them in a mixin class so we don't have to keep repeating - them for sounds, filters, and spectrograms. - These are mostly copied from the Tkinter.Misc class.""" - - def _getboolean(self, astring): - if astring: - return self.tk.getboolean(astring) - - def _getints(self, astring): - if astring: - return tuple(map(int, self.tk.splitlist(astring))) - - def _getdoubles(self, astring): - if astring: - return tuple(map(float, self.tk.splitlist(astring))) - - def _options(self, cnf, kw=None): - if kw: - cnf = Tkinter._cnfmerge((cnf, kw)) - else: - cnf = Tkinter._cnfmerge(cnf) - res = () - for k,v in cnf.items(): - if v is not None: - if k[-1] == '_': k = k[:-1] - #if callable(v): - # v = self._register(v) - res = res + ('-'+k, v) - return res - - def configure(self, cnf=None, **kw): - self._configure(cnf, kw) - - def _configure(self, cnf=None, kw={}): - if kw: - cnf = Tkinter._cnfmerge((cnf, kw)) - elif cnf: - cnf = Tkinter._cnfmerge(cnf) - if cnf is None: - cnf = {} - for x in self.tk.split( - self.tk.call(self.name, 'configure')): - cnf[x[0][1:]] = (x[0][1:],) + x[1:] - return cnf - if type(cnf) is types.StringType: - x = self.tk.split(self.tk.call(self.name, 'configure', '-'+cnf)) - return (x[0][1:],) + x[1:] - self.tk.call((self.name, 'configure') + self._options(cnf)) - config = configure - - def cget(self, key): - return _cast(self.tk.call(self.name, 'cget' , '-'+key)) - - # Set "cget" as the method to handle dictionary-like attribute access - __getitem__ = cget - - def __setitem__(self, key, value): - self.configure({key: value}) - - def keys(self): - return map(lambda x: x[0][1:], - self.tk.split(self.tk.call(self.name, 'configure'))) - - def __str__(self): - return self.name - - - -class Sound (TkObject): - - def __init__(self, name=None, master=None, **kw): - self.name = None - if not master: - if Tkroot: - master = Tkroot - else: - raise RuntimeError, \ - 'Tk not intialized or not registered with Snack' - self.tk = master.tk - if not name: - self.name = self.tk.call(('sound',) + self._options(kw)) - else: - self.name = self.tk.call(('sound', name) + self._options(kw)) - #self._configure(cnf, kw) - - def append(self, binarydata, **kw): - """Appends binary string data to the end of the sound.""" - self.tk.call((self.name, 'append', binarydata) + self._options(kw)) - - def concatenate(self, othersound): - """Concatenates the sample data from othersound to the end of - this sound. Both sounds must be of the same type.""" - self.tk.call(self.name, 'concatenate', othersound.name) - - def configure(self, **kw): - """The configure command is used to set options for a sound.""" - self.tk.call((self.name, 'configure') + self._options(kw)) - - def copy(self, sound, **kw): - """Copies sample data from another sound into self.""" - self.tk.call((self.name, 'copy', sound.name) + self._options(kw)) - - def changed(self, flag): - """This command is used to inform Snack that the sound object has been - modified. Normally Snack tracks changes to sound objects automatically, - but in a few cases this must be performed explicitly. For example, - if individual samples are changed using the sample command these - will not be tracked for performance reasons.""" - self.tk.call((self.name, 'changed', flag)) - - def convert(self, **kw): - """Convert a sound to a different sample encoding, sample rate, - or number of channels.""" - self.tk.call((self.name, 'convert') + self._options(kw)) - - def crop(self, start=1, end=None, **kw): - """Removes all samples outside of the range [start..end].""" - if end is None: - end = self.length() - self.tk.call((self.name, 'crop', start, end) + self._options(kw)) - - def cut(self, start=1, end=None, **kw): - """Removes all samples inside the range [start..end].""" - if end is None: - end = self.length() - self.tk.call((self.name, 'cut', start, end) + self._options(kw)) - - def data(self, binarydata=None, **kw): - """Loads sound data from, or writes to, a binary string.""" - if binarydata: # copy data to sound - self.tk.call((self.name, 'data', binarydata) + self._options(kw)) - else: # return sound data - return self.tk.call((self.name, 'data') + self._options(kw)) - - def destroy(self): - """Removes the Tcl command for this sound and frees the storage - associated with it.""" - self.tk.call(self.name, 'destroy') - - def dBPowerSpectrum(self, **kw): - """Computes the log FFT power spectrum of the sound (at the time - given by the start option) and returns a list of dB values.""" - result = self.tk.call((self.name, 'dBPowerSpectrum') - + self._options(kw)) - return self._getdoubles(result) - - def powerSpectrum(self, **kw): - """Computes the FFT power spectrum of the sound (at the time - given by the start option) and returns a list of magnitude values.""" - result = self.tk.call((self.name, 'powerSpectrum') - + self._options(kw)) - return self._getdoubles(result) - - def filter(self, filter, **kw): - """Applies the given filter to the sound.""" - return self.tk.call((self.name, 'filter', filter.name) + - self._options(kw)) - - def formant(self, **kw): - """Returns a list of formant trajectories.""" - result = self.tk.call((self.name, 'formant') + self._options(kw)) - return map(self._getdoubles, self.tk.splitlist(result)) - - def flush(self): - """Removes all audio data from the sound.""" - self.tk.call(self.name, 'flush') - - def info(self, format='string'): - """Returns a list with information about the sound. The entries are - [length, rate, max, min, encoding, channels, fileFormat, headerSize] - """ - result = self.tk.call(self.name, 'info') - if format == 'list': - return map(self._cast, string.split(result)) - else: - return result - - def insert(self, sound, position, **kw): - """Inserts sound at position.""" - self.tk.call((self.name, 'insert', sound.name, position) + self._options(kw)) - - def length(self, n=None, **kw): - """Gets/sets the length of the sound in number of samples (default) - or seconds, as determined by the 'units' option.""" - if n is not None: - result = self.tk.call((self.name, 'length', n) + self._options(kw)) - else: - result = self.tk.call((self.name, 'length') + self._options(kw)) - return _cast(result) - - def load(self, filename, **kw): - """Reads new sound data from a file. Synonym for "read".""" - self.tk.call((self.name, 'read', filename) + self._options(kw)) - - def max(self, **kw): - """Returns the largest positive sample value of the sound.""" - return _cast(self.tk.call((self.name, 'max') + self._options(kw))) - - def min(self, **kw): - """Returns the largest negative sample value of the sound.""" - return _cast(self.tk.call((self.name, 'min') + self._options(kw))) - - def mix(self, sound, **kw): - """Mixes sample data from another sound into self.""" - self.tk.call((self.name, 'mix', sound.name) + self._options(kw)) - - def pause(self): - """Pause current record/play operation. Next pause invocation - resumes play/record.""" - self.tk.call(self.name, 'pause') - - def pitch(self, method=None, **kw): - """Returns a list of pitch values.""" - if method is None or method is "amdf" or method is "AMDF": - result = self.tk.call((self.name, 'pitch') + self._options(kw)) - return self._getdoubles(result) - else: - result = self.tk.call((self.name, 'pitch', '-method', method) + - self._options(kw)) - return map(self._getdoubles, self.tk.splitlist(result)) - - def play(self, **kw): - """Plays the sound.""" - self.tk.call((self.name, 'play') + self._options(kw)) - - def power(self, **kw): - """Computes the FFT power spectrum of the sound (at the time - given by the start option) and returns a list of power values.""" - result = self.tk.call((self.name, 'power') - + self._options(kw)) - return self._getdoubles(result) - - def read(self, filename, **kw): - """Reads new sound data from a file.""" - self.tk.call((self.name, 'read', filename) + self._options(kw)) - - def record(self, **kw): - """Starts recording data from the audio device into the sound object.""" - self.tk.call((self.name, 'record') + self._options(kw)) - - def reverse(self, **kw): - """Reverses a sound.""" - self.tk.call((self.name, 'reverse') + self._options(kw)) - - def sample(self, index, left=None, right=None): - """Without left/right, this gets the sample value at index. - With left/right, it sets the sample value at index in the left - and/or right channels.""" - if right is not None: - if left is None: - left = '?' - opts = (left, right) - elif left is not None: - opts = (left,) - else: - opts = () - return _cast(self.tk.call((self.name, 'sample', index) + opts)) - - def stop(self): - """Stops current play or record operation.""" - self.tk.call(self.name, 'stop') - - def stretch(self, **kw): - self.tk.call((self.name, 'stretch') + self._options(kw)) - - def write(self, filename, **kw): - """Writes sound data to a file.""" - self.tk.call((self.name, 'write', filename) + self._options(kw)) - - -class AudioControllerSingleton(TkObject): - """This class offers functions that control various aspects of the - audio devices. - It is written as a class instead of as a set of module-level functions - so that we can piggy-back on the Tcl-interface functions in TkObject, - and so that the user can invoke the functions in a way more similar to - how they're invoked in Tcl, e.g., snack.audio.rates(). - It is intended that there only be once instance of this class, the - one created in snack.initialize. - """ - - def __init__(self): - self.tk = Tkroot.tk - - def encodings(self): - """Returns a list of supported sample encoding formats for the - currently selected device.""" - result = self.tk.call('snack::audio', 'encodings') - return self.tk.splitlist(result) - - def rates(self): - """Returns a list of supported sample rates for the currently - selected device.""" - result = self.tk.call('snack::audio', 'frequencies') - return self._getints(result) - - def frequencies(self): - """Returns a list of supported sample rates for the currently - selected device.""" - result = self.tk.call('snack::audio', 'frequencies') - return self._getints(result) - - def inputDevices(self): - """Returns a list of available audio input devices""" - result = self.tk.call('snack::audio', 'inputDevices') - return self.tk.splitlist(result) - - def playLatency(self, latency=None): - """Sets/queries (in ms) how much sound will be queued up at any - time to the audio device to play back.""" - if latency is not None: - return _cast(self.tk.call('snack::audio', 'playLatency', latency)) - else: - return _cast(self.tk.call('snack::audio', 'playLatency')) - - def pause(self): - """Toggles between play/pause for all playback on the audio device.""" - self.tk.call('snack::audio', 'pause') - - def play(self): - """Resumes paused playback on the audio device.""" - self.tk.call('snack::audio', 'play') - - def play_gain(self, gain=None): - """Returns/sets the current play gain. Valid values are integers - in the range 0-100.""" - if gain is not None: - return _cast(self.tk.call('snack::audio', 'play_gain', gain)) - else: - return _cast(self.tk.call('snack::audio', 'play_gain')) - - def outputDevices(self): - """Returns a list of available audio output devices.""" - result = self.tk.call('snack::audio', 'outputDevices') - return self.tk.splitlist(result) - - def selectOutput(self, device): - """Selects an audio output device to be used as default.""" - self.tk.call('snack::audio', 'selectOutput', device) - - def selectInput(self, device): - """Selects an audio input device to be used as default.""" - self.tk.call('snack::audio', 'selectInput', device) - - def stop(self): - """Stops all playback on the audio device.""" - self.tk.call('snack::audio', 'stop') - - def elapsedTime(self): - """Return the time since the audio device started playback.""" - result = self.tk.call('snack::audio', 'elapsedTime') - return self.tk.getdouble(result) - -class Filter(TkObject): - - def __init__(self, name, *args, **kw): - global Tkroot - self.name = None - if Tkroot: - master = Tkroot - else: - raise RuntimeError, \ - 'Tk not intialized or not registered with Snack' - self.tk = master.tk - self.name = self.tk.call(('snack::filter', name) + args + - self._options(kw)) - - def configure(self, *args): - """Configures the filter.""" - self.tk.call((self.name, 'configure') + args) - - def destroy(self): - """Removes the Tcl command for the filter and frees its storage.""" - self.tk.call(self.name, 'destroy') - - -class MixerControllerSingleton(TkObject): - - """Like AudioControllerSingleton, this class is intended to have only - a single instance object, which will control various aspects of the - mixers.""" - - def __init__(self): - self.tk = Tkroot.tk - - def channels(self, line): - """Returns a list with the names of the channels for the - specified line.""" - result = self.tk.call('snack::mixer', 'channels', line) - return self.tk.splitlist(result) - - def devices(self): - """Returns a list of the available mixer devices.""" - result = self.tk.call('snack::mixer', 'devices') - return self.tk.splitlist(result) - - def input(self, jack=None, tclVar=None): - """Gets/sets the current input jack. Optionally link a boolean - Tcl variable.""" - opts = () - if jack is not None: - opts = opts + jack - if tclVar is not None: - opts = opts + tclVar - return self.tk.call(('snack::mixer', 'input') + opts) - - def inputs(self): - """Returns a list of available input ports.""" - result = self.tk.call('snack::mixer', 'inputs') - return self.tk.splitlist(result) - - def lines(self): - """Returns a list with the names of the lines of the mixer device.""" - result = self.tk.call('snack::mixer', 'lines') - return self.tk.splitlist(result) - - def output(self, jack=None, tclVar=None): - """Gets/sets the current output jack. Optionally link a boolean - Tcl variable.""" - opts = () - if jack is not None: - opts = opts + jack - if tclVar is not None: - opts = opts + tclVar - return self.tk.call(('snack::mixer', 'output') + opts) - - def outputs(self): - """Returns a list of available output ports.""" - result = self.tk.call('snack::mixer', 'outputs') - return self.tk.splitlist(result) - - def update(self): - """Updates all linked variables to reflect the status of the - mixer device.""" - self.tk.call('snack::mixer', 'update') - - def volume(self, line, leftVar=None, rightVar=None): - if self.channels(line)[0] == 'Mono': - return self.tk.call('snack::mixer', 'volume', line, rightVar) - else: - return self.tk.call('snack::mixer', 'volume', line, leftVar, rightVar) - - def select(self, device): - """Selects a device to be used as default.""" - self.tk.call('snack::mixer', 'select', device) - - - -class SoundFrame(Tkinter.Frame): - - """A simple "tape recorder" widget.""" - - def __init__(self, parent=None, sound=None, *args, **kw): - Tkinter.Frame.__init__(self) - if sound: - self.sound = sound - else: - self.sound = Sound() - self.canvas = SnackCanvas(self, height=100) - kw['sound'] = self.sound.name - self.canvas.create_waveform(0, 0, kw) - self.canvas.pack(side='top') - bbar = Tkinter.Frame(self) - bbar.pack(side='left') - Tkinter.Button(bbar, image='snackOpen', command=self.load - ).pack(side='left') - Tkinter.Button(bbar, bitmap='snackPlay', command=self.play - ).pack(side='left') - Tkinter.Button(bbar, bitmap='snackRecord', fg='red', - command=self.record).pack(side='left') - Tkinter.Button(bbar, bitmap='snackStop', command=self.stop - ).pack(side='left') - Tkinter.Button(bbar, text='Info', command=self.info).pack(side='left') - - - def load(self): - file = Tkroot.tk.call('eval', 'snack::getOpenFile') - self.sound.read(file, progress='snack::progressCallback') - - def play(self): - self.sound.play() - - def stop(self): - self.sound.stop() - - def record(self): - self.sound.record() - - def info(self): - print self.sound.info() - -def createSpectrogram(canvas, *args, **kw): - """Draws a spectrogram of a sound on canvas.""" - return canvas._create('spectrogram', args, kw) - -def createSection(canvas, *args, **kw): - """Draws and FFT log power spectrum section on canvas.""" - return canvas._create('section', args, kw) - -def createWaveform(canvas, *args, **kw): - """Draws a waveform on canvas.""" - return canvas._create('waveform', args, kw) - - -class SnackCanvas(Tkinter.Canvas): - - def __init__(self, master=None, cnf={}, **kw): - Tkinter.Widget.__init__(self, master, 'canvas', cnf, kw) - - def create_spectrogram(self, *args, **kw): - """Draws a spectrogram of a sound on the canvas.""" - return self._create('spectrogram', args, kw) - - def create_section(self, *args, **kw): - """Draws an FFT log power spectrum section.""" - return self._create('section', args, kw) - - def create_waveform(self, *args, **kw): - """Draws a waveform.""" - return self._create('waveform', args, kw) - - -if __name__ == '__main__': - # Create a test SoundFrame if the module is called as the main program - root = Tkinter.Tk() - initializeSnack(root) - frame = SoundFrame(root) - frame.pack(expand=0) - root.mainloop() diff --git a/telemeta/visualization/wav2png.py b/telemeta/visualization/wav2png.py deleted file mode 100755 index 0525e008..00000000 --- a/telemeta/visualization/wav2png.py +++ /dev/null @@ -1,436 +0,0 @@ -#!/usr/bin/env python - -# wav2png.py -- converts wave files to wave file and spectrogram images -# Copyright (C) 2008 MUSIC TECHNOLOGY GROUP (MTG) -# UNIVERSITAT POMPEU FABRA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# Authors: -# Bram de Jong -# Contributors: -# Guillaume Pellerin - - -import optparse, math, sys -import ImageFilter, ImageChops, Image, ImageDraw, ImageColor -import numpy -import scikits.audiolab as audiolab - -color_schemes = { - 'default': { - 'waveform': [(50,0,200), (0,220,80), (255,224,0), (255,0,0)], - 'spectrogram': [(0, 0, 0), (58/4,68/4,65/4), (80/2,100/2,153/2), (90,180,100), - (224,224,44), (255,60,30), (255,255,255)] - }, - 'iso': { - 'waveform': [(0,0,255), (0,255,255), (255,255,0), (255,0,0)], - 'spectrogram': [(0, 0, 0), (58/4,68/4,65/4), (80/2,100/2,153/2), (90,180,100), - (224,224,44), (255,60,30), (255,255,255)] - }, - 'purple': { - 'waveform': [(173,173,173), (147,149,196), (77,80,138), (108,66,0)], - 'spectrogram': [(0, 0, 0), (58/4,68/4,65/4), (80/2,100/2,153/2), (90,180,100), - (224,224,44), (255,60,30), (255,255,255)] - } -} - -class TestAudioFile(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 - self.has_broken_header = has_broken_header - - def seek(self, seekpoint): - self.seekpoint = seekpoint - - def get_nframes(self): - return self.num_frames - - def get_samplerate(self): - return 44100 - - def get_channels(self): - return 1 - - def read_frames(self, frames_to_read): - if self.has_broken_header and self.seekpoint + frames_to_read > self.num_frames / 2: - raise IOError() - - num_frames_left = self.num_frames - self.seekpoint - if num_frames_left < frames_to_read: - will_read = num_frames_left - else: - will_read = frames_to_read - self.seekpoint += will_read - return numpy.random.random(will_read)*2 - 1 - - -class AudioProcessor(object): - def __init__(self, audio_file, fft_size, window_function=numpy.ones): - self.fft_size = fft_size - self.window = window_function(self.fft_size) - self.audio_file = audio_file - self.frames = audio_file.get_nframes() - self.samplerate = audio_file.get_samplerate() - self.channels = audio_file.get_channels() - 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 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) - - -def interpolate_colors(colors, flat=False, num_colors=256): - """ 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 """ - - palette = [] - - for i in range(num_colors): - index = (i * (len(colors) - 1))/(num_colors - 1.0) - index_int = int(index) - alpha = index - float(index_int) - - if alpha > 0: - r = (1.0 - alpha) * colors[index_int][0] + alpha * colors[index_int + 1][0] - g = (1.0 - alpha) * colors[index_int][1] + alpha * colors[index_int + 1][1] - b = (1.0 - alpha) * colors[index_int][2] + alpha * colors[index_int + 1][2] - else: - r = (1.0 - alpha) * colors[index_int][0] - g = (1.0 - alpha) * colors[index_int][1] - b = (1.0 - alpha) * colors[index_int][2] - - if flat: - palette.extend((int(r), int(g), int(b))) - else: - palette.append((int(r), int(g), int(b))) - - return palette - - -class WaveformImage(object): - def __init__(self, image_width, image_height, bg_color = None, color_scheme = None): - if not bg_color: - bg_color = (0,0,0) - if not color_scheme: - color_scheme = 'default' - - self.image = Image.new("RGB", (image_width, image_height), bg_color) - - self.image_width = image_width - self.image_height = image_height - - self.draw = ImageDraw.Draw(self.image) - self.previous_x, self.previous_y = None, None - - colors = color_schemes[color_scheme]['waveform'] - - # this line gets the old "screaming" colors back... - # colors = [self.color_from_value(value/29.0) for value in range(0,30)] - - self.color_lookup = interpolate_colors(colors) - self.pix = self.image.load() - - def color_from_value(self, value): - """ given a value between 0 and 1, return an (r,g,b) tuple """ - - return ImageColor.getrgb("hsl(%d,%d%%,%d%%)" % (int( (1.0 - value) * 360 ), 80, 50)) - - def draw_peaks(self, x, peaks, spectral_centroid): - """ draw 2 peaks at x using the spectral_centroid for color """ - - y1 = self.image_height * 0.5 - peaks[0] * (self.image_height - 4) * 0.5 - y2 = self.image_height * 0.5 - peaks[1] * (self.image_height - 4) * 0.5 - - line_color = self.color_lookup[int(spectral_centroid*255.0)] - - if self.previous_y != None: - self.draw.line([self.previous_x, self.previous_y, x, y1, x, y2], line_color) - else: - self.draw.line([x, y1, x, y2], line_color) - - self.previous_x, self.previous_y = x, y2 - - self.draw_anti_aliased_pixels(x, y1, y2, line_color) - - def draw_anti_aliased_pixels(self, x, y1, y2, color): - """ vertical anti-aliasing at y1 and y2 """ - - y_max = max(y1, y2) - y_max_int = int(y_max) - alpha = y_max - y_max_int - - if alpha > 0.0 and alpha < 1.0 and y_max_int + 1 < self.image_height: - current_pix = self.pix[x, y_max_int + 1] - - r = int((1-alpha)*current_pix[0] + alpha*color[0]) - g = int((1-alpha)*current_pix[1] + alpha*color[1]) - b = int((1-alpha)*current_pix[2] + alpha*color[2]) - - self.pix[x, y_max_int + 1] = (r,g,b) - - y_min = min(y1, y2) - y_min_int = int(y_min) - alpha = 1.0 - (y_min - y_min_int) - - if alpha > 0.0 and alpha < 1.0 and y_min_int - 1 >= 0: - current_pix = self.pix[x, y_min_int - 1] - - r = int((1-alpha)*current_pix[0] + alpha*color[0]) - g = int((1-alpha)*current_pix[1] + alpha*color[1]) - b = int((1-alpha)*current_pix[2] + alpha*color[2]) - - self.pix[x, y_min_int - 1] = (r,g,b) - - def save(self, filename): - # draw a zero "zero" line - a = 25 - for x in range(self.image_width): - self.pix[x, self.image_height/2] = tuple(map(lambda p: p+a, self.pix[x, self.image_height/2])) - - self.image.save(filename) - - -class SpectrogramImage(object): - def __init__(self, image_width, image_height, fft_size, bg_color = None, color_scheme = None): - - #FIXME: bg_color is ignored - - if not color_scheme: - color_scheme = 'default' - - self.image = Image.new("P", (image_height, image_width)) - - self.image_width = image_width - self.image_height = image_height - self.fft_size = fft_size - - colors = color_schemes[color_scheme]['spectrogram'] - - self.image.putpalette(interpolate_colors(colors, True)) - - # generate the lookup which translates y-coordinate to fft-bin - self.y_to_bin = [] - f_min = 100.0 - f_max = 22050.0 - y_min = math.log10(f_min) - y_max = math.log10(f_max) - for y in range(self.image_height): - freq = math.pow(10.0, y_min + y / (image_height - 1.0) *(y_max - y_min)) - bin = freq / 22050.0 * (self.fft_size/2 + 1) - - if bin < self.fft_size/2: - alpha = bin - int(bin) - - self.y_to_bin.append((int(bin), alpha * 255)) - - # this is a bit strange, but using image.load()[x,y] = ... is - # a lot slower than using image.putadata and then rotating the image - # so we store all the pixels in an array and then create the image when saving - self.pixels = [] - - def draw_spectrum(self, x, spectrum): - for (index, alpha) in self.y_to_bin: - self.pixels.append( int( ((255.0-alpha) * spectrum[index] + alpha * spectrum[index + 1] )) ) - - for y in range(len(self.y_to_bin), self.image_height): - self.pixels.append(0) - - def save(self, filename): - self.image.putdata(self.pixels) - self.image.transpose(Image.ROTATE_90).save(filename) - - -def create_wavform_png(input_filename, output_filename_w, image_width, image_height, fft_size, - bg_color = None, color_scheme = None): - audio_file = audiolab.sndfile(input_filename, 'read') - - samples_per_pixel = audio_file.get_nframes() / float(image_width) - processor = AudioProcessor(audio_file, fft_size, numpy.hanning) - - waveform = WaveformImage(image_width, image_height, bg_color, color_scheme) - - for x in range(image_width): - - seek_point = int(x * samples_per_pixel) - next_seek_point = int((x + 1) * samples_per_pixel) - - (spectral_centroid, db_spectrum) = processor.spectral_centroid(seek_point) - peaks = processor.peaks(seek_point, next_seek_point) - - waveform.draw_peaks(x, peaks, spectral_centroid) - - waveform.save(output_filename_w) - -def create_spectrogram_png(input_filename, output_filename_s, image_width, image_height, fft_size, - bg_color = None, color_scheme = None): - audio_file = audiolab.sndfile(input_filename, 'read') - - samples_per_pixel = audio_file.get_nframes() / float(image_width) - processor = AudioProcessor(audio_file, fft_size, numpy.hanning) - - spectrogram = SpectrogramImage(image_width, image_height, fft_size, bg_color, color_scheme) - - for x in range(image_width): - - seek_point = int(x * samples_per_pixel) - next_seek_point = int((x + 1) * samples_per_pixel) - (spectral_centroid, db_spectrum) = processor.spectral_centroid(seek_point) - spectrogram.draw_spectrum(x, db_spectrum) - - spectrogram.save(output_filename_s) - - diff --git a/telemeta/visualization/waveform_audiolab.py b/telemeta/visualization/waveform_audiolab.py deleted file mode 100644 index c042864b..00000000 --- a/telemeta/visualization/waveform_audiolab.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Parisson SARL -# All rights reserved. -# -# This software is a computer program whose purpose is to backup, analyse, -# transcode and stream any audio content with its metadata over a web frontend. - -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". - -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. - -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. - -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. -# -# Author: Guillaume Pellerin - -from telemeta.core import * -from telemeta.visualization.api import IMediaItemVisualizer -from django.conf import settings -from tempfile import NamedTemporaryFile -from telemeta.visualization.wav2png import * - -class WaveFormVisualizerAudiolab(Component): - """WaveForm visualization driver (python style thanks to wav2png.py and scikits.audiolab)""" - - implements(IMediaItemVisualizer) - - bg_color = None - color_scheme = None - - def get_id(self): - return "waveform_audiolab" - - def get_name(self): - return "Waveform (audiolab)" - - def set_colors(self, background=None, scheme=None): - self.bg_color = background - self.color_scheme = scheme - - def render(self, media_item, width=None, height=None, options=None): - """Generator that streams the waveform as a PNG image with a python method""" - - wav_file = media_item.file.path - pngFile = NamedTemporaryFile(suffix='.png') - - if not width == None: - image_width = width - else: - image_width = 1500 - if not height == None: - image_height = height - else: - image_height = 200 - - fft_size = 2048 - args = (wav_file, pngFile.name, image_width, image_height, fft_size, - self.bg_color, self.color_scheme) - create_wavform_png(*args) - - buffer = pngFile.read(0xFFFF) - while buffer: - yield buffer - buffer = pngFile.read(0xFFFF) - - pngFile.close() - diff --git a/telemeta/web/__init__.py b/telemeta/web/__init__.py index dce752c1..22d21398 100644 --- a/telemeta/web/__init__.py +++ b/telemeta/web/__init__.py @@ -1 +1,2 @@ from telemeta.web.base import WebView +from pages import * diff --git a/telemeta/web/base.py b/telemeta/web/base.py index 8be7cc9b..d1853093 100644 --- a/telemeta/web/base.py +++ b/telemeta/web/base.py @@ -35,6 +35,7 @@ import re import os import sys +import datetime from django.template import RequestContext, loader from django import template @@ -46,33 +47,46 @@ from django.conf import settings from django.contrib import auth from django.contrib.auth.decorators import login_required -import telemeta from telemeta.models import MediaItem, Location, MediaCollection, EthnicGroup from telemeta.models import dublincore, Enumeration -from telemeta.core import Component, ExtensionPoint -from telemeta.export import * -from telemeta.visualization import * -from telemeta.analysis import * -from telemeta.analysis.vamp import * +#from telemeta.core import Component, ExtensionPoint +#from telemeta.export import * +#from telemeta.visualization import * +#from telemeta.analysis import * +#from telemeta.analysis.vamp import * import telemeta.interop.oai as oai from telemeta.interop.oaidatasource import TelemetaOAIDataSource from django.core.exceptions import ObjectDoesNotExist from telemeta.util.unaccent import unaccent from telemeta.web import pages -import datetime from telemeta.util.unaccent import unaccent_icmp +import timeside + + def render(request, template, data = None, mimetype = None): return render_to_response(template, data, context_instance=RequestContext(request), - mimetype=mimetype) + mimetype=mimetype) + +def stream(file): + chunk_size = 0x10000 + f = open(file, 'r') + while True: + _chunk = f.read(chunk_size) + if not len(_chunk): + break + yield _chunk + f.close() -class WebView(Component): - """Provide web UI methods""" - exporters = ExtensionPoint(IExporter) - visualizers = ExtensionPoint(IMediaItemVisualizer) - analyzers = ExtensionPoint(IMediaItemAnalyzer) +class WebView: + """Provide web UI methods""" + graphers = timeside.processors(timeside.api.IGrapher) + decoders = timeside.processors(timeside.api.IDecoder) + encoders= timeside.processors(timeside.api.IEncoder) + analyzers = timeside.processors(timeside.api.IAnalyzer) + def index(self, request): """Render the homepage""" @@ -95,63 +109,88 @@ class WebView(Component): item = MediaItem.objects.get(public_id=public_id) formats = [] - for exporter in self.exporters: - formats.append({'name': exporter.get_format(), 'extension': exporter.get_file_extension()}) - - visualizers = [] - for visualizer in self.visualizers: - visualizers.append({'name':visualizer.get_name(), 'id': - visualizer.get_id()}) - if request.REQUEST.has_key('visualizer_id'): - visualizer_id = request.REQUEST['visualizer_id'] + for encoder in self.encoders: + formats.append({'name': encoder.format(), 'extension': encoder.file_extension()}) + + graphers = [] + for grapher in self.graphers: + graphers.append({'name':grapher.name(), 'id': grapher.id()}) + if request.REQUEST.has_key('grapher_id'): + grapher_id = request.REQUEST['grapher_id'] else: - visualizer_id = 'waveform_audiolab' - - analyzers = [] - for analyzer in self.analyzers: + grapher_id = 'waveform' + + analyzers = [{'name':'','id':'','unit':'','value':''}] + # TODO: override timeside analyzer process when caching + self.analyzer_mode = 0 + + if self.analyzer_mode: + analyzers_sub = [] if item.file: - value = analyzer.render(item) - else: - value = 'N/A' - - analyzers.append({'name':analyzer.get_name(), - 'id':analyzer.get_id(), - 'unit':analyzer.get_unit(), - 'value':str(value)}) - - vamp = VampCoreAnalyzer() - vamp_plugins = vamp.get_plugins_list() - vamp_plugin_list = [] - for plugin in vamp_plugins: - vamp_plugin_list.append(':'.join(plugin[1:])) - + audio = os.path.join(os.path.dirname(__file__), item.file.path) + decoder = timeside.decoder.FileDecoder(audio) + self.pipe = decoder + for analyzer in self.analyzers: + subpipe = analyzer() + analyzers_sub.append(subpipe) + self.pipe = self.pipe | subpipe + self.pipe.run() + + for analyzer in analyzers_sub: + if item.file: + value = analyzer.result() + if analyzer.id() == 'duration': + approx_value = int(round(value)) + item.approx_duration = approx_value + item.save() + value = datetime.timedelta(0,value) + else: + value = 'N/A' + + analyzers.append({'name':analyzer.name(), + 'id':analyzer.id(), + 'unit':analyzer.unit(), + 'value':str(value)}) + +# vamp = VampCoreAnalyzer() +# vamp_plugins = vamp.get_plugins_list() +# vamp_plugin_list = [] +# for plugin in vamp_plugins: +# vamp_plugin_list.append(':'.join(plugin[1:])) + return render(request, template, {'item': item, 'export_formats': formats, - 'visualizers': visualizers, 'visualizer_id': visualizer_id, - 'analysers': analyzers, 'vamp_plugins': vamp_plugin_list, + 'visualizers': graphers, 'visualizer_id': grapher_id,'analysers': analyzers, 'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', False) }) - + def item_visualize(self, request, public_id, visualizer_id, width, height): - for visualizer in self.visualizers: - if visualizer.get_id() == visualizer_id: + grapher_id = visualizer_id + for grapher in self.graphers: + if grapher.id() == grapher_id: break - if visualizer.get_id() != visualizer_id: + if grapher.id() != grapher_id: raise Http404 - - item = MediaItem.objects.get(public_id=public_id) - visualizer.set_colors((255,255,255), 'purple') - stream = visualizer.render(item, width=int(width), height=int(height)) - response = HttpResponse(stream, mimetype = 'image/png') + media = settings.TELEMETA_EXPORT_DATA_DIR + os.sep + public_id + '_' + grapher_id + '_' + width + '_' + height + '.png' + #graph.set_colors((255,255,255), 'purple') + + if not os.path.exists(media): + item = MediaItem.objects.get(public_id=public_id) + audio = os.path.join(os.path.dirname(__file__), item.file.path) + decoder = timeside.decoder.FileDecoder(audio) + graph = grapher(width=int(width), height=int(height), output=media) + (decoder | graph).run() + graph.render() + response = HttpResponse(stream(media), mimetype = 'image/png') return response def list_export_extensions(self): "Return the recognized item export file extensions, as a list" list = [] - for exporter in self.exporters: - list.append(exporter.get_file_extension()) + for encoder in self.encoders: + list.append(encoder.file_extension()) return list def item_export(self, request, public_id, extension): @@ -160,24 +199,34 @@ class WebView(Component): if extension != 'mp3' and not getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', False): raise Http404 # FIXME: should be some sort of permissions denied error - for exporter in self.exporters: - if exporter.get_file_extension() == extension: + for encoder in self.encoders: + if encoder.file_extension() == extension: break - if exporter.get_file_extension() != extension: + if encoder.file_extension() != extension: raise Http404('Unknown export file extension: %s' % extension) - mime_type = exporter.get_mime_type() - - exporter.set_cache_dir(settings.TELEMETA_EXPORT_CACHE_DIR) - + mime_type = encoder.mime_type() + cache_dir = settings.TELEMETA_EXPORT_CACHE_DIR + media = cache_dir + os.sep + public_id + '.' + encoder.file_extension() + item = MediaItem.objects.get(public_id=public_id) - - infile = item.file.path - metadata = dublincore.express_item(item).to_list() - stream = exporter.process(item.id, infile, metadata) - - response = HttpResponse(stream, mimetype = mime_type) + audio = os.path.join(os.path.dirname(__file__), item.file.path) + decoder = timeside.decoder.FileDecoder(audio) + print decoder.format(), mime_type + if decoder.format() == mime_type: + # source > stream + media = audio + else: + if not os.path.exists(media): + # source > encoder > stream + decoder = timeside.decoder.FileDecoder(audio) + enc = encoder(media) + metadata = dublincore.express_item(item).to_list() + #enc.set_metadata(metadata) + (decoder | enc).run() + + response = HttpResponse(stream(media), mimetype = mime_type) response['Content-Disposition'] = 'attachment' return response -- 2.39.5