diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index d8b173b4fe0d43db2ed3c150bdec1b57d3835b28..2f5b75a97a51de248fc128835c50d1b3b926ffd0 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -242,8 +242,8 @@ def get_file_path(audio_file): 'You need to specify MUSIC_DIRECTORY_SERVE_PATH and ' 'MUSIC_DIRECTORY_PATH to serve in-place imported files' ) - path = audio_file.replace(prefix, serve_path, 1).encode('utf-8') - return path + path = audio_file.replace(prefix, serve_path, 1) + return path.encode('utf-8') def handle_serve(track_file): diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py index 6709930f56756abae175f1158bae0f9d94ec67b7..f63ad5d2ee92885e5ec15713bfbf82d92ac2495a 100644 --- a/api/funkwhale_api/subsonic/serializers.py +++ b/api/funkwhale_api/subsonic/serializers.py @@ -57,8 +57,10 @@ class GetArtistSerializer(serializers.Serializer): 'name': album.title, 'artist': artist.name, 'created': album.creation_date, - 'songCount': len(album.tracks.all()) + 'songCount': len(album.tracks.all()), } + if album.cover: + album_data['coverArt'] = 'al-{}'.format(album.id) if album.release_date: album_data['year'] = album.release_date.year payload['album'].append(album_data) @@ -81,6 +83,8 @@ def get_track_data(album, track, tf): 'artistId': album.artist.pk, 'type': 'music', } + if track.album.cover: + data['coverArt'] = 'al-{}'.format(track.album.id) if tf.bitrate: data['bitrate'] = int(tf.bitrate/1000) if tf.size: @@ -98,6 +102,9 @@ def get_album2_data(album): 'artist': album.artist.name, 'created': album.creation_date, } + if album.cover: + payload['coverArt'] = 'al-{}'.format(album.id) + try: payload['songCount'] = album._tracks_count except AttributeError: diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py index 2692a3dda804ad22c07ee340012ad037afb5e42b..87c9f72700ca8dcd7e5cbfeb3fadb37edb6c7e11 100644 --- a/api/funkwhale_api/subsonic/views.py +++ b/api/funkwhale_api/subsonic/views.py @@ -1,5 +1,6 @@ import datetime +from django.conf import settings from django.utils import timezone from rest_framework import exceptions @@ -459,7 +460,7 @@ class SubsonicViewSet(viewsets.GenericViewSet): 'code': 10, 'message': 'Playlist ID or name must be specified.' } - }, data) + }) playlist = request.user.playlists.create( name=name @@ -503,3 +504,51 @@ class SubsonicViewSet(viewsets.GenericViewSet): } } return response.Response(data) + + @list_route( + methods=['get', 'post'], + url_name='get_cover_art', + url_path='getCoverArt') + def get_cover_art(self, request, *args, **kwargs): + data = request.GET or request.POST + id = data.get('id', '') + if not id: + return response.Response({ + 'error': { + 'code': 10, + 'message': 'cover art ID must be specified.' + } + }) + + if id.startswith('al-'): + try: + album_id = int(id.replace('al-', '')) + album = music_models.Album.objects.exclude( + cover__isnull=True + ).exclude(cover='').get(pk=album_id) + except (TypeError, ValueError, music_models.Album.DoesNotExist): + return response.Response({ + 'error': { + 'code': 70, + 'message': 'cover art not found.' + } + }) + cover = album.cover + else: + return response.Response({ + 'error': { + 'code': 70, + 'message': 'cover art not found.' + } + }) + + mapping = { + 'nginx': 'X-Accel-Redirect', + 'apache2': 'X-Sendfile', + } + path = music_views.get_file_path(cover) + file_header = mapping[settings.REVERSE_PROXY_TYPE] + # let the proxy set the content-type + r = response.Response({}, content_type='') + r[file_header] = path + return r \ No newline at end of file diff --git a/api/tests/subsonic/test_serializers.py b/api/tests/subsonic/test_serializers.py index ad9f739a1dd63ae2036142b2bdab149dc9e63635..081b669c571cceff6d41fd973bfd8b3113a35a60 100644 --- a/api/tests/subsonic/test_serializers.py +++ b/api/tests/subsonic/test_serializers.py @@ -60,6 +60,7 @@ def test_get_artist_serializer(factories): 'album': [ { 'id': album.pk, + 'coverArt': 'al-{}'.format(album.id), 'artistId': artist.pk, 'name': album.title, 'artist': artist.name, @@ -88,11 +89,13 @@ def test_get_album_serializer(factories): 'songCount': 1, 'created': album.creation_date, 'year': album.release_date.year, + 'coverArt': 'al-{}'.format(album.id), 'song': [ { 'id': track.pk, 'isDir': 'false', 'title': track.title, + 'coverArt': 'al-{}'.format(album.id), 'album': album.title, 'artist': artist.name, 'track': track.position, diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py index bd445e070727c529e1295154c79302d0f37c51b1..65c2ad95f351ca2228f4d05fc835f34c4edaf1e7 100644 --- a/api/tests/subsonic/test_views.py +++ b/api/tests/subsonic/test_views.py @@ -391,3 +391,16 @@ def test_get_indexes(f, db, logged_in_api_client, factories): assert response.status_code == 200 assert response.data == expected + + +def test_get_cover_art_album(factories, logged_in_api_client): + url = reverse('api:subsonic-get-cover-art') + assert url.endswith('getCoverArt') is True + album = factories['music.Album']() + response = logged_in_api_client.get(url, {'id': 'al-{}'.format(album.pk)}) + + assert response.status_code == 200 + assert response['Content-Type'] == '' + assert response['X-Accel-Redirect'] == music_views.get_file_path( + album.cover + ).decode('utf-8') diff --git a/changes/changelog.d/258.enhancement b/changes/changelog.d/258.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..28f05c01bbee0b3af5a4aa54ac99ed461e1375e9 --- /dev/null +++ b/changes/changelog.d/258.enhancement @@ -0,0 +1 @@ +Implemented getCovertArt in Subsonic API to serve album covers (#258) \ No newline at end of file