diff --git a/api/catalog/api/docs/audio_docs.py b/api/catalog/api/docs/audio_docs.py index 6d89d07a1..86690e3eb 100644 --- a/api/catalog/api/docs/audio_docs.py +++ b/api/catalog/api/docs/audio_docs.py @@ -31,6 +31,7 @@ InputErrorSerializer, NotFoundErrorSerializer, ) +from catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer from catalog.api.serializers.provider_serializers import ProviderSerializer from drf_yasg import openapi @@ -206,3 +207,17 @@ class AudioComplain(MediaComplain): "responses": responses, "code_examples": code_examples, } + + +class AudioThumbnail: + desc = f""" +thumbnail is an API endpoint to retrieve the scaled down and compressed thumbnail +of the artwork of an audio track or its audio set. + +{refer_sample}""" # noqa + + swagger_setup = { + "operation_id": "audio_thumbnail", + "operation_description": desc, + "query_serializer": MediaThumbnailRequestSerializer, + } diff --git a/api/catalog/api/docs/image_docs.py b/api/catalog/api/docs/image_docs.py index 6bccc2d41..03fdb0b58 100644 --- a/api/catalog/api/docs/image_docs.py +++ b/api/catalog/api/docs/image_docs.py @@ -37,6 +37,7 @@ OembedRequestSerializer, OembedSerializer, ) +from catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer from catalog.api.serializers.provider_serializers import ProviderSerializer from drf_yasg import openapi @@ -240,3 +241,17 @@ class ImageOembed: "responses": responses, "code_examples": code_examples, } + + +class ImageThumbnail: + desc = f""" +thumbnail is an API endpoint to retrieve the scaled down and compressed thumbnail +of an image. + +{refer_sample}""" # noqa + + swagger_setup = { + "operation_id": "image_thumbnail", + "operation_description": desc, + "query_serializer": MediaThumbnailRequestSerializer, + } diff --git a/api/catalog/api/serializers/media_serializers.py b/api/catalog/api/serializers/media_serializers.py index 6bb046a49..570bc8008 100644 --- a/api/catalog/api/serializers/media_serializers.py +++ b/api/catalog/api/serializers/media_serializers.py @@ -1,3 +1,4 @@ +import logging as log from collections import namedtuple from urllib.parse import urlparse @@ -420,3 +421,32 @@ class MediaSearchSerializer(serializers.Serializer): page = serializers.IntegerField( help_text="The current page number returned in the response." ) + + +class MediaThumbnailRequestSerializer(serializers.Serializer): + """ + This serializer parses and validates thumbnail query string parameters. + """ + + full_size = serializers.BooleanField( + source="is_full_size", + allow_null=True, + required=False, + default=False, + help_text="whether to render the actual image and not a thumbnail version", + ) + compressed = serializers.BooleanField( + source="is_compressed", + allow_null=True, + default=None, + required=False, + help_text="whether to compress the output image to reduce file size," + "defaults to opposite of `full_size`", + ) + + def validate(self, data): + log.info(f"MediaThumbnailRequestSerializer data: {data}") + if data.get("is_compressed") is None: + data["is_compressed"] = not data["is_full_size"] + log.info(f"MediaThumbnailRequestSerializer validated data: {data}") + return data diff --git a/api/catalog/api/views/audio_views.py b/api/catalog/api/views/audio_views.py index 6ff4dd850..7791a0c6d 100644 --- a/api/catalog/api/views/audio_views.py +++ b/api/catalog/api/views/audio_views.py @@ -6,6 +6,7 @@ AudioRelated, AudioSearch, AudioStats, + AudioThumbnail, ) from catalog.api.models import Audio from catalog.api.serializers.audio_serializers import ( @@ -14,6 +15,7 @@ AudioSerializer, AudioWaveformSerializer, ) +from catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer from catalog.api.utils.exceptions import get_api_exception from catalog.api.utils.throttle import OneThousandPerMinute from catalog.api.views.media_views import MediaViewSet @@ -31,7 +33,7 @@ @method_decorator(swagger_auto_schema(**AudioDetail.swagger_setup), "retrieve") @method_decorator(swagger_auto_schema(**AudioRelated.swagger_setup), "related") @method_decorator(swagger_auto_schema(**AudioComplain.swagger_setup), "report") -@method_decorator(swagger_auto_schema(auto_schema=None), "thumbnail") +@method_decorator(swagger_auto_schema(**AudioThumbnail.swagger_setup), "thumbnail") @method_decorator(swagger_auto_schema(auto_schema=None), "waveform") class AudioViewSet(MediaViewSet): """ @@ -51,6 +53,7 @@ class AudioViewSet(MediaViewSet): detail=True, url_path="thumb", url_name="thumb", + serializer_class=MediaThumbnailRequestSerializer, throttle_classes=[OneThousandPerMinute], ) def thumbnail(self, request, *_, **__): diff --git a/api/catalog/api/views/image_views.py b/api/catalog/api/views/image_views.py index 46b4af667..b53e21464 100644 --- a/api/catalog/api/views/image_views.py +++ b/api/catalog/api/views/image_views.py @@ -11,6 +11,7 @@ ImageRelated, ImageSearch, ImageStats, + ImageThumbnail, ) from catalog.api.models import Image from catalog.api.serializers.image_serializers import ( @@ -21,6 +22,7 @@ OembedSerializer, WatermarkRequestSerializer, ) +from catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer from catalog.api.utils import ccrel from catalog.api.utils.exceptions import get_api_exception from catalog.api.utils.throttle import OneThousandPerMinute @@ -44,7 +46,7 @@ @method_decorator(swagger_auto_schema(**ImageRelated.swagger_setup), "related") @method_decorator(swagger_auto_schema(**ImageComplain.swagger_setup), "report") @method_decorator(swagger_auto_schema(**ImageOembed.swagger_setup), "oembed") -@method_decorator(swagger_auto_schema(auto_schema=None), "thumbnail") +@method_decorator(swagger_auto_schema(**ImageThumbnail.swagger_setup), "thumbnail") @method_decorator(swagger_auto_schema(auto_schema=None), "watermark") class ImageViewSet(MediaViewSet): """ @@ -93,6 +95,7 @@ def oembed(self, request, *_, **__): detail=True, url_path="thumb", url_name="thumb", + serializer_class=MediaThumbnailRequestSerializer, throttle_classes=[OneThousandPerMinute], ) def thumbnail(self, request, *_, **__): diff --git a/api/catalog/api/views/media_views.py b/api/catalog/api/views/media_views.py index 2ee8c424c..62ce01add 100644 --- a/api/catalog/api/views/media_views.py +++ b/api/catalog/api/views/media_views.py @@ -1,6 +1,5 @@ import json import logging as log -from distutils.util import strtobool from urllib.error import HTTPError from urllib.parse import urlencode from urllib.request import urlopen @@ -129,15 +128,10 @@ def report(self, request, *_, **__): return Response(data=serializer.data, status=status.HTTP_201_CREATED) def thumbnail(self, image_url, request, *_, **__): - full_size_param = request.query_params.get("full_size", "false").lower() - is_full_size = strtobool(full_size_param) - compressed_param = request.query_params.get( - "compressed", - "false" if is_full_size else "true", - ).lower() - is_compressed = strtobool(compressed_param) - - return self._get_proxied_image(image_url, is_full_size, is_compressed) + serializer = self.get_serializer(data=request.query_params) + if not serializer.is_valid(): + raise get_api_exception("Invalid input.", 400) + return self._get_proxied_image(image_url, **serializer.validated_data) # Helper functions