We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Oembed endpoint raises uncaught error on non-existent identifier
## Sentry link

<!-- The public (aka "share") Sentry issue link. -->
https://sentry.io/share/issue/797fb240716d49efaf6f0b762e3484dd/

## Description

<!-- Example: We are trying to access property foo of ImportantClass but the instance is null. -->
<!-- Include any additional information you may have, including potential remedies if any come to mind, and the general context of the code (what causes it to run in the app).  -->
When the `/v1/images/oembed/` URL receives an invalid identifier or URL for the `url` query parameter, an `AssertionError` is raised. This should probably return a 4XX error instead.

<!-- Mention whether this is a known regression, i.e., the feature used to work and now does not.  -->

## Reproduction

<!-- Share the steps to reproduce the issue, if you were able to, OR a note sharing that you tried to reproduce but weren’t able to. -->
1. `just up`
2. Visit: http://localhost:50280/v1/images/oembed/?url=00000000-0000-0000-0000-000000000000


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `api/catalog/api/views/image_views.py`
Content:
```
1 import io
2 import struct
3 
4 from django.conf import settings
5 from django.http.response import FileResponse, HttpResponse
6 from django.utils.decorators import method_decorator
7 from rest_framework.decorators import action
8 from rest_framework.exceptions import NotFound
9 from rest_framework.response import Response
10 
11 import piexif
12 import requests
13 from drf_yasg.utils import swagger_auto_schema
14 from PIL import Image as PILImage
15 
16 from catalog.api.constants.media_types import IMAGE_TYPE
17 from catalog.api.docs.image_docs import (
18     ImageComplain,
19     ImageDetail,
20     ImageOembed,
21     ImageRelated,
22     ImageSearch,
23     ImageStats,
24     ImageThumbnail,
25 )
26 from catalog.api.models import Image
27 from catalog.api.serializers.image_serializers import (
28     ImageReportRequestSerializer,
29     ImageSearchRequestSerializer,
30     ImageSerializer,
31     OembedRequestSerializer,
32     OembedSerializer,
33     WatermarkRequestSerializer,
34 )
35 from catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer
36 from catalog.api.utils.exceptions import get_api_exception
37 from catalog.api.utils.throttle import (
38     AnonThumbnailRateThrottle,
39     OAuth2IdThumbnailRateThrottle,
40 )
41 from catalog.api.utils.watermark import watermark
42 from catalog.api.views.media_views import MediaViewSet
43 
44 
45 @method_decorator(swagger_auto_schema(**ImageSearch.swagger_setup), "list")
46 @method_decorator(swagger_auto_schema(**ImageStats.swagger_setup), "stats")
47 @method_decorator(swagger_auto_schema(**ImageDetail.swagger_setup), "retrieve")
48 @method_decorator(swagger_auto_schema(**ImageRelated.swagger_setup), "related")
49 @method_decorator(swagger_auto_schema(**ImageComplain.swagger_setup), "report")
50 @method_decorator(swagger_auto_schema(**ImageOembed.swagger_setup), "oembed")
51 @method_decorator(swagger_auto_schema(**ImageThumbnail.swagger_setup), "thumbnail")
52 @method_decorator(swagger_auto_schema(auto_schema=None), "watermark")
53 class ImageViewSet(MediaViewSet):
54     """
55     Viewset for all endpoints pertaining to images.
56     """
57 
58     model_class = Image
59     query_serializer_class = ImageSearchRequestSerializer
60     default_index = settings.MEDIA_INDEX_MAPPING[IMAGE_TYPE]
61     qa_index = "search-qa-image"
62 
63     serializer_class = ImageSerializer
64 
65     OEMBED_HEADERS = {
66         "User-Agent": settings.OUTBOUND_USER_AGENT_TEMPLATE.format(purpose="OEmbed"),
67     }
68 
69     # Extra actions
70 
71     @action(
72         detail=False,
73         url_path="oembed",
74         url_name="oembed",
75         serializer_class=OembedSerializer,
76     )
77     def oembed(self, request, *_, **__):
78         params = OembedRequestSerializer(data=request.query_params)
79         params.is_valid(raise_exception=True)
80 
81         context = self.get_serializer_context()
82 
83         url = params.validated_data["url"]
84         identifier = url.rsplit("/", 1)[1]
85         try:
86             image = self.get_queryset().get(identifier=identifier)
87         except Image.DoesNotExist:
88             return get_api_exception("Could not find image.", 404)
89         if not (image.height and image.width):
90             image_file = requests.get(image.url, headers=self.OEMBED_HEADERS)
91             width, height = PILImage.open(io.BytesIO(image_file.content)).size
92             context |= {
93                 "width": width,
94                 "height": height,
95             }
96 
97         serializer = self.get_serializer(image, context=context)
98         return Response(data=serializer.data)
99 
100     @action(
101         detail=True,
102         url_path="thumb",
103         url_name="thumb",
104         serializer_class=MediaThumbnailRequestSerializer,
105         throttle_classes=[AnonThumbnailRateThrottle, OAuth2IdThumbnailRateThrottle],
106     )
107     def thumbnail(self, request, *_, **__):
108         image = self.get_object()
109 
110         image_url = image.url
111         if not image_url:
112             raise get_api_exception("Could not find image.", 404)
113 
114         return super().thumbnail(image_url, request)
115 
116     @action(detail=True, url_path="watermark", url_name="watermark")
117     def watermark(self, request, *_, **__):
118         if not settings.WATERMARK_ENABLED:
119             raise NotFound("The watermark feature is currently disabled.")
120 
121         params = WatermarkRequestSerializer(data=request.query_params)
122         params.is_valid(raise_exception=True)
123 
124         image = self.get_object()
125         image_url = image.url
126         image_info = {
127             attr: getattr(image, attr)
128             for attr in ["title", "creator", "license", "license_version"]
129         }
130 
131         # Create the actual watermarked image.
132         watermarked, exif = watermark(image_url, image_info, params.data["watermark"])
133         # Re-insert EXIF metadata.
134         if exif:
135             # piexif dump raises InvalidImageDataError which is a child class
136             # of ValueError, and a struct error when the value is not
137             # between -2147483648 and 2147483647
138             # https://github.com/WordPress/openverse-api/issues/849
139             try:
140                 exif_bytes = piexif.dump(exif)
141             except (struct.error, ValueError):
142                 exif_bytes = None
143         else:
144             exif_bytes = None
145         img_bytes = io.BytesIO()
146         self._save_wrapper(watermarked, exif_bytes, img_bytes)
147 
148         if params.data["embed_metadata"]:
149             # Embed ccREL metadata with XMP.
150             work_properties = {
151                 "creator": image.creator,
152                 "license_url": image.license_url,
153                 "attribution": image.attribution,
154                 "work_landing_page": image.foreign_landing_url,
155                 "identifier": str(image.identifier),
156             }
157 
158             # Import inside a function to allow server run without Exempi library
159             import libxmp
160 
161             from catalog.api.utils import ccrel
162 
163             try:
164                 with_xmp = ccrel.embed_xmp_bytes(img_bytes, work_properties)
165                 return FileResponse(with_xmp, content_type="image/jpeg")
166             except (libxmp.XMPError, AttributeError):
167                 # Just send the EXIF-ified file if libxmp fails to add metadata
168                 response = HttpResponse(content_type="image/jpeg")
169                 self._save_wrapper(watermarked, exif_bytes, response)
170                 return response
171         else:
172             response = HttpResponse(img_bytes, content_type="image/jpeg")
173             self._save_wrapper(watermarked, exif_bytes, response)
174             return response
175 
176     @action(
177         detail=True,
178         methods=["post"],
179         serializer_class=ImageReportRequestSerializer,
180     )
181     def report(self, *args, **kwargs):
182         return super().report(*args, **kwargs)
183 
184     # Helper functions
185 
186     @staticmethod
187     def _save_wrapper(pil_img, exif_bytes, destination):
188         """
189         PIL crashes if exif_bytes=None, so we have to wrap it to avoid littering
190         the code with branches.
191         """
192         if exif_bytes:
193             pil_img.save(destination, "jpeg", exif=exif_bytes)
194         else:
195             pil_img.save(destination, "jpeg")
196
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/api/catalog/api/views/image_views.py b/api/catalog/api/views/image_views.py
--- a/api/catalog/api/views/image_views.py
+++ b/api/catalog/api/views/image_views.py
@@ -81,11 +81,13 @@
         context = self.get_serializer_context()
 
         url = params.validated_data["url"]
+        if url.endswith("/"):
+            url = url[:-1]
         identifier = url.rsplit("/", 1)[1]
         try:
             image = self.get_queryset().get(identifier=identifier)
         except Image.DoesNotExist:
-            return get_api_exception("Could not find image.", 404)
+            raise get_api_exception("Could not find image.", 404)
         if not (image.height and image.width):
             image_file = requests.get(image.url, headers=self.OEMBED_HEADERS)
             width, height = PILImage.open(io.BytesIO(image_file.content)).size

{"golden_diff": "diff --git a/api/catalog/api/views/image_views.py b/api/catalog/api/views/image_views.py\n--- a/api/catalog/api/views/image_views.py\n+++ b/api/catalog/api/views/image_views.py\n@@ -81,11 +81,13 @@\n         context = self.get_serializer_context()\n \n         url = params.validated_data[\"url\"]\n+        if url.endswith(\"/\"):\n+            url = url[:-1]\n         identifier = url.rsplit(\"/\", 1)[1]\n         try:\n             image = self.get_queryset().get(identifier=identifier)\n         except Image.DoesNotExist:\n-            return get_api_exception(\"Could not find image.\", 404)\n+            raise get_api_exception(\"Could not find image.\", 404)\n         if not (image.height and image.width):\n             image_file = requests.get(image.url, headers=self.OEMBED_HEADERS)\n             width, height = PILImage.open(io.BytesIO(image_file.content)).size\n", "issue": "Oembed endpoint raises uncaught error on non-existent identifier\n## Sentry link\n\n<!-- The public (aka \"share\") Sentry issue link. -->\nhttps://sentry.io/share/issue/797fb240716d49efaf6f0b762e3484dd/\n\n## Description\n\n<!-- Example: We are trying to access property foo of ImportantClass but the instance is null. -->\n<!-- Include any additional information you may have, including potential remedies if any come to mind, and the general context of the code (what causes it to run in the app).  -->\nWhen the `/v1/images/oembed/` URL receives an invalid identifier or URL for the `url` query parameter, an `AssertionError` is raised. This should probably return a 4XX error instead.\n\n<!-- Mention whether this is a known regression, i.e., the feature used to work and now does not.  -->\n\n## Reproduction\n\n<!-- Share the steps to reproduce the issue, if you were able to, OR a note sharing that you tried to reproduce but weren\u2019t able to. -->\n1. `just up`\n2. Visit: http://localhost:50280/v1/images/oembed/?url=00000000-0000-0000-0000-000000000000\n\n", "before_files": [{"content": "import io\nimport struct\n\nfrom django.conf import settings\nfrom django.http.response import FileResponse, HttpResponse\nfrom django.utils.decorators import method_decorator\nfrom rest_framework.decorators import action\nfrom rest_framework.exceptions import NotFound\nfrom rest_framework.response import Response\n\nimport piexif\nimport requests\nfrom drf_yasg.utils import swagger_auto_schema\nfrom PIL import Image as PILImage\n\nfrom catalog.api.constants.media_types import IMAGE_TYPE\nfrom catalog.api.docs.image_docs import (\n    ImageComplain,\n    ImageDetail,\n    ImageOembed,\n    ImageRelated,\n    ImageSearch,\n    ImageStats,\n    ImageThumbnail,\n)\nfrom catalog.api.models import Image\nfrom catalog.api.serializers.image_serializers import (\n    ImageReportRequestSerializer,\n    ImageSearchRequestSerializer,\n    ImageSerializer,\n    OembedRequestSerializer,\n    OembedSerializer,\n    WatermarkRequestSerializer,\n)\nfrom catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer\nfrom catalog.api.utils.exceptions import get_api_exception\nfrom catalog.api.utils.throttle import (\n    AnonThumbnailRateThrottle,\n    OAuth2IdThumbnailRateThrottle,\n)\nfrom catalog.api.utils.watermark import watermark\nfrom catalog.api.views.media_views import MediaViewSet\n\n\n@method_decorator(swagger_auto_schema(**ImageSearch.swagger_setup), \"list\")\n@method_decorator(swagger_auto_schema(**ImageStats.swagger_setup), \"stats\")\n@method_decorator(swagger_auto_schema(**ImageDetail.swagger_setup), \"retrieve\")\n@method_decorator(swagger_auto_schema(**ImageRelated.swagger_setup), \"related\")\n@method_decorator(swagger_auto_schema(**ImageComplain.swagger_setup), \"report\")\n@method_decorator(swagger_auto_schema(**ImageOembed.swagger_setup), \"oembed\")\n@method_decorator(swagger_auto_schema(**ImageThumbnail.swagger_setup), \"thumbnail\")\n@method_decorator(swagger_auto_schema(auto_schema=None), \"watermark\")\nclass ImageViewSet(MediaViewSet):\n    \"\"\"\n    Viewset for all endpoints pertaining to images.\n    \"\"\"\n\n    model_class = Image\n    query_serializer_class = ImageSearchRequestSerializer\n    default_index = settings.MEDIA_INDEX_MAPPING[IMAGE_TYPE]\n    qa_index = \"search-qa-image\"\n\n    serializer_class = ImageSerializer\n\n    OEMBED_HEADERS = {\n        \"User-Agent\": settings.OUTBOUND_USER_AGENT_TEMPLATE.format(purpose=\"OEmbed\"),\n    }\n\n    # Extra actions\n\n    @action(\n        detail=False,\n        url_path=\"oembed\",\n        url_name=\"oembed\",\n        serializer_class=OembedSerializer,\n    )\n    def oembed(self, request, *_, **__):\n        params = OembedRequestSerializer(data=request.query_params)\n        params.is_valid(raise_exception=True)\n\n        context = self.get_serializer_context()\n\n        url = params.validated_data[\"url\"]\n        identifier = url.rsplit(\"/\", 1)[1]\n        try:\n            image = self.get_queryset().get(identifier=identifier)\n        except Image.DoesNotExist:\n            return get_api_exception(\"Could not find image.\", 404)\n        if not (image.height and image.width):\n            image_file = requests.get(image.url, headers=self.OEMBED_HEADERS)\n            width, height = PILImage.open(io.BytesIO(image_file.content)).size\n            context |= {\n                \"width\": width,\n                \"height\": height,\n            }\n\n        serializer = self.get_serializer(image, context=context)\n        return Response(data=serializer.data)\n\n    @action(\n        detail=True,\n        url_path=\"thumb\",\n        url_name=\"thumb\",\n        serializer_class=MediaThumbnailRequestSerializer,\n        throttle_classes=[AnonThumbnailRateThrottle, OAuth2IdThumbnailRateThrottle],\n    )\n    def thumbnail(self, request, *_, **__):\n        image = self.get_object()\n\n        image_url = image.url\n        if not image_url:\n            raise get_api_exception(\"Could not find image.\", 404)\n\n        return super().thumbnail(image_url, request)\n\n    @action(detail=True, url_path=\"watermark\", url_name=\"watermark\")\n    def watermark(self, request, *_, **__):\n        if not settings.WATERMARK_ENABLED:\n            raise NotFound(\"The watermark feature is currently disabled.\")\n\n        params = WatermarkRequestSerializer(data=request.query_params)\n        params.is_valid(raise_exception=True)\n\n        image = self.get_object()\n        image_url = image.url\n        image_info = {\n            attr: getattr(image, attr)\n            for attr in [\"title\", \"creator\", \"license\", \"license_version\"]\n        }\n\n        # Create the actual watermarked image.\n        watermarked, exif = watermark(image_url, image_info, params.data[\"watermark\"])\n        # Re-insert EXIF metadata.\n        if exif:\n            # piexif dump raises InvalidImageDataError which is a child class\n            # of ValueError, and a struct error when the value is not\n            # between -2147483648 and 2147483647\n            # https://github.com/WordPress/openverse-api/issues/849\n            try:\n                exif_bytes = piexif.dump(exif)\n            except (struct.error, ValueError):\n                exif_bytes = None\n        else:\n            exif_bytes = None\n        img_bytes = io.BytesIO()\n        self._save_wrapper(watermarked, exif_bytes, img_bytes)\n\n        if params.data[\"embed_metadata\"]:\n            # Embed ccREL metadata with XMP.\n            work_properties = {\n                \"creator\": image.creator,\n                \"license_url\": image.license_url,\n                \"attribution\": image.attribution,\n                \"work_landing_page\": image.foreign_landing_url,\n                \"identifier\": str(image.identifier),\n            }\n\n            # Import inside a function to allow server run without Exempi library\n            import libxmp\n\n            from catalog.api.utils import ccrel\n\n            try:\n                with_xmp = ccrel.embed_xmp_bytes(img_bytes, work_properties)\n                return FileResponse(with_xmp, content_type=\"image/jpeg\")\n            except (libxmp.XMPError, AttributeError):\n                # Just send the EXIF-ified file if libxmp fails to add metadata\n                response = HttpResponse(content_type=\"image/jpeg\")\n                self._save_wrapper(watermarked, exif_bytes, response)\n                return response\n        else:\n            response = HttpResponse(img_bytes, content_type=\"image/jpeg\")\n            self._save_wrapper(watermarked, exif_bytes, response)\n            return response\n\n    @action(\n        detail=True,\n        methods=[\"post\"],\n        serializer_class=ImageReportRequestSerializer,\n    )\n    def report(self, *args, **kwargs):\n        return super().report(*args, **kwargs)\n\n    # Helper functions\n\n    @staticmethod\n    def _save_wrapper(pil_img, exif_bytes, destination):\n        \"\"\"\n        PIL crashes if exif_bytes=None, so we have to wrap it to avoid littering\n        the code with branches.\n        \"\"\"\n        if exif_bytes:\n            pil_img.save(destination, \"jpeg\", exif=exif_bytes)\n        else:\n            pil_img.save(destination, \"jpeg\")\n", "path": "api/catalog/api/views/image_views.py"}], "after_files": [{"content": "import io\nimport struct\n\nfrom django.conf import settings\nfrom django.http.response import FileResponse, HttpResponse\nfrom django.utils.decorators import method_decorator\nfrom rest_framework.decorators import action\nfrom rest_framework.exceptions import NotFound\nfrom rest_framework.response import Response\n\nimport piexif\nimport requests\nfrom drf_yasg.utils import swagger_auto_schema\nfrom PIL import Image as PILImage\n\nfrom catalog.api.constants.media_types import IMAGE_TYPE\nfrom catalog.api.docs.image_docs import (\n    ImageComplain,\n    ImageDetail,\n    ImageOembed,\n    ImageRelated,\n    ImageSearch,\n    ImageStats,\n    ImageThumbnail,\n)\nfrom catalog.api.models import Image\nfrom catalog.api.serializers.image_serializers import (\n    ImageReportRequestSerializer,\n    ImageSearchRequestSerializer,\n    ImageSerializer,\n    OembedRequestSerializer,\n    OembedSerializer,\n    WatermarkRequestSerializer,\n)\nfrom catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer\nfrom catalog.api.utils.exceptions import get_api_exception\nfrom catalog.api.utils.throttle import (\n    AnonThumbnailRateThrottle,\n    OAuth2IdThumbnailRateThrottle,\n)\nfrom catalog.api.utils.watermark import watermark\nfrom catalog.api.views.media_views import MediaViewSet\n\n\n@method_decorator(swagger_auto_schema(**ImageSearch.swagger_setup), \"list\")\n@method_decorator(swagger_auto_schema(**ImageStats.swagger_setup), \"stats\")\n@method_decorator(swagger_auto_schema(**ImageDetail.swagger_setup), \"retrieve\")\n@method_decorator(swagger_auto_schema(**ImageRelated.swagger_setup), \"related\")\n@method_decorator(swagger_auto_schema(**ImageComplain.swagger_setup), \"report\")\n@method_decorator(swagger_auto_schema(**ImageOembed.swagger_setup), \"oembed\")\n@method_decorator(swagger_auto_schema(**ImageThumbnail.swagger_setup), \"thumbnail\")\n@method_decorator(swagger_auto_schema(auto_schema=None), \"watermark\")\nclass ImageViewSet(MediaViewSet):\n    \"\"\"\n    Viewset for all endpoints pertaining to images.\n    \"\"\"\n\n    model_class = Image\n    query_serializer_class = ImageSearchRequestSerializer\n    default_index = settings.MEDIA_INDEX_MAPPING[IMAGE_TYPE]\n    qa_index = \"search-qa-image\"\n\n    serializer_class = ImageSerializer\n\n    OEMBED_HEADERS = {\n        \"User-Agent\": settings.OUTBOUND_USER_AGENT_TEMPLATE.format(purpose=\"OEmbed\"),\n    }\n\n    # Extra actions\n\n    @action(\n        detail=False,\n        url_path=\"oembed\",\n        url_name=\"oembed\",\n        serializer_class=OembedSerializer,\n    )\n    def oembed(self, request, *_, **__):\n        params = OembedRequestSerializer(data=request.query_params)\n        params.is_valid(raise_exception=True)\n\n        context = self.get_serializer_context()\n\n        url = params.validated_data[\"url\"]\n        if url.endswith(\"/\"):\n            url = url[:-1]\n        identifier = url.rsplit(\"/\", 1)[1]\n        try:\n            image = self.get_queryset().get(identifier=identifier)\n        except Image.DoesNotExist:\n            raise get_api_exception(\"Could not find image.\", 404)\n        if not (image.height and image.width):\n            image_file = requests.get(image.url, headers=self.OEMBED_HEADERS)\n            width, height = PILImage.open(io.BytesIO(image_file.content)).size\n            context |= {\n                \"width\": width,\n                \"height\": height,\n            }\n\n        serializer = self.get_serializer(image, context=context)\n        return Response(data=serializer.data)\n\n    @action(\n        detail=True,\n        url_path=\"thumb\",\n        url_name=\"thumb\",\n        serializer_class=MediaThumbnailRequestSerializer,\n        throttle_classes=[AnonThumbnailRateThrottle, OAuth2IdThumbnailRateThrottle],\n    )\n    def thumbnail(self, request, *_, **__):\n        image = self.get_object()\n\n        image_url = image.url\n        if not image_url:\n            raise get_api_exception(\"Could not find image.\", 404)\n\n        return super().thumbnail(image_url, request)\n\n    @action(detail=True, url_path=\"watermark\", url_name=\"watermark\")\n    def watermark(self, request, *_, **__):\n        if not settings.WATERMARK_ENABLED:\n            raise NotFound(\"The watermark feature is currently disabled.\")\n\n        params = WatermarkRequestSerializer(data=request.query_params)\n        params.is_valid(raise_exception=True)\n\n        image = self.get_object()\n        image_url = image.url\n        image_info = {\n            attr: getattr(image, attr)\n            for attr in [\"title\", \"creator\", \"license\", \"license_version\"]\n        }\n\n        # Create the actual watermarked image.\n        watermarked, exif = watermark(image_url, image_info, params.data[\"watermark\"])\n        # Re-insert EXIF metadata.\n        if exif:\n            # piexif dump raises InvalidImageDataError which is a child class\n            # of ValueError, and a struct error when the value is not\n            # between -2147483648 and 2147483647\n            # https://github.com/WordPress/openverse-api/issues/849\n            try:\n                exif_bytes = piexif.dump(exif)\n            except (struct.error, ValueError):\n                exif_bytes = None\n        else:\n            exif_bytes = None\n        img_bytes = io.BytesIO()\n        self._save_wrapper(watermarked, exif_bytes, img_bytes)\n\n        if params.data[\"embed_metadata\"]:\n            # Embed ccREL metadata with XMP.\n            work_properties = {\n                \"creator\": image.creator,\n                \"license_url\": image.license_url,\n                \"attribution\": image.attribution,\n                \"work_landing_page\": image.foreign_landing_url,\n                \"identifier\": str(image.identifier),\n            }\n\n            # Import inside a function to allow server run without Exempi library\n            import libxmp\n\n            from catalog.api.utils import ccrel\n\n            try:\n                with_xmp = ccrel.embed_xmp_bytes(img_bytes, work_properties)\n                return FileResponse(with_xmp, content_type=\"image/jpeg\")\n            except (libxmp.XMPError, AttributeError):\n                # Just send the EXIF-ified file if libxmp fails to add metadata\n                response = HttpResponse(content_type=\"image/jpeg\")\n                self._save_wrapper(watermarked, exif_bytes, response)\n                return response\n        else:\n            response = HttpResponse(img_bytes, content_type=\"image/jpeg\")\n            self._save_wrapper(watermarked, exif_bytes, response)\n            return response\n\n    @action(\n        detail=True,\n        methods=[\"post\"],\n        serializer_class=ImageReportRequestSerializer,\n    )\n    def report(self, *args, **kwargs):\n        return super().report(*args, **kwargs)\n\n    # Helper functions\n\n    @staticmethod\n    def _save_wrapper(pil_img, exif_bytes, destination):\n        \"\"\"\n        PIL crashes if exif_bytes=None, so we have to wrap it to avoid littering\n        the code with branches.\n        \"\"\"\n        if exif_bytes:\n            pil_img.save(destination, \"jpeg\", exif=exif_bytes)\n        else:\n            pil_img.save(destination, \"jpeg\")\n", "path": "api/catalog/api/views/image_views.py"}]}