Comfy-Org/ComfyUI · error · ValueError

File3DToSplat: invalid .spz (bad magic)

Error message

File3DToSplat: invalid .spz (bad magic)

What it means

Raised by _parse_spz_gaussian when the gzip-decompressed payload does not start with the expected .spz magic constant. _detect_splat_format routes any gzip file (\x1f\x8b) to the spz parser, so this error means the file is gzip but not an .spz once decompressed. Effectively a corrupted .spz or a different gzipped format.

Source

Thrown at comfy_extras/nodes_gaussian_splat.py:341

                nf = min(full_splats, cnt)
                idx[:nf] = np.arange(nf) // bucket_size
                if cnt > full_splats:
                    lengths = np.frombuffer(data, '<u4', count=partial_buckets, offset=base)
                    idx[full_splats:] = np.repeat(full_buckets + np.arange(partial_buckets), lengths)[:cnt - full_splats]
                xyz = (rec['center'].astype(np.float32) - scale_range) * (block_size / 2.0 / scale_range) + buckets[idx]
            parts.append((xyz, scale, rot, colf[:, 3].copy(), _rgb_to_sh_dc(colf[:, :3])))
        base += bytes_per_splat * sec_max + buckets_store

    if not parts:
        raise ValueError("File3DToSplat: .ksplat has no splats")
    return tuple(np.concatenate([p[i] for p in parts]) for i in range(5))


def _parse_spz_gaussian(data: bytes):
    # Niantic .spz (gzip-wrapped), versions 1-3. Base color only (SH skipped). See spark's SpzReader.
    raw = gzip.decompress(data)
    if struct.unpack_from('<I', raw, 0)[0] != _SPZ_MAGIC:
        raise ValueError("File3DToSplat: invalid .spz (bad magic)")
    version = struct.unpack_from('<I', raw, 4)[0]
    n = struct.unpack_from('<I', raw, 8)[0]
    frac_bits = raw[13]
    off = 16

    if version == 1:
        xyz = np.frombuffer(raw, '<f2', count=n * 3, offset=off).astype(np.float32).reshape(n, 3)
        off += n * 6
    elif version in (2, 3):
        b = np.frombuffer(raw, np.uint8, count=n * 9, offset=off).reshape(n, 3, 3).astype(np.int64)
        v = (b[..., 2] << 16) | (b[..., 1] << 8) | b[..., 0]
        v = np.where(v & 0x800000, v - 0x1000000, v)                             # sign-extend 24-bit
        xyz = (v / (1 << frac_bits)).astype(np.float32)
        off += n * 9
    else:
        raise ValueError(f"File3DToSplat: unsupported .spz version {version}")

    alpha = np.frombuffer(raw, np.uint8, count=n, offset=off).astype(np.float32) / 255.0

View on GitHub (pinned to 1c6d8d45b3)

Solutions

  1. Confirm the file is a genuine Niantic-format .spz (e.g., open it in a viewer that supports spz).
  2. If it is another gzipped format, gunzip it first and load the inner format directly.
  3. Re-download the .spz if corruption is suspected.

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

import gzip, struct
data = open(path, 'rb').read()
if data[:2] == b'\x1f\x8b':
    raw = gzip.decompress(data)
    if struct.unpack_from('<I', raw, 0)[0] != 0x5053474e:  # _SPZ_MAGIC value per repo
        raise ValueError(f'{path} is gzip but not spz')

Type guard

def is_spz(data: bytes) -> bool:
    if data[:2] != b'\x1f\x8b':
        return False
    try:
        raw = gzip.decompress(data)
    except OSError:
        return False
    return len(raw) >= 4 and struct.unpack_from('<I', raw, 0)[0] == _SPZ_MAGIC

Try / catch

try:
    result = _parse_spz_gaussian(data)
except ValueError as e:
    if 'bad magic' in str(e):
        gunzip_and_inspect(path)  # probably another gzipped format
    else:
        raise

Prevention

When it happens

Trigger: File3DToSplat on a random .gz / gzipped non-spz file (any gzip stream matches the detection heuristic), or an .spz whose decompressed header is corrupted so the magic integer mismatches.

Common situations: Feeding a .gz archive or gzipped PLY to the node; a partially corrupted .spz transfer; renaming other files to .spz.

Related errors


AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14). Data as JSON: /api/errors/e69c41f2a6e21018. Report an issue: GitHub.