← back to Sanderson Onboard
scripts/palette.py
41 lines
#!/usr/bin/env python3
"""Incremental local-PIL palette ($0). Reads data/palette_input.csv (mfr_sku,image_url) -> data/palette_updates.sql."""
import csv, io, json, os, urllib.request
from PIL import Image
DATA=os.path.join(os.path.dirname(__file__),'..','data')
UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36"
NAMED={"White":(245,245,245),"Alabaster":(237,234,224),"Cream":(240,230,210),"Ivory":(255,247,229),"Oatmeal":(214,203,182),"Greige":(190,180,165),"Beige":(200,185,160),"Taupe":(160,145,128),"Sand":(205,180,140),"Gold":(198,160,80),"Mustard":(190,155,50),"Ochre":(180,130,60),"Blush":(230,190,190),"Pink":(225,150,170),"Rose":(200,110,120),"Raspberry":(150,50,80),"Red":(170,40,40),"Terracotta":(180,90,60),"Orange":(210,120,50),"Brown":(110,80,55),"Chocolate":(70,50,40),"Charcoal":(55,55,58),"Black":(30,30,30),"Grey":(140,140,145),"Silver":(190,192,196),"Stone":(170,165,155),"Sage":(150,165,130),"Green":(70,120,70),"Olive":(110,115,60),"Forest":(40,80,55),"Teal":(50,120,120),"Duck Egg":(170,205,200),"Blue":(70,110,170),"Delft Blue":(60,90,150),"Navy":(35,45,80),"Sky":(170,200,225),"Denim":(90,110,140),"Lilac":(190,175,210),"Purple":(110,70,130),"Aubergine":(70,45,70),"Wedgwood":(90,130,165),"Linen":(225,215,195),"Mist":(200,205,205),"Marine":(40,70,110)}
def nearest(rgb):
r,g,b=rgb; best,bd=None,1e18
for n,(nr,ng,nb) in NAMED.items():
d=(r-nr)**2+(g-ng)**2+(b-nb)**2
if d<bd: bd,best=d,n
return best
def palette(bs,k=6):
im=Image.open(io.BytesIO(bs)).convert("RGB"); im.thumbnail((160,160))
q=im.quantize(colors=k,method=Image.MEDIANCUT); pal=q.getpalette(); counts=q.getcolors() or []
total=sum(c for c,_ in counts) or 1; out=[]
for cnt,idx in sorted(counts,reverse=True):
r,g,b=pal[idx*3:idx*3+3]; pct=round(100*cnt/total,1)
if pct<3: continue
out.append({"hex":"#%02x%02x%02x"%(r,g,b),"name":nearest((r,g,b)),"pct":pct})
merged={}
for c in out:
if c["name"] in merged: merged[c["name"]]["pct"]=round(merged[c["name"]]["pct"]+c["pct"],1)
else: merged[c["name"]]=c
return sorted(merged.values(),key=lambda x:-x["pct"])
def esc(s): return s.replace("'","''")
inp=os.path.join(DATA,'palette_input.csv')
if not os.path.exists(inp): print("no palette_input.csv (nothing new)"); raise SystemExit
_inp_fh=open(inp); rows=list(csv.DictReader(_inp_fh)); _inp_fh.close(); out=open(os.path.join(DATA,'palette_updates.sql'),'w'); ok=fail=0
for r in rows:
sku,url=r['mfr_sku'],r['image_url']
if not url: fail+=1; continue
try:
b=urllib.request.urlopen(urllib.request.Request(url,headers={"User-Agent":UA}),timeout=30).read(); pal=palette(b)
except Exception: fail+=1; continue
if not pal: fail+=1; continue
out.write(f"UPDATE sanderson_catalog SET ai_colors='{esc(json.dumps(pal))}'::jsonb, ai_background_color='{esc(pal[0]['name'])}' WHERE mfr_sku='{esc(sku)}';\n")
ok+=1
out.close(); print(f"palette ok={ok} fail={fail}")