From c96fd5d5399c64d6bd5ad50f64359a78ef45d1b9 Mon Sep 17 00:00:00 2001 From: Agate Date: Tue, 11 Aug 2020 13:07:39 +0200 Subject: [PATCH] Fixed compat with iTunes --- api/config/api_urls.py | 1 + api/funkwhale_api/audio/serializers.py | 5 +- api/funkwhale_api/common/routers.py | 4 +- api/funkwhale_api/music/views.py | 90 +++++++++++++++++--------- api/tests/audio/test_serializers.py | 6 +- api/tests/music/test_views.py | 24 +++++++ 6 files changed, 95 insertions(+), 35 deletions(-) diff --git a/api/config/api_urls.py b/api/config/api_urls.py index e90601470..04fbda87c 100644 --- a/api/config/api_urls.py +++ b/api/config/api_urls.py @@ -20,6 +20,7 @@ router.register(r"tracks", views.TrackViewSet, "tracks") router.register(r"uploads", views.UploadViewSet, "uploads") router.register(r"libraries", views.LibraryViewSet, "libraries") router.register(r"listen", views.ListenViewSet, "listen") +router.register(r"stream", views.StreamViewSet, "stream") router.register(r"artists", views.ArtistViewSet, "artists") router.register(r"channels", audio_views.ChannelViewSet, "channels") router.register(r"subscriptions", audio_views.SubscriptionsViewSet, "subscriptions") diff --git a/api/funkwhale_api/audio/serializers.py b/api/funkwhale_api/audio/serializers.py index be67539e0..fd57ed374 100644 --- a/api/funkwhale_api/audio/serializers.py +++ b/api/funkwhale_api/audio/serializers.py @@ -830,7 +830,10 @@ def rss_serialize_item(upload): { # we enforce MP3, since it's the only format supported everywhere "url": federation_utils.full_url( - upload.get_listen_url(to="mp3", download=False) + reverse( + "api:v1:stream-detail", kwargs={"uuid": str(upload.track.uuid)} + ) + + ".mp3" ), "length": upload.size or 0, "type": "audio/mpeg", diff --git a/api/funkwhale_api/common/routers.py b/api/funkwhale_api/common/routers.py index 11e9dec61..af519b742 100644 --- a/api/funkwhale_api/common/routers.py +++ b/api/funkwhale_api/common/routers.py @@ -1,7 +1,7 @@ -from rest_framework.routers import SimpleRouter +from rest_framework.routers import DefaultRouter -class OptionalSlashRouter(SimpleRouter): +class OptionalSlashRouter(DefaultRouter): def __init__(self): super().__init__() self.trailing_slash = "/?" diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 0df0d4640..314162420 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -10,6 +10,7 @@ import django.db.utils from django.utils import timezone from rest_framework import mixins +from rest_framework import renderers from rest_framework import settings as rest_settings from rest_framework import views, viewsets from rest_framework.decorators import action @@ -614,7 +615,7 @@ def handle_serve( return response -class ListenViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): +class ListenMixin(mixins.RetrieveModelMixin, viewsets.GenericViewSet): queryset = models.Track.objects.all() serializer_class = serializers.TrackSerializer authentication_classes = ( @@ -627,39 +628,66 @@ class ListenViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field = "uuid" def retrieve(self, request, *args, **kwargs): + config = { + "explicit_file": request.GET.get("upload"), + "download": request.GET.get("download", "true").lower() == "true", + "format": request.GET.get("to"), + "max_bitrate": request.GET.get("max_bitrate"), + } track = self.get_object() - actor = utils.get_actor_from_request(request) - queryset = track.uploads.prefetch_related( - "track__album__artist", "track__artist" - ) - explicit_file = request.GET.get("upload") - download = request.GET.get("download", "true").lower() == "true" - if explicit_file: - queryset = queryset.filter(uuid=explicit_file) - queryset = queryset.playable_by(actor) - queryset = queryset.order_by(F("audio_file").desc(nulls_last=True)) - upload = queryset.first() - if not upload: - return Response(status=404) + return handle_stream(track, request, **config) - format = request.GET.get("to") - max_bitrate = request.GET.get("max_bitrate") - try: - max_bitrate = min(max(int(max_bitrate), 0), 320) or None - except (TypeError, ValueError): - max_bitrate = None - if max_bitrate: - max_bitrate = max_bitrate * 1000 - return handle_serve( - upload=upload, - user=request.user, - format=format, - max_bitrate=max_bitrate, - proxy_media=settings.PROXY_MEDIA, - download=download, - wsgi_request=request._request, - ) +def handle_stream(track, request, download, explicit_file, format, max_bitrate): + actor = utils.get_actor_from_request(request) + queryset = track.uploads.prefetch_related("track__album__artist", "track__artist") + if explicit_file: + queryset = queryset.filter(uuid=explicit_file) + queryset = queryset.playable_by(actor) + queryset = queryset.order_by(F("audio_file").desc(nulls_last=True)) + upload = queryset.first() + if not upload: + return Response(status=404) + + try: + max_bitrate = min(max(int(max_bitrate), 0), 320) or None + except (TypeError, ValueError): + max_bitrate = None + + if max_bitrate: + max_bitrate = max_bitrate * 1000 + return handle_serve( + upload=upload, + user=request.user, + format=format, + max_bitrate=max_bitrate, + proxy_media=settings.PROXY_MEDIA, + download=download, + wsgi_request=request._request, + ) + + +class ListenViewSet(ListenMixin): + pass + + +class MP3Renderer(renderers.JSONRenderer): + format = "mp3" + media_type = "audio/mpeg" + + +class StreamViewSet(ListenMixin): + renderer_classes = [MP3Renderer] + + def retrieve(self, request, *args, **kwargs): + config = { + "explicit_file": None, + "download": False, + "format": "mp3", + "max_bitrate": None, + } + track = self.get_object() + return handle_stream(track, request, **config) class UploadViewSet( diff --git a/api/tests/audio/test_serializers.py b/api/tests/audio/test_serializers.py index 1667dfe2c..fbcd28a1a 100644 --- a/api/tests/audio/test_serializers.py +++ b/api/tests/audio/test_serializers.py @@ -6,6 +6,7 @@ import pytest import pytz from django.templatetags.static import static +from django.urls import reverse from funkwhale_api.audio import serializers from funkwhale_api.common import serializers as common_serializers @@ -315,7 +316,10 @@ def test_rss_item_serializer(factories): "enclosure": [ { "url": federation_utils.full_url( - upload.get_listen_url("mp3", download=False) + reverse( + "api:v1:stream-detail", kwargs={"uuid": str(upload.track.uuid)} + ) + + ".mp3" ), "length": upload.size, "type": "audio/mpeg", diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 81988a6b3..3ab5812f2 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -445,6 +445,30 @@ def test_listen_explicit_file(factories, logged_in_api_client, mocker, settings) ) +def test_stream(factories, logged_in_api_client, mocker, settings): + mocked_serve = mocker.spy(views, "handle_serve") + upload = factories["music.Upload"]( + library__privacy_level="everyone", import_status="finished" + ) + url = ( + reverse("api:v1:stream-detail", kwargs={"uuid": str(upload.track.uuid)}) + + ".mp3" + ) + assert url.endswith("/{}.mp3".format(upload.track.uuid)) + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + mocked_serve.assert_called_once_with( + upload=upload, + user=logged_in_api_client.user, + format="mp3", + download=False, + max_bitrate=None, + proxy_media=True, + wsgi_request=response.wsgi_request, + ) + + def test_listen_no_proxy(factories, logged_in_api_client, settings): settings.PROXY_MEDIA = False upload = factories["music.Upload"](