Creative Blueprint
BlueprintWorked exampleBefore / AfterSam batch — finalAll adsSkillsLessonsProtocols

← Both skills · static-ads · scripts/brief2spec.py

static-ads · files
static-ads/scripts/brief2spec.py · 73 lines
Turns hand-off briefs into a draft spec file.
Download the whole skill (zip)

brief2spec.py

#!/usr/bin/env python3
"""Convert 1-understand hand-off briefs (1-understand/new-brand-worksheet.md §9 JSON, one per template) into a spec file.
usage: brief2spec.py OUT_SPECS.py BRIEF.json [BRIEF.json ...] [--start 1]
       brief2spec.py $SAC_BATCH/s1specs.py <brand>-kit/briefs/*.json
The result is a DRAFT: read every spec, rewrite 'visual' as one scene description, fill sd_prompt/composite where the engine needs them,
then run lint.py. Briefs with a non-empty "missing" list are written but flagged (# MISSING) and must not be generated until resolved.
Mapping table: 2-make/spec-format.md ("Input from 1-understand")."""
import json, re, sys, pprint

ASPECT = {"9:16": "portrait_16_9", "3:4": "portrait_4_3", "4:5": "portrait_4_3", "1:1": "square_hd", "16:9": "landscape_16_9", "4:3": "landscape_4_3"}
DNA_ZONES = ("product", "product_spot", "material", "texture", "flavor", "symbol", "body_part", "logo", "brand", "innuendo", "packaging", "person")


def inv_line(i):
    act = i.get("action", "KEEP").upper(); zone = i.get("zone", "")
    base = f"{zone}: {i.get('template', '')}".strip(": ")
    line = f"KEEP {base}" if act == "KEEP" else f"SWAP {base} → {i.get('ours', '')}"
    return line + (f" ({i['reason']})" if i.get("reason") else "")


def convert(b, num):
    t, d, st = b.get("template", {}), b.get("decode", {}), b.get("strategy", {})
    inv = [inv_line(i) for i in b.get("inventory", [])]
    spot = next((i for i in b.get("inventory", []) if i.get("zone") == "product_spot"), None)
    placement = (f"{spot.get('ours') or spot.get('template')} — like the template" if spot else "No product (template has none).")
    dna = "; ".join(f"{i.get('template')} → {i.get('ours')}" for i in b.get("inventory", [])
                    if i.get("action", "").upper() == "SWAP" and any(z in i.get("zone", "") for z in DNA_ZONES)) or "see inventory"
    texts = [f"{s.get('slot', 'text').capitalize()}: \"{s.get('ours', '')}\"" for s in b.get("text_slots", [])]
    method = (t.get("production_method") or "").lower(); risk = (t.get("refusal_risk") or "none").lower()
    spec = {"num": num, "tpl_abs": t.get("path", ""), "angle": f"{st.get('angle_id', '')} {st.get('angle', '')}".strip(),
            "why": d.get("scroll_stop_trick", "") + (f" Hidden reading: {d['hidden_reading']}." if d.get("hidden_reading") not in (None, "", "none") else "")
                   + (f" Visual trick to copy literally: {d['visual_trick']}." if d.get("visual_trick") else ""),
            "dna": dna, "scene": d.get("scene_logic", ""), "placement": placement, "inventory": inv}
    swaps = [l for l in inv if l.startswith("SWAP")]
    keeps = [l for l in inv if l.startswith("KEEP")]
    vis = (f"Recreate Image 2 1:1. Render style: {d.get('render_style', 'same as Image 2')} (not another style). "
           + (f"Copy the action: {d['verb_of_the_scene']}. " if d.get("verb_of_the_scene") else "")
           + ("Changes: " + " ".join(swaps) + " " if swaps else "") + ("Keep: " + " ".join(keeps) + " " if keeps else "")
           + (f"Customer look: {b['people']['customer_look']}. " if b.get("people", {}).get("customer_look") else "")
           + (f"Text treatment: {d['text_treatment']}. " if d.get("text_treatment") else "")
           + ("Do NOT draw: " + "; ".join(b["visual_bans"]) + "." if b.get("visual_bans") else ""))
    spec.update(visual=vis.strip(), texts=texts, engine="openai", fallback="none")
    if "text-only" in method:
        spec.update(engine="pure_comp", composite={"bg": "#FFFFFF", "texts": []})   # fill bg + texts from the template
    if risk in ("sexual", "body") or "skin" in method:
        spec.update(sexual=risk == "sexual", fallback="seedream_comp", sd_prompt="TODO scene only — no text, no product, no logo; leave empty space for text/product",
                    composite={"jar": {"x": 0.8, "y": 0.95, "h": 0.3}, "texts": []})
    if "pasted product" in method:
        spec["why"] += " PRODUCTION METHOD = real photo + pasted product → use the A25 method (engines.md), not a regeneration."
    if t.get("aspect") and t["aspect"] != "1:1":
        spec["aspect"] = ASPECT.get(t["aspect"], t["aspect"])
    if any(re.search(r"\b\d+\s*days?\b|day \d+|week \d+", s.get("template_text", ""), re.I) for s in b.get("text_slots", [])):
        spec["template_has_deadline"] = True
    if b.get("text_slot_count") is not None and b["text_slot_count"] != len(texts):
        spec["why"] += f" WARNING: text_slot_count {b['text_slot_count']} != {len(texts)} slots (B1)."
    return spec, b.get("missing", []), b.get("compliance_flags", [])


args = [a for a in sys.argv[1:] if not a.startswith("--")]
start = int(sys.argv[sys.argv.index("--start") + 1]) if "--start" in sys.argv else 1
if "--start" in sys.argv: args.remove(str(start))
if len(args) < 2: sys.exit(__doc__)
out, briefs = args[0], args[1:]
lines = ["# Draft specs generated by brief2spec.py from 1-understand hand-off briefs. REVIEW every field, then lint.", "S = ["]
for k, f in enumerate(briefs):
    spec, missing, flags = convert(json.load(open(f)), start + k)
    lines.append(f"# from {f}")
    for m in missing: lines.append(f"# MISSING (do not generate until resolved): {m}")
    for fl in flags: lines.append(f"# compliance: {fl}")
    lines.append(pprint.pformat(spec, width=160, sort_dicts=False) + ",")
lines.append("]")
open(out, "w").write("\n".join(lines) + "\n")
print(f"wrote {len(briefs)} draft specs to {out} — review, then: python3 lint.py {out}")