# This file is part of photo21 # Copyright (C) 2022 Amicale des élèves de l'ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later import logging import os from django.core.files.base import ContentFile from io import BytesIO from PIL import Image import av logger = logging.getLogger("photologue.utils") def is_photo(file_obj): """Return True if file_obj is a valid image readable by Pillow.""" try: Image.open(file_obj).verify() file_obj.seek(0) return True except Exception: file_obj.seek(0) return False def is_video(file_obj): """Return True if file_obj is a valid video container (has at least one video stream).""" try: container = av.open(file_obj) ok = len(container.streams.video) > 0 container.close() file_obj.seek(0) return ok except Exception: file_obj.seek(0) return False def generate_video_thumbnail(video): """Extract the first frame of a video file and save it as the video thumbnail.""" try: container = av.open(video.file.path) frame = next(container.decode(video=0)) img = frame.to_image() rotation = frame.rotation container.close() if rotation: img = img.rotate(rotation, expand=True) except Exception: logger.error("Failed to extract video frame for thumbnail", exc_info=True) return try: buffer = BytesIO() img.save(buffer, "JPEG", quality=70, optimize=True) # Preserve directory structure: strip the "videos/" storage prefix so that # get_video_storage_path places the thumb alongside the video file. rel = video.file.name[len("videos/"):] if video.file.name.startswith("videos/") else video.file.name thumb_name = os.path.splitext(rel)[0] + "_thumb.jpg" video.thumbnail.save(thumb_name, ContentFile(buffer.getvalue()), save=True) except Exception: logger.error("Failed to save video thumbnail", exc_info=True)