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