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