mirror of
https://github.com/spl0k/supysonic.git
synced 2024-11-14 22:22:18 +00:00
74dbeca76b
supervisord compatible uwsgi ini file, vastly improved scanning speed, ignored logs in directory, fixed cover art searching algorithm to find more results
209 lines
6.5 KiB
Python
Executable File
209 lines
6.5 KiB
Python
Executable File
# coding: utf-8
|
|
|
|
from flask import request, send_file, Response
|
|
import os.path
|
|
from PIL import Image
|
|
import subprocess
|
|
import shlex
|
|
import mutagen
|
|
import fnmatch
|
|
import mimetypes
|
|
|
|
import config, scanner
|
|
from web import app
|
|
from db import Track, Folder, User, now, session
|
|
from api import get_entity
|
|
|
|
from flask import g
|
|
|
|
def after_this_request(func):
|
|
if not hasattr(g, 'call_after_request'):
|
|
g.call_after_request = []
|
|
g.call_after_request.append(func)
|
|
return func
|
|
|
|
@app.after_request
|
|
def per_request_callbacks(response):
|
|
for func in getattr(g, 'call_after_request', ()):
|
|
response = func(response)
|
|
return response
|
|
|
|
def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate):
|
|
if not base_cmdline:
|
|
return None
|
|
|
|
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():
|
|
status, res = get_entity(request, Track)
|
|
|
|
if not status:
|
|
return res
|
|
|
|
maxBitRate, format, timeOffset, size, estimateContentLength = map(request.args.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength' ])
|
|
if format:
|
|
format = format.lower()
|
|
|
|
do_transcoding = False
|
|
src_suffix = res.suffix()
|
|
dst_suffix = src_suffix
|
|
dst_bitrate = res.bitrate
|
|
dst_mimetype = mimetypes.guess_type('a.' + src_suffix)
|
|
|
|
if format != 'raw': # That's from API 1.9.0 but whatever
|
|
if maxBitRate:
|
|
try:
|
|
maxBitRate = int(maxBitRate)
|
|
except:
|
|
return request.error_formatter(0, 'Invalid bitrate value')
|
|
|
|
if dst_bitrate > maxBitRate and maxBitRate != 0:
|
|
do_transcoding = True
|
|
dst_bitrate = maxBitRate
|
|
|
|
if format and format != src_suffix:
|
|
do_transcoding = True
|
|
dst_suffix = format
|
|
dst_mimetype = mimetypes.guess_type(dst_suffix)
|
|
|
|
if not format and src_suffix == 'flac':
|
|
dst_suffix = 'ogg'
|
|
dst_bitrate = 320
|
|
dst_mimetype = 'audio/ogg'
|
|
do_transcoding = True
|
|
|
|
duration = mutagen.File(res.path).info.length
|
|
app.logger.debug('Serving file: ' + res.path + '\n\tDuration of file: ' + str(duration))
|
|
|
|
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 = map(lambda s: s.decode('UTF8'), shlex.split(decoder.encode('utf8')))
|
|
encoder = map(lambda s: s.decode('UTF8'), shlex.split(encoder.encode('utf8')))
|
|
transcoder = map(lambda s: s.decode('UTF8'), shlex.split(transcoder.encode('utf8')))
|
|
|
|
app.logger.debug(str( decoder ) + '\n' + str( encoder ) + '\n' + str(transcoder))
|
|
|
|
if '|' in transcoder:
|
|
pipe_index = transcoder.index('|')
|
|
decoder = transcoder[:pipe_index]
|
|
encoder = transcoder[pipe_index+1:]
|
|
transcoder = None
|
|
|
|
app.logger.warn('decoder' + str(decoder) + '\nencoder' + str(encoder))
|
|
|
|
try:
|
|
if transcoder:
|
|
app.logger.warn('transcoder: '+str(transcoder))
|
|
proc = subprocess.Popen(transcoder, stdout = subprocess.PIPE, shell=False)
|
|
else:
|
|
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, 'X-Content-Duration': str(duration)})
|
|
except:
|
|
return request.error_formatter(0, 'Error while running the transcoding process')
|
|
|
|
else:
|
|
app.logger.warn('no transcode')
|
|
response = send_file(res.path)
|
|
response.headers['Content-Type'] = dst_mimetype
|
|
response.headers['Accept-Ranges'] = 'bytes'
|
|
response.headers['X-Content-Duration'] = str(duration)
|
|
redirect = config.get('base', 'accel-redirect')
|
|
if(redirect):
|
|
response.headers['X-Accel-Redirect'] = redirect + res.path
|
|
app.logger.debug('X-Accel-Redirect: ' + response.headers['X-Accel-Redirect'])
|
|
|
|
res.play_count = res.play_count + 1
|
|
res.last_play = now()
|
|
request.user.last_play = res
|
|
request.user.last_play_date = now()
|
|
session.commit()
|
|
|
|
return response
|
|
|
|
@app.route('/rest/download.view', methods = [ 'GET', 'POST' ])
|
|
def download_media():
|
|
status, res = get_entity(request, Track)
|
|
if not status:
|
|
return res
|
|
|
|
return send_file(res.path)
|
|
|
|
@app.route('/rest/getCoverArt.view', methods = [ 'GET', 'POST' ])
|
|
def cover_art():
|
|
@after_this_request
|
|
def add_header(response):
|
|
if 'X-Sendfile' in response.headers:
|
|
redirect = response.headers['X-Sendfile'] or ''
|
|
xsendfile = config.get('base', 'accel-redirect')
|
|
if redirect and xsendfile:
|
|
response.headers['X-Accel-Redirect'] = xsendfile + redirect
|
|
app.logger.debug('X-Accel-Redirect: ' + xsendfile + redirect)
|
|
return response
|
|
|
|
status, res = get_entity(request, Folder)
|
|
|
|
if not status:
|
|
return res
|
|
|
|
app.logger.debug('Cover Art Check: ' + res.path + '/*.jp*g')
|
|
|
|
coverfile = os.listdir(res.path)
|
|
coverfile = fnmatch.filter(coverfile, '*.jp*g')
|
|
app.logger.debug('Found Images: ' + str(coverfile))
|
|
|
|
if not coverfile:
|
|
app.logger.debug('No Art Found!')
|
|
res.has_cover_art = False
|
|
session.commit()
|
|
return request.error_formatter(70, 'Cover art not found')
|
|
|
|
coverfile = coverfile[0]
|
|
|
|
size = request.args.get('size')
|
|
if size:
|
|
try:
|
|
size = int(size)
|
|
except:
|
|
return request.error_formatter(0, 'Invalid size value')
|
|
else:
|
|
app.logger.debug('Serving cover art: ' + res.path + coverfile)
|
|
return send_file(os.path.join(res.path, coverfile))
|
|
|
|
im = Image.open(os.path.join(res.path, coverfile))
|
|
if size > im.size[0] and size > im.size[1]:
|
|
app.logger.debug('Serving cover art: ' + res.path + coverfile)
|
|
return send_file(os.path.join(res.path, coverfile))
|
|
|
|
size_path = os.path.join(config.get('base', 'cache_dir'), str(size))
|
|
path = os.path.join(size_path, str(res.id))
|
|
if os.path.exists(path):
|
|
app.logger.debug('Serving cover art: ' + path)
|
|
return send_file(path)
|
|
if not os.path.exists(size_path):
|
|
os.makedirs(size_path)
|
|
|
|
im.thumbnail([size, size], Image.ANTIALIAS)
|
|
im.save(path, 'JPEG')
|
|
|
|
app.logger.debug('Serving cover art: ' + path)
|
|
return send_file(path)
|
|
|