Files
mobea/generator/images.py
T
Frank Schwenk 8abd7ddb59 feat: Archiv-Backfill, responsive Bilder und Site-Meta
Ergänzt Backfill- und Hilfsskripte, erzeugt nach der Pixazo-Generierung
komprimierte srcset-Varianten, validiert leere Bilder und fügt Favicon,
Web Manifest sowie Open-Graph-/Twitter-Tags in die Templates ein.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 18:14:40 +02:00

91 lines
3.1 KiB
Python

"""Responsive JPEG variants for hero images."""
from __future__ import annotations
import re
from pathlib import Path
from PIL import Image
VARIANT_WIDTHS = (360, 560, 800)
JPEG_QUALITY = 82
DATE_IMAGE_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})\.jpg$")
def image_public_path(date_iso: str, width: int | None = None) -> str:
if width is None:
return f"/img/{date_iso}.jpg"
return f"/img/{date_iso}-{width}w.jpg"
def build_srcset(date_iso: str, widths: list[int], full_width: int) -> str:
parts = [f"{image_public_path(date_iso, w)} {w}w" for w in widths]
parts.append(f"{image_public_path(date_iso)} {full_width}w")
return ", ".join(parts)
def create_responsive_variants(img_dir: Path, date_iso: str) -> dict[str, str] | None:
source = img_dir / f"{date_iso}.jpg"
if not source.exists():
return None
with Image.open(source) as im:
im = im.convert("RGB")
full_width, full_height = im.size
generated: list[int] = []
for width in VARIANT_WIDTHS:
if width >= full_width:
continue
height = round(full_height * width / full_width)
resized = im.resize((width, height), Image.Resampling.LANCZOS)
out = img_dir / f"{date_iso}-{width}w.jpg"
resized.save(out, "JPEG", quality=JPEG_QUALITY, optimize=True)
generated.append(width)
im.save(source, "JPEG", quality=JPEG_QUALITY, optimize=True)
srcset = build_srcset(date_iso, generated, full_width)
default_src = image_public_path(date_iso, 560) if 560 in generated else image_public_path(date_iso)
thumb_src = image_public_path(date_iso, 360) if 360 in generated else image_public_path(date_iso)
return {
"src": default_src,
"srcset": srcset,
"sizes": "(max-width: 768px) 100vw, 560px",
"og": image_public_path(date_iso),
"thumb_src": thumb_src,
"thumb_srcset": srcset,
"thumb_sizes": "(max-width: 640px) 50vw, 360px",
}
def load_image_descriptor(img_dir: Path, date_iso: str) -> dict[str, str] | None:
if not (img_dir / f"{date_iso}.jpg").exists():
return None
if not (img_dir / f"{date_iso}-360w.jpg").exists():
return create_responsive_variants(img_dir, date_iso)
widths = [w for w in VARIANT_WIDTHS if (img_dir / f"{date_iso}-{w}w.jpg").exists()]
with Image.open(img_dir / f"{date_iso}.jpg") as im:
full_width = im.size[0]
srcset = build_srcset(date_iso, widths, full_width)
return {
"src": image_public_path(date_iso, 560) if 560 in widths else image_public_path(date_iso),
"srcset": srcset,
"sizes": "(max-width: 768px) 100vw, 560px",
"og": image_public_path(date_iso),
"thumb_src": image_public_path(date_iso, 360) if 360 in widths else image_public_path(date_iso),
"thumb_srcset": srcset,
"thumb_sizes": "(max-width: 640px) 50vw, 360px",
}
def iter_source_images(img_dir: Path) -> list[Path]:
if not img_dir.exists():
return []
sources = []
for path in sorted(img_dir.glob("*.jpg")):
if DATE_IMAGE_RE.match(path.name):
sources.append(path)
return sources