Repository: matrix-org/synapse

Issue:
Synapse failed to process uploaded images with python-pillow 10.0.0
### Description

When I'm trying to upload an image to a federated room or update the avatar of a room, synapse fails to process the image.

### Steps to reproduce

- Open Element.
- Try to update any room avatar.

### Homeserver

chat.kimiblock.top

### Synapse Version

1.85.2

### Installation Method

Other (please mention below)

### Database

Single PostgreSQL, never restored any backup nor ported from SQLite.

### Workers

Single process

### Platform

Running synapse on Arch Linux with PostgreSQL. System is up to date.

Using package `postgresql 15.3-2` `matrix-synapse 1.85.2-1` from Arch's official repos.

### Configuration

Configurations related to media and messages:

```
media_store_path: "/mnt/main/Cache/SynapseData"
media_storage_providers:
  - module: file_system
    store_local: true
    store_remote: true
    store_synchronous: true
    config:
        directory: /mnt/main/Cache/SynapseData/media-storage
max_image_pixels: 108M
media_retention:
    local_media_lifetime: 4d
    remote_media_lifetime: 1d

max_upload_size: 100M

max_avatar_size: 10M
retention:
    enabled: true
    default_policy:
        min_lifetime: 90d
        max_lifetime: 180d
        allowed_lifetime_min: 90d
        allowed_lifetime_max: 180d
```

### Relevant log output

```shell

Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]: 2023-07-04 18:45:18,152 - synapse.media.media_repository - 204 - INFO - POST-1438 - Stored local media in file '/mnt/main/Cache/SynapseData/local_content/ck/qF/AEGVQmRDqPDZMWxZhEKe'
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]: 2023-07-04 18:45:18,185 - synapse.http.server - 130 - ERROR - POST-1438 - Failed handle request via 'UploadResource': <XForwardedForRequest at 0x55d8caaa1250 method='POST' uri='/_matrix/media/r0/upload?filename=annoucement.png' clientproto='HTTP/1.1' site='61145'>
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]: Traceback (most recent call last):
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/http/server.py", line 313, in _async_render_wrapper
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     callback_return = await self._async_render(request)
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/http/server.py", line 343, in _async_render
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     callback_return = await raw_callback_return
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:                       ^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/rest/media/upload_resource.py", line 96, in _async_render_POST
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     content_uri = await self.media_repo.create_content(
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/media/media_repository.py", line 215, in create_content
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     await self._generate_thumbnails(None, media_id, media_id, media_type)
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/media/media_repository.py", line 797, in _generate_thumbnails
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     t_byte_source = await defer_to_thread(
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:                     ^^^^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/twisted/python/threadpool.py", line 244, in inContext
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     result = inContext.theWork()  # type: ignore[attr-defined]
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:              ^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/twisted/python/threadpool.py", line 260, in <lambda>
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     inContext.theWork = lambda: context.call(  # type: ignore[attr-defined]
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/twisted/python/context.py", line 117, in callWithContext
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     return self.currentContext().callWithContext(ctx, func, *args, **kw)
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/twisted/python/context.py", line 82, in callWithContext
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     return func(*args, **kw)
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:            ^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/logging/context.py", line 969, in g
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     return f(*args, **kwargs)
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:            ^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/media/thumbnailer.py", line 172, in crop
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     with self._resize(scaled_width, scaled_height) as scaled_image:
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:   File "/usr/lib/python3.11/site-packages/synapse/media/thumbnailer.py", line 134, in _resize
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:     return self.image.resize((width, height), Image.ANTIALIAS)
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]:                                               ^^^^^^^^^^^^^^^
Jul 04 18:45:18 serverOS-kimiblock.top synapse[71156]: AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'
```


### Anything else that would be useful to know?

It was working properly until I restarted synapse today.

Sending images in a room seems to be working fine.


Discussion:
Seems like Arch updated `python-pillow` today and synapse is not playing well with Pillow 10.0.0

```
[2023-07-04T11:47:30+0800] [ALPM] upgraded python-pillow (9.5.0-2 -> 10.0.0-1)
```

I've downgraded `python-pillow` to 9.5.0-2 and the issue is gone.
Reopen this issue.

https://github.com/python-pillow/Pillow/blob/be828f4d62b3802733a8fac6b6b59cced2092696/docs/deprecations.rst#constants 

(Link provided by q234rty)
> Seems like Arch updated `python-pillow` today and synapse is not playing well with Pillow 10.0.0
> 
> ```
> [2023-07-04T11:47:30+0800] [ALPM] upgraded python-pillow (9.5.0-2 -> 10.0.0-1)
> ```
> 
> I've downgraded `python-pillow` to 9.5.0-2 and the issue is gone.

In case anyone else is wondering how to downgrade with `pip` (e.g. from the virtual environment):

`pip install 'Pillow<=9.5.0-2'`
Replacing this line

https://github.com/matrix-org/synapse/blob/670d590f8a167d909f8d76a5ecbd240d9609d2b0/synapse/media/thumbnailer.py#L134

with 

```python
return self.image.resize((width, height), Image.LANCZOS)
```

fixes this. For fellow arch users, that is in `/usr/lib/python3.11/site-packages/synapse/media/thumbnailer.py` line 134.

`ANTIALIAS` has been [renamed a long time ago](https://pillow.readthedocs.io/en/stable/releasenotes/2.7.0.html#antialias-renamed-to-lanczos).

Would you mind making a PR?

Resolving patch:
diff --git a/synapse/media/thumbnailer.py b/synapse/media/thumbnailer.py
--- a/synapse/media/thumbnailer.py
+++ b/synapse/media/thumbnailer.py
@@ -131,7 +131,7 @@ def _resize(self, width: int, height: int) -> Image.Image:
             else:
                 with self.image:
                     self.image = self.image.convert("RGB")
-        return self.image.resize((width, height), Image.ANTIALIAS)
+        return self.image.resize((width, height), Image.LANCZOS)
 
     def scale(self, width: int, height: int, output_type: str) -> BytesIO:
         """Rescales the image to the given dimensions.


Test patch: