From 074126774c2dfad1925b6d07a4d03fe21b43b830 Mon Sep 17 00:00:00 2001 From: type-two Date: Tue, 7 Jul 2026 15:16:13 +1000 Subject: [PATCH] Add manual-art generator (Gemini image API, key via env var) Co-Authored-By: Claude Fable 5 --- tools/gen_manual_art.py | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tools/gen_manual_art.py diff --git a/tools/gen_manual_art.py b/tools/gen_manual_art.py new file mode 100644 index 0000000..a2103aa --- /dev/null +++ b/tools/gen_manual_art.py @@ -0,0 +1,76 @@ +"""Generate backnforth manual illustrations via Gemini image API (nano banana).""" +import base64 +import json +import os +import sys +import time +import urllib.request + +# key from disk, never printed +KEY = os.environ.get("GEMINI_API_KEY") +if not KEY: + sys.exit('set GEMINI_API_KEY in the environment first') + +MODELS = ['gemini-2.5-flash-image', 'gemini-2.5-flash-image-preview', + 'gemini-2.0-flash-preview-image-generation'] + +STYLE = ("Flat vector cartoon illustration in a warm, friendly picture-book style. " + "Soft rounded shapes, thick clean outlines, muted palette of deep teal, warm coral, " + "cream and soft mustard yellow. Cozy, calm, reassuring mood. Simple, uncluttered " + "composition on a plain cream background. Absolutely no text, no letters, no numbers, " + "no words anywhere in the image. Landscape orientation. ") + +SCENES = { + '01-cover': "A friendly therapist and a relaxed patient sitting in comfy armchairs in a cozy " + "counselling room with a potted plant and a soft floor lamp. On a low table between " + "them, an open laptop shows a dark screen with a single glowing green dot.", + '02-open': "Close-up of gentle hands opening a slightly-worn laptop on a wooden table. The screen " + "shows a simple dark page with one large round green play button and a small green dot.", + '03-link': "One hand passing an old smartphone to another person's open hand. The phone screen " + "glows dark with a single bright green dot. In the soft-focus background, a laptop " + "shows the same green dot, suggesting the two screens are connected by a dotted arc.", + '04-follow': "Side profile of a calm person's face with a serene expression, eyes looking to one " + "side, following a small glowing green dot that trails a gentle curved motion path " + "from the left side of the image to the right.", + '05-adjust': "A hand with one finger adjusting a single very large horizontal slider on a dark " + "tablet screen. At the left end of the slider sits a tiny cute cartoon turtle, at " + "the right end a tiny cute cartoon rabbit.", + '06-care': "A therapist leaning forward attentively with a kind expression, offering a glass of " + "water to a patient who looks calm and relieved. The laptop on the side table is " + "closed. Warm afternoon light through a window.", +} + +OUT = os.path.expanduser('~/Documents/backnforth/img/manual') +os.makedirs(OUT, exist_ok=True) + + +def generate(prompt): + body = json.dumps({'contents': [{'parts': [{'text': prompt}]}]}).encode() + last = None + for model in MODELS: + url = ('https://generativelanguage.googleapis.com/v1beta/models/' + '%s:generateContent?key=%s' % (model, KEY)) + req = urllib.request.Request(url, data=body, + headers={'Content-Type': 'application/json'}) + try: + with urllib.request.urlopen(req, timeout=120) as r: + data = json.load(r) + for part in data['candidates'][0]['content']['parts']: + if 'inlineData' in part: + return base64.b64decode(part['inlineData']['data']), model + last = 'no image part in response from %s' % model + except Exception as e: + last = '%s: %s' % (model, e) + raise RuntimeError(last) + + +for name, scene in SCENES.items(): + path = os.path.join(OUT, name + '.png') + if os.path.exists(path): + print(name, 'exists, skipping') + continue + img, model = generate(STYLE + scene) + open(path, 'wb').write(img) + print('%s: %d KB via %s' % (name, len(img) // 1024, model)) + time.sleep(2) +print('done')