# glbtex.py extract model.glb outdir  |  glbtex.py replace model.glb image_index new.png out.glb
import json, struct, sys, os
def read(p):
    b=open(p,"rb").read(); assert b[:4]==b"glTF"
    off=12; chunks=[]
    while off<len(b):
        ln,typ=struct.unpack_from("<II",b,off); chunks.append((typ,b[off+8:off+8+ln])); off+=8+ln
    return json.loads(chunks[0][1]), bytearray(chunks[1][1])
def write(p,js,bin_):
    j=json.dumps(js,separators=(",",":")).encode(); j+=b" "*((4-len(j)%4)%4); bin_=bytes(bin_)+b"\0"*((4-len(bin_)%4)%4)
    out=struct.pack("<III",0x46546C67,2,12+8+len(j)+8+len(bin_))+struct.pack("<II",len(j),0x4E4F534A)+j+struct.pack("<II",len(bin_),0x004E4942)+bin_
    open(p,"wb").write(out)
if __name__!="__main__": pass
elif sys.argv[1]=="extract":
    js,bin_=read(sys.argv[2])
    os.makedirs(sys.argv[3],exist_ok=True)
    mats=js.get("materials",[]); print(json.dumps([{k:m.get(k) for k in ("name","pbrMetallicRoughness","normalTexture")} for m in mats])[:600])
    for i,im in enumerate(js.get("images",[])):
        bv=js["bufferViews"][im["bufferView"]]; data=bin_[bv.get("byteOffset",0):bv.get("byteOffset",0)+bv["byteLength"]]
        ext={"image/png":"png","image/jpeg":"jpg","image/webp":"webp"}.get(im.get("mimeType"),"bin")
        open(f"{sys.argv[3]}/img{i}.{ext}","wb").write(data); print(i,im.get("name"),im.get("mimeType"),len(data))
else:
    js,bin_=read(sys.argv[2])
    idx=int(sys.argv[3]); new=open(sys.argv[4],"rb").read(); im=js["images"][idx]
    # append new image at end of binary and repoint its bufferView
    bin_=bytearray(bin_); pad=(4-len(bin_)%4)%4; bin_+=b"\0"*pad
    js["bufferViews"].append({"buffer":0,"byteOffset":len(bin_),"byteLength":len(new)}); bin_+=new
    im["bufferView"]=len(js["bufferViews"])-1; im["mimeType"]="image/png" if new[:4]==b"\x89PNG" else "image/jpeg"
    js["buffers"][0]["byteLength"]=len(bin_)
    write(sys.argv[5],js,bin_); print("written",sys.argv[5])
