Thumbnails for images and videos #87

Merged
Anna Sirota merged 28 commits from thumbnails into main 2024-04-25 17:50:58 +02:00
5 changed files with 24 additions and 14 deletions
Showing only changes of commit 33587ba720 - Show all commits

View File

@ -106,6 +106,6 @@ ABUSE_TYPE = Choices(
# the code expecting thumbnails of new dimensions can be deployed! # the code expecting thumbnails of new dimensions can be deployed!
THUMBNAIL_SIZE_L = (1920, 1080) THUMBNAIL_SIZE_L = (1920, 1080)
THUMBNAIL_SIZE_S = (640, 360) THUMBNAIL_SIZE_S = (640, 360)
THUMBNAIL_SIZES = [THUMBNAIL_SIZE_L, THUMBNAIL_SIZE_S] THUMBNAIL_SIZES = {'l': THUMBNAIL_SIZE_L, 's': THUMBNAIL_SIZE_S}
THUMBNAIL_FORMAT = 'PNG' THUMBNAIL_FORMAT = 'PNG'
THUMBNAIL_QUALITY = 83 THUMBNAIL_QUALITY = 83

View File

@ -199,7 +199,11 @@ class File(CreatedModifiedMixin, TrackChangesMixin, models.Model):
@property @property
def thumbnail_l_url(self) -> str: def thumbnail_l_url(self) -> str:
"""Log absence of the thumbnail file instead of exploding somewhere in the templates.""" """Return absolute path portion of the URL of the large thumbnail of this file.
Fall back to the source file, if no thumbnail is stored.
Log absence of the thumbnail file instead of exploding somewhere in the templates.
"""
try: try:
return self.thumbnail.url return self.thumbnail.url
except ValueError: except ValueError:
@ -208,11 +212,16 @@ class File(CreatedModifiedMixin, TrackChangesMixin, models.Model):
@property @property
def thumbnail_s_url(self) -> str: def thumbnail_s_url(self) -> str:
"""Log absence of the thumbnail file instead of exploding somewhere in the templates.""" """Return absolute path portion of the URL of the small thumbnail of this file.
Fall back to the source file, if no thumbnail is stored.
Log absence of the thumbnail file instead of exploding somewhere in the templates.
"""
try: try:
return self.metadata['thumbnails']['s']['path'] s = self.metadata['thumbnails']['s']['path']
except KeyError: return self.thumbnail.storage.url(s)
log.exception(f'File pk={self.pk} is missing a small thumbnail') except (KeyError, TypeError):
log.exception(f'File pk={self.pk} is missing a small thumbnail: {self.metadata}')
return self.source.url return self.source.url

View File

@ -5,7 +5,6 @@ from background_task import background
from background_task.tasks import TaskSchedule from background_task.tasks import TaskSchedule
from django.conf import settings from django.conf import settings
from constants.base import THUMBNAIL_SIZE_L
import files.models import files.models
import files.utils import files.utils
@ -32,7 +31,7 @@ def clamdscan(file_id: int):
@background(schedule={'action': TaskSchedule.RESCHEDULE_EXISTING}) @background(schedule={'action': TaskSchedule.RESCHEDULE_EXISTING})
def make_thumbnails(file_id: int) -> None: def make_thumbnails(file_id: int) -> None:
"""Generate thumbnails for a given file.""" """Generate thumbnails for a given file, store them in thumbnail and metadata columns."""
file = files.models.File.objects.get(pk=file_id) file = files.models.File.objects.get(pk=file_id)
args = {'pk': file_id, 'type': file.get_type_display()} args = {'pk': file_id, 'type': file.get_type_display()}
if not file.is_image and not file.is_video: if not file.is_image and not file.is_video:
@ -51,7 +50,7 @@ def make_thumbnails(file_id: int) -> None:
source_path = output_path source_path = output_path
thumbnails = files.utils.make_thumbnails(source_path, file.hash) thumbnails = files.utils.make_thumbnails(source_path, file.hash)
thumbnail_default_path = next(_['path'] for _ in thumbnails if _['size'] == THUMBNAIL_SIZE_L) thumbnail_default_path = thumbnails['l']['path']
update_fields = set() update_fields = set()
if thumbnail_default_path != thumbnail_field.name: if thumbnail_default_path != thumbnail_field.name:

View File

@ -1,10 +1,12 @@
<div class="file-thumbnails"> <div class="file-thumbnails">
{% for thumb in file.metadata.thumbnails %} {% for size_key, thumb in file.metadata.thumbnails.items %}
<div class="file-thumbnail"> <div class="file-thumbnail">
<span class="file-thumbnail-size">{{ thumb.size.0 }}x{{ thumb.size.1 }}px</span> <span class="file-thumbnail-size">{{ thumb.size.0 }}x{{ thumb.size.1 }}px</span>
<img height="{% widthratio thumb.size.1 10 1 %}" src="{{ MEDIA_URL }}{{ thumb.path }}" title={{ thumb.path }}> <img height="{% widthratio thumb.size.1 10 1 %}" src="{{ MEDIA_URL }}{{ thumb.path }}" title={{ thumb.path }}>
</div> </div>
{% endfor %} {% endfor %}
{# alert about unexpected size of the default thumbnail, which might happen if source file was smaller than 1080p #}
{# TODO: do we want to make minimum resolusion a validation criteria during upload instead? #}
{% if file.thumbnail.width != THUMBNAIL_SIZE_L.0 or file.thumbnail.height != THUMBNAIL_SIZE_L.1 %} {% if file.thumbnail.width != THUMBNAIL_SIZE_L.0 or file.thumbnail.height != THUMBNAIL_SIZE_L.1 %}
<p> <p>
<b class="icon-alert"></b> Expected {{ THUMBNAIL_SIZE_L.0 }}x{{ THUMBNAIL_SIZE_L.1 }}px, <b class="icon-alert"></b> Expected {{ THUMBNAIL_SIZE_L.0 }}x{{ THUMBNAIL_SIZE_L.1 }}px,

View File

@ -227,14 +227,14 @@ def make_thumbnails(file_path: str, file_hash: str, output_format: str = THUMBNA
"""Generate thumbnail files for given file and a predefined list of dimensions. """Generate thumbnail files for given file and a predefined list of dimensions.
Resulting thumbnail paths a derived from the given file hash and thumbnail sizes. Resulting thumbnail paths a derived from the given file hash and thumbnail sizes.
Return a list of sizes and output paths of generated thumbnail images. Return a dict of size keys to output paths of generated thumbnail images.
""" """
thumbnail_ext = output_format.lower() thumbnail_ext = output_format.lower()
if thumbnail_ext == 'jpeg': if thumbnail_ext == 'jpeg':
thumbnail_ext = 'jpg' thumbnail_ext = 'jpg'
base_path = get_base_path(get_thumbnail_upload_to(file_hash)) base_path = get_base_path(get_thumbnail_upload_to(file_hash))
thumbnails = [] thumbnails = {}
for w, h in THUMBNAIL_SIZES: for size_key, (w, h) in THUMBNAIL_SIZES.items():
output_path = f'{base_path}_{w}x{h}.{thumbnail_ext}' output_path = f'{base_path}_{w}x{h}.{thumbnail_ext}'
size = (w, h) size = (w, h)
with tempfile.TemporaryFile() as f: with tempfile.TemporaryFile() as f:
@ -255,7 +255,7 @@ def make_thumbnails(file_path: str, file_hash: str, output_format: str = THUMBNA
logger.warning('%s exists, overwriting', output_path) logger.warning('%s exists, overwriting', output_path)
default_storage.delete(output_path) default_storage.delete(output_path)
default_storage.save(output_path, f) default_storage.save(output_path, f)
thumbnails.append({'size': size, 'path': output_path}) thumbnails[size_key] = {'size': size, 'path': output_path}
return thumbnails return thumbnails