"""Deterministic paired prompt manifest.""" from __future__ import annotations import hashlib import json from pathlib import Path CATEGORIES = ("animals", "people", "landscapes", "vehicles", "architecture", "food", "indoor", "abstract") SUBJECTS = { "animals": ["red fox crossing a snowy ridge", "sea turtle above a coral reef", "owl on a mossy branch", "elephant family at dusk", "golden retriever in a farmers market", "penguin colony on blue ice", "dragonfly on a lotus pond", "wild horse in desert wind", "sleeping koala in eucalyptus", "humpback whale breaching offshore"], "people": ["violinist backstage before a concert", "ceramic artist shaping a bowl", "runner crossing a bridge at dawn", "grandmother reading beside a window", "astronomer under a meteor shower", "street dancer in a neon alley", "gardener holding heirloom tomatoes", "chef plating a tasting menu", "child flying a paper kite", "sailor repairing a bright red net"], "landscapes": ["volcanic valley beneath low clouds", "lavender fields in Provence", "desert canyon after rain", "fjord with a small wooden ferry", "autumn forest reflected in a lake", "rice terraces in morning mist", "coastal cliffs under storm light", "alpine meadow with a footpath", "tropical waterfall behind ferns", "salt flats mirroring the night sky"], "vehicles": ["yellow tram turning through old streets", "sailboat in a harbor of lanterns", "red bicycle beside a canal", "electric train entering a tunnel", "vintage airplane in a hangar", "motorcycle on a coastal road", "blue pickup at a rural gas station", "submarine beneath polar ice", "hot air balloon over vineyards", "kayak on a glassy river"], "architecture": ["brutalist library in winter", "wooden temple surrounded by cedars", "Art Deco cinema at twilight", "floating pavilion on a lake", "desert observatory with domes", "narrow townhouse courtyard", "futuristic transit station", "stone bridge over a market canal", "greenhouse made of iron and glass", "lighthouse on a basalt shore"], "food": ["sourdough loaf cooling on linen", "ramen bowl in a tiny Tokyo shop", "summer peaches on a blue plate", "spice market with open jars", "chocolate cake with raspberry glaze", "breakfast table with coffee and oranges", "handmade dumplings on bamboo", "roasted vegetables on a ceramic platter", "lemon tart beside garden flowers", "night market skewers over charcoal"], "indoor": ["sunlit reading nook with a velvet chair", "rainy day kitchen with copper pans", "minimal studio with a drafting table", "old train compartment with luggage", "music room filled with records", "green apartment balcony after watering", "museum gallery with a skylight", "warm cabin bedroom with quilts", "maker workshop with wooden tools", "quiet cafe corner beside a window"], "abstract": ["ink spirals colliding with gold leaf", "isometric cubes in coral and teal", "watercolor gradients like a topographic map", "paper cut shapes casting long shadows", "glass ribbons twisting in sunlight", "monochrome circles with one yellow accent", "liquid metal waves on black", "woven threads forming a labyrinth", "pixel mosaic inspired by ocean tides", "floating geometric shards in soft fog"], } def build_prompts(count: int = 100) -> list[dict[str, object]]: """8カテゴリを循環し、stable IDとpaired seedを付けたpromptを作る。""" if count < 1: raise ValueError("count must be positive") result: list[dict[str, object]] = [] for index in range(count): category = CATEGORIES[index % len(CATEGORIES)] variant = index // len(CATEGORIES) prompt = f"{SUBJECTS[category][variant % len(SUBJECTS[category])]}, {('wide composition' if variant % 3 == 0 else 'intimate composition' if variant % 3 == 1 else 'cinematic composition')}, {('morning light' if variant % 4 == 0 else 'soft overcast light' if variant % 4 == 1 else 'late afternoon light' if variant % 4 == 2 else 'blue hour light')}, high detail, scene {index + 1}" prompt_id = f"pm4-{index + 1:03d}-{hashlib.sha256(prompt.encode()).hexdigest()[:8]}" seed = 10_000 + index result.append({"prompt_id": prompt_id, "category": category, "prompt": prompt, "seed": seed, "paired_seed": seed, "initial_latent_seed": seed}) return result def load_prompt_manifest(path: str | Path) -> list[dict[str, object]]: """version-controlled prompt manifestを検証して読む。""" entries = json.loads(Path(path).read_text(encoding="utf-8")) if not isinstance(entries, list) or len(entries) != 100: raise ValueError("prompt manifest must contain exactly 100 entries") texts = [str(item["prompt"]) for item in entries] if len(set(texts)) != 100: raise ValueError("prompt text must be unique") for item in entries: if {"prompt_id", "category", "prompt", "seed", "initial_latent_seed"} - item.keys(): raise ValueError("prompt entry schema is incomplete") if item["category"] not in CATEGORIES: raise ValueError(f"unknown prompt category: {item['category']}") if any(sum(item["category"] == category for item in entries) < 10 for category in CATEGORIES): raise ValueError("each prompt category requires at least 10 entries") return entries def write_prompt_manifest(path: str | Path, count: int = 100) -> str: """prompt manifestをJSONへ書き、sha256を返す。""" target = Path(path) if count == 100 and Path(path).exists(): payload = load_prompt_manifest(path) else: payload = build_prompts(count) target.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n") return hashlib.sha256(target.read_bytes()).hexdigest()