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()