Some checks failed
CI / changes (push) Successful in 7s
CI / frontend-tests (push) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (push) Successful in 1m45s
CI / openapi-codegen-check (pull_request) Successful in 1m43s
CI / backend-tests (push) Failing after 8m36s
CI / backend-tests (pull_request) Failing after 8m36s
Имплементация фиксов 2-го аудита backend/app/** (после merge #1543). Воркер на файл, точечные правки. Верификация: py_compile 58/58 .py. Полностью исправлено (82). Оставлены открытыми (13): partial/needs-cross-file/needs-leha — #1569, #1590, #1593, #1606, #1609, #1617, #1633, #1635, #1637, #1638, #1640, #1642, #1650. Closes #1560 Closes #1561 Closes #1562 Closes #1563 Closes #1564 Closes #1565 Closes #1566 Closes #1567 Closes #1570 Closes #1571 Closes #1572 Closes #1573 Closes #1574 Closes #1576 Closes #1577 Closes #1578 Closes #1579 Closes #1580 Closes #1581 Closes #1582 Closes #1583 Closes #1584 Closes #1585 Closes #1586 Closes #1587 Closes #1588 Closes #1589 Closes #1591 Closes #1592 Closes #1594 Closes #1595 Closes #1596 Closes #1597 Closes #1598 Closes #1599 Closes #1600 Closes #1601 Closes #1602 Closes #1603 Closes #1604 Closes #1605 Closes #1607 Closes #1608 Closes #1610 Closes #1611 Closes #1612 Closes #1613 Closes #1614 Closes #1615 Closes #1616 Closes #1618 Closes #1619 Closes #1620 Closes #1621 Closes #1622 Closes #1623 Closes #1624 Closes #1625 Closes #1626 Closes #1627 Closes #1628 Closes #1629 Closes #1630 Closes #1631 Closes #1632 Closes #1634 Closes #1636 Closes #1639 Closes #1641 Closes #1643 Closes #1644 Closes #1645 Closes #1646 Closes #1647 Closes #1648 Closes #1649 Closes #1651 Closes #1652 Closes #1653 Closes #1654 Closes #1655 Closes #1656
67 lines
2.2 KiB
Python
67 lines
2.2 KiB
Python
"""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
|