summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapp.py75
-rw-r--r--db.py38
-rw-r--r--directory.py70
-rw-r--r--events.py2
-rw-r--r--recode.py5
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 '<Directory("{0}")>'.format(self.path)
+ return '<Directory("{0}")>'.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'<Track("{0}")>'.format(self.name)
+ return '<Track("{0}")>'.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 '<a href="/files/{path}">{name}</a><br />'.format(path = self.path, name = os.path.basename(self.path))
+ return '<a href="/files/{path}">{name}</a><br />'.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)