From ff334be4c43f03d5c7fa60a8efc7f6a11c5bdb70 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 28 Jun 2026 22:51:36 +0200 Subject: [PATCH] fix(nanobanana): Aspect-Ratio-Bug doppelt behoben MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bug #1: img.resize((1920,1080)) war hartkodiert → auf _aspect_to_size() umgestellt - Bug #2: imageConfig.aspectRatio fehlte im Gemini-API-Call → ergänzt Alle Aspect-Ratios (1:1, 2:3, 9:16, 21:9 etc.) werden jetzt korrekt gerendert. --- article_server.py | 133 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/article_server.py b/article_server.py index 7a6c0e8..bc5e725 100644 --- a/article_server.py +++ b/article_server.py @@ -30,6 +30,90 @@ ACCESS_TOKEN = "br3ttsp1el-n3ws-2026" USERNAME = "DK-Adminchef" PASSWORD = "Mcik7%vbdhXa_" +# ── BSN Article CSS (aus Joomla bsn-custom.css) ─────────────────────────── +# Wird in Artikel-HTML injiziert, damit bsn-Klassen auch auf dem +# Article-Server gerendert werden (nicht nur im Joomla-Template). +BSN_ARTICLE_CSS = """ +/* ── Basis-Typografie (Article-Server) ── */ +body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.7; color: #3B3B3C; background: #fff; max-width: 720px; margin: 0 auto; padding: 2rem 1.5rem; -webkit-font-smoothing: antialiased; } +article { display: block; } +article h1, article h1.title { font-size: 2rem; font-weight: 800; color: #171D4F; margin: 0 0 0.5rem 0; line-height: 1.2; letter-spacing: -0.02em; } +article h2 { font-size: 1.4rem; font-weight: 700; color: #171D4F; margin: 2rem 0 0.75rem; } +article h3 { font-size: 1.15rem; font-weight: 700; color: #20228A; margin: 1.5rem 0 0.5rem; } +article p { margin: 0 0 1rem 0; } +article a { color: #20228A; text-decoration: underline; text-underline-offset: 2px; } +article a:hover { color: #161e6e; } +article ul, article ol { margin: 0.5rem 0 1rem 1.5rem; } +article li { margin-bottom: 0.4rem; } +article img { max-width: 100%; height: auto; border-radius: 8px; margin: 1.5rem 0; } +article hr { border: none; border-top: 1px solid #e0e0e0; margin: 2rem 0; } + +/* ── Meta / Dachzeile ── */ +.bsn-meta, .meta, p.meta { font-size: 0.85rem; color: #999; margin-bottom: 1.5rem; } +.bsn-dachzeile, .dachzeile, p.dachzeile { font-size: 1.05rem; color: #555; margin-bottom: 1.5rem; font-weight: 500; } + +/* ── Infobox ── */ +.bsn-infobox { background: #f2f2f2; border: 1px solid #d0d8e0; border-left: 4px solid #20228A; padding: 1rem 1.5rem; margin: 1.5rem 0; border-radius: 16px; font-size: 0.95rem; } +.bsn-infobox h3 { margin-top: 0; color: #20228A; font-family: Arial, Helvetica, sans-serif; font-size: 1.15rem; } +.bsn-infobox table { width: 100%; border-collapse: collapse; } +.bsn-infobox td { padding: 0.3rem 0.5rem; border-bottom: 1px solid #dce4ec; } +.bsn-infobox tr:last-child td { border-bottom: none; } +.bsn-infobox td:first-child { font-weight: 600; color: #171D4F; width: 160px; white-space: nowrap; } + +/* ── Tabellen ── */ +.bsn-table { width: 100%; border-collapse: collapse; margin: 1rem 0; } +.bsn-table td { padding: 0.5rem 0.8rem; border-bottom: 1px solid #e8e8f0; } +.bsn-table tr:last-child td { border-bottom: none; } +.bsn-table td:first-child { font-weight: 600; color: #171D4F; width: 30%; } + +/* ── Gamecard ── */ +.bsn-gamecard { background: #f2f2f2; border: 1px solid #e0e3e8; border-left: 4px solid #20228A; padding: 1rem 1.5rem; margin: 1.2rem 0; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease; } +.bsn-gamecard h3 { margin-top: 0; color: #171D4F; font-family: Arial, Helvetica, sans-serif; font-size: 1.15rem; } +.bsn-gamecard .game-meta { font-size: 0.85rem; color: #888; margin-bottom: 0.5rem; } +@media (hover: hover) { .bsn-gamecard:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(32,34,138,0.12); border-color: #c8cde0; } } + +/* ── Quellen / Recherche ── */ +.bsn-quellen { margin-top: 2rem; font-size: 0.9rem; } +.bsn-quellen h3 { color: #171D4F; font-family: Arial, Helvetica, sans-serif; font-size: 1.1rem; margin-bottom: 0.5rem; } +.bsn-quellen ul { padding-left: 1.3rem; margin: 0; } +.bsn-quellen li { margin-bottom: 0.3rem; } +.bsn-recherche, .recherche, p.bsn-recherche { margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid #ddd; font-size: 0.9rem; color: #777; } + +/* ── Blockquote ── */ +blockquote { border-left: 4px solid #20228A; background: #f8f9ff; padding: 0.8rem 1.2rem; margin: 1.2rem 0; color: #3B3B3C; font-style: italic; border-radius: 0 8px 8px 0; } +blockquote p { margin-bottom: 0.25rem; } + +/* ── System-Readmore (Joomla) ── */ +hr#system-readmore { border: none; border-top: 2px dashed #e0e0e0; margin: 1.5rem 0; } + +/* ── Quellen (alte Klasse) ── */ +.quellen { margin-top: 2rem; font-size: 0.9rem; } +.quellen h3, .quellen strong { color: #171D4F; font-size: 1.1rem; display: block; margin-bottom: 0.5rem; } +.quellen ol, .quellen ul { padding-left: 1.3rem; margin: 0.5rem 0; } +.quellen li { margin-bottom: 0.3rem; } + +@media (prefers-color-scheme: dark) { + body { background: #1a1a2e; color: #c8c8d8; } + article h1, article h2 { color: #e0e0f0; } + article h3 { color: #6b6bcc; } + .bsn-infobox, .bsn-gamecard { background: #252530; border-color: #3a3a45; } + .bsn-infobox td, .bsn-table td { border-bottom-color: #3a3a45; } + .bsn-infobox td:first-child, .bsn-table td:first-child { color: #a0a0c0; } + .bsn-infobox h3 { color: #6b6bcc; } + .bsn-gamecard h3 { color: #c8c8d8; } + .bsn-gamecard .game-meta { color: #888; } + .bsn-quellen h3, .quellen h3, .quellen strong { color: #c8c8d8; } + .bsn-quellen a, .quellen a { color: #6b6bcc; } + .bsn-meta, .meta, p.meta { color: #777; } + .bsn-recherche, .recherche, p.bsn-recherche { color: #888; border-top-color: #3a3a45; } + .bsn-dachzeile, .dachzeile, p.dachzeile { color: #999; border-bottom-color: #3a3a45; } + .bsn-gamecard:hover { box-shadow: 0 6px 20px rgba(100,100,200,0.15); border-color: #505068; } + blockquote { background: #252530; border-left-color: #6b6bcc; color: #c0c0d0; } + hr#system-readmore { border-top-color: #3a3a45; } + hr { border-top-color: #3a3a45; } +} +""" + # ── Joomla API Configuration ────────────────────────────────────────────── JOOMLA_API_URL = "https://www.brettspiel-news.de/api/index.php/v1/content/articles" # JOOMLA_TOKEN is read from environment or from a token file @@ -140,7 +224,10 @@ def _gemini_generate_image( body = json.dumps({ "contents": [{"parts": parts}], "generationConfig": { - "responseModalities": ["image", "text"] + "responseModalities": ["image", "text"], + "imageConfig": { + "aspectRatio": aspect_ratio + } } }).encode() @@ -188,7 +275,7 @@ SKIP_PATTERNS = [ r'^envyborn', r'^brettspiel_news_newsletter', r'^boardgamewire', r'^ddg_', r'^blocked', r'^error', r'^just.a.moment', r'^duckduckgo', r'^update.regarding', - r'deals', r'kennerspiele', r'kombi_deals', r'top_deals', + r'kennerspiele_deals', r'kombi_deals', r'top_deals', r'kinderspiele_deals', r'amazon_deals', r'amazon_primeday', ] @@ -943,6 +1030,17 @@ h1{color:#c0392b;}p{color:#888;} if os.path.isfile(filepath) and filepath.startswith(WORKSPACE): with open(filepath, "rb") as f: data = f.read() + if filepath.endswith(".html"): + # BSN-CSS in Artikel injizieren (vor ) + html_str = data.decode("utf-8", errors="replace") + css_block = f"" + if "" in html_str: + html_str = html_str.replace("", f"{css_block}\n", 1) + elif "\n{css_block}{html_str}" + data = html_str.encode("utf-8") self.send_response(200) self.send_header("Content-Length", str(len(data))) if filepath.endswith(".html"): @@ -1085,12 +1183,39 @@ h1{color:#c0392b;}p{color:#888;} with open(filepath, "wb") as f: f.write(img_bytes) - # Optional: PIL-Konvertierung zu JPEG (1920x1080 @ 72 DPI) + # Optional: PIL-Konvertierung zu JPEG (aspect-ratio-bewusst) try: from PIL import Image + + # Zielauflösung aus Aspect-Ratio berechnen + def _aspect_to_size(ratio_str: str, base: int = 1920) -> tuple[int, int]: + """Aspect-Ratio-String in Ziel-Pixelmaße umrechnen.""" + mapping = { + "16:9": (1920, 1080), + "1:1": (1080, 1080), + "2:3": (720, 1080), + "3:2": (1620, 1080), + "3:4": (810, 1080), + "4:3": (1440, 1080), + "4:5": (864, 1080), + "5:4": (1350, 1080), + "9:16": (607, 1080), + "21:9": (1920, 823), + } + # Fallback: selbst parsen + try: + w, h = ratio_str.split(":") + w_ratio, h_ratio = int(w), int(h) + # Immer Höhe = 1080, Breite proportional + width = int(1080 * w_ratio / h_ratio) + return mapping.get(ratio_str, (width, 1080)) + except (ValueError, ZeroDivisionError): + return (1920, 1080) + + target_w, target_h = _aspect_to_size(aspect_ratio) img = Image.open(filepath) img = img.convert("RGB") - img = img.resize((1920, 1080), Image.LANCZOS) + img = img.resize((target_w, target_h), Image.LANCZOS) jpg_path = os.path.join(month_dir, base_name + ".jpg") img.save(jpg_path, "JPEG", dpi=(72, 72), quality=92) filepath = jpg_path