shithub: aubio

Download patch

ref: 5140276e1926e5cbbbbb8ffdc2dd923c5a7a94dc
parent: 660c1d824f10572727d987d7c3e166e286c6749e
author: Paul Brossier <piem@altern.org>
date: Wed Feb 15 06:36:17 EST 2006

added aubioweb
added aubioweb


--- /dev/null
+++ b/python/aubio/web/browser.py
@@ -1,0 +1,167 @@
+ #
+ # Copyright 2004 Apache Software Foundation 
+ # 
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
+ # may not use this file except in compliance with the License.  You
+ # may obtain a copy of the License at
+ #
+ #      http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS,
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ # implied.  See the License for the specific language governing
+ # permissions and limitations under the License.
+ #
+ # Originally developed by Gregory Trubetskoy.
+ #
+ # $Id: publisher.py,v 1.36 2004/02/16 19:47:27 grisha Exp $
+
+"""
+  This handler is conceputally similar to Zope's ZPublisher, except
+  that it:
+
+  1. Is written specifically for mod_python and is therefore much faster
+  2. Does not require objects to have a documentation string
+  3. Passes all arguments as simply string
+  4. Does not try to match Python errors to HTTP errors
+  5. Does not give special meaning to '.' and '..'.
+
+  This is a modified version of mod_python.publisher.handler Only the first
+  directory argument is matched, the rest is left for path_info. A default
+  one must be provided.
+
+"""
+
+from mod_python import apache
+from mod_python import util
+from mod_python.publisher import resolve_object,process_auth,imp_suffixes
+
+import sys
+import os
+import re
+
+from types import *
+
+def configure_handler(req,default):
+
+    req.allow_methods(["GET", "POST"])
+    if req.method not in ["GET", "POST"]:
+        raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
+
+    func_path = ""
+    if req.path_info:
+        func_path = req.path_info[1:] # skip first /
+        #func_path = func_path.replace("/", ".")
+        #if func_path[-1:] == ".":
+        #    func_path = func_path[:-1] 
+        # changed: only keep the first directory
+        func_path = re.sub('/.*','',func_path)
+
+    # default to 'index' if no path_info was given
+    if not func_path:
+        func_path = "index"
+
+    # if any part of the path begins with "_", abort
+    if func_path[0] == '_' or func_path.count("._"):
+        raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
+
+    ## import the script
+    path, module_name =  os.path.split(req.filename)
+    if not module_name:
+        module_name = "index"
+
+    # get rid of the suffix
+    #   explanation: Suffixes that will get stripped off
+    #   are those that were specified as an argument to the
+    #   AddHandler directive. Everything else will be considered
+    #   a package.module rather than module.suffix
+    exts = req.get_addhandler_exts()
+    if not exts:
+        # this is SetHandler, make an exception for Python suffixes
+        exts = imp_suffixes
+    if req.extension:  # this exists if we're running in a | .ext handler
+        exts += req.extension[1:] 
+    if exts:
+        suffixes = exts.strip().split()
+        exp = "\\." + "$|\\.".join(suffixes)
+        suff_matcher = re.compile(exp) # python caches these, so its fast
+        module_name = suff_matcher.sub("", module_name)
+
+    # import module (or reload if needed)
+    # the [path] argument tells import_module not to allow modules whose
+    # full path is not in [path] or below.
+    config = req.get_config()
+    autoreload=int(config.get("PythonAutoReload", 1))
+    log=int(config.get("PythonDebug", 0))
+    try:
+        module = apache.import_module(module_name,
+                                      autoreload=autoreload,
+                                      log=log,
+                                      path=[path])
+    except ImportError:
+        et, ev, etb = sys.exc_info()
+        # try again, using default module, perhaps this is a
+        # /directory/function (as opposed to /directory/module/function)
+        func_path = module_name
+        module_name = "index"
+        try:
+            module = apache.import_module(module_name,
+                                          autoreload=autoreload,
+                                          log=log,
+                                          path=[path])
+        except ImportError:
+            # raise the original exception
+            raise et, ev, etb
+        
+    # does it have an __auth__?
+    realm, user, passwd = process_auth(req, module)
+
+    # resolve the object ('traverse')
+    try:
+        object = resolve_object(req, module, func_path, realm, user, passwd)
+    except AttributeError:
+        # changed, return the default path instead
+        #raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
+        object = default
+    # not callable, a class or an unbound method
+    if (not callable(object) or 
+        type(object) is ClassType or
+        (hasattr(object, 'im_self') and not object.im_self)):
+
+        result = str(object)
+        
+    else:
+        # callable, (but not a class or unbound method)
+        
+        # process input, if any
+        req.form = util.FieldStorage(req, keep_blank_values=1)
+        
+        result = util.apply_fs_data(object, req.form, req=req)
+
+    if result or req.bytes_sent > 0 or req.next:
+        
+        if result is None:
+            result = ""
+        else:
+            result = str(result)
+
+        # unless content_type was manually set, we will attempt
+        # to guess it
+        if not req._content_type_set:
+            # make an attempt to guess content-type
+            if result[:100].strip()[:6].lower() == '<html>' \
+               or result.find('</') > 0:
+                req.content_type = 'text/html'
+            else:
+                req.content_type = 'text/plain'
+
+        if req.method != "HEAD":
+            req.write(result)
+        else:
+            req.write("")
+        return apache.OK
+    else:
+        req.log_error("mod_python.publisher: %s returned nothing." % `object`)
+        return apache.HTTP_INTERNAL_SERVER_ERROR
+
--- /dev/null
+++ b/python/aubio/web/html.py
@@ -1,0 +1,287 @@
+from aubio.bench.node import *
+
+def parse_args(req):
+    req.basehref = BASEHREF
+    req.datadir = DATADIR
+    if req.path_info: path_info = req.path_info
+    else: path_info = '/'
+    location = re.sub('^/show_[a-z0-9]*/','',path_info)
+    location = re.sub('^/play_[a-z0-9]*/','',location)
+    location = re.sub('^/index/','',location)
+    location = re.sub('^/','',location)
+    location = re.sub('/$','',location)
+    datapath = "%s/%s" % (DATADIR,location)
+    respath  = "%s/%s" % (DATADIR,location)
+    last     = re.sub('/$','',location)
+    last     = last.split('/')[-1]
+    first    = path_info.split('/')[1]
+    # store some of this in the mp_request
+    req.location, req.datapath, req.respath = location, datapath, respath
+    req.first, req.last = first, last
+
+    if location:
+        if not (os.path.isfile(datapath) or 
+		os.path.isdir(datapath) or 
+		location in ['feedback','email']):
+		# the path was not understood
+		from mod_python import apache
+		req.write("<html> path not found %s</html>" % (datapath))
+		raise apache.SERVER_RETURN, apache.OK
+		#from mod_python import apache
+		#raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
+
+def navigation(req):
+    """ main html navigation header """
+    from mod_python import psp
+    req.content_type = "text/html"
+    parse_args(req)
+    datapath = req.datapath
+    location = req.location
+
+    # deal with session
+    if req.sess.is_new():
+	    msg = "<b>Welcome %s</b><br>" % req.sess['login']
+    else:
+	    msg = "<b>Welcome back %s</b><br>" % req.sess['login']
+
+    # start writing
+    tmpl = psp.PSP(req, filename='header.tmpl')
+    tmpl.run(vars = { 'title': "aubioweb / %s / %s" % (req.first,location),
+    		'basehref': '/~piem/',
+		'message': msg,
+    		'action': req.first})
+
+    req.write("<h2>Content of ")
+    print_link(req,"","/")
+    y = location.split('/')
+    for i in range(len(y)-1): 
+    	print_link(req,"/".join(y[:i+1]),y[i])
+	req.write(" / ")
+    req.write("%s</h2>\n" % y[-1])
+
+    a = {'show_info' : 'info',
+    	 'show_sound': 'waveform',
+    	 'show_onset': 'onset',
+    	 'index'     : 'index',
+	 'show_pitch': 'pitch',
+	 'play_m3u': 'stream (m3u/ogg)',
+	 'play_ogg': 'save (ogg)',
+	 'play_wav': 'save (wav)',
+	 }
+
+    # print task lists (only remaining tasks)
+    print_link(req,re.sub('%s.*'%req.last,'',location),"go up")
+    akeys = a.keys(); akeys.sort();
+    curkey = req.first
+    for akey in akeys: 
+        if akey != curkey:
+    		req.write(":: ")
+		print_link(req,"/".join((akey,location)),a[akey])
+	else:
+    		req.write(":: ")
+		req.write("<b>%s</b>" % a[akey])
+    req.write("<br>")
+
+    # list the content of the directories
+    listdir,listfiles = [],[]
+    if os.path.isdir(datapath):
+        listfiles = list_snd_files(datapath)
+    	listdir = list_dirs(datapath)
+	listdir.pop(0) # kick the current dir
+    elif os.path.isfile(datapath):
+        listfiles = [datapath]
+	listdir = [re.sub(req.last,'',location)]
+
+    link_list(req,listdir,title="Subdirectories")
+    link_list(req,listfiles,title="Files")
+
+def footer(req):
+    """ html navigation footer """
+    from mod_python import psp
+    tmpl = psp.PSP(req, filename='footer.tmpl')
+    tmpl.run(vars = { 'time': -req.mtime+req.request_time })
+
+def apply_on_data(req, func,**keywords):
+    # bug: hardcoded snd file filter
+    act_on_data(func,req.datapath,req.respath,
+    	filter="f  -maxdepth 1 -name '*.wav' -o -name '*.aif'",**keywords)
+
+def print_link(req,target,name,basehref=BASEHREF):
+    req.write("<a href='%s/%s'>%s</a>\n" % (basehref,target,name))
+
+def print_img(req,target,name='',basehref=BASEHREF):
+    if name == '': name = target
+    req.write("<img src='%s/%s' alt='%s' title='%s'>\n" % (basehref,target,name,name))
+
+def link_list(req,targetlist,basehref=BASEHREF,title=None):
+    if len(targetlist) > 1:
+        if title: req.write("<h3>%s</h3>"%title)
+        req.write('<ul>')
+        for i in targetlist:
+            s = re.split('%s/'%DATADIR,i,maxsplit=1)[1]
+            if s: 
+        	req.write('<li>')
+	    	print_link(req,s,s)
+        	req.write('</li>')
+        req.write('</ul>')
+
+def print_list(req,list):
+    req.write("<pre>\n")
+    for i in list: req.write("%s\n" % i)
+    req.write("</pre>\n")
+
+def print_command(req,command):
+    req.write("<h4>%s</h4>\n" % re.sub('%%','%',command))
+    def print_runcommand(input,output):
+        cmd = re.sub('(%)?%i','%s' % input, command)
+        cmd = re.sub('(%)?%o','%s' % output, cmd)
+        print_list(req,runcommand(cmd))
+    apply_on_data(req,print_runcommand)
+
+def datapath_to_location(input):
+    location = re.sub(DATADIR,'',input)
+    return re.sub('^/*','',location)
+
+## drawing hacks
+def draw_func(req,func):
+    import re
+    req.content_type = "image/png"
+    # build location (strip the func_path, add DATADIR)
+    location = re.sub('^/draw_[a-z]*/','%s/'%DATADIR,req.path_info)
+    location = re.sub('.png$','',location)
+    if not os.path.isfile(location):
+	from mod_python import apache
+	raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
+    # replace location in func
+    cmd = re.sub('(%)?%i','%s' % location, func)
+    # add PYTHONPATH at the beginning, 
+    cmd = "%s%s 2> /dev/null" % (PYTHONPATH,cmd)
+    for each in runcommand(cmd):
+	req.write("%s\n"%each)
+
+def show_task(req,task):
+    def show_task_file(input,output,task):
+        location = datapath_to_location(input)
+        print_img(req,"draw_%s/%s" % (task,location))
+    navigation(req)
+    req.write("<h3>%s</h3>\n" % task)
+    apply_on_data(req,show_task_file,task=task)
+    footer(req)
+
+## waveform_foo
+def draw_sound(req):
+    #draw_func(req,"aubioplot-audio %%i stdout 2> /dev/null")
+    draw_func(req,"gdtest %%i 2> /dev/null")
+
+def show_sound(req):
+    show_task(req,"sound")
+
+## pitch foo
+def draw_pitch(req,threshold='0.3'):
+    draw_func(req,"aubiopitch -i %%i -p -m yin -t %s -O stdout -B 2048 -H 1024" % threshold)
+
+def show_pitch(req):
+    show_task(req,"pitch")
+
+## onset foo
+def draw_onset(req,threshold='0.3'):
+    draw_func(req,"aubiocut -i %%i -p -m complex -t %s -O stdout" % threshold)
+
+def show_onset(req,threshold='0.3',details=''):
+    def onset_file(input,output):
+        location = datapath_to_location(input)
+        print_img(req,"draw_onset/%s?threshold=%s"%(location,threshold))
+        print_link(req,"?threshold=%s" % (float(threshold)-0.1),"-")
+        req.write("%s\n" % threshold)
+        print_link(req,"?threshold=%s" % (float(threshold)+0.1),"+")
+	# bug: hardcoded sndfile extension 
+        anote = re.sub('\.wav$','.txt',input)
+	if anote == input: anote = ""
+        res = get_extract(input,threshold)
+        if os.path.isfile(anote):
+            tru = get_anote(anote)
+            print_list(req,get_results(tru,res,0.05))
+        else:
+            req.write("no ground truth found<br>\n")
+        if details:
+            req.write("<h4>Extraction</h4>\n")
+            print_list(req,res)
+        else:
+            req.write("<a href='%s/show_onset/%s?details=yes&amp;threshold=%s'>details</a><br>\n" %
+            	(req.basehref,location,threshold))
+        if details and os.path.isfile(anote):
+            req.write("<h4>Computed differences</h4>\n")
+            ldiffs = get_diffs(tru,res,0.05)
+            print_list(req,ldiffs)
+            req.write("<h4>Annotations</h4>\n")
+            print_list(req,tru)
+    navigation(req)
+    req.write("<h3>Onset</h3>\n")
+    apply_on_data(req,onset_file)
+    footer(req)
+
+def get_anote(anote):
+    import aubio.onsetcompare
+    # FIXME: should import with txtfile.read_datafile
+    return aubio.onsetcompare.load_onsets(anote)
+
+def get_diffs(anote,extract,tol):
+    import aubio.onsetcompare
+    return aubio.onsetcompare.onset_diffs(anote,extract,tol)
+
+def get_extract(datapath,threshold='0.3'):
+    cmd = "%saubiocut -v -m complex -t %s -i %s" % (PYTHONPATH,threshold,datapath)
+    lo = runcommand(cmd)
+    for i in range(len(lo)): lo[i] = float(lo[i])
+    return lo
+
+def get_results(anote,extract,tol):
+    import aubio.onsetcompare
+    orig, missed, merged, expc, bad, doubled = aubio.onsetcompare.onset_roc(anote,extract,tol)
+    s =("GD %2.8f\t"        % (100*float(orig-missed-merged)/(orig)),
+        "FP %2.8f\t"        % (100*float(bad+doubled)/(orig))       , 
+        "GD-merged %2.8f\t" % (100*float(orig-missed)/(orig))       , 
+        "FP-pruned %2.8f\t" % (100*float(bad)/(orig))		    )
+    return s
+
+# play m3u foo
+def play_m3u(req):
+    def show_task_file(input,output,task):
+        location = datapath_to_location(input)
+        req.write("http://%s%s/play_ogg/%s\n" % (HOSTNAME,BASEHREF,re.sub("play_m3u",task,location)))
+    req.content_type = "audio/mpegurl"
+    parse_args(req)
+    apply_on_data(req,show_task_file,task="play_ogg")
+
+# play wav foo
+def play_wav(req):
+    req.content_type = "audio/x-wav"
+    func = "cat %%i"
+    # build location (strip the func_path, add DATADIR)
+    location = re.sub('^/play_wav/','%s/'%DATADIR,req.path_info)
+    if not os.path.isfile(location):
+	from mod_python import apache
+	raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
+    # replace location in func
+    cmd = re.sub('(%)?%i','%s' % location, func)
+    # add PYTHONPATH at the beginning, 
+    cmd = "%s 2> /dev/null" % cmd
+    for each in runcommand(cmd):
+	req.write("%s\n"%each)
+
+# play ogg foo
+def play_ogg(req):
+    req.content_type = "application/ogg"
+    func = "oggenc -o - %%i"
+    # build location (strip the func_path, add DATADIR)
+    location = re.sub('^/play_ogg/','%s/'%DATADIR,req.path_info)
+    location = re.sub('.ogg$','',location)
+    if not os.path.isfile(location):
+	from mod_python import apache
+	raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
+    # replace location in func
+    cmd = re.sub('(%)?%i','%s' % location, func)
+    # add PYTHONPATH at the beginning, 
+    cmd = "%s 2> /dev/null" % cmd
+    for each in runcommand(cmd):
+	req.write("%s\n"%each)
--- /dev/null
+++ b/python/aubioweb.py
@@ -1,0 +1,113 @@
+#!/usr/bin/python
+
+doc = """
+This script works with mod_python to browse a collection of annotated wav files
+and results.
+
+you will need to have at least the following packages need to be installed (the
+name of the command line tool is precised in parenthesis):
+
+libapache-mod-python (apache2 prefered)
+sndfile-programs (sndfile-info)
+vorbis-tools (oggenc)
+python-gnuplot
+python-numarray
+
+Try the command line tools in aubio/python to test your installation.
+
+NOTE: this script is probably horribly insecure.
+
+example configuration for apache to put in your preferred virtual host.
+
+<Directory /home/piem/public_html/aubioweb>
+    # Minimal config
+    AddHandler mod_python .py
+    # Disable these in production
+    PythonDebug On
+    PythonAutoReload on
+    # Default handler in url
+    PythonHandler aubioweb
+    ## Authentication stuff (optional)
+    #PythonAuthenHandler aubioweb
+    #AuthType Basic
+    #AuthName "Restricted Area"
+    #require valid-user
+    # make default listing
+    DirectoryIndex aubioweb/
+</Directory>
+
+"""
+
+from aubio.web.html import *
+
+def handler(req):
+    from aubio.web.browser import *
+    from mod_python import Session
+    req.sess = Session.Session(req)
+    req.sess['login']='new aubio user'
+    req.sess.save()
+    return configure_handler(req,index)
+
+def index(req,threshold='0.3'):
+    navigation(req)
+    print_command(req,"sfinfo %%i")
+    return footer(req)
+
+def show_info(req,verbose=''):
+    navigation(req)
+    print_command(req,"sndfile-info %%i")
+    return footer(req)
+
+def feedback(req):
+    navigation(req)
+    req.write("""
+    Please provide feedback below:
+  <p>                           
+  <form action="/~piem/aubioweb/email" method="POST">
+      Name:    <input type="text" name="name"><br>
+      Email:   <input type="text" name="email"><br>
+      Comment: <textarea name="comment" rows=4 cols=20></textarea><br>
+      <input type="submit">
+  </form>
+    """)
+
+WEBMASTER='piem@calabaza'
+SMTP_SERVER='localhost'
+
+def email(req,name,email,comment):
+    import smtplib
+    # make sure the user provided all the parameters
+    if not (name and email and comment):
+        return "A required parameter is missing, \
+               please go back and correct the error"
+    # create the message text
+    msg = """\
+From: %s                                                                                                                                           
+Subject: feedback
+To: %s
+
+I have the following comment:
+
+%s
+
+Thank You,
+
+%s
+
+""" % (email, WEBMASTER, comment, name)
+    # send it out
+    conn = smtplib.SMTP(SMTP_SERVER)
+    try:
+	conn.sendmail(email, [WEBMASTER], msg)
+    except smtplib.SMTPSenderRefused:
+	return """<html>please provide a valid email</html>"""
+	
+    conn.quit()
+    # provide feedback to the user
+    s = """\
+<html>
+Dear %s,<br>
+Thank You for your kind comments, we
+will get back to you shortly.
+</html>""" % name
+    return s