backnforth/tools/gen_manual_art.py
type-two a01a53648f Manual illustrations: six nano-banana images, web-sized JPEGs
Generated with tools/gen_manual_art.py (gemini-3-pro / 3.1-flash /
2.5-flash), consistent picture-book style, no text in images, ~480KB total.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 17:16:44 +10:00

79 lines
3.8 KiB
Python

"""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-3-pro-image', 'gemini-3.1-flash-image',
'gemini-2.5-flash-image']
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'})
for attempt in (1, 2):
try:
with urllib.request.urlopen(req, timeout=300) 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
break # empty response: don't retry same model, fall through
except Exception as e:
last = '%s (try %d): %s' % (model, attempt, 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')