#!/usr/bin/env python3 """Generiert Dish-Bilder (Top-down-Teller, flat scandinavian) für die Weltküche-Sim. Aufruf: python generate-dish-images.py [ ...] Ohne Argumente: alle Gerichte aus DISHES generieren. Output: App/sims/weltkueche/assets/img/dishes/.webp (512x512) Original-PNG geht in _original/-Unterordner.""" import json, os, subprocess, sys, tempfile, base64 from pathlib import Path # Key aus .env.local lesen KEY = None env_path = 'C:/xampp/htdocs/geograsim/App/.env.local' with open(env_path, 'r', encoding='utf-8') as f: for line in f: if line.startswith('OPENAI_API_KEY='): KEY = line.split('=', 1)[1].strip().strip('"').strip("'") break if not KEY: sys.exit('OPENAI_API_KEY nicht in .env.local') OUT_DIR = Path('C:/xampp/htdocs/geograsim/App/sims/weltkueche/assets/img/dishes') ORIG_DIR = OUT_DIR / '_original' OUT_DIR.mkdir(parents=True, exist_ok=True) ORIG_DIR.mkdir(parents=True, exist_ok=True) PROMPT_BASE = ("CRITICAL STYLE: flat vector illustration, children's-book / Scandinavian poster art, " "NO photography, NO 3D rendering, NO realistic shading, NO photographic textures, NO gradients. " "Style is wide-shape silhouettes with flat solid color fills, clean cut edges, simple geometric forms. " "Strict palette: dark forest green #1f4b37, sage green #4a7c4e, yellow-mustard #e8c547, beige #e8e4d8, white highlights, optional small red-orange accent #c97b5a. " "COMPOSITION: a single round plate or bowl centered, viewed top-down, filling about 80% of the square frame. " "Background is a flat beige #e8e4d8 surface with optional very subtle wood-grain hint (one or two thin sage lines). " "FORBIDDEN: text, letters, numbers, watermarks, logos, borders, frames, photo-realistic lighting, lens-flare, " "people faces, hands, brand names.") # Pro Gericht ein kurzer Szenen-Suffix mit den charakteristischen visuellen Elementen DISHES = { # === Österreich regional (5) === 'tiroler-groestl': "On the plate: roughly-cubed beige potato pieces with sage-green herb flakes, a flat-shape forest-green fried egg with a circular yellow-mustard yolk in the middle, small red-orange speck/bacon bits scattered around. Bowl rim in dark forest-green.", 'kaerntner-kasnudeln': "On the plate: 4-5 half-moon-shaped beige pasta pockets (Nudeln) arranged in a circle, each with a small sage-green mint leaf accent on top, drizzled with yellow-mustard browned butter (a few flat curved drop-shapes). Bowl rim in dark forest-green.", 'steirisches-wurzelfleisch': "On the plate: chunks of beige boiled beef with orange-toned carrot rounds and sage-green parsley root pieces in a clear broth (lightly tinted beige), topped with a small mound of bright white grated horseradish (Kren) and chopped chives in dark green. Bowl in dark forest-green.", 'vorarlberger-kaesespaetzle': "On the plate: a heap of golden yellow-mustard small irregular dumplings (Spätzle) topped with melted cheese strands, crowned by a ring of crispy beige-brown fried onions, sage-green chive accents. Bowl rim in dark forest-green.", 'wachauer-marillenknoedel': "On the plate: 3 round beige dumplings dusted with white powdered sugar and rolled in golden yellow-mustard breadcrumbs, one cut in half showing a bright orange-yellow apricot heart inside. A small sage-green mint leaf as garnish. Bowl rim in dark forest-green.", } def generate(dish_id, scene): out_png = ORIG_DIR / f'{dish_id}.png' out_webp = OUT_DIR / f'{dish_id}.webp' if out_webp.exists(): print(f' SKIP (existiert): {out_webp.name}') return True prompt = PROMPT_BASE + " SCENE: " + scene body = json.dumps({'model': 'gpt-image-2', 'prompt': prompt, 'n': 1, 'size': '1024x1024'}, ensure_ascii=False) with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', suffix='.json', delete=False) as f: f.write(body); bpath = f.name r = subprocess.run(['curl', '-sS', '-X', 'POST', 'https://api.openai.com/v1/images/generations', '-H', 'Authorization: Bearer ' + KEY, '-H', 'Content-Type: application/json', '--data-binary', '@' + bpath], capture_output=True, text=True, timeout=300) os.unlink(bpath) try: data = json.loads(r.stdout) item = data['data'][0] if 'b64_json' in item: with open(out_png, 'wb') as f: f.write(base64.b64decode(item['b64_json'])) elif 'url' in item: subprocess.run(['curl', '-sS', '-o', str(out_png), item['url']], capture_output=True, text=True, timeout=60) else: return False except Exception as e: print(f' FAIL: {r.stdout[:200] if r.stdout else e}') return False if not out_png.exists() or out_png.stat().st_size < 10000: return False # PNG → WebP 512x512 (Display) — Pillow try: from PIL import Image img = Image.open(out_png) img = img.resize((512, 512), Image.LANCZOS) img.save(out_webp, 'webp', quality=85, method=6) except Exception as e: print(f' WebP-Fail: {e}') return False return True ids = sys.argv[1:] if len(sys.argv) > 1 else list(DISHES.keys()) for d in ids: if d not in DISHES: print(f'-> {d}: UNBEKANNT') continue print(f'-> {d} ...', flush=True) ok = generate(d, DISHES[d]) print(f' {"OK" if ok else "FAIL"}: {d}.webp')