1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-09-20 03:11:04 +00:00

Add 'transcoded' info to API responses if the server is set to transcode for that client

Closes #62
This commit is contained in:
spl0k 2017-07-02 17:08:40 +02:00
parent fb79fc3f18
commit 1e1b475fe6
9 changed files with 70 additions and 35 deletions

View File

@ -0,0 +1,25 @@
# coding: utf-8
# This file is part of Supysonic.
#
# Supysonic is a Python implementation of the Subsonic server API.
# Copyright (C) 2013-2017 Alban 'spl0k' Féron
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import mimetypes
def get_mime(ext):
return mimetypes.guess_type('dummy.' + ext, False)[0] or config.get('mimetypes', ext) or 'application/octet-stream'

View File

@ -75,6 +75,22 @@ def authorize():
request.username = username request.username = username
request.user = user request.user = user
@app.before_request
def get_client_prefs():
if not request.path.startswith('/rest/'):
return
client = request.values.get('c')
prefs = store.get(ClientPrefs, (request.user.id, client))
if not prefs:
prefs = ClientPrefs()
prefs.user_id = request.user.id
prefs.client_name = client
store.add(prefs)
store.commit()
request.prefs = prefs
@app.after_request @app.after_request
def set_headers(response): def set_headers(response):
if not request.path.startswith('/rest/'): if not request.path.startswith('/rest/'):

View File

@ -63,7 +63,7 @@ def rand_songs():
return request.formatter({ return request.formatter({
'randomSongs': { 'randomSongs': {
'song': [ t.as_subsonic_child(request.user) for t in tracks ] 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in tracks ]
} }
}) })
@ -171,7 +171,7 @@ def now_playing():
return request.formatter({ return request.formatter({
'nowPlaying': { 'nowPlaying': {
'entry': [ dict( 'entry': [ dict(
u.last_play.as_subsonic_child(request.user).items() + u.last_play.as_subsonic_child(request.user, request.prefs).items() +
{ 'username': u.name, 'minutesAgo': (now() - u.last_play_date).seconds / 60, 'playerId': 0 }.items() { 'username': u.name, 'minutesAgo': (now() - u.last_play_date).seconds / 60, 'playerId': 0 }.items()
) for u in query if u.last_play_date + timedelta(seconds = u.last_play.duration * 2) > now() ] ) for u in query if u.last_play_date + timedelta(seconds = u.last_play.duration * 2) > now() ]
} }
@ -185,7 +185,7 @@ def get_starred():
'starred': { 'starred': {
'artist': [ { 'id': str(sf.starred_id), 'name': sf.starred.name } for sf in folders.find(Folder.parent_id == StarredFolder.starred_id, Track.folder_id == Folder.id).config(distinct = True) ], 'artist': [ { 'id': str(sf.starred_id), 'name': sf.starred.name } for sf in folders.find(Folder.parent_id == StarredFolder.starred_id, Track.folder_id == Folder.id).config(distinct = True) ],
'album': [ sf.starred.as_subsonic_child(request.user) for sf in folders.find(Track.folder_id == StarredFolder.starred_id).config(distinct = True) ], 'album': [ sf.starred.as_subsonic_child(request.user) for sf in folders.find(Track.folder_id == StarredFolder.starred_id).config(distinct = True) ],
'song': [ st.starred.as_subsonic_child(request.user) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ] 'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ]
} }
}) })
@ -195,7 +195,7 @@ def get_starred_id3():
'starred2': { 'starred2': {
'artist': [ sa.starred.as_subsonic_artist(request.user) for sa in store.find(StarredArtist, StarredArtist.user_id == User.id, User.name == request.username) ], 'artist': [ sa.starred.as_subsonic_artist(request.user) for sa in store.find(StarredArtist, StarredArtist.user_id == User.id, User.name == request.username) ],
'album': [ sa.starred.as_subsonic_album(request.user) for sa in store.find(StarredAlbum, StarredAlbum.user_id == User.id, User.name == request.username) ], 'album': [ sa.starred.as_subsonic_album(request.user) for sa in store.find(StarredAlbum, StarredAlbum.user_id == User.id, User.name == request.username) ],
'song': [ st.starred.as_subsonic_child(request.user) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ] 'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ]
} }
}) })

