From c797394d45a1ff820b4f4ffc465bc51c00fed7b1 Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Thu, 16 Feb 2012 23:27:11 +0100 Subject: Serve and play raw tracks from /track/ using SoundManager 2. --- app.py | 59 ++++++++++++++++++++++-- db.py | 5 ++ static/icons/folder.png | Bin 0 -> 537 bytes static/icons/music.png | Bin 0 -> 385 bytes static/index.html | 10 +++- static/init.js | 25 +++++++++- static/soundmanager2-jsmin.js | 104 ++++++++++++++++++++++++++++++++++++++++++ static/style.css | 5 ++ 8 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 static/icons/folder.png create mode 100644 static/icons/music.png create mode 100755 static/soundmanager2-jsmin.js create mode 100644 static/style.css diff --git a/app.py b/app.py index 0358b96..18c48f5 100755 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env python2 -import db, os, json, mimetypes +import db, os, json, mimetypes, datetime from config import config class JSONApplication(object): @@ -30,6 +30,8 @@ class JSONApplication(object): return self.handlers[module](self, environ, start_response, path) class Application(object): + rfc1123_format = '%a, %d %b %Y %H:%M:%S +0000' + def __init__(self): self.jsonapp = JSONApplication() @@ -37,20 +39,67 @@ class Application(object): path.pop(0) return self.jsonapp(environ, start_response, path) + def _serve_path(self, environ, start_response, filename): + if not os.path.exists(filename) or '..' in filename.split(os.path.sep): + start_response('404 Not Found', []) + return [] + + do_range = 'HTTP_RANGE' in environ + if do_range: + file_range = environ['HTTP_RANGE'].split('bytes=')[1] + + mime = mimetypes.guess_type(filename, strict = False)[0] or 'application/octet-stream' + last_modified = datetime.datetime.fromtimestamp(os.path.getmtime(filename)).strftime(self.rfc1123_format) + + # Range handling + if do_range: + start, end = [int(x or 0) for x in file_range.split('-')] + size = os.path.getsize(filename) + + if end == 0: + end = size-1 + + write_out = start_response('206 Partial Content', [('Content-Type', mime), + ('Content-Range', 'bytes {start}-{end}/{size}'.format(start = start, end = end, size = size)), + ('Content-Length', str(end - start + 1)), ('Last-Modified', last_modified)]) + + f = open(filename, 'rb') + f.seek(start) + remaining = end-start+1 + s = f.read(min(remaining, 1024)) + while s: + write_out(s) + remaining -= len(s) + s = f.read(min(remaining, 1024)) + return [] + + start_response('200 OK', [('Content-Type', mime), ('Content-Length', str(os.path.getsize(filename))), + ('Last-Modified', last_modified)]) + return open(filename, 'rb') + def static(self, environ, start_response, path): filename = os.path.join('static', *path[1:]) + return self._serve_path(environ, start_response, filename) - if not os.path.exists(filename) or '..' in path: + def track(self, environ, start_response, path): + track = int(path[1]) + session = db.Session() + try: + track = db.Track.get_by_id(session, track) + filename = track.get_path() + except db.NoResultFound: start_response('404 Not Found', []) return [] + finally: + session.close() - mime = mimetypes.guess_type(filename, strict = False)[0] or 'application/octet-stream' - start_response('200 OK', [('Content-Type', mime)]) - return open(filename, 'rb') + return self._serve_path(environ, start_response, filename) handlers = { 'json': json, 'static': static, + 'file': file, + 'track': track, } def __call__(self, environ, start_response): diff --git a/db.py b/db.py index e4e15db..aadcda5 100644 --- a/db.py +++ b/db.py @@ -133,6 +133,10 @@ class Track(Base): session.add(track) return track + @staticmethod + def get_by_id(session, id): + return session.query(Track).filter(Track.id == id).one() + @staticmethod def find(session, path, track = None): directory, filename = os.path.split(path) @@ -171,6 +175,7 @@ class Track(Base): def dict(self): return { + 'id': self.id, 'type': 'track', 'name': self.get_relpath(), 'track': self.file_index, diff --git a/static/icons/folder.png b/static/icons/folder.png new file mode 100644 index 0000000..784e8fa Binary files /dev/null and b/static/icons/folder.png differ diff --git a/static/icons/music.png b/static/icons/music.png new file mode 100644 index 0000000..a8b3ede Binary files /dev/null and b/static/icons/music.png differ diff --git a/static/index.html b/static/index.html index a81c08a..47f7e70 100644 --- a/static/index.html +++ b/static/index.html @@ -3,10 +3,16 @@ 音楽 + + - +
+
    +
