| 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() |
|
|