Fix generating thumbs for favicon.ico

This commit is contained in:
Mira
2024-03-19 13:54:02 +01:00
committed by GitHub
parent 7e60d13910
commit 4d00efb549
2 changed files with 30 additions and 14 deletions

View File

@@ -22,9 +22,9 @@
import logging
from django import template
from django.core.files import File
from django.core.files.storage import default_storage
from pretix import settings
from pretix.helpers.thumb import get_thumbnail
register = template.Library()
@@ -33,10 +33,16 @@ logger = logging.getLogger(__name__)
@register.filter
def thumb(source, arg):
if isinstance(source, File):
source = source.name
try:
return get_thumbnail(source, arg).thumb.url
formats = list(set().union(
settings.PILLOW_FORMATS_IMAGE,
settings.PILLOW_FORMATS_QUESTIONS_FAVICON,
settings.PILLOW_FORMATS_QUESTIONS_IMAGE
))
return get_thumbnail(source, arg, formats=formats).thumb.url
except:
logger.exception(f'Failed to create thumbnail of {source}')
return default_storage.url(source)
# HACK: source.url works for some types of files (e.g. FieldFile), and for all files retrieved from Hierarkey,
# default_storage.url works for all files in NanoCDNStorage. For others, this may return an invalid URL.
# But for a fallback, this can probably be accepted.
return source.url if hasattr(source, 'url') else default_storage.url(source.name)

View File

@@ -21,6 +21,7 @@
#
import hashlib
import math
import os
from io import BytesIO
from django.conf import settings
@@ -164,9 +165,16 @@ def resize_image(image, size):
return image
def create_thumbnail(sourcename, size, formats=None):
source = default_storage.open(sourcename)
image = Image.open(BytesIO(source.read()), formats=formats or settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
def create_thumbnail(source, size, formats=None):
source_name = str(source)
# HACK: this ensures that the file is opened in binary mode, which is not guaranteed otherwise, esp. for
# files retrieved from hierarkey. For Django Files in FileSystemStorage, where source.name is the absolute
# filesystem path, this only works because _open() uses safe_join, which accepts absolute paths if they match the
# expected base dir. For NanoCDN Files, this works because source.name is set to the storage path.
source_rb = default_storage.open(source_name, mode='rb')
image = Image.open(BytesIO(source_rb.read()), formats=formats or settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
try:
image.load()
except:
@@ -175,13 +183,14 @@ def create_thumbnail(sourcename, size, formats=None):
frames = [resize_image(frame, size) for frame in ImageSequence.Iterator(image)]
image_out = frames[0]
save_kwargs = {}
source_ext = os.path.splitext(source_name)[1].lower()
if source.name.lower().endswith('.jpg') or source.name.lower().endswith('.jpeg'):
if source_ext == '.jpg' or source_ext == '.jpeg':
# Yields better file sizes for photos
target_ext = 'jpeg'
quality = 95
elif source.name.lower().endswith('.gif') or source.name.lower().endswith('.png'):
target_ext = source.name.lower()[-3:]
elif source_ext =='.gif' or source_ext == '.png':
target_ext = source_name.lower()[-3:]
quality = None
image_out.info = image.info
save_kwargs = {
@@ -196,14 +205,14 @@ def create_thumbnail(sourcename, size, formats=None):
checksum = hashlib.md5(image.tobytes()).hexdigest()
name = checksum + '.' + size.replace('^', 'c') + '.' + target_ext
buffer = BytesIO()
if image_out.mode == "P" and source.name.lower().endswith('.png'):
if image_out.mode == "P" and source_ext == '.png':
image_out = image_out.convert('RGBA')
if image_out.mode not in ("1", "L", "RGB", "RGBA"):
image_out = image_out.convert('RGB')
image_out.save(fp=buffer, format=target_ext.upper(), quality=quality, **save_kwargs)
imgfile = ContentFile(buffer.getvalue())
t = Thumbnail.objects.create(source=sourcename, size=size)
t = Thumbnail.objects.create(source=source_name, size=size)
t.thumb.save(name, imgfile)
return t
@@ -211,6 +220,7 @@ def create_thumbnail(sourcename, size, formats=None):
def get_thumbnail(source, size, formats=None):
# Assumes files are immutable
try:
return Thumbnail.objects.get(source=source, size=size)
source_name = str(source)
return Thumbnail.objects.get(source=source_name, size=size)
except Thumbnail.DoesNotExist:
return create_thumbnail(source, size, formats=formats)