garibel / scripts /optimize_illustrations.py
wamaraga's picture
LAUNCH C's GariFuturism UI — convert garibel gradio→docker (Next UI + grounded brain), retire Gradio app.py
e2f6f15 verified
Raw
History Blame Contribute Delete
3.68 kB
#!/usr/bin/env python3
"""Optimize + publish the matched GariFuturism illustrations into the webapp (EB-021).
The lesson illustration refs point at high-res master images (~5 MB each) on the
director-works volume — far too heavy to serve, esp. for the offline-first Belize
pilot. This resizes each referenced PUBLISHABLE image to web size (max 1000px) and
re-encodes to WebP (~100-150 KB) into web/public/illustrations/, then stamps a
served `src` (/illustrations/<slug>.webp) onto each lesson illustration ref so the
UI can render it. Only images already attached to lessons (publishable, non-sacred,
fail-closed per EB-020) are published — sacred/SD-PENDING never reach here.
python3 scripts/optimize_illustrations.py # dry-run (report)
python3 scripts/optimize_illustrations.py --write # optimize + stamp src
"""
from __future__ import annotations
import hashlib, json, re, sys
from pathlib import Path
from PIL import Image
WEB = Path(__file__).resolve().parent.parent
LMS = WEB.parent / "lms"
MI = Path("/Volumes/AI External/NISAMINA_DIRECTOR_WORKS/03_ILLUSTRATIONS/MASTER_ILLUSTRATIONS")
OUT = WEB / "public" / "illustrations"
MAX_PX = 1000
WEBP_Q = 72
def slug(rel: str) -> str:
s = re.sub(r"\.[a-zA-Z]+$", "", rel).lower()
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")[:72]
# append a short hash of the full rel so distinct images never collide
h = hashlib.sha1(rel.encode("utf-8")).hexdigest()[:8]
return f"{s}-{h}"
def iter_lessons():
for ed in sorted(p for p in LMS.iterdir() if p.is_dir() and not p.name.startswith("_")):
for sub in sorted(ed.glob("lessons*")):
if sub.is_dir():
for fp in sorted(sub.glob("*.json")):
yield fp
def main():
write = "--write" in sys.argv
OUT.mkdir(parents=True, exist_ok=True)
# collect unique referenced images
refs = {}
lessons = []
for fp in iter_lessons():
try:
d = json.loads(fp.read_text(encoding="utf-8"))
except Exception:
continue
ims = (d.get("illustrations") or {}).get("images")
if ims:
lessons.append((fp, d))
for im in ims:
refs.setdefault(im["rel"], slug(im["rel"]))
print(f"{len(lessons)} illustrated lessons; {len(refs)} unique images to optimize")
done = bytes_out = 0
for rel, sg in refs.items():
src = MI / rel
dst = OUT / f"{sg}.webp"
if not src.exists():
print(" MISSING:", rel); continue
if write and not dst.exists():
try:
with Image.open(src) as im:
im = im.convert("RGB")
im.thumbnail((MAX_PX, MAX_PX))
im.save(dst, "WEBP", quality=WEBP_Q, method=6)
except Exception as e:
print(" ERR", rel, e); continue
if dst.exists():
bytes_out += dst.stat().st_size
done += 1
# stamp served src onto lesson illustration refs
stamped = 0
if write:
for fp, d in lessons:
ch = False
for im in d["illustrations"]["images"]:
sp = f"/illustrations/{slug(im['rel'])}.webp"
if im.get("src") != sp:
im["src"] = sp
ch = True
if ch:
fp.write_text(json.dumps(d, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
stamped += 1
print(f"optimized {done} images ({bytes_out/1e6:.1f} MB total) -> {OUT}"
+ (f"; stamped src on {stamped} lessons" if write else " [dry-run]"))
if __name__ == "__main__":
main()