mirror of
https://github.com/wader/fq.git
synced 2024-09-11 12:05:39 +03:00
mp4: Use box structure instead of track id to keep track for sample table data
Fixes issue with mp4s where tkhd is after sample table boxes
This commit is contained in:
parent
b05c7ec54f
commit
d2c5ce5575
@ -313,7 +313,7 @@ type irefBox struct {
|
||||
}
|
||||
|
||||
type trakBox struct {
|
||||
trackID int
|
||||
track *track
|
||||
}
|
||||
|
||||
type moofBox struct {
|
||||
@ -321,7 +321,7 @@ type moofBox struct {
|
||||
}
|
||||
|
||||
type trafBox struct {
|
||||
trackID int
|
||||
track *track
|
||||
baseDataOffset int64
|
||||
moof *moof
|
||||
}
|
||||
@ -422,7 +422,11 @@ func decodeBox(ctx *decodeContext, d *decode.D, typ string) {
|
||||
d.FieldU32("current_time")
|
||||
d.FieldU32("next_track_id")
|
||||
case "trak":
|
||||
decodeBoxesWithParentData(ctx, d, &trakBox{})
|
||||
t := &track{}
|
||||
ctx.tracks = append(ctx.tracks, t)
|
||||
decodeBoxesWithParentData(ctx, d, &trakBox{
|
||||
track: t,
|
||||
})
|
||||
case "edts":
|
||||
decodeBoxes(ctx, d)
|
||||
case "elst":
|
||||
@ -482,8 +486,7 @@ func decodeBox(ctx *decodeContext, d *decode.D, typ string) {
|
||||
d.FieldFP32("track_height")
|
||||
|
||||
if t := ctx.currentTrakBox(); t != nil {
|
||||
t.trackID = trackID
|
||||
_ = ctx.currentTrack()
|
||||
t.track.id = trackID
|
||||
}
|
||||
case "mdia":
|
||||
decodeBoxes(ctx, d)
|
||||
@ -964,16 +967,17 @@ func decodeBox(ctx *decodeContext, d *decode.D, typ string) {
|
||||
case "moof":
|
||||
offset := (d.Pos() / 8) - 8
|
||||
decodeBoxesWithParentData(ctx, d, &moofBox{offset: offset})
|
||||
// Track Fragment
|
||||
case "traf":
|
||||
decodeBoxesWithParentData(ctx, d, &trafBox{})
|
||||
// Movie Fragment Header
|
||||
case "mfhd":
|
||||
case "traf": // Track Fragment
|
||||
t := &track{fragment: true}
|
||||
ctx.tracks = append(ctx.tracks, t)
|
||||
decodeBoxesWithParentData(ctx, d, &trafBox{
|
||||
track: t,
|
||||
})
|
||||
case "mfhd": // Movie Fragment Header
|
||||
d.FieldU8("version")
|
||||
d.FieldU24("flags")
|
||||
d.FieldU32("sequence_number")
|
||||
// Track Fragment Header
|
||||
case "tfhd":
|
||||
case "tfhd": // Track Fragment Header
|
||||
d.FieldU8("version")
|
||||
baseDataOffsetPresent := false
|
||||
sampleDescriptionIndexPresent := false
|
||||
@ -1016,15 +1020,14 @@ func decodeBox(ctx *decodeContext, d *decode.D, typ string) {
|
||||
}
|
||||
|
||||
if t := ctx.currentTrafBox(); t != nil {
|
||||
t.trackID = trackID
|
||||
t.track.id = trackID
|
||||
t.moof = m
|
||||
t.baseDataOffset = baseDataOffset
|
||||
}
|
||||
if t := ctx.currentTrack(); t != nil {
|
||||
t.moofs = append(t.moofs, m)
|
||||
}
|
||||
// Track Fragment Run
|
||||
case "trun":
|
||||
case "trun": // Track Fragment Run
|
||||
m := &moof{}
|
||||
if t := ctx.currentTrafBox(); t != nil {
|
||||
m = t.moof
|
||||
@ -1140,9 +1143,9 @@ func decodeBox(ctx *decodeContext, d *decode.D, typ string) {
|
||||
d.FieldU8("version")
|
||||
d.FieldU24("flags")
|
||||
d.FieldU32("mfra_size")
|
||||
|
||||
case "iloc": // HEIC image
|
||||
// TODO: item location
|
||||
// HEIC image
|
||||
case "iloc":
|
||||
version := d.FieldU8("version")
|
||||
d.FieldU24("flags")
|
||||
|
||||
|
@ -134,6 +134,7 @@ type stsz struct {
|
||||
|
||||
type track struct {
|
||||
seenHdlr bool
|
||||
fragment bool
|
||||
id int
|
||||
sampleDescriptions []sampleDescription
|
||||
subType string
|
||||
@ -154,16 +155,7 @@ type pathEntry struct {
|
||||
type decodeContext struct {
|
||||
opts format.MP4_In
|
||||
path []pathEntry
|
||||
tracks map[int]*track
|
||||
}
|
||||
|
||||
func (ctx *decodeContext) lookupTrack(id int) *track {
|
||||
t, ok := ctx.tracks[id]
|
||||
if !ok {
|
||||
t = &track{id: id}
|
||||
ctx.tracks[id] = t
|
||||
}
|
||||
return t
|
||||
tracks []*track
|
||||
}
|
||||
|
||||
func (ctx *decodeContext) isParent(typ string) bool {
|
||||
@ -211,24 +203,49 @@ func (ctx *decodeContext) currentMetaBox() *metaBox {
|
||||
|
||||
func (ctx *decodeContext) currentTrack() *track {
|
||||
if t := ctx.currentTrakBox(); t != nil {
|
||||
return ctx.lookupTrack(t.trackID)
|
||||
return t.track
|
||||
}
|
||||
if t := ctx.currentTrafBox(); t != nil {
|
||||
return ctx.lookupTrack(t.trackID)
|
||||
return t.track
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mp4Tracks(d *decode.D, ctx *decodeContext) {
|
||||
// keep track order stable
|
||||
var sortedTracks []*track
|
||||
for _, t := range ctx.tracks {
|
||||
sortedTracks = append(sortedTracks, t)
|
||||
type trackCollected struct {
|
||||
track *track
|
||||
order int
|
||||
moofss [][]*moof
|
||||
}
|
||||
slices.SortFunc(sortedTracks, func(a, b *track) int { return cmpex.Compare(a.id, b.id) })
|
||||
|
||||
var tracksCollected []*trackCollected
|
||||
tracksCollectedSeen := map[int]*trackCollected{}
|
||||
for i, t := range ctx.tracks {
|
||||
tc, ok := tracksCollectedSeen[t.id]
|
||||
if !ok {
|
||||
tc = &trackCollected{
|
||||
order: i,
|
||||
track: t,
|
||||
}
|
||||
tracksCollectedSeen[t.id] = tc
|
||||
tracksCollected = append(tracksCollected, tc)
|
||||
}
|
||||
|
||||
// TODO: error if not fragmented and seen before?
|
||||
|
||||
tc.moofss = append(tc.moofss, t.moofs)
|
||||
}
|
||||
|
||||
// sort by id then order in file
|
||||
slices.SortStableFunc(tracksCollected, func(a, b *trackCollected) int {
|
||||
if r := cmpex.Compare(a.track.id, b.track.id); r != 0 {
|
||||
return r
|
||||
}
|
||||
return cmpex.Compare(a.order, b.order)
|
||||
})
|
||||
|
||||
d.FieldArray("tracks", func(d *decode.D) {
|
||||
for _, t := range sortedTracks {
|
||||
for _, tc := range tracksCollected {
|
||||
decodeSampleRange := func(d *decode.D, t *track, decodeSample bool, dataFormat string, name string, firstBit int64, nBits int64, inArg any) {
|
||||
d.RangeFn(firstBit, nBits, func(d *decode.D) {
|
||||
if !decodeSample {
|
||||
@ -277,6 +294,8 @@ func mp4Tracks(d *decode.D, ctx *decodeContext) {
|
||||
}
|
||||
|
||||
d.FieldStruct("track", func(d *decode.D) {
|
||||
t := tc.track
|
||||
|
||||
d.FieldValueUint("id", uint64(t.id))
|
||||
|
||||
trackSDDataFormat := "unknown"
|
||||
@ -361,8 +380,6 @@ func mp4Tracks(d *decode.D, ctx *decodeContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// log.Println(logStrFn())
|
||||
|
||||
decodeSampleRange(d, t, ctx.opts.DecodeSamples, trackSDDataFormat, "sample", sampleOffset*8, stszEntry.size*8, t.formatInArg)
|
||||
|
||||
sampleOffset += stszEntry.size
|
||||
@ -373,48 +390,50 @@ func mp4Tracks(d *decode.D, ctx *decodeContext) {
|
||||
}
|
||||
|
||||
sampleNr := 0
|
||||
for _, m := range t.moofs {
|
||||
for trunNr, trun := range m.truns {
|
||||
var senc senc
|
||||
if trunNr < len(m.sencs) {
|
||||
senc = m.sencs[trunNr]
|
||||
}
|
||||
sampleOffset := m.offset + trun.dataOffset
|
||||
|
||||
for trunSampleNr, sz := range trun.samplesSizes {
|
||||
dataFormat := trackSDDataFormat
|
||||
if m.defaultSampleDescriptionIndex != 0 && m.defaultSampleDescriptionIndex-1 < len(t.sampleDescriptions) {
|
||||
sd := t.sampleDescriptions[m.defaultSampleDescriptionIndex-1]
|
||||
dataFormat = sd.dataFormat
|
||||
if sd.originalFormat != "" {
|
||||
dataFormat = sd.originalFormat
|
||||
for _, ms := range tc.moofss {
|
||||
for _, m := range ms {
|
||||
for trunNr, trun := range m.truns {
|
||||
var senc senc
|
||||
if trunNr < len(m.sencs) {
|
||||
senc = m.sencs[trunNr]
|
||||
}
|
||||
sampleOffset := m.offset + trun.dataOffset
|
||||
|
||||
for trunSampleNr, sz := range trun.samplesSizes {
|
||||
dataFormat := trackSDDataFormat
|
||||
if m.defaultSampleDescriptionIndex != 0 && m.defaultSampleDescriptionIndex-1 < len(t.sampleDescriptions) {
|
||||
sd := t.sampleDescriptions[m.defaultSampleDescriptionIndex-1]
|
||||
dataFormat = sd.dataFormat
|
||||
if sd.originalFormat != "" {
|
||||
dataFormat = sd.originalFormat
|
||||
}
|
||||
}
|
||||
|
||||
// logStrFn := func() string {
|
||||
// return fmt.Sprintf("%d: %s: %d: (%s): sz=%d %d+%d=%d",
|
||||
// t.id,
|
||||
// dataFormat,
|
||||
// sampleNr,
|
||||
// trackSDDataFormat,
|
||||
// sz,
|
||||
// m.offset,
|
||||
// m.dataOffset,
|
||||
// sampleOffset,
|
||||
// )
|
||||
// }
|
||||
|
||||
decodeSample := ctx.opts.DecodeSamples
|
||||
if trunSampleNr < len(senc.entries) {
|
||||
// TODO: encrypted
|
||||
decodeSample = false
|
||||
}
|
||||
|
||||
decodeSampleRange(d, t, decodeSample, dataFormat, "sample", sampleOffset*8, sz*8, t.formatInArg)
|
||||
|
||||
sampleOffset += sz
|
||||
sampleNr++
|
||||
}
|
||||
|
||||
// logStrFn := func() string {
|
||||
// return fmt.Sprintf("%d: %s: %d: (%s): sz=%d %d+%d=%d",
|
||||
// t.id,
|
||||
// dataFormat,
|
||||
// sampleNr,
|
||||
// trackSDDataFormat,
|
||||
// sz,
|
||||
// m.offset,
|
||||
// m.dataOffset,
|
||||
// sampleOffset,
|
||||
// )
|
||||
// }
|
||||
// log.Println(logStrFn())
|
||||
|
||||
decodeSample := ctx.opts.DecodeSamples
|
||||
if trunSampleNr < len(senc.entries) {
|
||||
// TODO: encrypted
|
||||
decodeSample = false
|
||||
}
|
||||
|
||||
decodeSampleRange(d, t, decodeSample, dataFormat, "sample", sampleOffset*8, sz*8, t.formatInArg)
|
||||
|
||||
sampleOffset += sz
|
||||
sampleNr++
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -431,7 +450,7 @@ func mp4Decode(d *decode.D) any {
|
||||
ctx := &decodeContext{
|
||||
opts: mi,
|
||||
path: []pathEntry{{typ: "root"}},
|
||||
tracks: map[int]*track{},
|
||||
tracks: []*track{},
|
||||
}
|
||||
|
||||
// TODO: nicer, validate functions without field?
|
||||
|
Loading…
Reference in New Issue
Block a user