#!/usr/bin/env python3 """ Relaunch: Fotos der erfundenen Beispiel-Website „Bäckerei Sonnenschein“ (Station Live-Vorschau und Chat-Demo). Alle Fotos stammen von Wikimedia Commons und stehen unter CC0 (gemeinfrei, keine Namensnennung nötig). Es sind nur Backwaren zu sehen, keine Menschen. Quellen: docs/relaunch/assets/baeckerei/QUELLEN.md. Wikimedia Commons (einmalig, Zwischenspeicher docs/relaunch/assets/baeckerei/roh/, nicht im Repository) -> public/relaunch/demos/baeckerei/-.webp Aufruf (aus dem Projektverzeichnis): python3 scripts/relaunch-baeckerei-fotos.py Braucht Python 3 und Pillow mit WebP-Unterstützung. """ from pathlib import Path import sys import urllib.parse import urllib.request from PIL import Image WURZEL = Path(__file__).resolve().parent.parent ROH = WURZEL / "docs" / "relaunch" / "assets" / "baeckerei" / "roh" ZIEL = WURZEL / "public" / "relaunch" / "demos" / "baeckerei" KENNUNG = {"User-Agent": "WEBklar-relaunch/1.0 (support@webklar.com)"} KB = 1024 QUALITAET = 78 MIN_QUALITAET = 50 # Name -> (Datei auf Commons, Ausschnitt als Anteile (links, oben, rechts, unten), Zielbreiten, Seitenverhältnis B/H, Budget je Datei) FOTOS = { # Startseite und Chat-Foto: dasselbe Roggenbrot (Marlene schickt es im Chat, danach steht es auf der Website) "roggenbrot": ("Rye bread Roggenbengel 2025-02-14 13.jpg", (0.0, 0.0, 1.0, 1.0), (600,), 1.08, 58 * KB), "roggenbrot-anschnitt": ("Cut rye bread Kartoffelkruste 2025-02-20 02.jpg", (0.12, 0.12, 0.92, 0.95), (400,), 0.8, 30 * KB), "broetchen": ("Bread-rolls.jpg", (0.0, 0.0, 1.0, 1.0), (400,), 0.8, 30 * KB), "croissants": ("Freshippo croissant bread.jpg", (0.05, 0.0, 0.95, 1.0), (400,), 0.8, 34 * KB), "zimtschnecken": ("IKEA cinnamon bun.jpg", (0.05, 0.0, 0.95, 1.0), (400,), 0.8, 30 * KB), "baguettes": ("Baguette-246424.jpg", (0.0, 0.0, 1.0, 1.0), (700,), 1.2, 40 * KB), "zwetschgenkuchen": ("Plum cake, Ehrenbach.jpg", (0.18, 0.08, 1.0, 1.0), (520,), 1.0, 50 * KB), } def lade(datei: str) -> Path: """Lädt das Foto einmal in 2000 px Breite von Commons (Special:FilePath) und hält es im Zwischenspeicher.""" ROH.mkdir(parents=True, exist_ok=True) ziel = ROH / datei.replace(" ", "_") if not ziel.exists(): url = "https://commons.wikimedia.org/w/index.php?title=Special:FilePath/" + urllib.parse.quote(datei) + "&width=2000" with urllib.request.urlopen(urllib.request.Request(url, headers=KENNUNG), timeout=60) as antwort: ziel.write_bytes(antwort.read()) return ziel def zuschneiden(bild: Image.Image, anteile: tuple, verhaeltnis: float) -> Image.Image: """Schneidet erst den Ausschnitt aus, dann mittig auf das Seitenverhältnis.""" b, h = bild.size l, o, r, u = anteile bild = bild.crop((round(l * b), round(o * h), round(r * b), round(u * h))) b, h = bild.size if b / h > verhaeltnis: neu = round(h * verhaeltnis) return bild.crop(((b - neu) // 2, 0, (b - neu) // 2 + neu, h)) neu = round(b / verhaeltnis) return bild.crop((0, (h - neu) // 2, b, (h - neu) // 2 + neu)) def speichern(bild: Image.Image, ziel: Path, budget: int) -> tuple[int, int]: qualitaet = QUALITAET while True: bild.save(ziel, "WEBP", quality=qualitaet, method=6) groesse = ziel.stat().st_size if groesse <= budget or qualitaet - 4 < MIN_QUALITAET: return groesse, qualitaet qualitaet -= 4 def main() -> int: ZIEL.mkdir(parents=True, exist_ok=True) for alt in ZIEL.glob("*.webp"): alt.unlink() summe = 0 for name, (datei, anteile, breiten, verhaeltnis, budget) in FOTOS.items(): roh = zuschneiden(Image.open(lade(datei)).convert("RGB"), anteile, verhaeltnis) for breite in breiten: klein = roh.resize((breite, round(breite / verhaeltnis)), Image.LANCZOS) groesse, q = speichern(klein, ZIEL / f"{name}-{breite}.webp", budget) summe += groesse print(f"{name}-{breite}.webp {klein.size[0]}x{klein.size[1]} {groesse / KB:.1f} KB q{q}") print(f"Summe: {summe / KB:.0f} KB") return 0 if __name__ == "__main__": sys.exit(main())