1e51ef7def
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/ - 12 code-workspace-Files - STATUS-*.md - viele M/D/R-Änderungen an bereits getrackten Files - .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
89 lines
4.9 KiB
Python
89 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""V2: Erweiterte Palette für Lebensmittel-Naturfarben.
|
|
Stil bleibt flat-vector, aber Lebensmittel dürfen ihre echten Farben tragen.
|
|
Aufruf: python generate-dish-images-v2.py [<dish_id> ...]
|
|
"""
|
|
import json, os, subprocess, sys, tempfile, base64
|
|
from pathlib import Path
|
|
|
|
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
|
|
|
|
OUT_DIR = Path('C:/xampp/htdocs/geograsim/App/sims/weltkueche/assets/img/dishes')
|
|
ORIG_DIR = OUT_DIR / '_original_v2'
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
ORIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Plattform-Palette (Plate + Hintergrund) + erweiterte Lebensmittel-Palette
|
|
PROMPT_BASE = (
|
|
"CRITICAL STYLE: flat vector illustration, Scandinavian poster art style, "
|
|
"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 — like a children's textbook illustration. "
|
|
"BACKGROUND + PLATE: stays in the brand palette — dark forest green #1f4b37 plate rim, beige #e8e4d8 table surface, sage green #4a7c4e accents. "
|
|
"FOOD COLORS: use realistic but flat-illustrated food tones — "
|
|
"yellow egg yolk #f5c63a, orange carrot #d97a2c, red tomato #c44a35, brown bread/meat #a87253, "
|
|
"warm yellow potato/dough #e8c547, white rice/dairy #fafaf6, fresh green herbs #4a7c4e. "
|
|
"All shapes stay simple, geometric, flat — like cut paper. No textures, no glossy highlights, no shadows under food. "
|
|
"COMPOSITION: a single round plate or bowl centered, viewed top-down or slightly tilted, filling about 75% of the square frame. "
|
|
"FORBIDDEN: text, letters, numbers, watermarks, logos, borders, frames, photo-realistic lighting, lens-flare, "
|
|
"people faces, hands, brand names, realistic skin texture on food."
|
|
)
|
|
|
|
DISHES = {
|
|
'tiroler-groestl': "Top-down view of a dark green ceramic plate. ON the plate: cubed warm-yellow #e8c547 potato pieces in a heap, scattered with small brown #a87253 bacon bits, topped CENTRED with ONE flat fried egg — the egg WHITE is white #fafaf6, the YOLK is bright yellow #f5c63a as a clean round circle in the middle. Fresh sage-green chive bits sprinkled on top. NO green egg.",
|
|
|
|
'wachauer-marillenknoedel': "Top-down view of a dark green ceramic plate. ON the plate: 3 round dumplings shown as warm-beige #e8c547 spheres dusted with white #fafaf6 powdered sugar. ONE dumpling is cut in half to reveal a BRIGHT ORANGE #d97a2c apricot heart inside. A tiny sage-green mint leaf as garnish at the edge.",
|
|
|
|
'steirisches-wurzelfleisch': "Top-down view of a dark green ceramic bowl. INSIDE the bowl: chunks of brown #a87253 boiled beef with ORANGE #d97a2c carrot rounds and warm-beige parsley root pieces in a clear beige-tinted broth. Crowned with a small mound of pure WHITE #fafaf6 grated horseradish in the center. Fresh green chive bits sprinkled."
|
|
}
|
|
|
|
def generate(dish_id, scene):
|
|
out_png = ORIG_DIR / f'{dish_id}.png'
|
|
out_webp = OUT_DIR / f'{dish_id}-v2.webp'
|
|
prompt = PROMPT_BASE + "\n\nSCENE: " + 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
|
|
try:
|
|
from PIL import Image
|
|
img = Image.open(out_png).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} v2 ...', flush=True)
|
|
ok = generate(d, DISHES[d])
|
|
print(f' {"OK" if ok else "FAIL"}: {d}-v2.webp')
|