#!/usr/bin/env python3 """ Generate the social-share thumbnail for the semantic-image-field Space. Always the CURRENT 12 newest photographs: 8 newest interiors (by created_at) from bep40/vai-avatar2-interior + 4 newest added products (by _added_at) from bep40/grob-products-updated. Composed as a 4x3 grid at 1200x675 (2:1 social-card crop). Written to BOTH public/ and dist/ so whichever path the static Space serves, the resolved URL exists. Intended to run from a scheduled HF job (or locally) BEFORE the catalogue sync commits, so the committed thumbnail is refreshed at least as often as the mirror itself. """ import io, json, os, sys, urllib.request INTERIOR_INDEX = "https://huggingface.co/datasets/bep40/vai-avatar2-interior/resolve/main/index.json" INTERIOR_API = "https://bep40-vai-avatar2-interior-api.hf.space" PRODUCTS_URL = "https://huggingface.co/datasets/bep40/grob-products-updated/resolve/main/products_url_added.json" N_INTERIORS = 8 N_PRODUCTS = 4 W, H = 1200, 675 GAP = 6 COLS, ROWS = 4, 3 BG = (17, 20, 28) UA = {"User-Agent": "V.AI-STUDIO-thumbnail/1.0"} def fetch_json(url, timeout=25): req = urllib.request.Request(url, headers=UA) with urllib.request.urlopen(req, timeout=timeout) as r: return json.loads(r.read().decode("utf-8")) def fetch_bytes(url, timeout=25): req = urllib.request.Request(url, headers=UA) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read() def main(): from PIL import Image interiors = fetch_json(INTERIOR_INDEX) products = fetch_json(PRODUCTS_URL) dated = [r for r in interiors if isinstance(r, dict) and isinstance(r.get("created_at"), str) and r.get("created_at") and r.get("image_id")] dated.sort(key=lambda r: r["created_at"], reverse=True) top_i = dated[:N_INTERIORS] prods = [p for p in products if isinstance(p, dict) and isinstance(p.get("_added_at"), str) and p.get("_added_at") and (p.get("imgs") or p.get("i"))] prods.sort(key=lambda p: str(p["_added_at"]), reverse=True) top_p = prods[:N_PRODUCTS] urls = [f"{INTERIOR_API}/image/{r['image_id']}.jpg" for r in top_i] urls += [(p.get("imgs") or [p.get("i")])[0] for p in top_p] cell_w = (W - GAP * (COLS - 1)) // COLS cell_h = (H - GAP * (ROWS - 1)) // ROWS canvas = Image.new("RGB", (W, H), BG) cells = [] for u in urls: try: data = fetch_bytes(u) im = Image.open(io.BytesIO(data)) if im.mode not in ("RGB", "RGBA"): im = im.convert("RGB") if im.mode == "RGBA": im = im.convert("RGB") cells.append(im) except Exception as e: print(f"skip {u[:80]}: {e}", file=sys.stderr) if not cells: print("FATAL: no images could be fetched", file=sys.stderr) sys.exit(1) idx = 0 for row in range(ROWS): for col in range(COLS): if idx >= len(cells): break im = cells[idx] idx += 1 iw, ih = im.size scale = max(cell_w / iw, cell_h / ih) sw, sh = max(1, round(iw * scale)), max(1, round(ih * scale)) im2 = im.resize((sw, sh), Image.LANCZOS) left = (sw - cell_w) // 2 top = (sh - cell_h) // 2 im2 = im2.crop((left, top, left + cell_w, top + cell_h)) canvas.paste(im2, (col * (cell_w + GAP), row * (cell_h + GAP))) out = io.BytesIO() canvas.save(out, format="PNG", optimize=True) png = out.getvalue() os.makedirs("public/share", exist_ok=True) os.makedirs("dist/share", exist_ok=True) for path in ("public/share/og-image.png", "dist/share/og-image.png"): with open(path, "wb") as f: f.write(png) print(f"OK wrote {len(png)} bytes {W}x{H} from {len(cells)} images") if __name__ == "__main__": main()