lilith-platform.live/tooling/scripts/watermark/preview_auto.py
2026-06-03 06:20:38 -07:00

91 lines
2.8 KiB
Python

"""
Render the watermark at the coordinates the placement engine chose (reads
placements.json) and build a contact sheet for review. Auto placements use the
ledger coords; fallbacks render dual-corner. Fallbacks are labelled so Quinn can
see exactly where the compromise landed.
Usage:
python3 preview_auto.py # all bases in the ledger
python3 preview_auto.py --only a,b,c
"""
from __future__ import annotations
import json
import os
import sys
from PIL import Image, ImageDraw, ImageFont
from place_engine import LEDGER, STYLE
from sources import OUT, clean_source
from watermark_lib import WatermarkStyle, render_explicit, render_watermark, FONT_DIR
PREVIEW = os.path.join(OUT, "_preview", "auto")
CARD_W = 300
DUAL = WatermarkStyle(mode="dual-corner", text_width_frac=0.34)
def render_for(base: str, p: dict) -> Image.Image:
im = Image.open(clean_source(base)).convert("RGB")
if p.get("fallback"):
return render_watermark(im, DUAL)
return render_explicit(im, STYLE, (p["x"], p["y"]), int(im.width * STYLE.text_width_frac))
def label(im, text, warn=False):
bar = 22
out = Image.new("RGB", (im.width, im.height + bar), (40, 14, 14) if warn else (18, 18, 22))
out.paste(im, (0, 0))
try:
f = ImageFont.truetype(os.path.join(FONT_DIR, "Audiowide-Regular.ttf"), 11)
except Exception:
f = ImageFont.load_default()
ImageDraw.Draw(out).text((4, im.height + 4), text, fill=(255, 120, 190), font=f)
return out
def thumb(im, w=CARD_W):
return im.resize((w, round(im.height * w / im.width)), Image.LANCZOS)
def grid(cards, cols=4, gap=10):
rows = (len(cards) + cols - 1) // cols
cw = max(c.width for c in cards)
ch = max(c.height for c in cards)
sheet = Image.new("RGB", (cols * cw + (cols + 1) * gap, rows * ch + (rows + 1) * gap), (10, 10, 12))
for i, c in enumerate(cards):
r, col = divmod(i, cols)
sheet.paste(c, (gap + col * (cw + gap), gap + r * (ch + gap)))
return sheet
def main():
os.makedirs(PREVIEW, exist_ok=True)
ledger = json.load(open(LEDGER))
only = None
if "--only" in sys.argv:
only = sys.argv[sys.argv.index("--only") + 1].split(",")
bases = only or sorted(ledger)
cards = []
for base in bases:
p = ledger.get(base)
if not p:
continue
wm = render_for(base, p)
if p.get("fallback"):
tag = f"{base}\nFALLBACK dual-corner"
cards.append(label(thumb(wm), tag, warn=True))
else:
tag = f"{base}\nauto bite={p['bite_pct']}%"
cards.append(label(thumb(wm), tag))
sheet = grid(cards)
out = os.path.join(PREVIEW, "contact_sheet.jpg")
sheet.save(out, quality=92)
print("contact sheet →", out, f"({len(cards)} images)")
if __name__ == "__main__":
main()