From 2c49a03494ec2cebf2c820efb257c950f170face Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Tue, 23 Aug 2011 13:54:59 +0200 Subject: Read tracks and directories from the database. --- app.py | 75 +++++++++++++++++++++++++++++++++++++++++++++--------------- db.py | 38 +++++++++++++++++++++++++----- directory.py | 70 ++++++++++++++++++++++++++------------------------------ events.py | 2 +- recode.py | 5 ++-- 5 files changed, 125 insertions(+), 65 deletions(-) diff --git a/app.py b/app.py index 3b887d8..f02dcc6 100755 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env python2 -import os, mimetypes, json, cgi, recode, time, urllib, events +import os, mimetypes, json, cgi, recode, time, urllib, events, db, itertools from config import config from directory import Directory, File @@ -16,12 +16,11 @@ class Application(object): start_response('404 Not Found', []) return [] - rel_path = os.path.join(*path[1:] or '.') if os.path.isdir(full_path): start_response('200 OK', [('Content-Type', 'text/html; charset=UTF-8')]) - return (str(x) for x in Directory(rel_path).listdir()) + return (str(x) for x in Directory(full_path).listdir()) else: - return File(rel_path).send(environ, start_response) + return File(full_path).send(environ, start_response) def static(self, environ, start_response, path): filename = os.path.join('static', *path[1:]) @@ -37,7 +36,17 @@ class Application(object): def cache(self, environ, start_response, path): args = cgi.FieldStorage(environ = environ) path = os.path.join(*path[1:]) - track = args.getvalue('track') if 'track' in args else None + path = os.path.join(config.get('music_root'), path) + track = int(args.getvalue('track')) if 'track' in args else None + + try: + session = db.Session() + t = db.Track.find(session, path, track) + except: + start_response('404 Not Fonud', []) + return [] + finally: + session.close() cache_path = File(path, track = track).get_cache_path() if not os.path.exists(cache_path) or '..' in path: @@ -51,19 +60,40 @@ class Application(object): def json_list(self, environ, start_response, path): args = cgi.FieldStorage(environ = environ) - directory = args.getvalue('directory') if 'directory' in args else '/' - d = Directory(directory) - - contents = d.listdir() + directory = args.getvalue('directory') if 'directory' in args else '' + + if directory[-1:] == '/': + directory = directory[:-1] + directory = os.path.join(config.get('music_root'), directory) + if directory[-1:] == '/': + directory = directory[:-1] + contents = [] + try: + session = db.Session() + directory = db.Directory.get(session, directory) + directories = (Directory(d.path) for d in directory.children) + tracks = (File(t.get_path(), track = t.file_index, metadata = t.get_metadata()) for t in directory.tracks) + contents = [x.json() for x in itertools.chain(directories, tracks)] + finally: + session.close() start_response('200 OK', [('Content-Type', 'text/plain')]) - s = json.dumps([x.json() for x in contents]) - return s + return json.dumps(contents) def json_recode(self, environ, start_response, path): args = cgi.FieldStorage(environ = environ) - path = args.getvalue('path') if 'path' in args else None - track = args.getvalue('track') if 'track' in args else None + path = args.getvalue('path') if 'path' in args else '' + path = os.path.join(config.get('music_root'), path) + track = int(args.getvalue('track')) if 'track' in args else None + + try: + session = db.Session() + t = db.Track.find(session, path, track) + except: + start_response('404 Not Fonud', []) + return [] + finally: + session.close() f = File(path, track = track) # see json_play() @@ -77,9 +107,18 @@ class Application(object): def json_play(self, environ, start_response, path): args = cgi.FieldStorage(environ = environ) - - path = args.getvalue('path') - track = args.getvalue('track') if 'track' in args else None + rel_path = args.getvalue('path') + path = os.path.join(config.get('music_root'), rel_path) + track = int(args.getvalue('track')) if 'track' in args else None + + try: + session = db.Session() + t = db.Track.find(session, path, track) + except: + start_response('404 Not Fonud', []) + return [] + finally: + session.close() f = File(path, track = track) # TODO: replace this with some sane logic @@ -90,9 +129,9 @@ class Application(object): if not os.path.exists(cache_path): f.start_recode(decoder, encoder, environ['sessionid']) else: - events.event_pub.play(environ['sessionid'], '/cache/{0}{1}'.format(path, ('?track=' + track if track else ''))) + events.event_pub.play(environ['sessionid'], '/cache/{0}{1}'.format(rel_path, ('?track=' + str(track) if track else ''))) else: - events.event_pub.play(environ['sessionid'], '/files/{0}'.format(path)) + events.event_pub.play(environ['sessionid'], '/files/{0}'.format(rel_path)) start_response('200 OK', [('Content-Type', 'text/plain')]) return [] diff --git a/db.py b/db.py index 4819159..1f68711 100644 --- a/db.py +++ b/db.py @@ -1,3 +1,4 @@ +import os from config import config from sqlalchemy import create_engine engine = create_engine(config.get('db_path')) @@ -18,20 +19,22 @@ class Directory(Base): path = Column(String, nullable = False, index = True) parent_id = Column(Integer, ForeignKey('directories.id')) - parent = relationship('Directory') + parent = relationship('Directory', backref = backref('children'), remote_side = [id]) + #children = relationship('Directory', lazy = 'joined', join_depth = 2) - def __init__(self, path): + def __init__(self, path, parent_id = None): self.path = path + self.parent_id = parent_id def __repr__(self): - return ''.format(self.path) + return ''.format(self.path.encode('utf-8')) @staticmethod - def get(session, path): + def get(session, path, parent_id = None): try: directory = session.query(Directory).filter(Directory.path == path).one() except NoResultFound: - directory = Directory(path) + directory = Directory(path, parent_id) session.add(directory) session.commit() return directory @@ -97,6 +100,8 @@ class Track(Base): album_id = Column(Integer, ForeignKey('albums.id')) directory = relationship(Directory, backref = backref('tracks', order_by = filename)) + artist = relationship(Artist, backref = backref('artists')) + album = relationship(Album, backref = backref('tracks')) def __init__(self, name, num, filename, file_index, directory_id, artist_id, album_id): self.name = name @@ -108,7 +113,7 @@ class Track(Base): self.album_id = album_id def __repr__(self): - return u''.format(self.name) + return ''.format(self.filename.encode('utf-8')) @staticmethod def get(session, name, num, filename, file_index, directory_id, artist_id, album_id): @@ -119,6 +124,27 @@ class Track(Base): session.add(track) return track + @staticmethod + def find(session, path, track = None): + directory, filename = os.path.split(path) + return session.query(Track).filter(and_(Track.filename == filename, Directory.path == directory, Track.file_index == track)).one() + + def get_path(self): + return os.path.join(self.directory.path, self.filename) + + def get_relpath(self): + return os.path.relpath(self.get_path(), config.get('music_root')) + + def get_metadata(self): + metadata = {} + if self.name: + metadata['title'] = self.name + if self.artist: + metadata['artist'] = self.artist.name + if self.album: + metadata['album'] = self.album.name + return metadata + Base.metadata.create_all(engine) from sqlalchemy.orm import sessionmaker diff --git a/directory.py b/directory.py index 2b79126..1518c7a 100644 --- a/directory.py +++ b/directory.py @@ -5,19 +5,15 @@ from config import config class DirectoryEntry(object): '''Base class for directory entries.''' - def __init__(self, path, isabs = False, track = None, metadata = {}): + def __init__(self, path, track = None, metadata = {}): self.path = path self.track = track self.metadata = metadata - if isabs: - self.abs_path = path - else: - path_list = os.path.split(config.get('music_root')) + os.path.split(self.path) - if '..' in path_list: - raise Exception('Invalid path') + if '..' in path.split(): + raise Exception('Invalid path') - self.abs_path = os.path.normpath(os.path.sep.join(path_list)) + self.rel_path = os.path.relpath(path, config.get('music_root')) def __cmp__(self, other): return cmp(self.path, other.path) @@ -26,10 +22,10 @@ class DirectoryEntry(object): return self.path < other.path def __str__(self): - return '{name}
'.format(path = self.path, name = os.path.basename(self.path)) + return '{name}
'.format(path = self.rel_path, name = os.path.basename(self.path)) def json(self): - return {'type': self.entry_type, 'name': self.path, 'track': self.track, 'metadata': self.metadata} + return {'type': self.entry_type, 'name': self.rel_path, 'track': self.track, 'metadata': self.metadata} class Directory(DirectoryEntry): '''A directory entry inside a directory.''' @@ -40,14 +36,13 @@ class Directory(DirectoryEntry): directories = [] files = [] - for f in os.listdir(self.abs_path): - abs_path = os.path.join(self.abs_path, f) - rel_path = os.path.relpath(abs_path, config.get('music_root')) - if os.path.isdir(abs_path): - directories.append(Directory(rel_path)) - elif os.path.isfile(abs_path): + for f in os.listdir(self.path): + path = os.path.join(self.path, f) + if os.path.isdir(path): + directories.append(Directory(path)) + elif os.path.isfile(path): if os.path.splitext(f)[1] == '.cue': - cue = cuesheet.Cuesheet(abs_path) + cue = cuesheet.Cuesheet(path) for t in cue.tracks: metadata = {} info = cue.info[0] @@ -57,10 +52,10 @@ class Directory(DirectoryEntry): metadata['album'] = info.title if t.title: metadata['title'] = t.title - files.append(File(rel_path, track = t.track[0], metadata = metadata)) + files.append(File(path, track = t.track[0], metadata = metadata)) else: metadata = {} - tags = mutagen.File(abs_path) or [] + tags = mutagen.File(path) or [] if isinstance(tags, mutagen.mp3.MP3): for id3, tn in (('TPE1', 'artist'), ('TALB', 'album'), ('TIT2', 'title')): if id3 in tags: @@ -69,7 +64,7 @@ class Directory(DirectoryEntry): for tn in ('artist', 'album', 'title'): if tn in tags: metadata[tn] = tags[tn][0].encode('utf-8') - files.append(File(rel_path, metadata = metadata)) + files.append(File(path, metadata = metadata)) return sorted(directories) + sorted(files) class File(DirectoryEntry): @@ -82,8 +77,8 @@ class File(DirectoryEntry): if do_range: file_range = environ['HTTP_RANGE'].split('bytes=')[1] - mime = mimetypes.guess_type(self.abs_path, strict = False)[0] or 'application/octet-stream' - size = os.path.getsize(self.abs_path) + mime = mimetypes.guess_type(self.path, strict = False)[0] or 'application/octet-stream' + size = os.path.getsize(self.path) if do_range: start, end = [int(x or 0) for x in file_range.split('-')] if end == 0: @@ -94,7 +89,7 @@ class File(DirectoryEntry): ('Content-Range', 'bytes {start}-{end}/{size}'.format(start = start, end = end, size = size)), ('Content-Length', str(end - start + 1))]) - f = open(self.abs_path, 'rb') + f = open(self.path, 'rb') f.seek(start) remaining = end-start+1 s = f.read(min(remaining, 1024)) @@ -107,22 +102,22 @@ class File(DirectoryEntry): start_response('200 OK', [ ('Content-Type', mime), ('Content-Length', str(size))]) - return open(self.abs_path, 'rb') + return open(self.path, 'rb') def get_cache_path(self): - cache_path = os.path.join(config.get('cache_dir'), self.path) + cache_path = os.path.join(config.get('cache_dir'), self.rel_path) cache_path = os.path.splitext(cache_path)[0] if self.track: - cache_path += '.' + self.track + cache_path += '.' + str(self.track) cache_path += '.ogg' return cache_path def get_cache_file(self): - return File(self.get_cache_path(), True) + return File(self.get_cache_path()) def recode(self, decoder, encoder, sessionid = None): if self.track: - cue = cuesheet.Cuesheet(self.abs_path) + cue = cuesheet.Cuesheet(self.path) t = cue.tracks[int(self.track)-1] start_time = t.get_start_time() next = cue.get_next(t) @@ -130,9 +125,9 @@ class File(DirectoryEntry): end_time = next.get_start_time() else: end_time = None - path = os.path.join(os.path.dirname(self.abs_path), cue.info[0].file[0]) + path = os.path.join(os.path.dirname(self.path), cue.info[0].file[0]) else: - path = self.abs_path + path = self.path start_time, end_time = None, None decoder = recode.decoders[decoder]() @@ -146,12 +141,12 @@ class File(DirectoryEntry): os.makedirs(cache_path_dir) # check if file is cached if not os.path.exists(cache_path): - events.event_pub.recoding(self.path, self.track) + events.event_pub.recoding(self.rel_path, self.track) recoder.recode(path, cache_path, start_time = start_time, end_time = end_time) - events.event_pub.cached(self.path, self.track) + events.event_pub.cached(self.rel_path, self.track) if sessionid: - events.event_pub.play(sessionid, '/cache/{0}'.format(self.path)) + events.event_pub.play(sessionid, '/cache/{0}'.format(self.rel_path)) def start_recode(self, decoder, encoder, sessionid = None): recode.RecodeThread.add((self.recode, decoder, encoder, sessionid)) @@ -162,13 +157,14 @@ class File(DirectoryEntry): d.update({'cached': os.path.exists(cache_path)}) return d -def rec_scan(session, root): - directory = db.Directory.get(session, root) +def rec_scan(session, root, parent_id = None): + directory = db.Directory.get(session, root, parent_id) - d = Directory(root, isabs = True) + d = Directory(root) for de in d.listdir(): if isinstance(de, Directory): - rec_scan(session, de.abs_path) + print 'id:', directory.id + rec_scan(session, de.path, directory.id) else: print de.metadata artist = db.Artist.get(session, de.metadata['artist']) if 'artist' in de.metadata else None diff --git a/events.py b/events.py index 0d0360d..1c9d681 100644 --- a/events.py +++ b/events.py @@ -28,7 +28,7 @@ def EventSubscriber(app, environ, start_response, path): data = None if address in ('cached', 'recoding'): track, path = message.split(None, 1) - data = json.dumps({'type': address, 'path': path, 'track': track}) + data = json.dumps({'type': address, 'path': path, 'track': None if track == '_' else track}) yield 'data: {0}\n\n'.format(data) elif address in ('play',): data = json.dumps({'type': address, 'path': message}) diff --git a/recode.py b/recode.py index e67328e..a3cf8c3 100644 --- a/recode.py +++ b/recode.py @@ -35,12 +35,11 @@ class FFmpeg(Decoder): def decode(self, source, dest, *args, **kwargs): cmd = 'ffmpeg -loglevel quiet'.split() - if 'start_time' in kwargs: + 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', source, '-y', dest] - #cmd = (x.format(infile = source, outfile = dest) for x in 'ffmpeg -loglevel quiet -i {infile} -y {outfile}'.split()) p = subprocess.Popen(cmd, stderr = subprocess.PIPE, close_fds = True) p.stderr.close() p.wait() @@ -62,7 +61,7 @@ class Recoder(object): if self.decoder.__class__ == self.encoder.__class__ and hasattr(self.decoder, 'recode'): self.decoder.recode(source, dest) else: - with tempfile.NamedTemporaryFile(mode = 'wb', prefix = 'foo', suffix = '.wav', delete = True) as temp: + with tempfile.NamedTemporaryFile(mode = 'wb', prefix = 'ongaku-', suffix = '.wav', delete = True) as temp: self.decoder.decode(source, temp.name, **kwargs) self.encoder.encode(temp.name, dest, **kwargs) -- cgit v1.2.3