: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
$ 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
: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:
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"
"""
Telemeta
-Parisson SARL
U{http://telemeta.org}
+++ /dev/null
-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 *
-
-
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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"""
-
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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
+++ /dev/null
-# -*- 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 <http://www.gnu.org/licenses/>.
-#
-# Authors:
-# Bram de Jong <bram.dejong at domain.com where domain in gmail>
-# Guillaume Pellerin <yomguy at parisson.com>
-
-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)
-
-
-
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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)
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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)
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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)
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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
+++ /dev/null
-# -*- 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 <yomguy@parisson.com>
-
-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
+++ /dev/null
-from telemeta.analysis.vamp.core import *
+++ /dev/null
-
-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)
+++ /dev/null
-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
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2007-2009 Parisson
-# Copyright (c) 2007 Olivier Guilyardi <olivier@samalyse.com>
-# Copyright (c) 2007-2009 Guillaume Pellerin <pellerin@parisson.com>
-#
-# This 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)
+++ /dev/null
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@parisson.com>
-
-# 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"')
-
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@parisson.com>
-
-# 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 <pellerin@parisson.com>
-
-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)
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2007 Parisson SARL
-# Copyright (c) 2006-2007 Guillaume Pellerin <pellerin@parisson.com>
-
-# 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)
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2007 Parisson
-# Copyright (c) 2006-2009 Guillaume Pellerin <pellerin@parisson.com>
-
-# 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)
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright Guillaume Pellerin, (2006-2009)
-# <yomguy@parisson.com>
-
-# 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'
-
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')
<li>
<a href="{% url telemeta-item-detail item.public_id %}"><b>{{item}}</b></a>
<br />
- <span class="info">{{item.computed_duration}} {{item.apparent_collector|prepend:' - '}} - {{item.country_or_continent}}</span>
+ <span class="info">{{item.apparent_collector|prepend:' - '}} - {{item.country_or_continent}}</span>
</li>
{% endfor %}
</ul>
-</div>
+</div>
{% endblock %}
</div>
soundManager.flashVersion = 9;\r
soundManager.debugMode = false;\r
set_player_image_url('{% url telemeta-item-visualize item.public_id,visualizer_id,"WIDTH","HEIGHT" %}');\r
-load_player({{ item.computed_duration.as_seconds }});\r
+load_player({{ item.approx_duration.as_seconds }});\r
</script>\r
\r
{% endblock %}\r
<div class="ts-wave">\r
<div class="ts-image-container">\r
<a href="{% url telemeta-item-export item.public_id,"mp3" %}">\r
- <img class="ts-image" src="{% url telemeta-item-visualize item.public_id,visualizer_id,360,130 %}" \r
+ <img class="ts-image" src="{% url telemeta-item-visualize item.public_id,visualizer_id,360,130 %}"\r
alt="" /></a>\r
</div>\r
- </div> \r
+ </div>\r
</div>\r
</div>\r
</div>\r
<input type="submit" value="Set" />\r
</form>\r
\r
- <div class="analyzer">\r
+<!-- <div class="analyzer">\r
<table width="100%">\r
<tr class="analyzer-title">\r
<td>Property</td>\r
<td>\r
{{ analyser.name }}\r
</td>\r
- <td> \r
+ <td>\r
{{ analyser.value }}\r
</td>\r
<td>\r
</tr>\r
{% endfor %}\r
</table>\r
- </div>\r
-\r
+ </div>-->\r
+<!--\r
<form method="get" action="#">\r
<p> Vamp plugin analysis</p>\r
<select name="vamp_id">\r
</select>\r
<input type="submit" value="Get" />\r
</form>\r
- \r
+-->\r
</div>\r
\r
{% if audio_export_enabled %}\r
<dl class="listing">\r
<dt>{% trans "Media type" %}</dt><dd>{% trans "Audio" %}</dd>\r
{% dl_field item "approx_duration" %}\r
- {% dl_field item "computed_duration" %}\r
</dl>\r
</div>\r
</div>\r
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
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(), }
+++ /dev/null
-from telemeta.visualization.api import *
-from telemeta.visualization.waveform_audiolab import *
-from telemeta.visualization.spectrogram_audiolab import *
-from telemeta.visualization.spectrogram_octave import *
+++ /dev/null
-# -*- 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 <olivier@samalyse.com>
-
-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"""
+++ /dev/null
-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
+++ /dev/null
-## 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
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@parisson.com>
-
-# 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 <pellerin@parisson.com>
-#
-# 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;
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@parisson.com>
-
-# 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 <pellerin@parisson.com>
-#
-# 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;
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@parisson.com>
-
-# 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)
-
-
+++ /dev/null
-# 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 <olivier@samalyse.com>
-
-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
-
-
-
-
-
-
-
-
+++ /dev/null
-# 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 <pellerin@parisson.com>
-
-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()
+++ /dev/null
-# 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 <olivier@samalyse.com>
-
-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()
-
+++ /dev/null
-# 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 <olivier@samalyse.com>
-# Guillaume Pellerin <pellerin@parisson.com>
-
-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
-
-
+++ /dev/null
-# 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 <pellerin@parisson.com>
-
-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()
-
+++ /dev/null
-
-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()
-
+++ /dev/null
-# -*- 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 <pellerin@parisson.com>
-
-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()
+++ /dev/null
-# -*- coding: utf-8 -*-
-# Copyright (C) 2007 Samalyse SARL
-# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@parisson.com>
-#
-# 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 <olivier@samalyse.com>
-# Guillaume Pellerin <pellerin@parisson.com>
-
-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
-
+++ /dev/null
-"""
-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()
+++ /dev/null
-#!/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 <http://www.gnu.org/licenses/>.
-#
-# Authors:
-# Bram de Jong <bram.dejong at domain.com where domain in gmail>
-# Contributors:
-# Guillaume Pellerin <pellerin@parisson.com>
-
-
-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)
-
-
+++ /dev/null
-# -*- 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 <pellerin@parisson.com>
-
-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()
-
from telemeta.web.base import WebView
+from pages import *
import re
import os
import sys
+import datetime
from django.template import RequestContext, loader
from django import template
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"""
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):
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