- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitignore | ||
| extract.py | ||
| README.md | ||
meshy-extract
Assemble a standard .glb from a Meshy AI .meshy model container, without an
account, the site UI, or the WASM decoder the viewer uses.
Why the download button is not enough
The page exposes only:
preview.pngand a watermark-free 4K PBR set (colour / metallic-roughness / normal / emission) as plain signed CloudFront URLs, andmodel.meshy, which is not a mesh — it is an encrypted container.
model.json is a 43-byte stub ({"verts_count":...,"faces_count":...}), so no
URL swap or extension change yields geometry. The mesh only exists inside
model.meshy, and the site's mesh_loader.wasm refuses to decode it unless
authorize(hostname, timestamp, signature) passes. That gate is an
access-control layer for the viewer, not part of the file format: the container
is decryptable with a public, fixed key.
Container format
offset bytes content
0..7 8 magic "MESHY.AI"
8..9 2 version (uint16 LE, observed: 1)
10..21 12 AES nonce
22..31 10 reserved
32..8224 8192 AES-256-CTR ciphertext
(GLB header + glTF JSON + BIN chunk header)
8224..8240 16 AES-GCM authentication tag
8240..EOF rest plaintext (WebP textures + meshopt streams)
- Cipher: AES-256-CTR.
- Key: the 32-byte ASCII constant
b'JSON{"accessors":[{"bufferView":'— a known-plaintext key, not a secret. - Counter block:
nonce || uint32be(2), incremented per 16-byte block. - The GCM tag is present but not required to recover the plaintext.
The decrypted stream is a real GLB whose geometry bufferViews carry
EXT_meshopt_compression, so a second step decompresses them with
meshoptimizer into raw vertex/index buffers.
Usage
Requires uv. Dependencies are declared inline (PEP 723), so nothing needs installing first.
The single argument accepts either a task id (UUID) or a URL. A bare id, a
model page URL, or a task API URL is reduced to its UUID and resolved through
the public task API; a .meshy CDN URL is used as-is.
# bare task id
uv run extract.py <task-uuid>
# model page url
uv run extract.py 'https://www.meshy.ai/3d-models/<slug>-<task-uuid>'
# task api url
uv run extract.py 'https://www.meshy.ai/meshyd-api/web/public/v2/tasks/<task-uuid>'
# signed .meshy CDN url captured in devtools
uv run extract.py 'https://api.meshy.ai/misc/cdn-models/<uid>/tasks/<task-uuid>/output/model.meshy?sign=...'
Options:
-o, --outputoutput path (defaultout/<task-id>.glb; the directory is gitignored)--keep-webpkeep the embedded WebP textures instead of converting to PNG
Texture note
The bundled textures are WebP and the material keeps them inside
EXT_texture_webp. The script unwraps that extension and re-encodes the images
to PNG by default, because Blender's glTF importer (and several other tools) do
not read WebP. Pass --keep-webp only if your consumer supports WebP and you
want the smaller file.
Pipeline
- Resolve — a bare id or any non-
.meshyURL is reduced to its task UUID and looked up through the public task API (/meshyd-api/web/public/v2/tasks/<uuid>) to get the signedmodel.meshyURL; a.meshyURL is used directly. - Download the container.
- Decrypt the 8192-byte header block (AES-256-CTR) and splice it onto the plaintext tail, skipping the 16-byte GCM tag.
- Decompress every bufferView carrying
EXT_meshopt_compression(ATTRIBUTES / TRIANGLES / INDICES modes, octahedral/quaternion/exponential filters) into raw buffers. - Rebuild a clean single-buffer GLB: empty and meshopt side-buffer views are dropped, accessors and images are remapped, offsets are re-aligned.
Files
extract.py— the extractor (single file, inline dependencies).out/— default output directory (gitignored).
Output validation
The produced file is a plain single-buffer glTF 2.0 GLB (PNG textures, no
extensions) that loads in any standards-compliant importer; the pipeline's
output passes @gltf-transform/cli inspect and Blender's glTF importer.
Credits
- arnab-mazumder/meshymax — the
.meshycontainer layout, key constant and CTR counter used here are documented in its clean-room decoder (lib/decrypt.js). - IamOumarIbrahim/meshydownloader-glb-extractor
— alternative approach that drives Meshy's own
mesh_loaderWASM module.
Legal
Only run this on models you are entitled to use. Community models on Meshy are
published under the licence shown on the model page. The .meshy container
format was documented by public reverse engineering; the key is a known
plaintext constant and the WASM authorization gate protects the site's viewer,
not the file.