gendesign/backend/app/services/photos/thumbs.py
bot-backend 14f3ef2019
All checks were successful
Deploy / build-worker (push) Successful in 2m47s
Deploy / deploy (push) Successful in 1m20s
Deploy / changes (push) Successful in 9s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m52s
fix(week-review): backend-аудит v2 — 82 фиксов (#1660)
Co-authored-by: bot-backend <bot-backend@gendsgn.local>
Co-committed-by: bot-backend <bot-backend@gendsgn.local>
2026-06-17 17:13:38 +00:00

67 lines
2.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Thumbnail generation for DOM.РФ photos.
Strategy: original PNG/JPG ~1-3 MB downloaded by scraper to
data/raw/domrf_photos/<obj_id>/<file_id>.{png,jpg}. We additionally write a
WebP cover-thumbnail at 320×240 (~10-30 KB) next to it, suffix `_thumb.webp`.
The frontend grid uses /api/v1/photos/<obj_id>/<file_id>?size=thumb. Lightbox
opens the original DOM.РФ URL (we don't need to mirror originals).
"""
from __future__ import annotations
import logging
import os
from pathlib import Path
from PIL import Image, ImageOps
logger = logging.getLogger(__name__)
THUMB_SIZE: tuple[int, int] = (320, 240)
THUMB_QUALITY: int = 70
THUMB_SUFFIX: str = "_thumb.webp"
def thumb_path_for(src: Path) -> Path:
"""Return canonical thumbnail path next to the source file."""
return src.with_name(src.stem + THUMB_SUFFIX)
def make_thumbnail(
src: Path,
*,
size: tuple[int, int] = THUMB_SIZE,
quality: int = THUMB_QUALITY,
overwrite: bool = False,
) -> Path | None:
"""Generate a WebP cover-thumbnail next to src. Returns thumb path on success.
Skips if thumb already exists and overwrite=False. Returns None on any error
(logged) so callers can keep going with the next file.
"""
if not src.exists():
return None
dst = thumb_path_for(src)
if dst.exists() and not overwrite:
return dst
try:
with Image.open(src) as im:
im = ImageOps.exif_transpose(im)
im = im.convert("RGB")
im = ImageOps.fit(im, size, method=Image.Resampling.LANCZOS)
dst.parent.mkdir(parents=True, exist_ok=True)
# Atomic write: encode to a sibling temp file, then os.replace() so a
# crash/OOM mid-encode never leaves a truncated .webp at the canonical
# path (which dst.exists() would later treat as a valid cached thumb).
tmp = dst.with_name(f".{dst.name}.tmp")
try:
im.save(tmp, format="WEBP", quality=quality, method=4)
os.replace(tmp, dst)
except BaseException:
tmp.unlink(missing_ok=True)
raise
return dst
except Exception as e:
logger.warning("thumbnail %s failed: %s", src, e)
return None