View File

@ -97,7 +97,7 @@ def list_indexes():
'name': a.name 'name': a.name
} for a in sorted(v, key = lambda a: a.name.lower()) ] } for a in sorted(v, key = lambda a: a.name.lower()) ]
} for k, v in sorted(indexes.iteritems()) ], } for k, v in sorted(indexes.iteritems()) ],
'child': [ c.as_subsonic_child(request.user) for c in sorted(childs, key = lambda t: t.sort_key()) ] 'child': [ c.as_subsonic_child(request.user, request.prefs) for c in sorted(childs, key = lambda t: t.sort_key()) ]
} }
}) })
@ -110,7 +110,7 @@ def show_directory():
directory = { directory = {
'id': str(res.id), 'id': str(res.id),
'name': res.name, 'name': res.name,
'child': [ f.as_subsonic_child(request.user) for f in sorted(res.children, key = lambda c: c.name.lower()) ] + [ t.as_subsonic_child(request.user) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ] 'child': [ f.as_subsonic_child(request.user) for f in sorted(res.children, key = lambda c: c.name.lower()) ] + [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
} }
if not res.root: if not res.root:
directory['parent'] = str(res.parent_id) directory['parent'] = str(res.parent_id)
@ -162,7 +162,7 @@ def album_info():
return res return res
info = res.as_subsonic_album(request.user) info = res.as_subsonic_album(request.user)
info['song'] = [ t.as_subsonic_child(request.user) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ] info['song'] = [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
return request.formatter({ 'album': info }) return request.formatter({ 'album': info })
@ -172,7 +172,7 @@ def track_info():
if not status: if not status:
return res return res
return request.formatter({ 'song': res.as_subsonic_child(request.user) }) return request.formatter({ 'song': res.as_subsonic_child(request.user, request.prefs) })
@app.route('/rest/getVideos.view', methods = [ 'GET', 'POST' ]) @app.route('/rest/getVideos.view', methods = [ 'GET', 'POST' ])
def list_videos(): def list_videos():

View File

@ -45,7 +45,7 @@ def stream_media():
if not status: if not status:
return res return res
maxBitRate, format, timeOffset, size, estimateContentLength, client = map(request.values.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength', 'c' ]) maxBitRate, format, timeOffset, size, estimateContentLength = map(request.values.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength' ])
if format: if format:
format = format.lower() format = format.lower()
@ -54,18 +54,10 @@ def stream_media():
dst_bitrate = res.bitrate dst_bitrate = res.bitrate
dst_mimetype = res.content_type dst_mimetype = res.content_type
if client: if request.prefs.format:
prefs = store.get(ClientPrefs, (request.user.id, client)) dst_suffix = request.prefs.format
if not prefs: if request.prefs.bitrate and request.prefs.bitrate < dst_bitrate:
prefs = ClientPrefs() dst_bitrate = request.prefs.bitrate
prefs.user_id = request.user.id
prefs.client_name = client
store.add(prefs)
if prefs.format:
dst_suffix = prefs.format
if prefs.bitrate and prefs.bitrate < dst_bitrate:
dst_bitrate = prefs.bitrate
if maxBitRate: if maxBitRate:
try: try:
@ -87,7 +79,9 @@ def stream_media():
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)) message = 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix)
app.logger.info(message)
return request.error_formatter(0, message)
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 ])
try: try:

View File

@ -45,7 +45,7 @@ def show_playlist():
return res return res
info = res.as_subsonic_playlist(request.user) info = res.as_subsonic_playlist(request.user)
info['entry'] = [ t.as_subsonic_child(request.user) for t in res.tracks ] info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.tracks ]
return request.formatter({ 'playlist': info }) return request.formatter({ 'playlist': info })
@app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ]) @app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ])

View File

