--- /dev/null
+from telemeta.analysis.api import *
+from telemeta.analysis.core import *
+from telemeta.analysis.max_level import *
+from telemeta.analysis.mean_level import *
+from telemeta.analysis.length import *
--- /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 <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 a list containing data results of the process"""
+
--- /dev/null
+# 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 = settings.MEDIA_ROOT + '/' + media_item.file
+ 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()
+
+ def post_process(self, audio_file):
+ pass
+
+ def get_mono_samples(self):
+ samples = self.audio_file.read_frames(self.frames)
+ # convert to mono by selecting left channel only
+ if self.channels > 1:
+ samples = samples[:,0]
+ 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:
+ return numpy.zeros(size) if resize_if_less else 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...
+ return numpy.zeros(size) if resize_if_less else 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
+
+ return (min_value, max_value) if min_index < max_index else (max_value, min_value)
+
+
+
\ No newline at end of file
--- /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 <yomguy@parisson.com>
+
+from telemeta.analysis.core import *
+from telemeta.analysis.api import IMediaItemAnalyzer
+import numpy
+
+class LengthAnalyzer(AudioProcessor):
+ """Media item analyzer driver interface"""
+
+ implements(IMediaItemAnalyzer)
+
+ def __init__(self):
+ self.fft_size = 2048
+ self.window_function = numpy.hanning
+ self.window = self.window_function(self.fft_size)
+
+ def get_id(self):
+ return "length"
+
+ def get_name(self):
+ return "Length"
+
+ def get_unit(self):
+ return "s"
+
+ def render(self, media_item, options=None):
+ self.pre_process(media_item)
+ return numpy.round(numpy.divide(self.frames, self.samplerate),2)
--- /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 <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 __init__(self):
+ self.fft_size = 2048
+ self.window_function = numpy.hanning
+ self.window = self.window_function(self.fft_size)
+
+ def get_id(self):
+ return "max_level"
+
+ def get_name(self):
+ return "Maximum level"
+
+ def get_unit(self):
+ return "dB"
+
+ def render(self, media_item, options=None):
+ self.pre_process(media_item)
+ samples = self.get_mono_samples()
+ print str(numpy.max(samples))
+ return numpy.round(20*numpy.log10(numpy.max(samples)),2)
\ No newline at end of file
--- /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 <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 __init__(self):
+ self.fft_size = 2048
+ self.window_function = numpy.hanning
+ self.window = self.window_function(self.fft_size)
+
+ def get_id(self):
+ return "mean_level"
+
+ def get_name(self):
+ return "Mean level"
+
+ def get_unit(self):
+ return "dB"
+
+ def render(self, media_item, options=None):
+ self.pre_process(media_item)
+ samples = self.get_mono_samples()
+ size = numpy.size(samples)
+ return numpy.round(20*numpy.log10(numpy.mean(numpy.sqrt(numpy.square(samples)))),2)
font-size: 1em;\r
}\r
\r
+.analyser {\r
+ background-color: #fff;\r
+ color: #555;\r
+ border: 1px solid #adadad;\r
+ width: 301px;\r
+ padding: 2px;\r
+ margin: 5px 0 0;\r
+ overflow: auto;\r
+ font-size: 1em;\r
+}\r
+\r
/* Geographic navigator */\r
ul.continents, ul.continents ul { list-style: none; margin: 0; padding: 0;}\r
ul.continents { margin: 1em 0; }\r
<a href="{% url telemeta-item-export item.id|urlencode,format.extension %}">{{ format.name }}</a>\r
{% endfor %}</p>\r
</div>\r
+ <div class="analyser">\r
+ <p>Analysis:</p>\r
+ <br>\r
+ <table>\r
+ {% for analyser in analysers %}\r
+ <tr>\r
+ <td>\r
+ {{ analyser.name }}\r
+ </td>\r
+ <td> =\r
+ {{ analyser.value }}\r
+ </td>\r
+ <td>\r
+ {{ analyser.unit }}\r
+ </td>\r
+ </tr>\r
+ {% endfor %}\r
+ </table>\r
+ </div>\r
</div>\r
{% endif %}\r
<div id="leftcol">\r
COPYING.txt
+Changelog
FLAC_SUPPORT.txt
+INSTALL.txt
MANIFEST.in
+Makefile
README.txt
+TODO
+generate.sh
generate_const.py
+generate_const.pyc
header_parser.py
+header_parser.pyc
setup.cfg
setup.py
site.cfg.win32
+site.cfg_noflac
+tester.py
scikits/__init__.py
+scikits/__init__.pyc
scikits.audiolab.egg-info/PKG-INFO
scikits.audiolab.egg-info/SOURCES.txt
scikits.audiolab.egg-info/dependency_links.txt
scikits.audiolab.egg-info/top_level.txt
scikits.audiolab.egg-info/zip-safe
scikits/audiolab/__init__.py
+scikits/audiolab/__init__.pyc
scikits/audiolab/info.py
+scikits/audiolab/info.pyc
scikits/audiolab/matapi.py
+scikits/audiolab/matapi.pyc
scikits/audiolab/pyaudioio.py
scikits/audiolab/pysndfile.py
scikits/audiolab/pysndfile.py.in
+scikits/audiolab/pysndfile.pyc
+scikits/audiolab/docs/Makefile
+scikits/audiolab/docs/audiolab1.png
+scikits/audiolab/docs/base.tex
+scikits/audiolab/docs/index.txt
+scikits/audiolab/docs/user.tex
+scikits/audiolab/docs/examples/format1.py
+scikits/audiolab/docs/examples/format2.py
+scikits/audiolab/docs/examples/matlab1.py
+scikits/audiolab/docs/examples/quick1.py
+scikits/audiolab/docs/examples/usage1.py
+scikits/audiolab/docs/examples/usage2.py
+scikits/audiolab/docs/examples/write1.py
+scikits/audiolab/misc/Makefile
+scikits/audiolab/misc/Sconstruct
+scikits/audiolab/misc/badflac.c
+scikits/audiolab/misc/badflac.flac
+scikits/audiolab/misc/winfdopen.c
+scikits/audiolab/soundio/SConstruct
+scikits/audiolab/soundio/_alsa.pyx
+scikits/audiolab/soundio/alsa.py
+scikits/audiolab/soundio/alsa_ctypes.py
+scikits/audiolab/soundio/setup.py
+scikits/audiolab/soundio/simple.c
+scikits/audiolab/soundio/simple2.c
+scikits/audiolab/test_data/original.aif
+scikits/audiolab/test_data/test.aiff
+scikits/audiolab/test_data/test.au
+scikits/audiolab/test_data/test.flac
+scikits/audiolab/test_data/test.raw
+scikits/audiolab/test_data/test.sdif
+scikits/audiolab/test_data/test.wav
scikits/audiolab/tests/__init__.py
scikits/audiolab/tests/test_matapi.py
scikits/audiolab/tests/test_pysndfile.py
from telemeta.visualization.api import *
-from telemeta.visualization.waveform import *
+#from telemeta.visualization.waveform import *
#from telemeta.visualization.waveform2 import *
from telemeta.visualization.waveform3 import *
#from telemeta.visualization.spectrogram import *
from telemeta.visualization.spectrogram2 import *
-from telemeta.visualization.spectrogram3 import *
\ No newline at end of file
+from telemeta.visualization.spectrogram3 import *
+from telemeta.visualization.waveform4 import *
+from telemeta.visualization.spectrogram4 import *
\ No newline at end of file
from telemeta.core import *
-from telemeta.export import *
-from telemeta.visualization.api import IMediaItemVisualizer
+#from telemeta.visualization.api import IMediaItemVisualizer
from django.conf import settings
from tempfile import NamedTemporaryFile
import os
wav_file = settings.MEDIA_ROOT + '/' + media_item.file
pngFile_w = NamedTemporaryFile(suffix='.png')
pngFile_s = NamedTemporaryFile(suffix='.png')
- image_width = 1800
+ image_width = 305
image_height = 150
fft_size = 2048
args = (wav_file, pngFile_w.name, pngFile_s.name, image_width, image_height, fft_size)
wav_file = settings.MEDIA_ROOT + '/' + media_item.file
pngFile_w = NamedTemporaryFile(suffix='.png')
pngFile_s = NamedTemporaryFile(suffix='.png')
- image_width = 300
+ image_width = 305
image_height = 152
fft_size = 2048
args = (wav_file, pngFile_w.name, pngFile_s.name, image_width, image_height, fft_size)
from telemeta.core import Component, ExtensionPoint
from telemeta.export import *
from telemeta.visualization import *
+from telemeta.analysis import *
class WebView(Component):
"""Provide web UI methods"""
exporters = ExtensionPoint(IExporter)
visualizers = ExtensionPoint(IMediaItemVisualizer)
+ analyzers = ExtensionPoint(IMediaItemAnalyzer)
def index(self, request):
"""Render the homepage"""
def item_detail(self, request, item_id, template='mediaitem_detail.html'):
"""Show the details of a given item"""
item = MediaItem.objects.get(pk=item_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':
else:
visualizer_id = 'waveform3'
+ analyzers = []
+ for analyzer in self.analyzers:
+ value = analyzer.render(item)
+ analyzers.append({'name':analyzer.get_name(),
+ 'id':analyzer.get_id(),
+ 'unit':analyzer.get_unit(),
+ 'value':str(value)})
+
return render_to_response(template,
{'item': item, 'export_formats': formats,
- 'visualizers': visualizers, 'visualizer_id': visualizer_id})
+ 'visualizers': visualizers, 'visualizer_id': visualizer_id,
+ 'analysers': analyzers})
def item_visualize(self, request, item_id, visualizer_id):
for visualizer in self.visualizers: