mirror of
https://github.com/spl0k/supysonic.git
synced 2024-11-14 22:22:18 +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:
parent
3c1c26390b
commit
a4c989c36a
55
api/media.py
55
api/media.py
@ -4,6 +4,7 @@ from flask import request, send_file, Response
|
||||
import os.path
|
||||
from PIL import Image
|
||||
import subprocess
|
||||
import shlex
|
||||
|
||||
import config, scanner
|
||||
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):
|
||||
if not base_cmdline:
|
||||
return None
|
||||
ret = base_cmdline.split()
|
||||
for i in xrange(len(ret)):
|
||||
ret[i] = ret[i].replace('%srcpath', input_file).replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate))
|
||||
return ret
|
||||
|
||||
return base_cmdline.replace('%srcpath', '"'+input_file+'"').replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate))
|
||||
|
||||
def transcode(process):
|
||||
for chunk in iter(process, ''):
|
||||
yield chunk
|
||||
|
||||
@app.route('/rest/stream.view', methods = [ 'GET', 'POST' ])
|
||||
def stream_media():
|
||||
@ -51,37 +54,53 @@ def stream_media():
|
||||
dst_suffix = format
|
||||
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:
|
||||
transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix))
|
||||
|
||||
decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder')
|
||||
encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder')
|
||||
|
||||
if not transcoder and (not decoder or not encoder):
|
||||
transcoder = config.get('transcoding', 'transcoder')
|
||||
if not transcoder:
|
||||
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 ])
|
||||
|
||||
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:
|
||||
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:
|
||||
dec_proc = subprocess.Popen(decoder, stdout = subprocess.PIPE)
|
||||
proc = subprocess.Popen(encoder, stdin = dec_proc.stdout, stdout = subprocess.PIPE)
|
||||
app.logger.warn('multi process transcode: ')
|
||||
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:
|
||||
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:
|
||||
app.logger.warn('no transcode')
|
||||
response = send_file(res.path, mimetype = dst_mimetype)
|
||||
|
||||
res.play_count = res.play_count + 1
|
||||
|
61
db.py
61
db.py
@ -13,6 +13,45 @@ from sqlalchemy.dialects.postgresql import UUID as pgUUID
|
||||
import uuid, datetime, time
|
||||
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):
|
||||
"""Platform-somewhat-independent UUID type
|
||||
|
||||
@ -58,7 +97,7 @@ def now():
|
||||
return datetime.datetime.now().replace(microsecond = 0)
|
||||
|
||||
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.query = session.query_property()
|
||||
@ -138,13 +177,21 @@ class Folder(Base):
|
||||
|
||||
return info
|
||||
|
||||
class Artist(Base):
|
||||
class Artist(UniqueMixin, Base):
|
||||
__tablename__ = 'artist'
|
||||
|
||||
id = UUID.gen_id_column()
|
||||
name = Column(String(256), unique = True)
|
||||
name = Column(String(256), unique = True, nullable=False)
|
||||
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):
|
||||
info = {
|
||||
'id': str(self.id),
|
||||
@ -257,8 +304,9 @@ class Track(Base):
|
||||
if avgRating:
|
||||
info['averageRating'] = avgRating
|
||||
|
||||
# transcodedContentType
|
||||
# transcodedSuffix
|
||||
if self.suffix() == 'flac':
|
||||
info['transcodedContentType'] = 'audio/ogg'
|
||||
info['transcodedSuffix'] = 'ogg'
|
||||
|
||||
return info
|
||||
|
||||
@ -272,7 +320,7 @@ class Track(Base):
|
||||
return os.path.splitext(self.path)[1][1:].lower()
|
||||
|
||||
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):
|
||||
__tablename__ = 'starred_folder'
|
||||
@ -389,4 +437,3 @@ def init_db():
|
||||
def recreate_db():
|
||||
Base.metadata.drop_all(bind = engine)
|
||||
Base.metadata.create_all(bind = engine)
|
||||
|
||||
|
39
scanner.py
39
scanner.py
@ -12,6 +12,9 @@ class Scanner:
|
||||
def __init__(self, session):
|
||||
self.__session = session
|
||||
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.__folders = db.Folder.query.all()
|
||||
|
||||
@ -24,11 +27,15 @@ class Scanner:
|
||||
|
||||
def scan(self, folder):
|
||||
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 p in subfolders:
|
||||
db.session.flush()
|
||||
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)
|
||||
|
||||
folder.last_scan = int(time.time())
|
||||
|
||||
def prune(self, folder):
|
||||
@ -52,25 +59,24 @@ class Scanner:
|
||||
self.check_cover_art(f)
|
||||
|
||||
def __scan_file(self, path, folder):
|
||||
tr = filter(lambda t: t.path == path, self.__tracks)
|
||||
if tr:
|
||||
tr = tr[0]
|
||||
if path in self.__tracks:
|
||||
tr = self.__tracks[path]
|
||||
if not os.path.getmtime(path) > tr.last_modification:
|
||||
return
|
||||
return False
|
||||
|
||||
tag = self.__try_load_tag(path)
|
||||
if not tag:
|
||||
self.__remove_track(tr)
|
||||
return
|
||||
return False
|
||||
else:
|
||||
print "Added ", path
|
||||
tag = self.__try_load_tag(path)
|
||||
if not tag:
|
||||
return
|
||||
return False
|
||||
|
||||
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
|
||||
print "Added ", path
|
||||
|
||||
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]))
|
||||
@ -78,13 +84,16 @@ class Scanner:
|
||||
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.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.content_type = get_mime(os.path.splitext(path)[1][1:])
|
||||
tr.last_modification = os.path.getmtime(path)
|
||||
|
||||
return True
|
||||
|
||||
def __find_album(self, artist, album):
|
||||
ar = self.__find_artist(artist)
|
||||
|
||||
al = filter(lambda a: a.name == album, ar.albums)
|
||||
if al:
|
||||
return al[0]
|
||||
@ -95,13 +104,9 @@ class Scanner:
|
||||
return al
|
||||
|
||||
def __find_artist(self, artist):
|
||||
ar = filter(lambda a: a.name.lower() == artist.lower(), self.__artists)
|
||||
if ar:
|
||||
return ar[0]
|
||||
ar = db.Artist.as_unique(self.__session, name = artist)
|
||||
|
||||
ar = db.Artist(name = artist)
|
||||
self.__artists.append(ar)
|
||||
self.__session.add(ar)
|
||||
self.__added_artists += 1
|
||||
|
||||
return ar
|
||||
@ -128,7 +133,7 @@ class Scanner:
|
||||
|
||||
def __try_load_tag(self, path):
|
||||
try:
|
||||
return mutagen.File(path, easy = True)
|
||||
return mutagen.File(path, easy = False)
|
||||
except:
|
||||
return None
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user