8abd7ddb59
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>
91 lines
3.1 KiB
Python
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
|