← back to Pattern Vault
whimsical-compare/scripts/gen_faces.py
100 lines
#!/usr/bin/env python3
"""
"Tema e Variazioni" — 20 ORIGINAL face-medallion wallpaper tiles.
Homage to the FORMAT of Fornasetti's black-and-white repeating portrait plates
(theme-and-variations), rendered with our OWN invented face and our own whimsical
variations — NOT a copy of Fornasetti's design and NOT their model (Lina Cavalieri).
Original interpretation only.
Each tile = a seamless repeating wallpaper of a single circular b&w engraved
portrait medallion with one whimsical twist (pipe, monocle, Mona Lisa framing,
crown, keyhole, playing-card, etc.).
Replicate SDXL (~1c/img). Usage: python3 gen_faces.py [--n 20] [--seed-base N]
"""
import os, re, json, time, sys, urllib.request
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
OUT = f"{ROOT}/public/faces"
os.makedirs(OUT, exist_ok=True)
def argv(f, d=None):
a = sys.argv; return a[a.index(f)+1] if f in a else d
N = int(argv("--n", "20"))
SEED_BASE = int(argv("--seed-base", str(int(time.time()) % 1000000)))
tok = None
with open(os.path.expanduser("~/Projects/secrets-manager/.env")) as f:
m = re.search(r"REPLICATE_API_TOKEN=([A-Za-z0-9_-]+)", f.read()); tok = m and m.group(1)
assert tok, "no replicate token"
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
# 20 whimsical variations — our own "theme e variazioni"
VARIATIONS = [
("Mona Lisa", "the face wearing a subtle Mona Lisa smile with a soft renaissance veil"),
("Pipe Dream", "a slender pipe held at the lips with a curl of smoke"),
("Monocle", "a single monocle over one eye on a fine chain"),
("Crown", "a delicate line-drawn crown resting on the hair"),
("Keyhole", "a small keyhole motif where one eye would be, surreal"),
("Playing Card", "the portrait mirrored like a queen playing card, top and bottom"),
("Blindfold", "a lace blindfold across the eyes, mysterious"),
("Teacup", "balancing a tiny teacup and saucer on the crown of the head"),
("Clockface", "a small clock face set into the cheek, surreal collage"),
("Moon Phase", "crescent moons for earrings and stars scattered in the hair"),
("Pince-Nez", "old-fashioned pince-nez spectacles perched on the nose"),
("Ribbon", "an oversized bow ribbon tied at the chin"),
("Birdcage Veil", "a fine birdcage veil netting over the upper face"),
("Pearl Choker", "an ornate pearl-and-jewel choker at the throat"),
("Winking", "one eye closed in a playful wink"),
("Feather Hat", "a jaunty plumed hat tilted over the brow"),
("Mustache", "a curled dapper mustache, gender-play whimsy"),
("Veil of Lace", "half the face dissolving into an intricate lace pattern"),
("Sun Rays", "a halo of fine engraved sun rays behind the head"),
("Masquerade", "an ornate venetian masquerade mask over the eyes"),
]
BASE = ("black and white vintage engraving wallpaper, a single elegant original woman's face "
"inside a circular medallion plate, fine cross-hatch etching and stipple, "
"monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations "
"portrait-plate wallpaper, our own original face, not a copy of any existing design, "
"seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering")
NEG = ("color, colour, photograph, photo-realistic, modern, text, watermark, signature, logo, "
"blurry, low quality, jpeg artifacts, seams, frame, border, vignette, extra faces, deformed")
def hj(url, payload=None):
hdr = {"User-Agent": UA, "Content-Type": "application/json", "Authorization": f"Bearer {tok}"}
data = json.dumps(payload).encode() if payload else None
req = urllib.request.Request(url, data=data, headers=hdr, method="POST" if data else "GET")
with urllib.request.urlopen(req, timeout=180) as r: return json.load(r)
def sdxl(prompt, seed):
ver = hj("https://api.replicate.com/v1/models/stability-ai/sdxl")["latest_version"]["id"]
pred = hj("https://api.replicate.com/v1/predictions", {"version": ver, "input": {
"prompt": prompt, "negative_prompt": NEG, "width": 1024, "height": 1024,
"num_inference_steps": 34, "guidance_scale": 8.0, "seed": seed, "refine": "no_refiner"}})
for _ in range(90):
if pred.get("status") in ("succeeded", "failed", "canceled"): break
time.sleep(2); pred = hj(pred["urls"]["get"])
return pred
designs = []; total = 0.0
for i, (name, desc) in enumerate(VARIATIONS[:N], 1):
prompt = f"{BASE}; variation: {desc}."
pred = sdxl(prompt, SEED_BASE + i * 17)
secs = (pred.get("metrics") or {}).get("predict_time", 0) or 0; total += secs
if pred.get("status") != "succeeded":
print(f"[{i}] {name} FAIL {pred.get('status')}", flush=True); continue
out = pred["output"]; url = out[0] if isinstance(out, list) else out
fn = f"face_{i:02d}.png"; dest = f"{OUT}/{fn}"
urllib.request.urlretrieve(url, dest)
designs.append({"n": i, "name": name, "desc": desc, "file": f"/faces/{fn}", "prompt": prompt})
print(f"[{i}/{N}] {name} OK {secs:.1f}s", flush=True)
time.sleep(0.6)
cost = round(total * 0.000725, 4)
json.dump({"title": "Tema e Variazioni — our faces", "count": len(designs),
"est_cost_usd": cost, "designs": designs}, open(f"{OUT}/manifest.json", "w"), indent=2)
print(f"\n=== {len(designs)} tiles | est ${cost} ===", flush=True)