78 lines
2.8 KiB
Python
Executable File
78 lines
2.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""promptc — compile shot spec + look into a ready-to-paste Flow/Veo prompt.
|
|
|
|
tools/promptc.py shots/t2_s3_sharkrun.json # canonical look (cel80s)
|
|
tools/promptc.py shots/t2_s3_sharkrun.json --look scannerdarkly
|
|
tools/promptc.py shots/t2_s3_sharkrun.json --all # every look, labeled
|
|
tools/promptc.py --flux hero --look scannerdarkly # ingredient still prompt
|
|
# (hero_face for that look)
|
|
Paths resolve relative to production/prompts/. Zero deps, stdlib only.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent / "production" / "prompts"
|
|
CANONICAL = "cel80s"
|
|
|
|
|
|
def load(p):
|
|
return json.loads(Path(p).read_text())
|
|
|
|
|
|
def looks():
|
|
return {f.stem: load(f) for f in (ROOT / "looks").glob("*.json")}
|
|
|
|
|
|
def compile_prompt(shot, look, blocks):
|
|
parts = [look["style"]]
|
|
parts += [blocks["characters"][c] for c in shot.get("characters", [])]
|
|
if shot.get("set"):
|
|
parts.append(blocks["sets"][shot["set"]])
|
|
parts.append(shot["action"])
|
|
if shot.get("camera"):
|
|
parts.append(shot["camera"])
|
|
neg = ", ".join(x for x in [blocks["negative_base"], look.get("negative", "")] if x)
|
|
return ". ".join(p.strip().rstrip(".") for p in parts) + ". Avoid: " + neg + "."
|
|
|
|
|
|
def flux_ingredient(char, look, blocks):
|
|
"""Prompt for the character ingredient still in a given look (FLUX, free)."""
|
|
return (f"{look['style']}. {blocks['characters'][char]}. close-up head and "
|
|
f"shoulders portrait, three-quarter view, confident smirk, plain warm "
|
|
f"gray background. Avoid: {blocks['negative_base']}, {look.get('negative','')}.")
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("shot", nargs="?", help="path to shot json (rel to production/prompts/)")
|
|
ap.add_argument("--look", default=CANONICAL)
|
|
ap.add_argument("--all", action="store_true", help="compile shot in every look")
|
|
ap.add_argument("--flux", metavar="CHAR", help="emit FLUX ingredient-still prompt for a character")
|
|
args = ap.parse_args()
|
|
|
|
blocks = load(ROOT / "blocks.json")
|
|
lks = looks()
|
|
if args.look not in lks:
|
|
sys.exit(f"unknown look {args.look!r}; have: {', '.join(sorted(lks))}")
|
|
|
|
if args.flux:
|
|
print(flux_ingredient(args.flux, lks[args.look], blocks))
|
|
return
|
|
|
|
if not args.shot:
|
|
ap.error("need a shot json (or --flux CHAR)")
|
|
p = Path(args.shot)
|
|
shot = load(p if p.exists() else ROOT / args.shot)
|
|
|
|
chosen = sorted(lks) if args.all else [args.look]
|
|
for name in chosen:
|
|
if args.all:
|
|
print(f"\n=== {name} — {lks[name]['label']} ===")
|
|
print(compile_prompt(shot, lks[name], blocks))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|