From 8d50743b3bc6501bae138d9c6f1b5b74a7ba3e3e Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Thu, 31 May 2018 23:46:15 +0200 Subject: [PATCH] Fix #258: Implemented getCovertArt in Subsonic API to serve album covers --- api/funkwhale_api/music/views.py | 4 +- api/funkwhale_api/subsonic/serializers.py | 9 +++- api/funkwhale_api/subsonic/views.py | 51 ++++++++++++++++++++++- api/tests/subsonic/test_serializers.py | 3 ++ api/tests/subsonic/test_views.py | 13 ++++++ changes/changelog.d/258.enhancement | 1 + 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 changes/changelog.d/258.enhancement diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index d8b173b4..2f5b75a9 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 6709930f..f63ad5d2 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 2692a3dd..87c9f727 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 ad9f739a..081b669c 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 bd445e07..65c2ad95 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 00000000..28f05c01 --- /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 -- GitLab