import os, mimetypes, recode, events, cuesheet, mutagen, db
from config import config
class DirectoryEntry(object):
'''Base class for directory entries.'''
def __init__(self, path, track = None, metadata = {}):
self.path = path
self.track = track
self.metadata = metadata
if '..' in path.split():
raise Exception('Invalid path')
self.rel_path = os.path.relpath(path, config.get('music_root'))
def __cmp__(self, other):
return cmp(self.path, other.path)
def __lt__(self, other):
return self.path < other.path
def __str__(self):
return '{name}
'.format(path = self.rel_path, name = os.path.basename(self.path))
def json(self):
return {'type': self.entry_type, 'name': self.rel_path, 'track': self.track, 'metadata': self.metadata}
class Directory(DirectoryEntry):
'''A directory entry inside a directory.'''
entry_type = 'dir'
def listdir(self):
directories = []
files = []
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(path)
for t in cue.tracks:
metadata = {}
info = cue.info[0]
if info.performer:
metadata['artist'] = info.performer
if info.title:
metadata['album'] = info.title
if t.title:
metadata['title'] = t.title
files.append(File(path, track = t.track[0], metadata = metadata))
else:
metadata = {}
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:
metadata[tn] = tags[id3].text[0]
else:
for tn in ('artist', 'album', 'title'):
if tn in tags:
metadata[tn] = tags[tn][0].encode('utf-8')
files.append(File(path, metadata = metadata))
return sorted(directories) + sorted(files)
class File(DirectoryEntry):
'''A file entry inside a directory.'''
entry_type = 'file'
def send(self, environ, start_response):
do_range = 'HTTP_RANGE' in environ
if do_range:
file_range = environ['HTTP_RANGE'].split('bytes=')[1]
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:
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))])
f = open(self.path, '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(size))])
return open(self.path, 'rb')
def get_cache_path(self):
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 += '.' + str(self.track)
cache_path += '.ogg'
return cache_path
def get_cache_file(self):
return File(self.get_cache_path())
def recode(self, decoder, encoder, sessionid = None):
if self.track:
cue = cuesheet.Cuesheet(self.path)
t = cue.tracks[int(self.track)-1]
start_time = t.get_start_time()
next = cue.get_next(t)
if next:
end_time = next.get_start_time()
else:
end_time = None
path = os.path.join(os.path.dirname(self.path), cue.info[0].file[0])
else:
path = self.path
start_time, end_time = None, None
decoder = recode.decoders[decoder]()
encoder = recode.encoders[encoder]()
recoder = recode.Recoder(decoder, encoder)
cache_path = self.get_cache_path()
cache_path_dir = os.path.dirname(cache_path)
# check and create cache directory
if not os.path.exists(cache_path_dir):
os.makedirs(cache_path_dir)
# check if file is cached
if not os.path.exists(cache_path):
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.rel_path, self.track)
if sessionid:
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))
def json(self):
cache_path = self.get_cache_path()
d = DirectoryEntry.json(self)
d.update({'cached': os.path.exists(cache_path)})
return d
def rec_scan(session, root, parent_id = None):
directory = db.Directory.get(session, root, parent_id)
d = Directory(root)
for de in d.listdir():
if isinstance(de, Directory):
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
album = db.Album.get(session, de.metadata['album'], artist.id if artist else None) if 'album' in de.metadata else None
track = db.Track.get(session, de.metadata['title'] if 'title' in de.metadata else None, None,
os.path.basename(de.path), de.track, directory.id, artist.id if artist else None, album.id if album else None)
def scan(root = None):
if not root:
root = config.get('music_root')
try:
session = db.Session()
rec_scan(session, root)
session.commit()
finally:
session.close()
if __name__ == '__main__':
scan()