MOV to MP4 Transition: Status and Completion Plan
Categories:
Summary
The recording pipeline switched from MOV to MP4 in build 75 (commit
8724785): iOS records via AVAssetWriter .mpeg4Movie, telemetry SRT tracks
convert to mov_text (tx3g, the MP4-native timed-text codec), and C2PA
signing operates on BMFF/MP4 (c2pa-rs 0.48). The switch is ~90% complete. Two
regressions it caused are fixed on pending branches; the remaining work is
itemised below with its governing constraint: signing is the terminal byte
mutation.
The publish boundary
“At publish” is a byte-stream moment, not a UX moment: the last point on device where the file’s bytes change. After it, the artifact must be immutable through S3 and every future verifier.
capture (container A) working files, unsigned
-> trim (FFmpeg -c copy) working file, unsigned
-> ONE final FFmpeg pass: the publish boundary starts here
remux/mux SRT (mov_text)
+ -movflags +faststart
-> SHA-256 hashes
-> C2PA sign (manifest embedded) TERMINAL byte mutation
-> upload exactly those bytes content-type video/mp4
Why the ordering is forced: the manifest’s hard binding for video is the BMFF
hash assertion. Any post-sign remux (including moving the moov box for
faststart) rewrites box offsets, invalidates the binding, and FFmpeg drops the
manifest’s uuid box entirely. Therefore faststart and every container
operation must precede signing; c2pa-rs corrects stco/co64 offsets when
inserting the manifest, so sign-after-faststart is safe. The backend must be
byte-transparent: no server-side transcode or in-place rewrite of published
videos, ever. Derived assets get their own manifest with the original as a
C2PA ingredient.
Fixed and verified (build 80 code)
| Item | Where |
|---|---|
| SRT to mov_text conversion, both tracks survive MP4 | HybridVideoPostprocessor.swift (FFmpeg -c:s mov_text, maps for readable + JSON tracks) |
Track identification on MP4: handler_name survives the mux (title does not) |
HybridVideoPostprocessor.swift (#196 fix) |
Extension-agnostic output paths (the .mov.mp4 class of bug) |
deletingPathExtension() + explicit .mp4 append (commit 5734663) |
| C2PA signing on BMFF/MP4, zero-silent-failure status file | c2pa-rs 0.48 path + signstatus.json |
Trim is lossless and time-coherent: -ss/-to -c copy -avoid_negative_ts make_zero, sidecar re-windowed via cropSidecar(), SRT regenerated from the cropped sidecar |
TrimScreen.tsx, useSensorSidecar.ts |
MIME types video/mp4 end-to-end in the app layer |
PublishScreen.tsx, HasuraAPIAdapter.ts |
Pending merge (blocked on org Actions billing)
| Item | Reference |
|---|---|
| Sidecar forwarding + upload restored (TDD, regression test pins the APPEND naming convention) | PR #195, ruling in ADR-001 |
| HUD playback fix rebased to keep the fix, drop the architecture change | #199 |
Remaining work items
| # | Item | Issue |
|---|---|---|
| 1 | +faststart in the final pre-sign mux (signed outputs are moov-at-end today; streaming penalty) |
#201 |
| 2 | Sidecar SHA-256 assertion in the C2PA manifest (mind the CBOR serialization bug; spike first) | #200 |
| 3 | HUD burn-in: last MOV remnant, leaves device unsigned; switch to MP4 + sign with the published video as ingredient | #202 |
| 4 | Android postprocessor parity (full stub today: no mux, no hash, no signing; trim publishes naked video) | Peregrine ③ milestone, gate 1 of #203 |
| 5 | Trim keyframe skew: -ss -c copy snaps to keyframes while cropSidecar() cuts exact milliseconds; validate duration before signing |
follow-up under #198 |
| 6 | Playback selector robustness: explicit handler_name-aware selection with index/language fallback (builds 75-79 clips have anonymous tracks and are permanently unaddressable by title) | follow-up under #198 |
| 7 | Audio codec / multi-track validation before mux; rotation transform validation post-mux | follow-up under #198 |
| 8 | Release/dist tagging in the Sentry/GlitchTip init (events currently carry no build attribution) | follow-up under #198 |
Known content generations in production
| Generation | Container | Track identification | Sidecar on S3 | Playback HUD |
|---|---|---|---|---|
| pre-build-75 | MOV | title preserved |
yes (when publish path worked) | works |
| builds 75-79 | MP4 | anonymous (no title, no handler_name) | no (#194 bug) | permanently broken by title-selection |
| build 80 | MP4 | handler_name |
no (deliberately removed, overruled) | works |
| build 81+ (planned) | MP4 | handler_name + title |
yes (ADR-001) | works |
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.