From 609dd3b4952d54510a3c03fa68c8eff5c9027feb Mon Sep 17 00:00:00 2001
From: Tony Wasserka <neobrainx@gmail.com>
Date: Thu, 7 May 2020 19:18:19 +0200
Subject: [PATCH] Increase image quality of downscaled images from 70 to 95

---
 api/config/settings/common.py                |  5 +-
 api/funkwhale_api/cli/main.py                |  1 +
 api/funkwhale_api/cli/media.py               | 58 ++++++++++++++++++++
 api/funkwhale_api/common/scripts/__init__.py |  2 -
 api/funkwhale_api/common/utils.py            | 12 ++++
 changes/changelog.d/thunbnails.enhancement   |  1 +
 changes/notes.rst                            | 13 +++++
 7 files changed, 89 insertions(+), 3 deletions(-)
 create mode 100644 api/funkwhale_api/cli/media.py
 create mode 100644 changes/changelog.d/thunbnails.enhancement

diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 9ba1ecd09a..492d67ca1b 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -1211,7 +1211,10 @@ VERSATILEIMAGEFIELD_RENDITION_KEY_SETS = {
         ("medium_square_crop", "crop__200x200"),
     ],
 }
-VERSATILEIMAGEFIELD_SETTINGS = {"create_images_on_demand": False}
+VERSATILEIMAGEFIELD_SETTINGS = {
+    "create_images_on_demand": False,
+    "jpeg_resize_quality": env.int("THUMBNAIL_JPEG_RESIZE_QUALITY", default=95),
+}
 RSA_KEY_SIZE = 2048
 # for performance gain in tests, since we don't need to actually create the
 # thumbnails
diff --git a/api/funkwhale_api/cli/main.py b/api/funkwhale_api/cli/main.py
index 9bad5a8eb8..1453ca5d25 100644
--- a/api/funkwhale_api/cli/main.py
+++ b/api/funkwhale_api/cli/main.py
@@ -3,6 +3,7 @@ import sys
 
 from . import base
 from . import library  # noqa
+from . import media  # noqa
 from . import users  # noqa
 
 from rest_framework.exceptions import ValidationError
diff --git a/api/funkwhale_api/cli/media.py b/api/funkwhale_api/cli/media.py
new file mode 100644
index 0000000000..c50df6b770
--- /dev/null
+++ b/api/funkwhale_api/cli/media.py
@@ -0,0 +1,58 @@
+import click
+
+from django.core.cache import cache
+from django.conf import settings
+
+from versatileimagefield.image_warmer import VersatileImageFieldWarmer
+from versatileimagefield import settings as vif_settings
+
+from funkwhale_api.common import utils as common_utils
+from funkwhale_api.common.models import Attachment
+
+from . import base
+
+
+@base.cli.group()
+def media():
+    """Manage media files (avatars, covers, attachments…)"""
+    pass
+
+
+@media.command("generate-thumbnails")
+def generate_thumbnails():
+    """
+    Generate thumbnails for all images (avatars, covers, etc.).
+
+    This can take a long time and generate a lot of I/O depending of the size
+    of your library.
+    """
+    MODELS = [
+        (Attachment, "file", "attachment_square"),
+    ]
+    for model, attribute, key_set in MODELS:
+        click.echo(
+            "Generating thumbnails for {}.{}…".format(model._meta.label, attribute)
+        )
+        qs = model.objects.exclude(**{"{}__isnull".format(attribute): True})
+        qs = qs.exclude(**{attribute: ""})
+        cache_key = "*{}{}*".format(
+            settings.MEDIA_URL, vif_settings.VERSATILEIMAGEFIELD_SIZED_DIRNAME
+        )
+        entries = cache.keys(cache_key)
+        if entries:
+            click.echo(
+                "  Clearing {} cache entries: {}…".format(len(entries), cache_key)
+            )
+            for keys in common_utils.batch(iter(entries)):
+                cache.delete_many(keys)
+        warmer = VersatileImageFieldWarmer(
+            instance_or_queryset=qs,
+            rendition_key_set=key_set,
+            image_attr=attribute,
+            verbose=True,
+        )
+        click.echo("  Creating images")
+        num_created, failed_to_create = warmer.warm()
+        click.echo(
+            "  {} created, {} in error".format(num_created, len(failed_to_create))
+        )
diff --git a/api/funkwhale_api/common/scripts/__init__.py b/api/funkwhale_api/common/scripts/__init__.py
index 7c468d9849..42160e30d8 100644
--- a/api/funkwhale_api/common/scripts/__init__.py
+++ b/api/funkwhale_api/common/scripts/__init__.py
@@ -1,5 +1,4 @@
 from . import create_actors
-from . import create_image_variations
 from . import django_permissions_to_user_permissions
 from . import migrate_to_user_libraries
 from . import delete_pre_017_federated_uploads
@@ -8,7 +7,6 @@ from . import test
 
 __all__ = [
     "create_actors",
-    "create_image_variations",
     "django_permissions_to_user_permissions",
     "migrate_to_user_libraries",
     "delete_pre_017_federated_uploads",
diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py
index 2a91524d53..5d7d401d73 100644
--- a/api/funkwhale_api/common/utils.py
+++ b/api/funkwhale_api/common/utils.py
@@ -23,6 +23,18 @@ from django.utils import timezone
 logger = logging.getLogger(__name__)
 
 
+def batch(iterable, n=1):
+    has_entries = True
+    while has_entries:
+        current = []
+        for i in range(0, n):
+            try:
+                current.append(next(iterable))
+            except StopIteration:
+                has_entries = False
+        yield current
+
+
 def rename_file(instance, field_name, new_name, allow_missing_file=False):
     field = getattr(instance, field_name)
     current_name, extension = os.path.splitext(field.name)
diff --git a/changes/changelog.d/thunbnails.enhancement b/changes/changelog.d/thunbnails.enhancement
new file mode 100644
index 0000000000..a402c654a5
--- /dev/null
+++ b/changes/changelog.d/thunbnails.enhancement
@@ -0,0 +1 @@
+Increased quality of JPEG thumbnails
diff --git a/changes/notes.rst b/changes/notes.rst
index 96ac3d7651..ee03756587 100644
--- a/changes/notes.rst
+++ b/changes/notes.rst
@@ -5,3 +5,16 @@ Next release notes
 
     Those release notes refer to the current development branch and are reset
     after each release.
+
+
+Increased quality of JPEG thumbnails [manual action required]
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Default quality for JPEG thumbnails was increased from 70 to 95, as 70 was producing visible artifacts in resized images.
+
+Because of this change, existing thumbnails will not load, and you will need to:
+
+1. delete the ``__sized__`` directory in your ``MEDIA_ROOT`` directory
+2. run ``python manage.py fw media generate-thumbnails`` to regenerate thumbnails with the enhanced quality
+
+If you don't want to regenerate thumbnails, you can keep the old ones by adding ``THUMBNAIL_JPEG_RESIZE_QUALITY=70`` to your .env file.
-- 
GitLab