#!/usr/bin/env python3 """ Generator fuer die 42 neuen Avatar-PNGs via OpenRouter + Gemini 2.5 Flash Image. Fallback weil OpenAI-Org (gpt-image-*) wegen fehlender Verification blockiert ist. Usage: set -a; source App/.env.local; set +a python App/assets/img/avatars/scripts/generate-via-openrouter.py # FORCE=1 ueberschreibt bestehende PNGs """ import os, sys, json, time, base64, subprocess, tempfile from pathlib import Path API_KEY = os.environ.get("OPENROUTER_API_KEY") if not API_KEY: print("ERR: OPENROUTER_API_KEY fehlt — source App/.env.local !") sys.exit(1) DEST = Path(__file__).resolve().parents[1] FORCE = os.environ.get("FORCE", "") == "1" STYLE = ( "Flat Scandinavian portrait illustration, square 1:1 composition, painted style " "with clean vector shapes, dark forest green (#1f4b37) and sage green (#4a7c4e) tones, " "yellow-mustard (#e8c547) accents, beige (#e8e4d8) backgrounds, white highlights. " "Friendly character portrait, centered face, soft shoulder framing, plain calm background. " "No text, no watermark, no logo, no borders, no frame." ) # slug → szenenspezifischer Prompt-Suffix SPECS = [ ("ozeanographin", "Portrait of a young female oceanographer named Linnea, Scandinavian features, navy blue research-vessel jacket with a small whale-tail brooch, holding a clipboard."), ("tropenbiologe", "Portrait of a young male tropical biologist named Kwame, West African features, olive field shirt, brass magnifier on a lanyard, suggested green tropical leaves behind."), ("skaterin", "Portrait of a young female skater named Reni, multi-ethnic, short pink hair, beanie cap, skateboard slung over the shoulder."), ("dj", "Portrait of a young male DJ named Kofi, West African features, wireless headphones around the neck, casual yellow hoodie."), ("wolf-tundra", "Portrait of a friendly tundra wolf cub named Yuki, white-gray fur, kind blue eyes, sitting in fresh snow."), ("krake-tiefsee", "Portrait of a friendly deep-sea octopus named Otto, purple-pink skin, large curious eyes, two arms gently waving, soft underwater glow."), # Aliens — bewusst comic-/cartoon-freundlich: grosse runde Augen, weiches # Laecheln, sanfte Proportionen, freundliche kindgerechte Erscheinung. ("alien-geologin", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien geologist named Zorblax, lavender skin, three big sparkling eyes, small wiggly antennae with glowing tips, holding a sparkly quartz crystal. Cheerful, approachable, suitable for children."), ("alien-ozean", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien oceanographer named Aquana, teal-blue translucent skin, big curious eyes, tiny cute fish-like gills on the cheeks, holding a glowing water-sample tube. Cheerful, approachable."), ("alien-wetter", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien meteorologist named Tempest, fluffy cloud-gray skin, tiny smiling lightning bolts dancing around the head, holding a happy mini cloud."), ("alien-stadt", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien urban analyst named Urbanix, silver-mint skin, cute geometric markings on the cheeks like glowing pixels, holding a tiny holographic city in the palm."), ("alien-tiere", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien biologist named Faunor, soft sand-yellow fur, big floppy cat-like ears, gently holding a colorful alien butterfly. Adorable, kid-friendly."), ("alien-pflanzen", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien botanist named Floranth, leaf-green skin, tiny cheerful flowers blooming from the hair, holding a sprouting seed with a smile."), ("alien-vulkan", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien volcanologist named Magmara, warm orange-red skin, gentle glowing seams like cooling lava on the arms, holding a small smooth obsidian stone, friendly expression — not scary."), ("alien-arktis", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien glaciologist named Glacium, pale ice-blue skin, sparkly frost crystals on the shoulders, holding a small glowing ice core. Bundled in a cozy scarf."), ("alien-wueste", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien desert researcher named Solara, golden-tan skin, cute sun-disc headpiece, holding a tiny smiling cactus."), ("alien-regenwald", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien rainforest scout named Veridia, jade-green skin, vine-like hair with tiny leaves, a small colorful parrot perched on the shoulder."), ("alien-berg", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien mountaineer named Altimos, stone-gray skin with gentle geological strata pattern on the cheeks, holding a small brass compass with a smile."), ("alien-meer", "CUTE FRIENDLY CARTOON style with big round expressive eyes, soft warm smile, rounded child-like proportions. Portrait of a friendly cartoon alien ocean explorer named Marinus, deep blue skin, cute coral-like crown, holding a glowing pearly seashell."), ("bathyscaph-trieste", "Portrait of the Trieste bathyscaph, a 1960 deep-sea submersible used by Jacques Piccard and Don Walsh, sturdy steel sphere with portholes, deep ocean blue background."), ("zeppelin-graf", "Portrait of the Graf Zeppelin airship from 1929, silver fabric envelope with crew gondola, gentle clouds and earth horizon."), ("ballon-charliere", "Portrait of the Charlière hydrogen balloon from 1783, brown wicker basket beneath a beige spherical envelope with rope netting, calm golden sky."), ("polar-fram", "Portrait of the polar research vessel Fram from 1893 (Nansen), wooden hull designed for drift ice, sails furled, surrounded by ice and northern light."), ("dampfschiff-beagle", "Portrait of HMS Beagle from 1831 (Darwin), three-masted brig with steam funnel, anchored at a tropical island shore, palm silhouettes."), ("tauchglocke-halley", "Portrait of Halley's diving bell from 1690, wooden barrel-shaped chamber with weighted base, suspended underwater by ropes, fish silhouettes around."), ("sonde-voyager", "Portrait of Voyager 1 space probe from 1977, golden record disc visible, parabolic dish antenna, deep starfield background."), ("eisbohrer-vostok", "Portrait of a Vostok ice-core sample being lifted from an Antarctic drill, scientists in red parkas in the background, clear ice cylinder with visible layers."), ("rover-curiosity", "Portrait of the Curiosity Mars rover from 2012, six wheels, robotic arm extended, mast camera, on red Martian terrain with butte."), ("satellit-landsat", "Portrait of a Landsat earth-observation satellite, large solar panel arrays like wings, orbiting Earth, blue planet in the background."), ("forschungsschiff-meteor", "Portrait of FS Meteor research vessel, white hull with German flag, scientific equipment on deck, calm sea."), ("wettersatellit-meteosat", "Portrait of a Meteosat geostationary weather satellite, cylindrical body with solar cells, viewed against Earth with European cloud patterns."), ("humboldt-alexander", "Portrait of Alexander von Humboldt (1769-1859), European scholar in formal early-19th-century coat, holding a botanical sample, gentle Andean landscape behind."), ("mercator-gerhard", "Portrait of Gerhard Mercator (1512-1594), Flemish cartographer in Renaissance clothing with beard, holding a globe and pair of compasses."), ("cook-james", "Portrait of Captain James Cook (1728-1779), British naval officer in 18th-century uniform with tricorn hat, Pacific island behind."), ("polo-marco", "Portrait of Marco Polo (1254-1324), young Venetian merchant in medieval traveling clothes, holding a small jade figurine, hint of Silk Road."), ("ibn-battuta", "Portrait of Ibn Battuta (1304-1369), Moroccan scholar-traveler in traditional robes and turban, holding a travel journal, suggestion of desert and minaret."), ("nansen-fridtjof", "Portrait of Fridtjof Nansen (1861-1930), Norwegian polar explorer in fur-trimmed parka, ice-axe in hand, polar light."), ("piccard-auguste", "Portrait of Auguste Piccard (1884-1962), Swiss physicist with characteristic large forehead and round glasses, vintage flight suit, stratosphere balloon in background."), ("amundsen-roald", "Portrait of Roald Amundsen (1872-1928), Norwegian polar explorer in fur parka with hood, holding the Norwegian flag, Antarctic ice horizon."), ("earhart-amelia", "Portrait of Amelia Earhart (1897-1937), American aviator in leather flight jacket with aviator goggles pushed up, short tousled hair, vintage propeller plane behind."), ("earle-sylvia", "Portrait of Sylvia Earle (born 1935), American marine biologist in diving suit holding helmet under arm, ocean reef behind, white wavy hair, warm smile."), ("anning-mary", "Portrait of Mary Anning (1799-1847), English paleontologist in 19th-century shawl and bonnet, holding an ammonite fossil, Jurassic coast cliffs behind."), ("carson-rachel", "Portrait of Rachel Carson (1907-1964), American environmental scientist with notebook and binoculars, gentle pastoral nature background."), ] URL = "https://openrouter.ai/api/v1/chat/completions" def generate(slug, suffix): out = DEST / f"avatar-{slug}.png" if out.exists() and not FORCE: print(f" skip {out.name} (existiert; FORCE=1 zum Ueberschreiben)") return "skip" prompt = f"{STYLE} {suffix}" payload_str = json.dumps({ "model": "google/gemini-2.5-flash-image", "messages": [{"role": "user", "content": prompt}], "modalities": ["image", "text"], }) # curl statt urllib, weil Inkscape-Python keine root certs hat. with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as tf: tf.write(payload_str) payload_path = tf.name try: proc = subprocess.run( ["curl", "-s", "-X", "POST", URL, "-H", f"Authorization: Bearer {API_KEY}", "-H", "Content-Type: application/json", "-H", "HTTP-Referer: https://geograsim.at", "-H", "X-Title: GeoGraSim Avatar Generator", "-d", "@" + payload_path], capture_output=True, text=False, timeout=180, ) if proc.returncode != 0: print(f" fail {out.name}: curl exit={proc.returncode} stderr={proc.stderr[:120]}") return "fail" raw = proc.stdout data = json.loads(raw.decode("utf-8")) except Exception as e: print(f" fail {out.name}: {e}") return "fail" finally: try: os.unlink(payload_path) except Exception: pass if "error" in data: print(f" fail {out.name}: {data['error']}") return "fail" try: imgs = data["choices"][0]["message"].get("images") or [] if not imgs: print(f" fail {out.name}: no images in response") return "fail" url = imgs[0]["image_url"]["url"] b64 = url.split(",", 1)[1] out.write_bytes(base64.b64decode(b64)) size_kb = out.stat().st_size / 1024 print(f" ok {out.name} ({size_kb:.0f} KB)") return "ok" except Exception as e: print(f" fail {out.name}: parse {e}") return "fail" def main(): # Optional: LIMIT=10 begrenzt auf erste 10 Slugs (fuer Qualitaets-Check). limit = int(os.environ.get("LIMIT", "0")) or len(SPECS) specs = SPECS[:limit] stats = {"ok": 0, "skip": 0, "fail": 0} print(f"Generiere {len(specs)} Avatare (insgesamt definiert: {len(SPECS)})") print() for slug, suffix in specs: r = generate(slug, suffix) stats[r] = stats.get(r, 0) + 1 time.sleep(1.5) print() print(f"Fertig: {stats['ok']} neu, {stats['skip']} geskippt, {stats['fail']} Fehler.") if stats["ok"] > 0: print("Naechster Schritt: powershell -ExecutionPolicy Bypass -File App/Don_t_Deploy/build-avatar-thumbs.ps1") return 0 if stats["fail"] == 0 else 1 if __name__ == "__main__": sys.exit(main())