From 69984a8c93ec8910b3cb026c9056572644c059ac Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Sun, 4 Mar 2012 18:00:23 +0100 Subject: Working recoding on demand for single files and cue sheets. --- app.py | 27 +++++++++++++++++++++++++-- codec.py | 31 +++++++++++++++++++++++++------ db.py | 5 ++++- static/icons/loading.gif | Bin 0 -> 1737 bytes static/icons/music_nocache.png | Bin 0 -> 646 bytes static/icons/music_playing.png | Bin 0 -> 633 bytes static/init.js | 29 +++++++++++++++++++++++++++-- static/style.css | 3 +++ 8 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 static/icons/loading.gif create mode 100644 static/icons/music_nocache.png create mode 100644 static/icons/music_playing.png diff --git a/app.py b/app.py index e947f80..7d88ebe 100755 --- a/app.py +++ b/app.py @@ -1,9 +1,26 @@ #!/usr/bin/env python2 -import db, os, json, mimetypes, datetime +import db, codec, os, json, mimetypes, datetime from config import config class JSONApplication(object): + @staticmethod + def cache_check(track): + ext = os.path.splitext(track.filename)[-1][1:] + if ext in config.get('allow_extensions').split(): + return True + try: + r = codec.Recoder(track.get_path(), config.get('encoder'), track.file_index, os.path.join(config.get('cache_dir'), str(track.id))) + except codec.DecoderNotFoundError: + return False + return os.path.exists(r.enc_destination) + + @staticmethod + def format_track(track): + d = track.dict() + d['cache'] = JSONApplication.cache_check(track) + return d + def list(self, environ, start_response, path): root_id = int(path[1]) if len(path) > 1 and len(path[1]) else 0 session = db.Session() @@ -15,7 +32,7 @@ class JSONApplication(object): directories = directory.children tracks = directory.tracks contents = json.dumps([x.dict() for x in directories] + - [x.dict() for x in tracks]) + [self.format_track(x) for x in tracks]) finally: session.close() start_response('200 OK', [('Content-Type', 'application/json'), ('Content-Length', str(len(contents)))]) @@ -88,6 +105,12 @@ class Application(object): try: track = db.Track.get_by_id(session, track) filename = track.get_path() + ext = os.path.splitext(filename)[-1][1:] + if not ext in config.get('allow_extensions').split(): + r = codec.Recoder(filename, config.get('encoder'), track.file_index, os.path.join(config.get('cache_dir'), str(track.id))) + filename = r.enc_destination + if not os.path.exists(filename): + r.recode() except db.NoResultFound: start_response('404 Not Found', []) return [] diff --git a/codec.py b/codec.py index 8d8c3d6..417e9e3 100644 --- a/codec.py +++ b/codec.py @@ -1,4 +1,4 @@ -import subprocess, os +import subprocess, os, cuesheet from config import config decoders = {} @@ -54,8 +54,12 @@ class FFmpeg(Decoder): test_exists = test_executable('ffmpeg') - def decode(self): + def decode(self, **kwargs): cmd = 'ffmpeg -loglevel quiet'.split() + if 'start_time' in kwargs and kwargs['start_time']: + cmd += ['-ss', str(kwargs['start_time'])] + if 'end_time' in kwargs and kwargs['end_time']: + cmd += ['-t', str(kwargs['end_time'] - kwargs['start_time'])] cmd += ['-i', self.source, '-y', self.destination] devnull = open('/dev/null', 'a+') p = subprocess.Popen(cmd, stderr = devnull, close_fds = True) @@ -80,7 +84,16 @@ class DecoderNotFoundError(Exception): pass class EncoderNotFoundError(Exception): pass class Recoder(object): - def __init__(self, source, encoder): + def __init__(self, source, encoder, track = None, destination = None): + if track: + cue = cuesheet.Cuesheet(source) + source = os.path.join(os.path.dirname(source), cue.info[0].file[0]) + track = cue.tracks[track-1] + self.start_time = track.get_start_time() + track = cue.get_next(track) + self.end_time = track.get_start_time() if track else None + else: + self.start_time, self.end_time = None, None # TODO: Python 3 breakage (must be str) if isinstance(encoder, basestring): if not encoder in encoders: @@ -89,9 +102,15 @@ class Recoder(object): self.dec_source = source # Boldly assume all decoders can convert to wave format. - self.dec_destination = os.path.join(config.get('cache_dir'), os.path.splitext(os.path.basename(source))[0] + '.wav') + if destination: + self.dec_destination = os.path.splitext(destination)[0] + '.wav' + else: + self.dec_destination = os.path.join(config.get('cache_dir'), os.path.splitext(os.path.basename(source))[0] + '.wav') self.enc_source = self.dec_destination - self.enc_destination = os.path.splitext(self.dec_destination)[0] + encoder.extension + if destination: + self.enc_destination = os.path.splitext(destination)[0] + encoder.extension + else: + self.enc_destination = os.path.splitext(self.dec_destination)[0] + encoder.extension self.decoder = None for decoder in decoders.itervalues(): @@ -103,7 +122,7 @@ class Recoder(object): self.encoder = encoder(self.enc_source, self.enc_destination) def recode(self): - self.decoder.decode() + self.decoder.decode(start_time = self.start_time, end_time = self.end_time) self.encoder.encode() os.unlink(self.dec_destination) diff --git a/db.py b/db.py index aa7ea6a..248d56a 100644 --- a/db.py +++ b/db.py @@ -165,7 +165,10 @@ class Track(Base): return r.all() def get_path(self): - return os.path.join(self.directory.path, self.filename) + s = os.path.join(self.directory.path, self.filename) + if isinstance(s, unicode): + s = s.encode('utf-8') + return s def get_relpath(self): return os.path.relpath(self.get_path(), config.get('music_root')) diff --git a/static/icons/loading.gif b/static/icons/loading.gif new file mode 100644 index 0000000..1560b64 Binary files /dev/null and b/static/icons/loading.gif differ diff --git a/static/icons/music_nocache.png b/static/icons/music_nocache.png new file mode 100644 index 0000000..5034c06 Binary files /dev/null and b/static/icons/music_nocache.png differ diff --git a/static/icons/music_playing.png b/static/icons/music_playing.png new file mode 100644 index 0000000..6802f99 Binary files /dev/null and b/static/icons/music_playing.png differ diff --git a/static/init.js b/static/init.js index 0dd0af6..cdb806d 100644 --- a/static/init.js +++ b/static/init.js @@ -13,6 +13,16 @@ function pause() { sound.togglePause(); } +function preload_images() { + var cache_images = new Array( + 'loading.gif', + 'music_playing.png' + ); + $.each(cache_images, function() { + (new Image()).src = '/static/icons/' + this; + }); +} + Handlebars.registerHelper('trackname', function() { var item = this; if(!item.metadata) @@ -33,7 +43,7 @@ Handlebars.registerHelper('trackname', function() { }); var templates = new (function Templates() { - this.directory_item = Handlebars.compile('
  • {{trackname}}'); + this.directory_item = Handlebars.compile('
  • {{trackname}}'); })(); function load_directory(dir_id, dir_item) { @@ -54,11 +64,15 @@ function load_directory(dir_id, dir_item) { ); } $.each(data, function(i, item) { + if(item.type == "track") + item.nocache = !item.cache; var el = $(templates.directory_item(item)); + var id = el.attr('id'); if(item.type == "track") { $(el, 'a').click(function() { - console.log(item); + el.addClass('loading'); if(sound) { + sound.stop(); sound.destruct(); } sound = soundManager.createSound({ @@ -67,7 +81,11 @@ function load_directory(dir_id, dir_item) { whileloading: function() { $('#status').text('Loading... ' + this.bytesLoaded); }, + onload: function(success) { + el.removeClass('loading').removeClass('nocache'); + }, whileplaying: function() { + $('#' + id).addClass('playing'); var seconds = (this.position / 1000).toFixed(0); var minutes = Math.floor(seconds / 60).toFixed(0); seconds %= 60; @@ -75,6 +93,12 @@ function load_directory(dir_id, dir_item) { seconds = '0' + seconds; var pos = minutes + ':' + seconds; $('#status').text(pos); + }, + onstop: function() { + $('#' + id).removeClass('playing'); + }, + onfinish: function() { + $('#' + id).removeClass('playing'); } }); sound.play(); @@ -93,5 +117,6 @@ function load_directory(dir_id, dir_item) { } $(document).ready(function() { + preload_images(); load_directory(0); }); diff --git a/static/style.css b/static/style.css index 28fea31..a7ea139 100644 --- a/static/style.css +++ b/static/style.css @@ -5,3 +5,6 @@ #directory-list .dir, #directory-list .track { background-repeat: no-repeat; padding-left: 20px; } #directory-list .dir { background-image: url('/static/icons/folder.png'); } #directory-list .track { background-image: url('/static/icons/music.png'); } +#directory-list .nocache { background-image: url('/static/icons/music_nocache.png'); } +#directory-list .loading { background-image: url('/static/icons/loading.gif'); } +#directory-list .playing { background-image: url('/static/icons/music_playing.png'); } -- cgit v1.2.3