edou-3d / extract_glb_textures.py
Anderson Melo
Production-ready Hunyuan3D-2.1 PBR pipeline + Unity glTFast integration
a80f760
"""Extract embedded images from a GLB file (stdlib only).
Usage:
python3 extract_glb_textures.py <input.glb> <output_dir>
"""
import json
import struct
import sys
from pathlib import Path
def extract(glb_path: Path, out_dir: Path) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
with open(glb_path, "rb") as f:
magic, version, total = struct.unpack("<4sII", f.read(12))
if magic != b"glTF":
raise SystemExit(f"not a GLB: {glb_path}")
json_len, json_type = struct.unpack("<II", f.read(8))
if json_type != 0x4E4F534A:
raise SystemExit("missing JSON chunk")
gltf = json.loads(f.read(json_len))
bin_len, bin_type = struct.unpack("<II", f.read(8))
if bin_type != 0x004E4942:
raise SystemExit("missing BIN chunk")
bin_data = f.read(bin_len)
images = gltf.get("images", [])
buffer_views = gltf.get("bufferViews", [])
materials = gltf.get("materials", [])
textures = gltf.get("textures", [])
print(f"GLB: {glb_path}")
print(f" JSON: {json_len} bytes, BIN: {bin_len} bytes")
print(f" images: {len(images)}, textures: {len(textures)}, materials: {len(materials)}")
ext_for_mime = {"image/png": "png", "image/jpeg": "jpg", "image/jpg": "jpg"}
image_paths = []
for i, img in enumerate(images):
mime = img.get("mimeType", "image/png")
ext = ext_for_mime.get(mime, "bin")
bv_idx = img.get("bufferView")
if bv_idx is None:
print(f" image[{i}]: skipped (no bufferView, name={img.get('name')})")
image_paths.append(None)
continue
bv = buffer_views[bv_idx]
offset = bv.get("byteOffset", 0)
length = bv["byteLength"]
data = bin_data[offset:offset + length]
name = img.get("name") or f"image_{i}"
safe = "".join(c if c.isalnum() or c in "._-" else "_" for c in name)
out = out_dir / f"{i:02d}_{safe}.{ext}"
out.write_bytes(data)
print(f" image[{i}]: {out.name} ({length / 1024:.1f} KB, {mime})")
image_paths.append(out)
print("\n[textures]")
for i, tex in enumerate(textures):
src = tex.get("source")
print(f" texture[{i}] -> image[{src}]"
+ (f" = {image_paths[src].name}" if src is not None and image_paths[src] else ""))
print("\n[materials]")
for i, mat in enumerate(materials):
name = mat.get("name", f"mat_{i}")
pbr = mat.get("pbrMetallicRoughness", {})
bct = pbr.get("baseColorTexture", {}).get("index")
mrt = pbr.get("metallicRoughnessTexture", {}).get("index")
nrm = mat.get("normalTexture", {}).get("index")
emi = mat.get("emissiveTexture", {}).get("index")
occ = mat.get("occlusionTexture", {}).get("index")
print(f" material[{i}] {name}:")
print(f" baseColor -> texture[{bct}]" if bct is not None else " baseColor -> (none)")
print(f" metalRough -> texture[{mrt}]" if mrt is not None else " metalRough -> (none)")
print(f" normal -> texture[{nrm}]" if nrm is not None else " normal -> (none)")
print(f" emissive -> texture[{emi}]" if emi is not None else " emissive -> (none)")
print(f" occlusion -> texture[{occ}]" if occ is not None else " occlusion -> (none)")
if __name__ == "__main__":
if len(sys.argv) != 3:
print(__doc__)
sys.exit(1)
extract(Path(sys.argv[1]).expanduser(), Path(sys.argv[2]).expanduser())