#!/usr/bin/env python3 """ Relaunch: verkleinert die Rohexporte der Kundenportal-Screenshots für die Website. docs/relaunch/assets/portal/*.webp (Rohdateien, doppelte Auflösung) -> public/relaunch/portal/-.webp Aufruf (aus dem Projektverzeichnis): python3 scripts/relaunch-bilder.py Nur die Bilder, die der Abschnitt "Kundenportal" (Spezifikation 7.2) wirklich zeigt, landen in public/. Format und Budgets laut Spezifikation 9.5: - WebP, Qualität etwa 80, Desktop-Bilder in 800 / 1200 / 1600 px Breite, Handy-Bilder in 390 / 780 px - ein einzelnes Desktop-Bild höchstens 120 KB, ein einzelnes Handy-Bild höchstens 60 KB (der lange Screenshot hat ein eigenes, größeres Budget, weil er mehr als dreimal so hoch ist) - wird ein Budget überschritten, sinkt die Qualität pro Datei schrittweise (nicht unter MIN_QUALITAET) Braucht Python 3 und Pillow mit WebP-Unterstützung. """ from pathlib import Path import sys from PIL import Image WURZEL = Path(__file__).resolve().parent.parent QUELLE = WURZEL / "docs" / "relaunch" / "assets" / "portal" ZIEL = WURZEL / "public" / "relaunch" / "portal" QUALITAET = 80 MIN_QUALITAET = 46 SCHRITT = 4 KB = 1024 BUDGET_DESKTOP = 120 * KB BUDGET_HANDY = 60 * KB # Der lange Screenshot ist gut dreimal so hoch wie ein Handy-Bild (5131 statt 1748 px). Sein Budget # wächst mit der Höhe (je Bildschirmhöhe weiterhin unter 60 KB), sonst wäre der Text nur noch Matsch. BUDGET_LANG = {390: 80 * KB, 780: 150 * KB} # Unter der letzten Inhaltszeile (Zeile 5091) ist der Rohexport nur noch schwarz. Abgeschnitten wird # 40 px darunter, sonst endet die Verschiebung im Handy-Rahmen auf einem leeren Streifen. LANG_HOEHE = 5131 # Dateiname (ohne .webp) -> (Art, Zielbreiten) BILDER = { # Desktop-Fenster (Roh 3024 x 1786 = MacBook-Safari-Fläche 1512 x 893 @2x) "desktop-1-projekte": ("desktop", (800, 1200, 1600)), "desktop-2-fortschritt": ("desktop", (800, 1200, 1600)), "desktop-3b-chat-foto": ("desktop", (800, 1200, 1600)), "desktop-4-rechnungen": ("desktop", (800, 1200, 1600)), "desktop-7-news-pinnwand": ("desktop", (800, 1200, 1600)), # Handy (Roh 804 x 1748 = iPhone 16 Pro @2x, Safe Area 59/34; das lange Bild 804 x 5340, beschnitten auf 5131) "phone-1-projekte": ("handy", (390, 780)), "phone-3-projektdetails": ("handy", (390, 780)), "phone-4b-chat-foto": ("handy", (390, 780)), "phone-5-rechnungen": ("handy", (390, 780)), "phone-8-news-vorschau": ("handy", (390, 780)), # langer Screenshot: behält sein hohes Seitenverhältnis (eigenes Budget, siehe BUDGET_LANG) "phone-2-projekte-lang": ("handy", (390, 780)), } def speichern(bild: Image.Image, ziel: Path, budget: int) -> tuple[int, int]: """Speichert als WebP; senkt die Qualität, bis das Budget passt. Gibt (Bytes, Qualität) zurück.""" qualitaet = QUALITAET while True: bild.save(ziel, "WEBP", quality=qualitaet, method=6) groesse = ziel.stat().st_size if groesse <= budget or qualitaet - SCHRITT < MIN_QUALITAET: return groesse, qualitaet qualitaet -= SCHRITT def main() -> int: ZIEL.mkdir(parents=True, exist_ok=True) # alte Ausgaben entfernen, damit nichts Verwaistes liegen bleibt for alt in ZIEL.glob("*.webp"): alt.unlink() fehler = 0 summe = 0 print(f"{'Datei':44} {'Pixel':>11} {'KB':>7} {'Q':>3} Budget") for name, (art, breiten) in BILDER.items(): quelle = QUELLE / f"{name}.webp" if not quelle.exists(): print(f"FEHLT: {quelle}", file=sys.stderr) fehler += 1 continue roh = Image.open(quelle).convert("RGB") if name.endswith("-lang"): roh = roh.crop((0, 0, roh.width, LANG_HOEHE)) for breite in breiten: if name.endswith("-lang"): budget = BUDGET_LANG[breite] else: budget = BUDGET_DESKTOP if art == "desktop" else BUDGET_HANDY if breite > roh.width: print(f"übersprungen: {name} {breite} (Rohbild nur {roh.width} px breit)") continue hoehe = round(roh.height * breite / roh.width) klein = roh if breite == roh.width else roh.resize((breite, hoehe), Image.LANCZOS) datei = ZIEL / f"{name}-{breite}.webp" groesse, qualitaet = speichern(klein, datei, budget) summe += groesse ok = "ok" if groesse <= budget else f"ÜBER {budget // KB} KB" print(f"{datei.name:44} {breite:>5}x{hoehe:<5} {groesse / KB:7.1f} {qualitaet:>3} {ok}") print(f"Summe aller Dateien: {summe / KB:.0f} KB") return 1 if fehler else 0 if __name__ == "__main__": sys.exit(main())