Thumbnailing CMYK colorspace image gives Internal Server Error
This issue has been migrated from #10741.
When a jpeg image using the CMYK colorspace is uploaded to synapse, any attempts to get a thumbnail with the /media/r0/thumbnail endpoint return Internal Server Error. The logs contain the following exception:
synapse.http.server - 93 - ERROR - GET-393903 - Failed handle request via 'ThumbnailResource': <XForwardedForRequest at 0xffff176a5e20 method='GET' uri='/_matrix/media/r0/thumbnail/alephc.xyz/onBfeogmWNGmprAkaswiXiOC?width=64&height=64&method=crop' clientproto='HTTP/1.1' site='8008'>
Traceback (most recent call last):
File "/home/synapse/synapse/env/lib/python3.8/site-packages/PIL/PngImagePlugin.py", line 1230, in _save
rawmode, mode = _OUTMODES[mode]
KeyError: 'CMYK'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/http/server.py", line 258, in _async_render_wrapper
callback_return = await self._async_render(request)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/http/server.py", line 286, in _async_render
callback_return = await raw_callback_return
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/rest/media/v1/thumbnail_resource.py", line 69, in _async_render_GET
await self._select_or_generate_local_thumbnail(
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/rest/media/v1/thumbnail_resource.py", line 170, in _select_or_generate_local_thumbnail
file_path = await self.media_repo.generate_local_exact_thumbnail(
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/rest/media/v1/media_repository.py", line 535, in generate_local_exact_thumbnail
t_byte_source = await defer_to_thread(
File "/home/synapse/synapse/env/lib/python3.8/site-packages/twisted/python/threadpool.py", line 238, in inContext
result = inContext.theWork() # type: ignore[attr-defined]
File "/home/synapse/synapse/env/lib/python3.8/site-packages/twisted/python/threadpool.py", line 254, in <lambda>
inContext.theWork = lambda: context.call( # type: ignore[attr-defined]
File "/home/synapse/synapse/env/lib/python3.8/site-packages/twisted/python/context.py", line 118, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/twisted/python/context.py", line 83, in callWithContext
return func(*args, **kw)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/logging/context.py", line 902, in g
return f(*args, **kwargs)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/rest/media/v1/media_repository.py", line 501, in _generate_thumbnail
return thumbnailer.crop(t_width, t_height, t_type)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/rest/media/v1/thumbnailer.py", line 152, in crop
return self._encode_image(cropped, output_type)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/synapse/rest/media/v1/thumbnailer.py", line 159, in _encode_image
output_image.save(output_bytes_io, fmt, quality=80)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/PIL/Image.py", line 2235, in save
save_handler(self, fp, filename)
File "/home/synapse/synapse/env/lib/python3.8/site-packages/PIL/PngImagePlugin.py", line 1232, in _save
raise OSError(f"cannot write mode {mode} as PNG") from e
OSError: cannot write mode CMYK as PNG
Accessing the image via the /media/r0/download endpoint does not have any issue.
I have attached a small image that exhibits the issue, otherwise one can be created by taking any jpeg and using image magick: convert in.jpg -colorspace cmyk.jpg
Version & Config Info
Synapse: 1.41.1, with dynamic_thumbnails: true Python: 3.8.10 Pillow: 8.3.1 Running on Ubuntu 20.04
Test Image

the related upstream library issue: https://github.com/python-pillow/Pillow/issues/1380 the application-side solution to this (png doesn't support the cymk colorspace) is to bolt this logic in:
if image_file.mode == "CMYK":
image_file = image_file.convert("RGB")
which would resolve the problem within the mediaserver component.