@ -52,7 +52,7 @@ def old_search():
return request.formatter({ 'searchResult': { return request.formatter({ 'searchResult': {
'totalHits': folders.count() + tracks.count(), 'totalHits': folders.count() + tracks.count(),
'offset': offset, 'offset': offset,
'match': [ r.as_subsonic_child(request.user) for r in res ] 'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in res ]
}}) }})
else: else:
return request.error_formatter(10, 'Missing search parameter') return request.error_formatter(10, 'Missing search parameter')
@ -60,7 +60,7 @@ def old_search():
return request.formatter({ 'searchResult': { return request.formatter({ 'searchResult': {
'totalHits': query.count(), 'totalHits': query.count(),
'offset': offset, 'offset': offset,
'match': [ r.as_subsonic_child(request.user) for r in query[offset : offset + count] ] 'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in query[offset : offset + count] ]
}}) }})
@app.route('/rest/search2.view', methods = [ 'GET', 'POST' ]) @app.route('/rest/search2.view', methods = [ 'GET', 'POST' ])
@ -89,7 +89,7 @@ def new_search():
return request.formatter({ 'searchResult2': { return request.formatter({ 'searchResult2': {
'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ], 'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ],
'album': [ f.as_subsonic_child(request.user) for f in album_query ], 'album': [ f.as_subsonic_child(request.user) for f in album_query ],
'song': [ t.as_subsonic_child(request.user) for t in song_query ] 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ]
}}) }})
@app.route('/rest/search3.view', methods = [ 'GET', 'POST' ]) @app.route('/rest/search3.view', methods = [ 'GET', 'POST' ])
@ -117,6 +117,6 @@ def search_id3():
return request.formatter({ 'searchResult3': { return request.formatter({ 'searchResult3': {
'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ], 'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ],
'album': [ a.as_subsonic_album(request.user) for a in album_query ], 'album': [ a.as_subsonic_album(request.user) for a in album_query ],
'song': [ t.as_subsonic_child(request.user) for t in song_query ] 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ]
}}) }})

View File

@ -27,6 +27,8 @@ from storm.variables import Variable
import uuid, datetime, time import uuid, datetime, time
import os.path import os.path
from supysonic import get_mime
def now(): def now():
return datetime.datetime.now().replace(microsecond = 0) return datetime.datetime.now().replace(microsecond = 0)
@ -168,7 +170,7 @@ class Track(object):
folder_id = UUID() folder_id = UUID()
folder = Reference(folder_id, Folder.id) folder = Reference(folder_id, Folder.id)
def as_subsonic_child(self, user): def as_subsonic_child(self, user, prefs):
info = { info = {
'id': str(self.id), 'id': str(self.id),
'parent': str(self.folder_id), 'parent': str(self.folder_id),
@ -209,8 +211,9 @@ class Track(object):
if avgRating: if avgRating:
info['averageRating'] = avgRating info['averageRating'] = avgRating
# transcodedContentType if prefs and prefs.format and prefs.format != self.suffix():
# transcodedSuffix info['transcodedSuffix'] = prefs.format
info['transcodedContentType'] = get_mime(prefs.format)
return info return info

View File

@ -19,20 +19,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, os.path import os, os.path
import time, mimetypes import time
import mutagen import mutagen
from storm.expr import ComparableExpr, compile, Like from storm.expr import ComparableExpr, compile, Like
from storm.exceptions import NotSupportedError from storm.exceptions import NotSupportedError
from supysonic import config from supysonic import config, get_mime
from supysonic.db import Folder, Artist, Album, Track, User, PlaylistTrack from supysonic.db import Folder, Artist, Album, Track, User, PlaylistTrack
from supysonic.db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack from supysonic.db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack
from supysonic.db import RatingFolder, RatingTrack from supysonic.db import RatingFolder, RatingTrack
def get_mime(ext):
return mimetypes.guess_type('dummy.' + ext, False)[0] or config.get('mimetypes', ext) or 'application/octet-stream'
# Hacking in support for a concatenation expression # Hacking in support for a concatenation expression
class Concat(ComparableExpr): class Concat(ComparableExpr):
__slots__ = ("left", "right", "db") __slots__ = ("left", "right", "db")