1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-15 06:32:16 +00:00

fixed up transcoding, transcode flac to ogg by default, sped up folder

scan a bit and made artists unique field work properly
okay thats good
This commit is contained in:
Emory P 2013-11-03 15:46:19 -05:00
parent 3c1c26390b
commit a4c989c36a
3 changed files with 114 additions and 43 deletions

View File

@ -4,6 +4,7 @@ from flask import request, send_file, Response
import os.path import os.path
from PIL import Image from PIL import Image
import subprocess import subprocess
import shlex
import config, scanner import config, scanner
from web import app from web import app
@ -13,10 +14,12 @@ from api import get_entity
def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate): def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate):
if not base_cmdline: if not base_cmdline:
return None return None
ret = base_cmdline.split()
for i in xrange(len(ret)): return base_cmdline.replace('%srcpath', '"'+input_file+'"').replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate))
ret[i] = ret[i].replace('%srcpath', input_file).replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate))
return ret def transcode(process):
for chunk in iter(process, ''):
yield chunk
@app.route('/rest/stream.view', methods = [ 'GET', 'POST' ]) @app.route('/rest/stream.view', methods = [ 'GET', 'POST' ])
def stream_media(): def stream_media():
@ -51,37 +54,53 @@ def stream_media():
dst_suffix = format dst_suffix = format
dst_mimetype = scanner.get_mime(dst_suffix) dst_mimetype = scanner.get_mime(dst_suffix)
if not format and src_suffix == 'flac':
dst_suffix = 'ogg'
dst_bitrate = 320
dst_mimetype = scanner.get_mime(dst_suffix)
do_transcoding = True
if do_transcoding: if do_transcoding:
transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix)) transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix))
decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder') decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder')
encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder') encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder')
if not transcoder and (not decoder or not encoder): if not transcoder and (not decoder or not encoder):
transcoder = config.get('transcoding', 'transcoder') transcoder = config.get('transcoding', 'transcoder')
if not transcoder: if not transcoder:
return request.error_formatter(0, 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix)) return request.error_formatter(0, 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix))
transcoder, decoder, encoder = map(lambda x: prepare_transcoding_cmdline(x, res.path, src_suffix, dst_suffix, dst_bitrate), [ transcoder, decoder, encoder ]) transcoder, decoder, encoder = map(lambda x: prepare_transcoding_cmdline(x, res.path, src_suffix, dst_suffix, dst_bitrate), [ transcoder, decoder, encoder ])
decoder = shlex.split(decoder)
encoder = shlex.split(encoder)
if '|' in shlex.split(transcoder):
transcoder = shlex.split(transcoder)
pipe_index = transcoder.index('|')
decoder = transcoder[:pipe_index]
encoder = transcoder[pipe_index+1:]
transcoder = None
try: try:
if transcoder: if transcoder:
proc = subprocess.Popen(transcoder, stdout = subprocess.PIPE) app.logger.warn('single line transcode: '+transcoder)
proc = subprocess.Popen(shlex.split(transcoder), stdout = subprocess.PIPE, shell=False)
else: else:
dec_proc = subprocess.Popen(decoder, stdout = subprocess.PIPE) app.logger.warn('multi process transcode: ')
proc = subprocess.Popen(encoder, stdin = dec_proc.stdout, stdout = subprocess.PIPE) app.logger.warn('decoder' + str(decoder))
app.logger.warn('encoder' + str(encoder))
dec_proc = subprocess.Popen(decoder, stdout = subprocess.PIPE, shell=False)
proc = subprocess.Popen(encoder, stdin = dec_proc.stdout, stdout = subprocess.PIPE, shell=False)
response = Response(transcode(proc.stdout.readline), 200, {'Content-Type': dst_mimetype})
except: except:
return request.error_formatter(0, 'Error while running the transcoding process') return request.error_formatter(0, 'Error while running the transcoding process')
def transcode():
while True:
data = proc.stdout.read(8192)
if not data:
break
yield data
proc.terminate()
proc.wait()
response = Response(transcode(), mimetype = dst_mimetype)
else: else:
app.logger.warn('no transcode')
response = send_file(res.path, mimetype = dst_mimetype) response = send_file(res.path, mimetype = dst_mimetype)
res.play_count = res.play_count + 1 res.play_count = res.play_count + 1

