feat(mp4): resolve overlapping fragments during defragmentation - #560
Draft
nchitkara-xai wants to merge 2 commits into
Draft
feat(mp4): resolve overlapping fragments during defragmentation#560nchitkara-xai wants to merge 2 commits into
nchitkara-xai wants to merge 2 commits into
Conversation
Add mp4.Defragment and mp4.DefragmentTracks, which convert a fragmented file to a progressive one: sample tables (stts, ctts, stsc, stsz, stss, stco/co64) are synthesized from the fragment metadata while payload bytes, sample descriptions, and fragment-declared timing are preserved. Every track is rebased so that its first tfdt becomes media time zero: edit-list media times shift by the same origin, and a track starting later than the earliest one keeps its presentation alignment through an empty edit (merged into an existing leading one, prepended, or as a synthesized edit list), with at most half a movie-timescale tick of rounding. Encrypted fragments (senc or traf-level saiz/saio), backward tfdt jumps, edits that cannot be shifted exactly, truncated byte ranges, declared payloads exceeding the input file size, and kept tracks without any samples (whose empty sample tables would classify the output as fragmented again) fail closed. The new mp4ff-defragment tool exposes the conversion with optional track selection. Files with an initial progressive part before the first fragment are rejected for now; support may come as a follow-up.
A fragment whose tfdt re-declares an earlier decode time supersedes the earlier samples of its track (a retransmission): the fragment appearing later in the file wins, whole re-sent fragments disappear, superseded tails are trimmed at sample granularity, and the timeline continues from the declaring fragment, whether or not the re-sent bytes are identical. Non-overlapping input converts byte-identically to before. Ambiguous overlaps keep failing closed instead of being guessed at: an overlap that starts inside a sample (unless it only shrinks earlier tfdt-gap padding), any abandoned time range that no surviving later fragment declares again (whether the superseded fragment was trimmed or dropped whole, and with voided declarations not counting as coverage), and overlapping files whose fragments use absolute base data offsets are rejected, so no declared content is ever silently dropped. The payload size bound now applies to the surviving samples, so re-sent declarations larger than the input file no longer reject a file that resolves cleanly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #557 — the first commit is #557; please review the last commit only.
Problem
Fragmented files from segmented capture or upload pipelines can re-declare
time ranges: a fragment is re-sent after a partial write, or a restart
re-declares part of the previous timeline with a backward tfdt. #557 rejects
such files outright, which makes the converter useless for exactly the files
that most need converting to a clean progressive form.
Fix
Overlap resolution as the default behavior, with no new API surface: a
fragment appearing later in the file whose tfdt re-declares an earlier decode
time wins (retransmission semantics). Whole re-sent fragments disappear,
superseded tails are trimmed at sample granularity, and the timeline
continues from the surviving declaration.
The guarantee that keeps this fail-closed in spirit: no declared time range
is ever silently dropped. Every abandoned range must be re-declared by
surviving fragments — a shorter resend, a transitively voided declaration, or
wrong-order concatenation (corruption rather than retransmission) all reject
with clear errors. Cuts landing inside a sample reject too, unless they only
shrink duration padding that an earlier tfdt gap introduced. Inputs that #557
already accepted convert byte-identically, since resolution only activates on
backward tfdts.
The declared-payload bound from #557 now applies after resolution, so a
legitimately re-sent fragment does not count twice against the input size.
Tests
Full resend, superseded tail, reset chains (covered resolves, shortening
rejects), byte-identical resend, multi-track with one overlapping track
(other track byte-verbatim), gap-padding shrink, mid-sample cuts, abandoned
ranges, wrong-order concatenation, and the payload-bound interaction. All
fragmented testdata files convert byte-identically to #557 output.
go test ./...,go vet,gofmt,golangci-lintpass.