← back to Japan Enrich
enrich_greenland.py
87 lines
#!/usr/bin/env python3
"""
Greenland enricher — per-colorway hex + color_family (Pillow, $0 local).
Greenland rows are PATTERNS holding {colorway_skus:[...], sku_images:{sku:url}}.
Material is already known from the vendor category tree (staging `material`), so
this pass does NOT run vision — it only needs the per-colorway dominant color for
the color-family sort/swatch nav.
DISK-SAFE: Greenland serves full-res (~1MB) even for thumbnails, so we DON'T keep
the originals (3117×1MB ≈ 3GB). Each image is downscaled to a ≤256px swatch on
download (~15KB) into images/greenland/, and hex is computed from that. Total
footprint ~47MB, and the swatches double as local swatches if we localize later.
Emits out/greenland-enriched.jsonl — ONE line per colorway SKU:
{"mfr_sku":"<sku>", "source":"greenland", "enrich":{"hex":[...], "color_family":..., "material":...}}
so the viewer's ENRICH index (keyed by normSku(mfr_sku)) picks it up per colorway card.
"""
import os, json, io, urllib.request, concurrent.futures as cf
from enrich import hex_palette, dominant_family, norm_sku
IMG_DIR = "images/greenland"
STAGING = "staging/greenland-staging.jsonl"
OUT = "out/greenland-enriched.jsonl"
os.makedirs(IMG_DIR, exist_ok=True)
os.makedirs("out", exist_ok=True)
def fetch_swatch(sku, url):
"""Download → downscale to ≤256px → save small jpg. Returns path or None."""
from PIL import Image
dst = os.path.join(IMG_DIR, norm_sku(sku) + ".jpg")
if os.path.exists(dst) and os.path.getsize(dst) > 0:
return dst
try:
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
data = urllib.request.urlopen(req, timeout=45).read()
if len(data) < 200:
return None
im = Image.open(io.BytesIO(data)).convert("RGB")
im.thumbnail((256, 256)) # discard full-res; keep only the swatch
im.save(dst, "JPEG", quality=82)
return dst
except Exception:
return None
def main():
rows = [json.loads(l) for l in open(STAGING) if l.strip()]
# (sku, url, material) for every colorway that has an image
jobs, mat = [], {}
for r in rows:
imgs = r.get("sku_images") or {}
material = r.get("material")
for sku, url in imgs.items():
if url:
jobs.append((sku, url))
mat[norm_sku(sku)] = material
print(f"greenland: {len(rows)} patterns, {len(jobs)} colorway swatches to enrich", flush=True)
done = 0
with open(OUT, "w") as out, cf.ThreadPoolExecutor(max_workers=8) as ex:
futs = {ex.submit(fetch_swatch, s, u): s for s, u in jobs}
for fut in cf.as_completed(futs):
sku = futs[fut]
done += 1
path = fut.result()
if not path:
continue
try:
hexes = hex_palette(path)
fam = dominant_family(hexes)
except Exception:
continue
rec = {"mfr_sku": sku, "source": "greenland",
"enrich": {"hex": hexes, "color_family": fam,
"material": mat.get(norm_sku(sku)), "style": None}}
out.write(json.dumps(rec, ensure_ascii=False) + "\n")
out.flush()
if done % 200 == 0:
print(f" {done}/{len(jobs)}", flush=True)
print(f"greenland: enriched → {OUT}", flush=True)
if __name__ == "__main__":
main()