import io import json import os import sys import time from pathlib import Path from PIL import Image from backend.providers.black_forest_image import generate_image ROOT = Path(__file__).resolve().parent.parent IMAGE_DIR = ROOT / "frontend" / "public" / "fallbacks" ASCII_DIR = ROOT / "backend" / "fallback_ascii" MANIFEST = ROOT / "assets" / "fallback-manifest.json" SUBJECTS = [ ("antique-key", "an oversized antique key with a circular bow and three distinct teeth"), ("lighthouse", "a solitary lighthouse projecting two sharp triangular beams"), ("watchful-eye", "a single watchful human eye with a bold iris and clean eyelid contour"), ("open-door", "an open doorway with one bright geometric ray emerging through it"), ("hourglass", "a large hourglass with clearly separated falling grains"), ("broken-chain", "two heavy broken chain links pulling apart at the center"), ("broadcast-microphone", "a vintage broadcast microphone with a simple circular signal halo"), ("burning-match", "one upright match with a large graphic flame"), ("theatrical-mask", "a single cracked theatrical mask with one strong diagonal fracture"), ("compass", "a large compass face with one bold needle pointing northeast"), ("raised-hand", "one open human hand reaching upward with clearly separated fingers"), ("spiral-shell", "a single nautilus shell with a bold geometric spiral"), ("alarm-clock", "a classic twin-bell alarm clock with clear hands near midnight"), ("paper-boat", "one folded paper boat riding on a single curved wave"), ("mountain-flag", "one steep triangular mountain peak with a flag at the summit"), ("sealed-letter", "a single closed envelope with a large circular wax seal"), ("labyrinth", "a simple circular labyrinth with one visible route toward the center"), ("fallen-crown", "one tilted crown with three tall points and a single missing jewel"), ("sprouting-seed", "one seed split open with a strong sprout and two leaves"), ("satellite-dish", "one satellite dish angled upward with three concentric signal arcs"), ] STYLE = ( "Create a terminal-screen pseudo-ASCII illustration. Faithfully preserve the " "object's defining geometry before adding stylistic detail. The image itself " "must visibly be made only from evenly spaced glowing green monospace terminal " "glyphs on pure black: Unicode Braille dots, block characters, box-drawing " "lines, slashes and punctuation. Preserve circles, ovals, straight rails, " "symmetry, gaps and radial lines where required. Use large readable glyph cells " "with visible black gaps between characters. Center one complete object. Crisp " "bitmap terminal screenshot, no photorealism, no smooth painted surfaces, no " "solid vector fills, no caption, no labels, no border, no interface chrome, " "no watermark." ) def main() -> None: if not os.getenv("BFL_API_KEY", "").strip(): raise SystemExit("BFL_API_KEY is not configured") IMAGE_DIR.mkdir(parents=True, exist_ok=True) MANIFEST.parent.mkdir(parents=True, exist_ok=True) records = [] force = "--force" in sys.argv for index, (slug, description) in enumerate(SUBJECTS, start=1): image_path = IMAGE_DIR / f"fallback-{index:02d}.webp" prompt = f"{description}. {STYLE}" if not force and image_path.exists(): print(f"[{index:02d}/20] skip {slug}") else: print(f"[{index:02d}/20] generate {slug}", flush=True) result = generate_image(prompt, width=768, height=512) with Image.open(io.BytesIO(result)) as image: image.convert("RGB").save(image_path, "WEBP", quality=88, method=6) time.sleep(0.35) records.append( { "index": index, "slug": slug, "description": description, "image": f"/fallbacks/fallback-{index:02d}.webp", } ) MANIFEST.write_text(json.dumps(records, indent=2) + "\n", encoding="utf-8") print(f"Wrote {len(records)} BFL fallback objects and manifest") if __name__ == "__main__": main()