Skip to content

feat: short-circuit MIME detection for legacy QuickTime pnot files#59

Merged
mindeng merged 2 commits into
mindeng:mainfrom
enjoypi:feat/legacy-quicktime-pnot
Jun 11, 2026
Merged

feat: short-circuit MIME detection for legacy QuickTime pnot files#59
mindeng merged 2 commits into
mindeng:mainfrom
enjoypi:feat/legacy-quicktime-pnot

Conversation

@only1zf

@only1zf only1zf commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Files from early consumer cameras (Nikon COOLPIX S5/P5000, Casio EX-Z50, Fujifilm FinePix older AVI/MOV mixes, etc., ca. 2003-2008) start with a pnot (preview) atom instead of ftyp. Such files were rejected by parse_bmff_mime with Error::UnsupportedFormat, even though their moov > mvhd structure is fully standard QuickTime and the downstream mov.rs::extract_moov_body_from_buf (which scans atoms in order and skips unrecognized ones) can extract creation_time without changes.

Root cause:

  1. get_ftyp_and_major_brand only accepts ftyp or wide as the first box and returns Malformed otherwise — caught and mapped to UnsupportedFormat by parse_bmff_mime.

  2. The wide path falls through to a travel_header(... != "mdat") scan. Even adding a pnot arm there would not help: the HEADER_PARSE_BUF_SIZE is 128 bytes, while a real-world moov atom typically pushes mdat well past byte 128, so the scan returns Incomplete and we fall into UnsupportedFormat.

Fix:

Short-circuit MIME detection: if the first box is pnot, the file is QuickTime — no need to scan further. The atom is a QuickTime-specific preview structure and unambiguously identifies the container.

Verified against three NIKON DIGITAL CAMERA / S5 MOV files captured in 2006-2007: before the patch populate_video_dates returned no TrackInfoTag::CreateDate; after the patch the value matches what ExifTool reads ([QuickTime] CreateDate).

No new dependencies. No behaviour change for ftyp/wide-prefixed files. Performance impact: one extra BoxHolder::parse on every call into parse_bmff_mime, which is cheap (single box header parse over the existing buffer).

enjoypi added 2 commits June 9, 2026 02:17
Files from early consumer cameras (Nikon COOLPIX S5/P5000, Casio EX-Z50,
Fujifilm FinePix older AVI/MOV mixes, etc., ca. 2003-2008) start with a
`pnot` (preview) atom instead of `ftyp`. Such files were rejected by
`parse_bmff_mime` with `Error::UnsupportedFormat`, even though their
`moov > mvhd` structure is fully standard QuickTime and the downstream
`mov.rs::extract_moov_body_from_buf` (which scans atoms in order and
skips unrecognized ones) can extract `creation_time` without changes.

Root cause:

  1. `get_ftyp_and_major_brand` only accepts `ftyp` or `wide` as the
     first box and returns `Malformed` otherwise — caught and mapped to
     `UnsupportedFormat` by `parse_bmff_mime`.

  2. The `wide` path falls through to a `travel_header(... != "mdat")`
     scan. Even adding a `pnot` arm there would not help: the
     `HEADER_PARSE_BUF_SIZE` is 128 bytes, while a real-world `moov`
     atom typically pushes `mdat` well past byte 128, so the scan
     returns `Incomplete` and we fall into `UnsupportedFormat`.

Fix:

Short-circuit MIME detection: if the first box is `pnot`, the file is
QuickTime — no need to scan further. The atom is a QuickTime-specific
preview structure and unambiguously identifies the container.

Verified against three NIKON DIGITAL CAMERA / S5 MOV files captured in
2006-2007: before the patch `populate_video_dates` returned no
`TrackInfoTag::CreateDate`; after the patch the value matches what
ExifTool reads (`[QuickTime] CreateDate`).

No new dependencies. No behaviour change for `ftyp`/`wide`-prefixed
files. Performance impact: one extra BoxHolder::parse on every call
into parse_bmff_mime, which is cheap (single box header parse over
the existing buffer).
Some legacy QuickTime MOV files (e.g. early consumer cameras) start
directly with a huge `mdat` atom — no `ftyp`/`wide`/`pnot` header at
all, with `moov` at the end of the file. `get_ftyp_and_major_brand`
rejects such files as malformed, and the generic mdat fallback scan
cannot help because the 128-byte header parse buffer ends long before
the mdat body does.

Extend the existing pnot short-circuit to also accept a leading mdat,
and switch it to header-only parsing (`BoxHeader::parse`): the previous
`BoxHolder::parse` requires the full box body in the buffer, which a
multi-megabyte leading mdat can never satisfy, so the short-circuit
silently fell through. `extract_moov_body_from_buf` already skips
arbitrarily large mdat bodies via `ClearAndSkip`, so track parsing
works unchanged.

Verified against a real-world mdat-first MOV (moov at EOF, 2.8 MB
mdat): CreateDate matches ExifTool output.
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.52%. Comparing base (a65033a) to head (8ed581b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #59      +/-   ##
==========================================
+ Coverage   90.50%   90.52%   +0.02%     
==========================================
  Files          38       38              
  Lines        8570     8593      +23     
==========================================
+ Hits         7756     7779      +23     
  Misses        814      814              
Flag Coverage Δ
nom-exif 90.52% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mindeng mindeng merged commit 13fbaa4 into mindeng:main Jun 11, 2026
5 checks passed
@mindeng

mindeng commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Thanks for the fix! This is a clean and well-explained change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants