mp4box 0.12.0

MP4/ISOBMFF parser and editor: box trees, typed decoding, sample tables, fragmented MP4, and non-destructive editing
Documentation
# CLAUDE.md

Guidance for working in the `mp4box` crate: a dependency-light MP4/ISOBMFF
parser, decoder, and non-destructive editor. Core parsing depends only on
`anyhow` + `serde`; `clap`/`serde_json` are pulled in solely by the CLI.

## Commands

```bash
cargo build                      # default features: edit + cli
cargo test                       # all unit + integration tests
cargo test --test decoder_regressions   # one integration test file
cargo fmt --check                # CI-checked; run cargo fmt before committing
cargo clippy --all-targets       # expected to be warning-clean

# Library-only consumers build without the CLI:
cargo build --no-default-features --features edit
```

Edition 2024, MSRV 1.88. Always run `cargo fmt` and `cargo clippy --all-targets`
before finishing a change — both are gates.

## Layout

- `src/lib.rs` — crate docs + the public re-export surface. The API is what's
  re-exported here (`get_boxes`, `Box`, `registry::*`, `parser::*`, ...).
- `src/parser.rs` — low-level box walking: `BoxHeader`, `read_box_header`,
  `parse_boxes`, tolerant parsing (`parse_boxes_tolerant``ParseIssue`).
- `src/boxes.rs``BoxKey`, `FourCC`, `NodeKind` (Leaf / FullBox / Container /
  FullContainer / Unknown).
- `src/api.rs` — the JSON-facing `Box` struct and `get_boxes*`. `build_box`
  turns a parsed node into a `Box`; `decode_value` runs the registry.
- `src/registry/` — decoding:
  - `mod.rs``BoxDecoder` trait, `Registry`, and `default_registry()` which
    wires every fourcc to its decoder.
  - `decoders.rs` — one `*Decoder` per box type.
  - `data.rs``StructuredData` enum + the typed `*Data` structs and their
    `summary()` one-liners. Also `FieldSpan`.
  - `codec_config.rs``esds`/AudioSpecificConfig, avcC/hvcC/... helpers.
- `src/samples.rs` — per-sample tables for progressive and fragmented files.
- `src/edit/` — non-destructive editor (`edit` feature): extent-tree model,
  size/chunk-offset fixup, field patching, tag writing.
- `src/drm.rs` — pssh/Widevine/PlayReady payload decoding.
- `src/bin/``mp4dump`, `mp4info`, `mp4samples`, `mp4edit` (`cli` feature).
- `tests/` — integration tests; `examples/` — runnable examples.

## Key invariants (easy to get wrong)

- **FullBox version/flags are stripped by the parser.** A decoder's `payload`
  reader starts *after* the 1-byte version + 3-byte flags; the values arrive as
  the `version`/`flags` params. Don't re-read them in a decoder.
- **`BoxHeader.header_size` excludes version/flags** — it's just size+type
  (+largesize/+uuid). So a full box's payload length is
  `size - header_size - 4`.
- **`Box.payload_offset`/`payload_size`** describe the region a decoder reads
  (after version/flags for full boxes); containers report `None`.
- **`field_spans` are payload-relative.** `start = 0` is the first payload
  byte. Consumers add `payload_offset` to map into box/file coordinates.
- **Decoding is opt-in via `decode`.** With `decode=false`, `decoded`,
  `structured_data`, and `field_spans` are all `None` and no payload is read —
  keep it that way (structure-only parsing must stay cheap).

## Adding or extending a decoder

1. Add/extend the typed struct + `StructuredData` variant in
   `registry/data.rs`, and its `summary()` arm.
2. Implement `BoxDecoder::decode` in `registry/decoders.rs`.
3. Register it in `default_registry()` in `registry/mod.rs`.
4. Add a synthetic-fixture test in the matching `tests/*.rs` file.

### `field_spans` (hex-field highlighting)

`BoxDecoder::field_spans(version, flags, payload_len) -> Vec<FieldSpan>` reports
each payload field's byte range for hex-view highlighting. It has a default of
empty (opt-in) and is computed from metadata **without reading the payload**, so
it's deterministic and cheap. Guidelines:

- Offsets are payload-relative and must mirror `decode()`'s exact layout,
  including version-dependent widths and flag-gated fields.
- Use the `span(name, len, &mut pos)` helper in `decoders.rs` to lay fields out
  in order.
- Name spans to match the `StructuredData` field names — UIs cross-highlight
  detail rows against spans by name.
- For variable-length or repeating layouts (sample-table bodies, arrays,
  descriptor-based boxes like `esds`, or variable-first layouts like `emsg` v0),
  span only the fixed header and stop. Don't try to enumerate array entries —
  their offsets need the payload, which `field_spans` deliberately can't read.

## Testing conventions

Integration tests build **synthetic fixtures** so they're self-contained.
`tests/decoder_regressions.rs` has the shared helpers: `plain_box`, `full_box`
(prepends version + 24-bit flags), `parse` (decode=true), `find`, and `span`.
Prefer that style — small hand-built boxes with asserted offsets — over binary
fixtures.

A few tests need **real media files** (e.g. `real_fragmented_file_ground_truth`
in `tests/fragmented.rs`). These read from `$MP4_FIXTURES_DIR` and skip when it's
unset, so plain `cargo test` still works offline. To run them, fetch a *pinned*
release of [`video-commander/mp4-fixtures`][fx] and point the env var at it:

```bash
scripts/fetch-fixtures.sh                    # downloads + verifies the pinned tag
export MP4_FIXTURES_DIR="$PWD/target/fixtures"
cargo test
```

The pin lives in `scripts/fetch-fixtures.sh` (`FIXTURES_TAG`, currently
`fixtures-v2`). **Never point these tests at a local working copy of the
fixtures repo** — it drifts on every regeneration and the ground-truth constants
go stale. When you bump `FIXTURES_TAG`, the fixture bytes change, so re-derive
the asserted counts/offsets (ffprobe + the parser must agree) in the same commit.

[fx]: https://github.com/video-commander/mp4-fixtures

## Downstream

This crate is consumed by **video-commander** (`~/Source/video-commander`) via
crates.io. To ship a change there, publish a new version and bump its
dependency. video-commander can also patch in this local checkout for
development (`[patch.crates-io]` in its `src-tauri/Cargo.toml`) — remember to
publish and remove that patch before merging downstream.