Files
NoN-Site/nonpacks/library/thumbnails.py
T
2026-08-04 01:46:58 -05:00

61 lines
1.7 KiB
Python

"""ffmpeg thumbnail generation for gallery media (videos + gifs).
Grid previews use a small cached JPEG extracted by ffmpeg; the browser only
receives the real file when the full-screen modal opens.
"""
import shutil
import subprocess
from pathlib import Path
from django.conf import settings
FFMPEG = shutil.which('ffmpeg')
MAX_WIDTH = 480
def thumbnail_stored_path(file_index):
"""Relative stored path of the cached thumbnail for an asset file."""
return f'thumbnails/{file_index.uuid}.jpg'
def thumbnail_ready(file_index):
return (Path(settings.MEDIA_ROOT) / thumbnail_stored_path(file_index)).is_file()
def generate_thumbnail(file_index):
"""Generate (and cache) a JPEG thumbnail for a video/gif FileIndex.
Returns the relative stored path, or None when ffmpeg is unavailable or
extraction fails. Idempotent — a cached thumbnail is reused.
"""
if not FFMPEG or not file_index.stored_path:
return None
src = (Path(settings.MEDIA_ROOT) / file_index.stored_path).resolve()
if not src.is_file():
return None
out_rel = thumbnail_stored_path(file_index)
out = (Path(settings.MEDIA_ROOT) / out_rel).resolve()
if out.is_file():
return out_rel
out.parent.mkdir(parents=True, exist_ok=True)
tmp = out.with_suffix('.tmp.jpg')
cmd = [
FFMPEG, '-y', '-i', str(src),
'-frames:v', '1',
'-vf', f"scale='min({MAX_WIDTH},iw)':-2",
'-q:v', '4',
str(tmp),
]
try:
result = subprocess.run(cmd, capture_output=True, timeout=30)
except (subprocess.TimeoutExpired, OSError):
return None
if result.returncode != 0 or not tmp.is_file():
return None
tmp.replace(out)
return out_rel