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>
79 lines
3.8 KiB
Python
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')
|