Thumbnails for images and videos #87
@ -105,8 +105,7 @@ ABUSE_TYPE = Choices(
|
||||
# thumbnails of existing images must exist in MEDIA_ROOT before
|
||||
# the code expecting the new dimensions can be deployed!
|
||||
THUMBNAIL_SIZE_DEFAULT = (1920, 1080)
|
||||
THUMBNAIL_SIZE_M = (1280, 720)
|
||||
THUMBNAIL_SIZE_S = (640, 360)
|
||||
THUMBNAIL_SIZES = [THUMBNAIL_SIZE_DEFAULT, THUMBNAIL_SIZE_M, THUMBNAIL_SIZE_S]
|
||||
THUMBNAIL_SIZES = [THUMBNAIL_SIZE_DEFAULT, THUMBNAIL_SIZE_S]
|
||||
THUMBNAIL_FORMAT = 'PNG'
|
||||
THUMBNAIL_QUALITY = 83
|
||||
|
@ -1,7 +1,8 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
from django.contrib.admin.models import LogEntry, DELETION
|
||||
from django.test import TestCase # , TransactionTestCase
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from common.tests.factories.extensions import create_approved_version, create_version
|
||||
from common.tests.factories.files import FileFactory
|
||||
@ -10,7 +11,10 @@ import extensions.models
|
||||
import files.models
|
||||
import reviewers.models
|
||||
|
||||
TEST_MEDIA_DIR = Path(__file__).resolve().parent / 'media'
|
||||
|
||||
|
||||
@override_settings(MEDIA_ROOT=TEST_MEDIA_DIR)
|
||||
class DeleteTest(TestCase):
|
||||
fixtures = ['dev', 'licenses']
|
||||
|
||||
|
@ -21,7 +21,7 @@ def scan_selected_files(self, request, queryset):
|
||||
|
||||
def make_thumbnails(self, request, queryset):
|
||||
"""Make thumbnails for selected files."""
|
||||
for instance in queryset:
|
||||
for instance in queryset.filter(type__in=(File.TYPES.IMAGE, File.TYPE.VIDEO)):
|
||||
files.tasks.make_thumbnails.task_function(file_id=instance.pk)
|
||||
|
||||
|
||||
@ -44,6 +44,8 @@ class FileAdmin(admin.ModelAdmin):
|
||||
css = {'all': ('files/admin/file.css',)}
|
||||
|
||||
def thumbnails(self, obj):
|
||||
if not obj or not (obj.is_image or obj.is_video):
|
||||
return ''
|
||||
try:
|
||||
context = {
|
||||
'MEDIA_URL': settings.MEDIA_URL,
|
||||
@ -58,6 +60,14 @@ class FileAdmin(admin.ModelAdmin):
|
||||
logger.exception('Failed to render thumbnails')
|
||||
raise
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
# Only override if the obj exisits
|
||||
if obj:
|
||||
if obj.is_image or obj.is_video:
|
||||
help_text = 'Additional information about the file, e.g. existing thumbnails.'
|
||||
kwargs.update({'help_texts': {'metadata': help_text}})
|
||||
return super().get_form(request, obj, **kwargs)
|
||||
|
||||
view_on_site = False
|
||||
save_on_top = True
|
||||
|
||||
@ -79,8 +89,8 @@ class FileAdmin(admin.ModelAdmin):
|
||||
'date_approved',
|
||||
'date_status_changed',
|
||||
'size_bytes',
|
||||
'thumbnail',
|
||||
'thumbnails',
|
||||
'thumbnail',
|
||||
'type',
|
||||
'user',
|
||||
'original_hash',
|
||||
@ -101,7 +111,7 @@ class FileAdmin(admin.ModelAdmin):
|
||||
{
|
||||
'fields': (
|
||||
'id',
|
||||
('source', 'thumbnail', 'thumbnails'),
|
||||
('source', 'thumbnails', 'thumbnail'),
|
||||
('type', 'content_type', 'original_name'),
|
||||
'status',
|
||||
)
|
||||
|
@ -51,7 +51,7 @@ def thumbnail_upload_to(instance, filename):
|
||||
|
||||
|
||||
class File(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
||||
track_changes_to_fields = {'status', 'size_bytes', 'hash', 'thumbnail'}
|
||||
track_changes_to_fields = {'status', 'size_bytes', 'hash', 'thumbnail', 'metadata'}
|
||||
|
||||
TYPES = FILE_TYPE_CHOICES
|
||||
STATUSES = FILE_STATUS_CHOICES
|
||||
@ -100,7 +100,7 @@ class File(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
||||
default=dict,
|
||||
blank=True,
|
||||
# TODO add link to the manifest file user manual page.
|
||||
help_text=('Meta information that was parsed from the `manifest file.'),
|
||||
help_text=('Meta information that was parsed from the manifest file.'),
|
||||
)
|
||||
|
||||
objects = FileManager()
|
||||
|
@ -38,11 +38,10 @@ def make_thumbnails(file_id: int) -> None:
|
||||
|
||||
# TODO: For an image, source of the thumbnail is file.source itself
|
||||
if file.is_image:
|
||||
image_field = file.thumbnail
|
||||
if not image_field:
|
||||
thumbnail_field = file.thumbnail
|
||||
if not thumbnail_field:
|
||||
# Use the source image if max-size thumbnail is not yet available
|
||||
image_field.name = file.source.name
|
||||
# base_path = files.utils.get_base_path(image_field.name)
|
||||
thumbnail_field.name = file.source.name
|
||||
thumbnails_paths = file.thumbnails
|
||||
files.utils.make_thumbnails(
|
||||
abs_path,
|
||||
@ -52,8 +51,18 @@ def make_thumbnails(file_id: int) -> None:
|
||||
optimize=True,
|
||||
progressive=True,
|
||||
)
|
||||
image_field.name = thumbnails_paths[THUMBNAIL_SIZE_DEFAULT]
|
||||
file.save(update_fields={'thumbnail'})
|
||||
thumbnail_default_path = thumbnails_paths[THUMBNAIL_SIZE_DEFAULT]
|
||||
# Reverse to make JSON-serialisable
|
||||
thumbnail_metadata = {path: size for size, path in thumbnails_paths.items()}
|
||||
update_fields = {}
|
||||
if thumbnail_default_path != thumbnail_field.name:
|
||||
thumbnail_field.name = thumbnail_default_path
|
||||
update_fields.add('thumbnail')
|
||||
if file.metadata.get('thumbnails') != thumbnail_metadata:
|
||||
file.metadata.update({'thumbnails': thumbnail_metadata})
|
||||
update_fields.add('metadata')
|
||||
if update_fields:
|
||||
file.save(update_fields=update_fields)
|
||||
# TODO: For a video, source of the thumbnail is some frame fetched with ffpeg
|
||||
elif file.is_video:
|
||||
raise NotImplementedError
|
||||
|
@ -42,9 +42,11 @@ class FileTest(TestCase):
|
||||
'new_state': {'status': 'Approved'},
|
||||
'object': '<File: test.zip (Approved)>',
|
||||
'old_state': {
|
||||
'status': 2,
|
||||
'hash': 'foobar',
|
||||
'metadata': {},
|
||||
'size_bytes': 7149,
|
||||
'status': 2,
|
||||
'thumbnail': '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user