import io import os from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage import pillow_heif from PIL import Image, ImageSequence pillow_heif.register_heif_opener() from storages.backends.s3boto3 import S3Boto3Storage, S3StaticStorage # All formats Pillow handles natively (no extra plugins needed). # Add '.heic' / '.heif' if you install pillow-heif. _IMAGE_EXTS = { '.jpg', '.jpeg', '.jpe', '.jfif', # JPEG variants '.png', # PNG '.gif', # GIF (animated preserved) '.bmp', '.dib', # BMP '.tiff', '.tif', # TIFF '.tga', # Truevision TGA '.ico', # ICO (largest frame used) '.ppm', '.pgm', '.pbm', '.pnm', # Portable pixmap family '.pcx', # PCX '.heic', '.heif', # Apple HEIC/HEIF (pillow-heif) } def _to_webp(content, quality: int = 85) -> io.BytesIO: img = Image.open(content) frames = list(ImageSequence.Iterator(img)) out = io.BytesIO() if len(frames) > 1: converted = [f.copy().convert('RGBA') for f in frames] converted[0].save( out, format='WEBP', save_all=True, append_images=converted[1:], loop=img.info.get('loop', 0), quality=quality, ) else: img.convert('RGBA').save(out, format='WEBP', quality=quality) out.seek(0) return out class WebPConversionMixin: """ Intercepts image/GIF uploads and converts them to WebP before storage. Videos are saved as-is; use `vontor_cz.tasks.convert_video_to_webm` async to transcode them to WebM after save. """ WEBP_QUALITY: int = 85 def _save(self, name: str, content) -> str: root, ext = os.path.splitext(name) if ext.lower() in _IMAGE_EXTS: try: webp_data = _to_webp(content, self.WEBP_QUALITY) content = ContentFile(webp_data.read()) name = root + '.webp' except Exception: content.seek(0) return super()._save(name, content) class MediaStorage(WebPConversionMixin, S3Boto3Storage): def exists(self, name): if not name: return False return super().exists(name) class LocalMediaStorage(WebPConversionMixin, FileSystemStorage): pass class StaticStorage(S3StaticStorage): def exists(self, name): if not name: return False return super().exists(name)