+ + +
diff --git a/static/init.js b/static/init.js index 369bb86..6ec2725 100644 --- a/static/init.js +++ b/static/init.js @@ -1,8 +1,31 @@ +soundManager.useHTML5Audio = true; +soundManager.preferFlash = false; + $(document).ready(function() { $.get('/json/list', function(data) { var dir_list = $('#directory-list'); $.each(data, function(i, item) { - dir_list.append($('
  • ').text(item.name)); + dir_list.append($('
  • ') + .text(item.name) + .addClass(item.type) + .click(function() { + console.log(item); + var sound = soundManager.createSound({ + id: 'audio', + url: '/track/' + item.id, + whileplaying: function() { + var seconds = (sound.position / 1000).toFixed(0); + var minutes = Math.floor(seconds / 60).toFixed(0); + seconds %= 60; + if(seconds < 10) + seconds = '0' + seconds; + var pos = minutes + ':' + seconds; + $('#status').text(pos); + } + }); + sound.play(); + }) + ); }); }); }); diff --git a/static/soundmanager2-jsmin.js b/static/soundmanager2-jsmin.js new file mode 100755 index 0000000..e8b14ae --- /dev/null +++ b/static/soundmanager2-jsmin.js @@ -0,0 +1,104 @@ +/** @license + + + SoundManager 2: JavaScript Sound for the Web + ---------------------------------------------- + http://schillmania.com/projects/soundmanager2/ + + Copyright (c) 2007, Scott Schiller. All rights reserved. + Code provided under the BSD License: + http://schillmania.com/projects/soundmanager2/license.txt + + V2.97a.20111220 +*/ +(function(G){function W(W,la){function l(b){return function(a){var d=this._t;return!d||!d._a?(d&&d.sID?c._wD(k+"ignoring "+a.type+": "+d.sID):c._wD(k+"ignoring "+a.type),null):b.call(this,a)}}this.flashVersion=8;this.debugMode=!0;this.debugFlash=!1;this.consoleOnly=this.useConsole=!0;this.waitForWindowLoad=!1;this.bgColor="#ffffff";this.useHighPerformance=!1;this.html5PollingInterval=this.flashPollingInterval=null;this.flashLoadTimeout=1E3;this.wmode=null;this.allowScriptAccess="always";this.useFlashBlock= +!1;this.useHTML5Audio=!0;this.html5Test=/^(probably|maybe)$/i;this.preferFlash=!0;this.noSWFCache=!1;this.audioFormats={mp3:{type:['audio/mpeg; codecs="mp3"',"audio/mpeg","audio/mp3","audio/MPA","audio/mpa-robust"],required:!0},mp4:{related:["aac","m4a"],type:['audio/mp4; codecs="mp4a.40.2"',"audio/aac","audio/x-m4a","audio/MP4A-LATM","audio/mpeg4-generic"],required:!1},ogg:{type:["audio/ogg; codecs=vorbis"],required:!1},wav:{type:['audio/wav; codecs="1"',"audio/wav","audio/wave","audio/x-wav"],required:!1}}; +this.defaultOptions={autoLoad:!1,autoPlay:!1,from:null,loops:1,onid3:null,onload:null,whileloading:null,onplay:null,onpause:null,onresume:null,whileplaying:null,onposition:null,onstop:null,onfailure:null,onfinish:null,multiShot:!0,multiShotEvents:!1,position:null,pan:0,stream:!0,to:null,type:null,usePolicyFile:!1,volume:100};this.flash9Options={isMovieStar:null,usePeakData:!1,useWaveformData:!1,useEQData:!1,onbufferchange:null,ondataerror:null};this.movieStarOptions={bufferTime:3,serverURL:null,onconnect:null, +duration:null};this.movieID="sm2-container";this.id=la||"sm2movie";this.debugID="soundmanager-debug";this.debugURLParam=/([#?&])debug=1/i;this.versionNumber="V2.97a.20111220";this.movieURL=this.version=null;this.url=W||null;this.altURL=null;this.enabled=this.swfLoaded=!1;this.oMC=null;this.sounds={};this.soundIDs=[];this.didFlashBlock=this.muted=!1;this.filePattern=null;this.filePatterns={flash8:/\.mp3(\?.*)?$/i,flash9:/\.mp3(\?.*)?$/i};this.features={buffering:!1,peakData:!1,waveformData:!1,eqData:!1, +movieStar:!1};this.sandbox={type:null,types:{remote:"remote (domain-based) rules",localWithFile:"local with file access (no internet access)",localWithNetwork:"local with network (internet access only, no local access)",localTrusted:"local, trusted (local+internet access)"},description:null,noRemote:null,noLocal:null};var ma;try{ma="undefined"!==typeof Audio&&"undefined"!==typeof(new Audio).canPlayType}catch(fb){ma=!1}this.hasHTML5=ma;this.html5={usingFlash:null};this.flash={};this.ignoreFlash=this.html5Only= +!1;var Ea,c=this,i=null,k="HTML5::",u,p=navigator.userAgent,j=G,O=j.location.href.toString(),h=document,na,X,m,B=[],oa=!0,w,P=!1,Q=!1,n=!1,y=!1,Y=!1,o,Za=0,R,v,pa,H,I,Z,Fa,qa,E,$,aa,J,ra,sa,ba,ca,K,Ga,ta,$a=["log","info","warn","error"],Ha,da,Ia,S=null,ua=null,q,va,L,Ja,ea,fa,wa,s,ga=!1,xa=!1,Ka,La,Ma,ha=0,T=null,ia,z=null,Na,ja,U,C,ya,za,Oa,r,Pa=Array.prototype.slice,F=!1,t,ka,Qa,A,Ra,Aa=p.match(/(ipad|iphone|ipod)/i),ab=p.match(/firefox/i),bb=p.match(/droid/i),D=p.match(/msie/i),cb=p.match(/webkit/i), +V=p.match(/safari/i)&&!p.match(/chrome/i),db=p.match(/opera/i),Ba=p.match(/(mobile|pre\/|xoom)/i)||Aa,Ca=!O.match(/usehtml5audio/i)&&!O.match(/sm2\-ignorebadua/i)&&V&&!p.match(/silk/i)&&p.match(/OS X 10_6_([3-7])/i),Sa="undefined"!==typeof console&&"undefined"!==typeof console.log,Da="undefined"!==typeof h.hasFocus?h.hasFocus():null,M=V&&"undefined"===typeof h.hasFocus,Ta=!M,Ua=/(mp3|mp4|mpa)/i,N=h.location?h.location.protocol.match(/http/i):null,Va=!N?"http://":"",Wa=/^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|mp4v|3gp|3g2)\s*(?:$|;)/i, +Xa="mpeg4,aac,flv,mov,mp4,m4v,f4v,m4a,mp4v,3gp,3g2".split(","),eb=RegExp("\\.("+Xa.join("|")+")(\\?.*)?$","i");this.mimePattern=/^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i;this.useAltURL=!N;this._global_a=null;if(Ba&&(c.useHTML5Audio=!0,c.preferFlash=!1,Aa))F=c.ignoreFlash=!0;this.supported=this.ok=function(){return z?n&&!y:c.useHTML5Audio&&c.hasHTML5};this.getMovie=function(c){return u(c)||h[c]||j[c]};this.createSound=function(b){function a(){f=ea(f);c.sounds[e.id]=new Ea(e);c.soundIDs.push(e.id); +return c.sounds[e.id]}var d,f=null,e=d=null;d="soundManager.createSound(): "+q(!n?"notReady":"notOK");if(!n||!c.ok())return wa(d),!1;2===arguments.length&&(b={id:arguments[0],url:arguments[1]});f=v(b);f.url=ia(f.url);e=f;e.id.toString().charAt(0).match(/^[0-9]$/)&&c._wD("soundManager.createSound(): "+q("badID",e.id),2);c._wD("soundManager.createSound(): "+e.id+" ("+e.url+")",1);if(s(e.id,!0))return c._wD("soundManager.createSound(): "+e.id+" exists",1),c.sounds[e.id];if(ja(e))d=a(),c._wD("Loading sound "+ +e.id+" via HTML5"),d._setup_html5(e);else{if(8=b)return!1;for(;b--;)if(c=x[b],!c.fired&&a.position>=c.position)c.fired=!0,l++,c.method.apply(c.scope,[c.position]);return!0};this._resetOnPosition=function(a){var b,c;b=x.length;if(!b)return!1;for(;b--;)if(c=x[b],c.fired&&a<=c.position)c.fired=!1,l--;return!0};r=function(){var b=a._iO,d=b.from,e=b.to,f,g;g=function(){c._wD(a.sID+': "to" time of '+e+" reached.");a.clearOnPosition(e,g);a.stop()};f=function(){c._wD(a.sID+ +': playing "from" '+d);if(null!==e&&!isNaN(e))a.onPosition(e,g)};if(null!==d&&!isNaN(d))b.position=d,b.multiShot=!1,f();return b};Ya=function(){var b=a._iO.onposition;if(b)for(var c in b)if(b.hasOwnProperty(c))a.onPosition(parseInt(c,10),b[c])};n=function(){var b=a._iO.onposition;if(b)for(var c in b)b.hasOwnProperty(c)&&a.clearOnPosition(parseInt(c,10))};g=function(){a.isHTML5&&Ka(a)};h=function(){a.isHTML5&&La(a)};d=function(){x=[];l=0;j=!1;a._hasTimer=null;a._a=null;a._html5_canplay=!1;a.bytesLoaded= +null;a.bytesTotal=null;a.duration=a._iO&&a._iO.duration?a._iO.duration:null;a.durationEstimate=null;a.eqData=[];a.eqData.left=[];a.eqData.right=[];a.failures=0;a.isBuffering=!1;a.instanceOptions={};a.instanceCount=0;a.loaded=!1;a.metadata={};a.readyState=0;a.muted=!1;a.paused=!1;a.peakData={left:0,right:0};a.waveformData={left:[],right:[]};a.playState=0;a.position=null};d();this._onTimer=function(b){var c,d=!1,e={};if(a._hasTimer||b){if(a._a&&(b||(0f.duration?a.duration:f.duration:parseInt(a.bytesTotal/a.bytesLoaded*a.duration,10),void 0===a.durationEstimate)a.durationEstimate=a.duration;3!==a.readyState&&f.whileloading&&f.whileloading.apply(a)};this._whileplaying=function(b,c,d,e,f){var g=a._iO;if(isNaN(b)|| +null===b)return!1;a.position=b;a._processOnPosition();if(!a.isHTML5&&8m)c._wD(q("needfl9")),c.flashVersion=m=9;c.version=c.versionNumber+(c.html5Only?" (HTML5-only mode)":9===m?" (AS3/Flash 9)":" (AS2/Flash 8)");8'}if(P&&Q)return!1;if(c.html5Only)return qa(),d(),c.oMC=u(c.movieID),X(),Q=P=!0,!1;var e=a||c.url,i=c.altURL||e,g;g=ba();var j,m,k=L(),l,n=null,n=(n=h.getElementsByTagName("html")[0])&&n.dir&&n.dir.match(/rtl/i),b="undefined"===typeof b?c.id:b;qa();c.url=Ia(N?e:i);a=c.url;c.wmode=!c.wmode&&c.useHighPerformance?"transparent":c.wmode;if(null!==c.wmode&&(p.match(/msie 8/i)||!D&&!c.useHighPerformance)&&navigator.platform.match(/win32|win64/i))o("spcWmode"), +c.wmode=null;g={name:b,id:b,src:a,width:"auto",height:"auto",quality:"high",allowScriptAccess:c.allowScriptAccess,bgcolor:c.bgColor,pluginspage:Va+"www.macromedia.com/go/getflashplayer",title:"JS/Flash audio component (SoundManager 2)",type:"application/x-shockwave-flash",wmode:c.wmode,hasPriority:"true"};if(c.debugFlash)g.FlashVars="debug=1";c.wmode||delete g.wmode;if(D)e=h.createElement("div"),m=['',f("movie",a),f("AllowScriptAccess",c.allowScriptAccess),f("quality",g.quality),c.wmode?f("wmode",c.wmode):"",f("bgcolor",c.bgColor),f("hasPriority","true"),c.debugFlash?f("FlashVars",g.FlashVars):"",""].join("");else for(j in e=h.createElement("embed"),g)g.hasOwnProperty(j)&&e.setAttribute(j,g[j]);ta();k=L();if(g=ba())if(c.oMC=u(c.movieID)||h.createElement("div"), +c.oMC.id){l=c.oMC.className;c.oMC.className=(l?l+" ":"movieContainer")+(k?" "+k:"");c.oMC.appendChild(e);if(D)j=c.oMC.appendChild(h.createElement("div")),j.className="sm2-object-box",j.innerHTML=m;Q=!0}else{c.oMC.id=c.movieID;c.oMC.className="movieContainer "+k;j=k=null;if(!c.useFlashBlock)if(c.useHighPerformance)k={position:"fixed",width:"8px",height:"8px",bottom:"0px",left:"0px",overflow:"hidden"};else if(k={position:"absolute",width:"6px",height:"6px",top:"-9999px",left:"-9999px"},n)k.left=Math.abs(parseInt(k.left, +10))+"px";if(cb)c.oMC.style.zIndex=1E4;if(!c.debugFlash)for(l in k)k.hasOwnProperty(l)&&(c.oMC.style[l]=k[l]);try{D||c.oMC.appendChild(e);g.appendChild(c.oMC);if(D)j=c.oMC.appendChild(h.createElement("div")),j.className="sm2-object-box",j.innerHTML=m;Q=!0}catch(r){throw Error(q("domError")+" \n"+r.toString());}}P=!0;d();c._wD("soundManager::createMovie(): Trying to load "+a+(!N&&c.altURL?" (alternate URL)":""),1);return!0};aa=function(){if(c.html5Only)return ca(),!1;if(i)return!1;i=c.getMovie(c.id); +if(!i)S?(D?c.oMC.innerHTML=ua:c.oMC.appendChild(S),S=null,P=!0):ca(c.id,c.url),i=c.getMovie(c.id);i&&o("waitEI");c.oninitmovie instanceof Function&&setTimeout(c.oninitmovie,1);return!0};Z=function(){setTimeout(Fa,1E3)};Fa=function(){if(ga)return!1;ga=!0;r.remove(j,"load",Z);if(M&&!Da)return o("waitFocus"),!1;var b;n||(b=c.getMoviePercent(),c._wD(q("waitImpatient",100===b?" (SWF loaded)":0