mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-23 01:16:18 +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:
parent
fb79fc3f18
commit
1e1b475fe6
@ -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'
|
||||||
|
|
@ -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/'):
|
||||||
|
@ -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) ]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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():
|
||||||
|
@ -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:
|
||||||
|
@ -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' ])
|
||||||
|
@ -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 ]
|
||||||
}})
|
}})
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user