61
db.py
View File

@ -13,6 +13,45 @@ from sqlalchemy.dialects.postgresql import UUID as pgUUID
import uuid, datetime, time import uuid, datetime, time
import os.path import os.path
def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw):
cache = getattr(session, '_unique_cache', None)
if cache is None:
session._unique_cache = cache = {}
key = (cls, hashfunc(*arg, **kw))
if key in cache:
return cache[key]
else:
with session.no_autoflush:
q = session.query(cls)
q = queryfunc(q, *arg, **kw)
obj = q.first()
if not obj:
obj = constructor(*arg, **kw)
session.add(obj)
cache[key] = obj
return obj
class UniqueMixin(object):
@classmethod
def unique_hash(cls, *arg, **kw):
raise NotImplementedError()
@classmethod
def unique_filter(cls, query, *arg, **kw):
raise NotImplementedError()
@classmethod
def as_unique(cls, session, *arg, **kw):
return _unique(
session,
cls,
cls.unique_hash,
cls.unique_filter,
cls,
arg, kw
)
class UUID(TypeDecorator): class UUID(TypeDecorator):
"""Platform-somewhat-independent UUID type """Platform-somewhat-independent UUID type
@ -58,7 +97,7 @@ def now():
return datetime.datetime.now().replace(microsecond = 0) return datetime.datetime.now().replace(microsecond = 0)
engine = create_engine(config.get('base', 'database_uri'), convert_unicode = True) engine = create_engine(config.get('base', 'database_uri'), convert_unicode = True)
session = scoped_session(sessionmaker(autoflush = True, bind = engine)) session = scoped_session(sessionmaker(autoflush = False, bind = engine))
Base = declarative_base() Base = declarative_base()
Base.query = session.query_property() Base.query = session.query_property()
@ -138,13 +177,21 @@ class Folder(Base):
return info return info
class Artist(Base): class Artist(UniqueMixin, Base):
__tablename__ = 'artist' __tablename__ = 'artist'
id = UUID.gen_id_column() id = UUID.gen_id_column()
name = Column(String(256), unique = True) name = Column(String(256), unique = True, nullable=False)
albums = relationship('Album', backref = 'artist') albums = relationship('Album', backref = 'artist')
@classmethod
def unique_hash(cls, name):
return name
@classmethod
def unique_filter(cls, query, name):
return query.filter(Artist.name == name)
def as_subsonic_artist(self, user): def as_subsonic_artist(self, user):
info = { info = {
'id': str(self.id), 'id': str(self.id),
@ -257,8 +304,9 @@ class Track(Base):
if avgRating: if avgRating:
info['averageRating'] = avgRating info['averageRating'] = avgRating
# transcodedContentType if self.suffix() == 'flac':
# transcodedSuffix info['transcodedContentType'] = 'audio/ogg'
info['transcodedSuffix'] = 'ogg'
return info return info
@ -272,7 +320,7 @@ class Track(Base):
return os.path.splitext(self.path)[1][1:].lower() return os.path.splitext(self.path)[1][1:].lower()
def sort_key(self): def sort_key(self):
return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower() return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + str(self.title)).lower()
class StarredFolder(Base): class StarredFolder(Base):
__tablename__ = 'starred_folder' __tablename__ = 'starred_folder'
@ -389,4 +437,3 @@ def init_db():
def recreate_db(): def recreate_db():
Base.metadata.drop_all(bind = engine) Base.metadata.drop_all(bind = engine)
Base.metadata.create_all(bind = engine) Base.metadata.create_all(bind = engine)

View File

@ -12,6 +12,9 @@ class Scanner:
def __init__(self, session): def __init__(self, session):
self.__session = session self.__session = session
self.__tracks = db.Track.query.all() self.__tracks = db.Track.query.all()
paths = {x.path for x in self.__tracks}
self.__tracks = dict(zip(paths,self.__tracks))
self.__artists = db.Artist.query.all() self.__artists = db.Artist.query.all()
self.__folders = db.Folder.query.all() self.__folders = db.Folder.query.all()
@ -24,11 +27,15 @@ class Scanner:
def scan(self, folder): def scan(self, folder):
print "scanning", folder.path print "scanning", folder.path
valid = [x.lower() for x in config.get('base','filetypes').split(',')]
print "valid filetypes: ",valid
for root, subfolders, files in os.walk(folder.path, topdown=False): for root, subfolders, files in os.walk(folder.path, topdown=False):
for p in subfolders:
db.session.flush()
for f in files: for f in files:
suffix = os.path.splitext(f)[1][1:].lower()
if suffix in valid:
self.__scan_file(os.path.join(root, f), folder) self.__scan_file(os.path.join(root, f), folder)
folder.last_scan = int(time.time()) folder.last_scan = int(time.time())
def prune(self, folder): def prune(self, folder):
@ -52,25 +59,24 @@ class Scanner:
self.check_cover_art(f) self.check_cover_art(f)
def __scan_file(self, path, folder): def __scan_file(self, path, folder):
tr = filter(lambda t: t.path == path, self.__tracks) if path in self.__tracks:
if tr: tr = self.__tracks[path]
tr = tr[0]
if not os.path.getmtime(path) > tr.last_modification: if not os.path.getmtime(path) > tr.last_modification:
return return False
tag = self.__try_load_tag(path) tag = self.__try_load_tag(path)
if not tag: if not tag:
self.__remove_track(tr) self.__remove_track(tr)
return return False
else: else:
print "Added ", path
tag = self.__try_load_tag(path) tag = self.__try_load_tag(path)
if not tag: if not tag:
return return False
tr = db.Track(path = path, root_folder = folder, folder = self.__find_folder(path, folder)) tr = db.Track(path = path, root_folder = folder, folder = self.__find_folder(path, folder))
self.__tracks.append(tr) self.__tracks[path] = tr
self.__added_tracks += 1 self.__added_tracks += 1
print "Added ", path
tr.disc = self.__try_read_tag(tag, 'discnumber', 1, lambda x: int(x[0].split('/')[0])) tr.disc = self.__try_read_tag(tag, 'discnumber', 1, lambda x: int(x[0].split('/')[0]))
tr.number = self.__try_read_tag(tag, 'tracknumber', 1, lambda x: int(x[0].split('/')[0])) tr.number = self.__try_read_tag(tag, 'tracknumber', 1, lambda x: int(x[0].split('/')[0]))
@ -78,13 +84,16 @@ class Scanner:
tr.year = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0])) tr.year = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0]))
tr.genre = self.__try_read_tag(tag, 'genre') tr.genre = self.__try_read_tag(tag, 'genre')
tr.duration = int(tag.info.length) tr.duration = int(tag.info.length)
tr.album = self.__find_album(self.__try_read_tag(tag, 'artist'), self.__try_read_tag(tag, 'album')) tr.album = self.__find_album(self.__try_read_tag(tag, 'artist', 'Unknown'), self.__try_read_tag(tag, 'album', 'Unknown'))
tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000 tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000
tr.content_type = get_mime(os.path.splitext(path)[1][1:]) tr.content_type = get_mime(os.path.splitext(path)[1][1:])
tr.last_modification = os.path.getmtime(path) tr.last_modification = os.path.getmtime(path)
return True
def __find_album(self, artist, album): def __find_album(self, artist, album):
ar = self.__find_artist(artist) ar = self.__find_artist(artist)
al = filter(lambda a: a.name == album, ar.albums) al = filter(lambda a: a.name == album, ar.albums)
if al: if al:
return al[0] return al[0]
@ -95,13 +104,9 @@ class Scanner:
return al return al
def __find_artist(self, artist): def __find_artist(self, artist):
ar = filter(lambda a: a.name.lower() == artist.lower(), self.__artists) ar = db.Artist.as_unique(self.__session, name = artist)
if ar:
return ar[0]
ar = db.Artist(name = artist)
self.__artists.append(ar) self.__artists.append(ar)
self.__session.add(ar)
self.__added_artists += 1 self.__added_artists += 1
return ar return ar
@ -128,7 +133,7 @@ class Scanner:
def __try_load_tag(self, path): def __try_load_tag(self, path):
try: try:
return mutagen.File(path, easy = True) return mutagen.File(path, easy = False)
except: except:
return None return None