Spaces:
Running
Running
File size: 3,933 Bytes
c913ae2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | #!/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()
|