diff --git a/supysonic/__init__.py b/supysonic/__init__.py
index e69de29..b10767a 100644
--- a/supysonic/__init__.py
+++ b/supysonic/__init__.py
@@ -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 .
+
+import mimetypes
+
+def get_mime(ext):
+ return mimetypes.guess_type('dummy.' + ext, False)[0] or config.get('mimetypes', ext) or 'application/octet-stream'
+
diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py
index 994b8b1..bf9fcb8 100644
--- a/supysonic/api/__init__.py
+++ b/supysonic/api/__init__.py
@@ -75,6 +75,22 @@ def authorize():
request.username = username
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
def set_headers(response):
if not request.path.startswith('/rest/'):
diff --git a/supysonic/api/albums_songs.py b/supysonic/api/albums_songs.py
index 2fc8dcf..9bc94ba 100644
--- a/supysonic/api/albums_songs.py
+++ b/supysonic/api/albums_songs.py
@@ -63,7 +63,7 @@ def rand_songs():
return request.formatter({
'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({
'nowPlaying': {
'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()
) 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': {
'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) ],
- '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': {
'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) ],
- '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) ]
}
})
diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py
index e05ec32..aaa431c 100644
--- a/supysonic/api/browse.py
+++ b/supysonic/api/browse.py
@@ -97,7 +97,7 @@ def list_indexes():
'name': a.name
} for a in sorted(v, key = lambda a: a.name.lower()) ]
} 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 = {
'id': str(res.id),
'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:
directory['parent'] = str(res.parent_id)
@@ -162,7 +162,7 @@ def album_info():
return res
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 })
@@ -172,7 +172,7 @@ def track_info():
if not status:
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' ])
def list_videos():
diff --git a/supysonic/api/media.py b/supysonic/api/media.py
index d231ad6..f308377 100644
--- a/supysonic/api/media.py
+++ b/supysonic/api/media.py
@@ -45,7 +45,7 @@ def stream_media():
if not status:
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:
format = format.lower()
@@ -54,18 +54,10 @@ def stream_media():
dst_bitrate = res.bitrate
dst_mimetype = res.content_type
- if client:
- 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)
-
- if prefs.format:
- dst_suffix = prefs.format
- if prefs.bitrate and prefs.bitrate < dst_bitrate:
- dst_bitrate = prefs.bitrate
+ if request.prefs.format:
+ dst_suffix = request.prefs.format
+ if request.prefs.bitrate and request.prefs.bitrate < dst_bitrate:
+ dst_bitrate = request.prefs.bitrate
if maxBitRate:
try:
@@ -87,7 +79,9 @@ def stream_media():
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))
+ 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 ])
try:
diff --git a/supysonic/api/playlists.py b/supysonic/api/playlists.py
index 535de8f..133a0e6 100644
--- a/supysonic/api/playlists.py
+++ b/supysonic/api/playlists.py
@@ -45,7 +45,7 @@ def show_playlist():
return res
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 })
@app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ])
diff --git a/supysonic/api/search.py b/supysonic/api/search.py
index e999b27..9bba11d 100644
--- a/supysonic/api/search.py
+++ b/supysonic/api/search.py
@@ -52,7 +52,7 @@ def old_search():
return request.formatter({ 'searchResult': {
'totalHits': folders.count() + tracks.count(),
'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:
return request.error_formatter(10, 'Missing search parameter')
@@ -60,7 +60,7 @@ def old_search():
return request.formatter({ 'searchResult': {
'totalHits': query.count(),
'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' ])
@@ -89,7 +89,7 @@ def new_search():
return request.formatter({ 'searchResult2': {
'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 ],
- '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' ])
@@ -117,6 +117,6 @@ def search_id3():
return request.formatter({ 'searchResult3': {
'artist': [ a.as_subsonic_artist(request.user) for a in artist_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 ]
}})
diff --git a/supysonic/db.py b/supysonic/db.py
index 0f45882..34f10e6 100644
--- a/supysonic/db.py
+++ b/supysonic/db.py
@@ -27,6 +27,8 @@ from storm.variables import Variable
import uuid, datetime, time
import os.path
+from supysonic import get_mime
+
def now():
return datetime.datetime.now().replace(microsecond = 0)
@@ -168,7 +170,7 @@ class Track(object):
folder_id = UUID()
folder = Reference(folder_id, Folder.id)
- def as_subsonic_child(self, user):
+ def as_subsonic_child(self, user, prefs):
info = {
'id': str(self.id),
'parent': str(self.folder_id),
@@ -209,8 +211,9 @@ class Track(object):
if avgRating:
info['averageRating'] = avgRating
- # transcodedContentType
- # transcodedSuffix
+ if prefs and prefs.format and prefs.format != self.suffix():
+ info['transcodedSuffix'] = prefs.format
+ info['transcodedContentType'] = get_mime(prefs.format)
return info
diff --git a/supysonic/scanner.py b/supysonic/scanner.py
index 783ae31..3a3683a 100644
--- a/supysonic/scanner.py
+++ b/supysonic/scanner.py
@@ -19,20 +19,17 @@
# along with this program. If not, see .
import os, os.path
-import time, mimetypes
+import time
import mutagen
from storm.expr import ComparableExpr, compile, Like
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 StarredFolder, StarredArtist, StarredAlbum, StarredTrack
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
class Concat(ComparableExpr):
__slots__ = ("left", "right", "db")