diff --git a/format/mp4/boxes.go b/format/mp4/boxes.go index e6778e67..25aa6f89 100644 --- a/format/mp4/boxes.go +++ b/format/mp4/boxes.go @@ -4,21 +4,19 @@ package mp4 import ( "bytes" + "encoding/binary" "fmt" "time" "github.com/wader/fq/format" "github.com/wader/fq/pkg/decode" "github.com/wader/fq/pkg/scalar" + "golang.org/x/text/encoding/charmap" ) // TODO: keep track of list of sampleSize/entries instead and change sample read code const maxSampleEntryCount = 10_000_000 -var boxAliases = map[string]string{ - "styp": "ftyp", -} - const ( boxSizeRestOfFile = 0 boxSizeUse64bitSize = 1 @@ -212,7 +210,7 @@ func decodeLang(d *decode.D) string { var quicktimeEpochDate = time.Date(1904, time.January, 4, 0, 0, 0, 0, time.UTC) var quicktimeEpoch = scalar.DescriptionTimeFn(scalar.S.TryActualU, quicktimeEpochDate, time.RFC3339) -func decodeFieldMatrix(d *decode.D, name string) { +func decodeMvhdFieldMatrix(d *decode.D, name string) { d.FieldStruct(name, func(d *decode.D) { d.FieldFP32("a") d.FieldFP32("b") @@ -237,12 +235,16 @@ func decodeSampleFlags(d *decode.D) { d.FieldU16("sample_degradation_priority") } -func decodeBoxWithParentData(ctx *decodeContext, d *decode.D, parentData any) { +func decodeBoxWithParentData(ctx *decodeContext, d *decode.D, parentData any, extraTypeMappers ...scalar.Mapper) { var typ string var dataSize uint64 + typeMappers := []scalar.Mapper{boxDescriptions} + if len(extraTypeMappers) > 0 { + typeMappers = append(typeMappers, extraTypeMappers...) + } boxSize := d.FieldU32("size", boxSizeNames) - typ = d.FieldUTF8("type", 4, boxDescriptions) + typ = d.FieldStr("type", 4, charmap.ISO8859_1, typeMappers...) switch boxSize { case boxSizeRestOfFile: @@ -259,42 +261,26 @@ func decodeBoxWithParentData(ctx *decodeContext, d *decode.D, parentData any) { } - // TODO: not sure about this - switch { - case typ == "�too": - typ = "_apple_list" - case typ[0] == 0xa9: - typ = "_apple_entry" - } - - if a, ok := boxAliases[typ]; ok { - typ = a - } - if parentData != nil { ctx.path[len(ctx.path)-1].data = parentData } ctx.path = append(ctx.path, pathEntry{typ: typ, data: parentData}) - - if decodeFn, ok := boxDecoders[typ]; ok { - d.FramedFn(int64(dataSize*8), func(d *decode.D) { - decodeFn(ctx, d) - }) - } else { - d.FieldRawLen("data", int64(dataSize*8)) - } - + d.FramedFn(int64(dataSize*8), func(d *decode.D) { + decodeBox(ctx, d, typ) + }) ctx.path = ctx.path[0 : len(ctx.path)-1] } -func decodeBoxes(ctx *decodeContext, d *decode.D) { - decodeBoxesWithParentData(ctx, d, nil) +func decodeBoxes(ctx *decodeContext, d *decode.D, extraTypeMappers ...scalar.Mapper) { + decodeBoxesWithParentData(ctx, d, nil, extraTypeMappers...) } -func decodeBoxesWithParentData(ctx *decodeContext, d *decode.D, parentData any) { - d.FieldStructArrayLoop("boxes", "box", func() bool { return d.BitsLeft() >= 8*8 }, func(d *decode.D) { - decodeBoxWithParentData(ctx, d, parentData) - }) +func decodeBoxesWithParentData(ctx *decodeContext, d *decode.D, parentData any, extraTypeMappers ...scalar.Mapper) { + d.FieldStructArrayLoop("boxes", "box", + func() bool { return d.BitsLeft() >= 8*8 }, + func(d *decode.D) { + decodeBoxWithParentData(ctx, d, parentData, extraTypeMappers...) + }) if d.BitsLeft() > 0 { // "Some sample descriptions terminate with four zero bytes that are not otherwise indicated." @@ -307,8 +293,6 @@ func decodeBoxesWithParentData(ctx *decodeContext, d *decode.D, parentData any) } } -var boxDecoders map[string]func(ctx *decodeContext, d *decode.D) - type irefBox struct { version int } @@ -327,7 +311,21 @@ type trafBox struct { moof *moof } -func irefEntryDecode(ctx *decodeContext, d *decode.D) { +type metaBox struct { + subType string + keys *keysBox +} + +type keysBoxKey struct { + namespace string + name string +} + +type keysBox struct { + keys []keysBoxKey +} + +func decodeBoxIrefEntry(ctx *decodeContext, d *decode.D) { irefBox, ok := ctx.parent().data.(*irefBox) if !ok { d.FieldRawLen("data", d.BitsLeft()) @@ -349,441 +347,404 @@ func irefEntryDecode(ctx *decodeContext, d *decode.D) { }) } -func init() { - boxDecoders = map[string]func(ctx *decodeContext, d *decode.D){ - "ftyp": func(_ *decodeContext, d *decode.D) { - d.FieldUTF8("major_brand", 4) - d.FieldU32("minor_version") - numBrands := d.BitsLeft() / 8 / 4 - var i int64 - d.FieldArrayLoop("brands", func() bool { return i < numBrands }, func(d *decode.D) { - d.FieldUTF8("brand", 4, brandDescriptions, scalar.ActualTrimSpace) - i++ - }) - }, - "mvhd": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - switch version { - case 0: - d.FieldU32("creation_time", quicktimeEpoch) - d.FieldU32("modification_time", quicktimeEpoch) - d.FieldU32("time_scale") - d.FieldU32("duration") - case 1: - d.FieldU64("creation_time", quicktimeEpoch) - d.FieldU64("modification_time", quicktimeEpoch) - d.FieldU32("time_scale") - d.FieldU64("duration") - default: - return - } - d.FieldFP32("preferred_rate") - d.FieldFP16("preferred_volume") - d.FieldUTF8("reserved", 10) - decodeFieldMatrix(d, "matrix_structure") - d.FieldU32("preview_time") - d.FieldU32("preview_duration") - d.FieldU32("poster_time") - d.FieldU32("selection_time") - d.FieldU32("selection_duration") - d.FieldU32("current_time") - d.FieldU32("next_track_id") - }, - "trak": func(ctx *decodeContext, d *decode.D) { - decodeBoxesWithParentData(ctx, d, &trakBox{}) - }, - "edts": decodeBoxes, - "elst": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - var i uint64 - d.FieldStructArrayLoop("entries", "entry", func() bool { return i < entryCount }, func(d *decode.D) { - switch version { - case 0: - d.FieldS32("segment_duration") - d.FieldS32("media_time", mediaTimeNames) - case 1: - d.FieldS64("segment_duration") - d.FieldS64("media_time", mediaTimeNames) - default: - return - } - d.FieldFP32("media_rate") - i++ - }) - }, - "tref": decodeBoxes, - "tkhd": func(ctx *decodeContext, d *decode.D) { - var trackID int - version := d.FieldU8("version") - d.FieldU24("flags") - switch version { - case 0: - d.FieldU32("creation_time", quicktimeEpoch) - d.FieldU32("modification_time", quicktimeEpoch) - trackID = int(d.FieldU32("track_id")) - d.FieldU32("reserved1") - d.FieldU32("duration") - case 1: - d.FieldU64("creation_time", quicktimeEpoch) - d.FieldU64("modification_time", quicktimeEpoch) - trackID = int(d.FieldU32("track_id")) - d.FieldU32("reserved1") - d.FieldU64("duration") - default: - return - } - d.FieldRawLen("reserved2", 8*8) - d.FieldU16("layer") - d.FieldU16("alternate_group") - d.FieldFP16("volume") - d.FieldU16("reserved3") - decodeFieldMatrix(d, "matrix_structure") - d.FieldFP32("track_width") - d.FieldFP32("track_height") +func decodeBoxFtyp(d *decode.D) { + d.FieldUTF8("major_brand", 4) + d.FieldU32("minor_version") + numBrands := d.BitsLeft() / 8 / 4 + var i int64 + d.FieldArrayLoop("brands", func() bool { return i < numBrands }, func(d *decode.D) { + d.FieldUTF8("brand", 4, brandDescriptions, scalar.ActualTrimSpace) + i++ + }) +} - if t := ctx.currentTrakBox(); t != nil { - t.trackID = trackID - _ = ctx.currentTrack() - } - }, - "mdia": decodeBoxes, - "mdhd": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - // TODO: timestamps +func decodeBox(ctx *decodeContext, d *decode.D, typ string) { + switch typ { + case "ftyp": + decodeBoxFtyp(d) + case "styp": + decodeBoxFtyp(d) + case "mvhd": + version := d.FieldU8("version") + d.FieldU24("flags") + switch version { + case 0: + d.FieldU32("creation_time", quicktimeEpoch) + d.FieldU32("modification_time", quicktimeEpoch) + d.FieldU32("time_scale") + d.FieldU32("duration") + case 1: + d.FieldU64("creation_time", quicktimeEpoch) + d.FieldU64("modification_time", quicktimeEpoch) + d.FieldU32("time_scale") + d.FieldU64("duration") + default: + return + } + d.FieldFP32("preferred_rate") + d.FieldFP16("preferred_volume") + d.FieldUTF8("reserved", 10) + decodeMvhdFieldMatrix(d, "matrix_structure") + d.FieldU32("preview_time") + d.FieldU32("preview_duration") + d.FieldU32("poster_time") + d.FieldU32("selection_time") + d.FieldU32("selection_duration") + d.FieldU32("current_time") + d.FieldU32("next_track_id") + case "trak": + decodeBoxesWithParentData(ctx, d, &trakBox{}) + case "edts": + decodeBoxes(ctx, d) + case "elst": + version := d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + var i uint64 + d.FieldStructArrayLoop("entries", "entry", func() bool { return i < entryCount }, func(d *decode.D) { switch version { case 0: - d.FieldU32("creation_time", quicktimeEpoch) - d.FieldU32("modification_time", quicktimeEpoch) - d.FieldU32("time_scale") - d.FieldU32("duration") + d.FieldS32("segment_duration") + d.FieldS32("media_time", mediaTimeNames) case 1: - d.FieldU64("creation_time", quicktimeEpoch) - d.FieldU64("modification_time", quicktimeEpoch) - d.FieldU32("time_scale") - d.FieldU64("duration") + d.FieldS64("segment_duration") + d.FieldS64("media_time", mediaTimeNames) default: return } - d.FieldStrFn("language", decodeLang) - d.FieldU16("quality") - }, - "vmhd": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU16("graphicsmode") - d.FieldArray("opcolor", func(d *decode.D) { - // TODO: is FP16? - d.FieldU16("value") - d.FieldU16("value") - d.FieldU16("value") - }) - }, - "hdlr": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldUTF8NullFixedLen("component_type", 4) - subType := d.FieldUTF8("component_subtype", 4, subTypeNames, scalar.ActualTrimSpace) - d.FieldUTF8NullFixedLen("component_manufacturer", 4) - d.FieldU32("component_flags") - d.FieldU32("component_flags_mask") - // TODO: sometimes has a length prefix byte, how to know? - d.FieldUTF8NullFixedLen("component_name", int(d.BitsLeft()/8)) + d.FieldFP32("media_rate") + i++ + }) + case "tref": + decodeBoxes(ctx, d) + case "tkhd": + var trackID int + version := d.FieldU8("version") + d.FieldU24("flags") + switch version { + case 0: + d.FieldU32("creation_time", quicktimeEpoch) + d.FieldU32("modification_time", quicktimeEpoch) + trackID = int(d.FieldU32("track_id")) + d.FieldU32("reserved1") + d.FieldU32("duration") + case 1: + d.FieldU64("creation_time", quicktimeEpoch) + d.FieldU64("modification_time", quicktimeEpoch) + trackID = int(d.FieldU32("track_id")) + d.FieldU32("reserved1") + d.FieldU64("duration") + default: + return + } + d.FieldRawLen("reserved2", 8*8) + d.FieldU16("layer") + d.FieldU16("alternate_group") + d.FieldFP16("volume") + d.FieldU16("reserved3") + decodeMvhdFieldMatrix(d, "matrix_structure") + d.FieldFP32("track_width") + d.FieldFP32("track_height") - if t := ctx.currentTrack(); t != nil { - t.seenHdlr = true - // component_type seems to be all zero sometimes so can't look for "mhlr" - switch subType { - case "vide", "soun": - t.subType = subType - } + if t := ctx.currentTrakBox(); t != nil { + t.trackID = trackID + _ = ctx.currentTrack() + } + case "mdia": + decodeBoxes(ctx, d) + case "mdhd": + version := d.FieldU8("version") + d.FieldU24("flags") + // TODO: timestamps + switch version { + case 0: + d.FieldU32("creation_time", quicktimeEpoch) + d.FieldU32("modification_time", quicktimeEpoch) + d.FieldU32("time_scale") + d.FieldU32("duration") + case 1: + d.FieldU64("creation_time", quicktimeEpoch) + d.FieldU64("modification_time", quicktimeEpoch) + d.FieldU32("time_scale") + d.FieldU64("duration") + default: + return + } + d.FieldStrFn("language", decodeLang) + d.FieldU16("quality") + case "vmhd": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU16("graphicsmode") + d.FieldArray("opcolor", func(d *decode.D) { + // TODO: is FP16? + d.FieldU16("value") + d.FieldU16("value") + d.FieldU16("value") + }) + case "hdlr": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldUTF8NullFixedLen("component_type", 4) + subType := d.FieldUTF8("component_subtype", 4, subTypeNames, scalar.ActualTrimSpace) + d.FieldUTF8NullFixedLen("component_manufacturer", 4) + d.FieldU32("component_flags") + d.FieldU32("component_flags_mask") + // TODO: sometimes has a length prefix byte, how to know? + d.FieldUTF8NullFixedLen("component_name", int(d.BitsLeft()/8)) + + if t := ctx.currentTrack(); t != nil { + t.seenHdlr = true + // component_type seems to be all zero sometimes so can't look for "mhlr" + switch subType { + case "vide", "soun": + t.subType = subType } - }, - "minf": decodeBoxes, - "dinf": decodeBoxes, - "dref": func(_ *decodeContext, d *decode.D) { + } else if m := ctx.currentMetaBox(); m != nil { + m.subType = subType + } + case "minf": + decodeBoxes(ctx, d) + case "dinf": + decodeBoxes(ctx, d) + case "dref": + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + var i uint64 + d.FieldStructArrayLoop("boxes", "box", func() bool { return i < entryCount }, func(d *decode.D) { + size := d.FieldU32("size") + d.FieldUTF8("type", 4) d.FieldU8("version") d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - var i uint64 - d.FieldStructArrayLoop("boxes", "box", func() bool { return i < entryCount }, func(d *decode.D) { + dataSize := size - 12 + d.FieldRawLen("data", int64(dataSize*8)) + i++ + }) + case "stbl": + decodeBoxes(ctx, d) + case "stsd": + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + var i uint64 + // note called "boxes" here instead of "sample_descriptions" and data format is named "type". + // this is to make it easier to threat them as normal boxes + d.FieldArrayLoop("boxes", func() bool { return i < entryCount }, func(d *decode.D) { + d.FieldStruct("box", func(d *decode.D) { size := d.FieldU32("size") - d.FieldUTF8("type", 4) - d.FieldU8("version") - d.FieldU24("flags") - dataSize := size - 12 - d.FieldRawLen("data", int64(dataSize*8)) - i++ - }) - }, - "stbl": decodeBoxes, - "stsd": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - var i uint64 - // note called "boxes" here instead of "sample_descriptions" and data format is named "type". - // this is to make it easier to threat them as normal boxes - d.FieldArrayLoop("boxes", func() bool { return i < entryCount }, func(d *decode.D) { - d.FieldStruct("box", func(d *decode.D) { - size := d.FieldU32("size") - dataFormat := d.FieldUTF8("type", 4, dataFormatNames) - subType := "" - if t := ctx.currentTrack(); t != nil { - t.sampleDescriptions = append(t.sampleDescriptions, sampleDescription{ - dataFormat: dataFormat, - }) + dataFormat := d.FieldUTF8("type", 4, dataFormatNames) + subType := "" + if t := ctx.currentTrack(); t != nil { + t.sampleDescriptions = append(t.sampleDescriptions, sampleDescription{ + dataFormat: dataFormat, + }) - if t.seenHdlr { - subType = t.subType - } else { - // TODO: seems to be ffmpeg mov.c, where is this documented in specs? - // no hdlr box found, guess using dataFormat - // ex PNG samples but there is no hdlr box saying it's video, but the esds says MPEGObjectTypePNG - switch dataFormat { - case "mp4v": - subType = "vide" - case "mp4a": - subType = "soun" - } + if t.seenHdlr { + subType = t.subType + } else { + // TODO: seems to be ffmpeg mov.c, where is this documented in specs? + // no hdlr box found, guess using dataFormat + // ex PNG samples but there is no hdlr box saying it's video, but the esds says MPEGObjectTypePNG + switch dataFormat { + case "mp4v": + subType = "vide" + case "mp4a": + subType = "soun" } } + } - d.FramedFn(int64(size-8)*8, func(d *decode.D) { - d.FieldRawLen("reserved", 6*8) - d.FieldU16("data_reference_index") + d.FramedFn(int64(size-8)*8, func(d *decode.D) { + d.FieldRawLen("reserved", 6*8) + d.FieldU16("data_reference_index") + + switch subType { + case "soun", "vide": + + version := d.FieldU16("version") + d.FieldU16("revision_level") + d.FieldU32("max_packet_size") // TODO: vendor for some subtype? switch subType { - case "soun", "vide": - - version := d.FieldU16("version") - d.FieldU16("revision_level") - d.FieldU32("max_packet_size") // TODO: vendor for some subtype? - - switch subType { - case "soun": - // AudioSampleEntry - // https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-SW1 - switch version { - case 0: - d.FieldU16("num_audio_channels") - d.FieldU16("sample_size") - d.FieldU16("compression_id") - d.FieldU16("packet_size") - d.FieldFP32("sample_rate") - if d.BitsLeft() > 0 { - decodeBoxes(ctx, d) - } - case 1: - d.FieldU16("num_audio_channels") - d.FieldU16("sample_size") - d.FieldU16("compression_id") - d.FieldU16("packet_size") - d.FieldFP32("sample_rate") - d.FieldU32("samples_per_packet") - d.FieldU32("bytes_per_packet") - d.FieldU32("bytes_per_frame") - d.FieldU32("bytes_per_sample") - if d.BitsLeft() > 0 { - decodeBoxes(ctx, d) - } - case 2: - d.FieldU16("always_3") - d.FieldU16("always_16") - d.FieldU16("always_minus_2") // TODO: as in const -2? - d.FieldU16("always_0") - d.FieldU32("always_65536") - d.FieldU32("size_of_struct_only") - d.FieldF64("audio_sample_rate") - d.FieldU32("num_audio_channels") - d.FieldU32("always_7f000000") - d.FieldU32("const_bits_per_channel") - d.FieldU32("format_specific_flags") - d.FieldU32("const_bytes_per_audio_packet") - d.FieldU32("const_lpcm_frames_per_audio_packet") - if d.BitsLeft() > 0 { - decodeBoxes(ctx, d) - } - default: - d.FieldRawLen("data", d.BitsLeft()) + case "soun": + // AudioSampleEntry + // https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-SW1 + switch version { + case 0: + d.FieldU16("num_audio_channels") + d.FieldU16("sample_size") + d.FieldU16("compression_id") + d.FieldU16("packet_size") + d.FieldFP32("sample_rate") + if d.BitsLeft() > 0 { + decodeBoxes(ctx, d) } - case "vide": - // VideoSampleEntry - // TODO: version 0 and 1 same? - switch version { - case 0, 1: - d.FieldU32("temporal_quality") - d.FieldU32("spatial_quality") - d.FieldU16("width") - d.FieldU16("height") - d.FieldFP32("horizontal_resolution") - d.FieldFP32("vertical_resolution") - d.FieldU32("data_size") - d.FieldU16("frame_count") - d.FieldUTF8ShortStringFixedLen("compressor_name", 32) - d.FieldU16("depth") - d.FieldS16("color_table_id") - // TODO: if 0 decode ctab - if d.BitsLeft() > 0 { - decodeBoxes(ctx, d) - } - default: - d.FieldRawLen("data", d.BitsLeft()) + case 1: + d.FieldU16("num_audio_channels") + d.FieldU16("sample_size") + d.FieldU16("compression_id") + d.FieldU16("packet_size") + d.FieldFP32("sample_rate") + d.FieldU32("samples_per_packet") + d.FieldU32("bytes_per_packet") + d.FieldU32("bytes_per_frame") + d.FieldU32("bytes_per_sample") + if d.BitsLeft() > 0 { + decodeBoxes(ctx, d) + } + case 2: + d.FieldU16("always_3") + d.FieldU16("always_16") + d.FieldU16("always_minus_2") // TODO: as in const -2? + d.FieldU16("always_0") + d.FieldU32("always_65536") + d.FieldU32("size_of_struct_only") + d.FieldF64("audio_sample_rate") + d.FieldU32("num_audio_channels") + d.FieldU32("always_7f000000") + d.FieldU32("const_bits_per_channel") + d.FieldU32("format_specific_flags") + d.FieldU32("const_bytes_per_audio_packet") + d.FieldU32("const_lpcm_frames_per_audio_packet") + if d.BitsLeft() > 0 { + decodeBoxes(ctx, d) } - // case "hint": TODO: Hint entry default: d.FieldRawLen("data", d.BitsLeft()) } + case "vide": + // VideoSampleEntry + // TODO: version 0 and 1 same? + switch version { + case 0, 1: + d.FieldU32("temporal_quality") + d.FieldU32("spatial_quality") + d.FieldU16("width") + d.FieldU16("height") + d.FieldFP32("horizontal_resolution") + d.FieldFP32("vertical_resolution") + d.FieldU32("data_size") + d.FieldU16("frame_count") + d.FieldUTF8ShortStringFixedLen("compressor_name", 32) + d.FieldU16("depth") + d.FieldS16("color_table_id") + // TODO: if 0 decode ctab + if d.BitsLeft() > 0 { + decodeBoxes(ctx, d) + } + default: + d.FieldRawLen("data", d.BitsLeft()) + } + // case "hint": TODO: Hint entry default: d.FieldRawLen("data", d.BitsLeft()) } - - }) - }) - i++ - }) - }, - "avcC": func(ctx *decodeContext, d *decode.D) { - dv, v := d.FieldFormat("descriptor", avcDCRFormat, nil) - avcDcrOut, ok := v.(format.AvcDcrOut) - if dv != nil && !ok { - panic(fmt.Sprintf("expected AvcDcrOut got %#+v", v)) - } - if t := ctx.currentTrack(); t != nil { - t.formatInArg = format.AvcAuIn{LengthSize: avcDcrOut.LengthSize} //nolint:gosimple - } - }, - "hvcC": func(ctx *decodeContext, d *decode.D) { - dv, v := d.FieldFormat("descriptor", hevcCDCRFormat, nil) - hevcDcrOut, ok := v.(format.HevcDcrOut) - if dv != nil && !ok { - panic(fmt.Sprintf("expected HevcDcrOut got %#+v", v)) - } - if t := ctx.currentTrack(); t != nil { - t.formatInArg = format.HevcAuIn{LengthSize: hevcDcrOut.LengthSize} //nolint:gosimple - } - }, - "dfLa": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - dv, v := d.FieldFormat("descriptor", flacMetadatablocksFormat, nil) - flacMetadatablockOut, ok := v.(format.FlacMetadatablocksOut) - if dv != nil && !ok { - panic(fmt.Sprintf("expected FlacMetadatablockOut got %#+v", v)) - } - if flacMetadatablockOut.HasStreamInfo { - if t := ctx.currentTrack(); t != nil { - t.formatInArg = format.FlacFrameIn{BitsPerSample: int(flacMetadatablockOut.StreamInfo.BitsPerSample)} - } - } - }, - "dOps": func(_ *decodeContext, d *decode.D) { - d.FieldFormat("descriptor", opusPacketFrameFormat, nil) - }, - "av1C": func(_ *decodeContext, d *decode.D) { - d.FieldFormat("descriptor", av1CCRFormat, nil) - }, - "vpcC": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldFormat("descriptor", vpxCCRFormat, nil) - }, - "esds": func(ctx *decodeContext, d *decode.D) { - d.FieldU32("version") - - dv, v := d.FieldFormat("descriptor", mpegESFormat, nil) - mpegEsOut, ok := v.(format.MpegEsOut) - if dv != nil && !ok { - panic(fmt.Sprintf("expected mpegEsOut got %#+v", v)) - } - - if t := ctx.currentTrack(); t != nil && len(mpegEsOut.DecoderConfigs) > 0 { - dc := mpegEsOut.DecoderConfigs[0] - t.objectType = dc.ObjectType - t.formatInArg = format.AACFrameIn{ObjectType: dc.ASCObjectType} - } - }, - "stts": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - numEntries := d.FieldU32("entry_count") - var i uint64 - d.FieldStructArrayLoop("entries", "entry", func() bool { return i < numEntries }, func(d *decode.D) { - d.FieldU32("count") - d.FieldU32("delta") - i++ - }) - }, - "stsc": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - var i uint64 - d.FieldStructArrayLoop("entries", "entry", func() bool { return i < entryCount }, func(d *decode.D) { - firstChunk := uint32(d.FieldU32("first_chunk")) - samplesPerChunk := uint32(d.FieldU32("samples_per_chunk")) - d.FieldU32("sample_description_id") - - if t := ctx.currentTrack(); t != nil { - t.stsc = append(t.stsc, stsc{ - firstChunk: int(firstChunk), - samplesPerChunk: int(samplesPerChunk), - }) - } - i++ - }) - }, - "stsz": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - // TODO: bytes_per_sample from audio stsd? - sampleSize := d.FieldU32("sample_size") - entryCount := d.FieldU32("entry_count") - - t := ctx.currentTrack() - - if t != nil && len(t.stsz) > 0 { - d.Errorf("multiple stsz or stz2 boxes") - } - if sampleSize == 0 { - var i uint64 - d.FieldArrayLoop("entries", func() bool { return i < entryCount }, func(d *decode.D) { - size := uint32(d.FieldU32("size")) - if t != nil { - t.stsz = append(t.stsz, stsz{ - size: int64(size), - count: 1, - }) + default: + d.FieldRawLen("data", d.BitsLeft()) } - i++ + }) - } else { - if t != nil { - t.stsz = append(t.stsz, stsz{ - size: int64(sampleSize), - count: int(entryCount), - }) - } + }) + i++ + }) + case "avcC": + dv, v := d.FieldFormat("descriptor", avcDCRFormat, nil) + avcDcrOut, ok := v.(format.AvcDcrOut) + if dv != nil && !ok { + panic(fmt.Sprintf("expected AvcDcrOut got %#+v", v)) + } + if t := ctx.currentTrack(); t != nil { + t.formatInArg = format.AvcAuIn{LengthSize: avcDcrOut.LengthSize} //nolint:gosimple + } + case "hvcC": + dv, v := d.FieldFormat("descriptor", hevcCDCRFormat, nil) + hevcDcrOut, ok := v.(format.HevcDcrOut) + if dv != nil && !ok { + panic(fmt.Sprintf("expected HevcDcrOut got %#+v", v)) + } + if t := ctx.currentTrack(); t != nil { + t.formatInArg = format.HevcAuIn{LengthSize: hevcDcrOut.LengthSize} //nolint:gosimple + } + case "dfLa": + d.FieldU8("version") + d.FieldU24("flags") + dv, v := d.FieldFormat("descriptor", flacMetadatablocksFormat, nil) + flacMetadatablockOut, ok := v.(format.FlacMetadatablocksOut) + if dv != nil && !ok { + panic(fmt.Sprintf("expected FlacMetadatablockOut got %#+v", v)) + } + if flacMetadatablockOut.HasStreamInfo { + if t := ctx.currentTrack(); t != nil { + t.formatInArg = format.FlacFrameIn{BitsPerSample: int(flacMetadatablockOut.StreamInfo.BitsPerSample)} } - }, - "stz2": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - fieldSize := d.FieldU32("field_size") - if fieldSize > 16 { - d.Errorf("field_size %d > 16", fieldSize) + } + case "dOps": + d.FieldFormat("descriptor", opusPacketFrameFormat, nil) + case "av1C": + d.FieldFormat("descriptor", av1CCRFormat, nil) + case "vpcC": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldFormat("descriptor", vpxCCRFormat, nil) + case "esds": + d.FieldU32("version") + + dv, v := d.FieldFormat("descriptor", mpegESFormat, nil) + mpegEsOut, ok := v.(format.MpegEsOut) + if dv != nil && !ok { + panic(fmt.Sprintf("expected mpegEsOut got %#+v", v)) + } + + if t := ctx.currentTrack(); t != nil && len(mpegEsOut.DecoderConfigs) > 0 { + dc := mpegEsOut.DecoderConfigs[0] + t.objectType = dc.ObjectType + t.formatInArg = format.AACFrameIn{ObjectType: dc.ASCObjectType} + } + case "stts": + d.FieldU8("version") + d.FieldU24("flags") + numEntries := d.FieldU32("entry_count") + var i uint64 + d.FieldStructArrayLoop("entries", "entry", func() bool { return i < numEntries }, func(d *decode.D) { + d.FieldU32("count") + d.FieldU32("delta") + i++ + }) + case "stsc": + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + var i uint64 + d.FieldStructArrayLoop("entries", "entry", func() bool { return i < entryCount }, func(d *decode.D) { + firstChunk := uint32(d.FieldU32("first_chunk")) + samplesPerChunk := uint32(d.FieldU32("samples_per_chunk")) + d.FieldU32("sample_description_id") + + if t := ctx.currentTrack(); t != nil { + t.stsc = append(t.stsc, stsc{ + firstChunk: int(firstChunk), + samplesPerChunk: int(samplesPerChunk), + }) } - entryCount := d.FieldU32("entry_count") + i++ + }) + case "stsz": + d.FieldU8("version") + d.FieldU24("flags") + // TODO: bytes_per_sample from audio stsd? + sampleSize := d.FieldU32("sample_size") + entryCount := d.FieldU32("entry_count") + + t := ctx.currentTrack() + + if t != nil && len(t.stsz) > 0 { + d.Errorf("multiple stsz or stz2 boxes") + } + if sampleSize == 0 { var i uint64 - t := ctx.currentTrack() - if t != nil && len(t.stsz) > 0 { - d.Errorf("multiple stsz or stz2 boxes") - } d.FieldArrayLoop("entries", func() bool { return i < entryCount }, func(d *decode.D) { - size := uint32(d.FieldU("size", int(fieldSize))) + size := uint32(d.FieldU32("size")) if t != nil { t.stsz = append(t.stsz, stsz{ size: int64(size), @@ -792,839 +753,864 @@ func init() { } i++ }) - }, - "stco": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - var i uint64 - t := ctx.currentTrack() - d.FieldArrayLoop("entries", func() bool { return i < entryCount }, func(d *decode.D) { - chunkOffset := d.FieldU32("chunk_offset") - if t != nil { - t.stco = append(t.stco, int64(chunkOffset)) - } - i++ - }) - }, - "stss": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - d.FieldArray("entries", func(d *decode.D) { - for i := uint64(0); i < entryCount; i++ { - d.FieldU32("sample_number") - } - }) - }, - "sdtp": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - // TODO: should be count from stsz - // TODO: can we know count here or do we need to defer decoding somehow? - d.FieldArray("entries", func(d *decode.D) { - for d.NotEnd() { - d.FieldStruct("entry", func(d *decode.D) { - d.FieldU2("reserved") - values := scalar.UToSymStr{ - 0: "unknown", - 1: "yes", - 2: "no", - } - d.FieldU2("sample_depends_on", values) - d.FieldU2("sample_is_depended_on", values) - d.FieldU2("sample_has_redundancy", values) - }) - } - }) - }, - "ctts": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - var i uint64 - d.FieldStructArrayLoop("entries", "entry", func() bool { return i < entryCount }, func(d *decode.D) { - d.FieldS32("sample_count") - d.FieldS32("sample_offset") - i++ - }) - }, + } else { + if t != nil { + t.stsz = append(t.stsz, stsz{ + size: int64(sampleSize), + count: int(entryCount), + }) + } + } + case "stz2": + d.FieldU8("version") + d.FieldU24("flags") + fieldSize := d.FieldU32("field_size") + if fieldSize > 16 { + d.Errorf("field_size %d > 16", fieldSize) + } + entryCount := d.FieldU32("entry_count") + var i uint64 + t := ctx.currentTrack() + if t != nil && len(t.stsz) > 0 { + d.Errorf("multiple stsz or stz2 boxes") + } + d.FieldArrayLoop("entries", func() bool { return i < entryCount }, func(d *decode.D) { + size := uint32(d.FieldU("size", int(fieldSize))) + if t != nil { + t.stsz = append(t.stsz, stsz{ + size: int64(size), + count: 1, + }) + } + i++ + }) + case "stco": + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + var i uint64 + t := ctx.currentTrack() + d.FieldArrayLoop("entries", func() bool { return i < entryCount }, func(d *decode.D) { + chunkOffset := d.FieldU32("chunk_offset") + if t != nil { + t.stco = append(t.stco, int64(chunkOffset)) + } + i++ + }) + case "stss": + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + d.FieldArray("entries", func(d *decode.D) { + for i := uint64(0); i < entryCount; i++ { + d.FieldU32("sample_number") + } + }) + case "sdtp": + d.FieldU8("version") + d.FieldU24("flags") + // TODO: should be count from stsz + // TODO: can we know count here or do we need to defer decoding somehow? + d.FieldArray("entries", func(d *decode.D) { + for d.NotEnd() { + d.FieldStruct("entry", func(d *decode.D) { + d.FieldU2("reserved") + values := scalar.UToSymStr{ + 0: "unknown", + 1: "yes", + 2: "no", + } + d.FieldU2("sample_depends_on", values) + d.FieldU2("sample_is_depended_on", values) + d.FieldU2("sample_has_redundancy", values) + }) + } + }) + case "ctts": + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + var i uint64 + d.FieldStructArrayLoop("entries", "entry", func() bool { return i < entryCount }, func(d *decode.D) { + d.FieldS32("sample_count") + d.FieldS32("sample_offset") + i++ + }) // TODO: refactor: merge with stco? - "co64": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - var i uint64 - t := ctx.currentTrack() - d.FieldArrayLoop("entries", func() bool { return i < entryCount }, func(d *decode.D) { - offset := d.FieldU64("offset") - if t != nil { - t.stco = append(t.stco, int64(offset)) - } - i++ - }) - }, - "sidx": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("reference_id") - d.FieldU32("timescale") - if version == 0 { - d.FieldU32("pts") - d.FieldU32("offset") - } else { - d.FieldU64("pts") - d.FieldU64("offset") + case "co64": + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + var i uint64 + t := ctx.currentTrack() + d.FieldArrayLoop("entries", func() bool { return i < entryCount }, func(d *decode.D) { + offset := d.FieldU64("offset") + if t != nil { + t.stco = append(t.stco, int64(offset)) } - d.FieldU16("reserved") - numEntries := d.FieldU16("entry_count") - var i uint64 - d.FieldStructArrayLoop("entries", "entry", func() bool { return i < numEntries }, func(d *decode.D) { - d.FieldU1("reference_type") - d.FieldU31("size") - d.FieldU32("duration") - d.FieldU1("starts_with_sap") - d.FieldU3("sap_type") - d.FieldU28("sap_delta_time") - i++ - }) - }, - "udta": decodeBoxes, - "meta": func(ctx *decodeContext, d *decode.D) { - // TODO: meta box sometimes has a 4 byte unknown field? (flag/version?) - maybeFlags := d.PeekBits(32) - if maybeFlags == 0 { - // TODO: rename? - d.FieldU32("maybe_flags") + i++ + }) + case "sidx": + version := d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("reference_id") + d.FieldU32("timescale") + if version == 0 { + d.FieldU32("pts") + d.FieldU32("offset") + } else { + d.FieldU64("pts") + d.FieldU64("offset") + } + d.FieldU16("reserved") + numEntries := d.FieldU16("entry_count") + var i uint64 + d.FieldStructArrayLoop("entries", "entry", func() bool { return i < numEntries }, func(d *decode.D) { + d.FieldU1("reference_type") + d.FieldU31("size") + d.FieldU32("duration") + d.FieldU1("starts_with_sap") + d.FieldU3("sap_type") + d.FieldU28("sap_delta_time") + i++ + }) + case "udta": + decodeBoxes(ctx, d) + case "meta": + // TODO: meta box sometimes has a 4 byte unknown field? (flag/version?) + maybeFlags := d.PeekBits(32) + if maybeFlags == 0 { + // TODO: rename? + d.FieldU32("maybe_flags") + } + decodeBoxesWithParentData(ctx, d, &metaBox{}) + case "ilst": + if mb := ctx.currentMetaBox(); mb != nil && mb.keys != nil && len(mb.keys.keys) > 0 { + // meta data box has keys box, sym map based on keys + // TODO: namespace? + var b [4]byte + typeSymMapper := scalar.StrToSymStr{} + for k, v := range mb.keys.keys { + // +1 as they seem to be counted from 1 + binary.BigEndian.PutUint32(b[:], uint32(k+1)) + typeSymMapper[string(b[:])] = v.name } + + decodeBoxes(ctx, d, typeSymMapper) + } else { decodeBoxes(ctx, d) - }, - "ilst": decodeBoxes, - "_apple_list": decodeBoxes, - "_apple_entry": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") + } + case "data": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("reserved") + if ctx.isParent("covr") { + d.FieldFormatOrRawLen("data", d.BitsLeft(), imageFormat, nil) + } else { d.FieldUTF8("data", int(d.BitsLeft()/8)) - }, - "data": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("reserved") - if ctx.isParent("covr") { - d.FieldFormatOrRawLen("data", d.BitsLeft(), imageFormat, nil) - } else { - d.FieldUTF8("data", int(d.BitsLeft()/8)) - } - }, - "moov": decodeBoxes, - "moof": func(ctx *decodeContext, d *decode.D) { - offset := (d.Pos() / 8) - 8 - decodeBoxesWithParentData(ctx, d, &moofBox{offset: offset}) - }, + } + case "moov": + decodeBoxes(ctx, d) + case "moof": + offset := (d.Pos() / 8) - 8 + decodeBoxesWithParentData(ctx, d, &moofBox{offset: offset}) // Track Fragment - "traf": func(ctx *decodeContext, d *decode.D) { - decodeBoxesWithParentData(ctx, d, &trafBox{}) - }, + case "traf": + decodeBoxesWithParentData(ctx, d, &trafBox{}) // Movie Fragment Header - "mfhd": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("sequence_number") - }, + case "mfhd": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("sequence_number") // Track Fragment Header - "tfhd": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - baseDataOffsetPresent := false - sampleDescriptionIndexPresent := false - defaultSampleDurationPresent := false - defaultSampleSizePresent := false - defaultSampleFlagsPresent := false - d.FieldStruct("flags", func(d *decode.D) { - d.FieldU7("unused0") - d.FieldBool("duration_is_empty") - d.FieldU10("unused1") - defaultSampleFlagsPresent = d.FieldBool("default_sample_flags_present") - defaultSampleSizePresent = d.FieldBool("default_sample_size_present") - defaultSampleDurationPresent = d.FieldBool("default_sample_duration_present") - d.FieldU1("unused2") - sampleDescriptionIndexPresent = d.FieldBool("sample_description_index_present") - baseDataOffsetPresent = d.FieldBool("base_data_offset_present") - }) - trackID := int(d.FieldU32("track_id")) + case "tfhd": + d.FieldU8("version") + baseDataOffsetPresent := false + sampleDescriptionIndexPresent := false + defaultSampleDurationPresent := false + defaultSampleSizePresent := false + defaultSampleFlagsPresent := false + d.FieldStruct("flags", func(d *decode.D) { + d.FieldU7("unused0") + d.FieldBool("duration_is_empty") + d.FieldU10("unused1") + defaultSampleFlagsPresent = d.FieldBool("default_sample_flags_present") + defaultSampleSizePresent = d.FieldBool("default_sample_size_present") + defaultSampleDurationPresent = d.FieldBool("default_sample_duration_present") + d.FieldU1("unused2") + sampleDescriptionIndexPresent = d.FieldBool("sample_description_index_present") + baseDataOffsetPresent = d.FieldBool("base_data_offset_present") + }) + trackID := int(d.FieldU32("track_id")) - m := &moof{} - if mb := ctx.currentMoofBox(); mb != nil { - m.offset = mb.offset - } + m := &moof{} + if mb := ctx.currentMoofBox(); mb != nil { + m.offset = mb.offset + } - baseDataOffset := int64(0) - if baseDataOffsetPresent { - baseDataOffset = int64(d.FieldU64("base_data_offset")) - } - if sampleDescriptionIndexPresent { - m.defaultSampleDescriptionIndex = int(d.FieldU32("sample_description_index")) - } - if defaultSampleDurationPresent { - d.FieldU32("default_sample_duration") - } - if defaultSampleSizePresent { - m.defaultSampleSize = int64(d.FieldU32("default_sample_size")) - } - if defaultSampleFlagsPresent { - d.FieldStruct("default_sample_flags", decodeSampleFlags) - } - - if t := ctx.currentTrafBox(); t != nil { - t.trackID = trackID - t.moof = m - t.baseDataOffset = baseDataOffset - } - if t := ctx.currentTrack(); t != nil { - t.moofs = append(t.moofs, m) - } - }, - // Track Fragment Run - "trun": func(ctx *decodeContext, d *decode.D) { - m := &moof{} - if t := ctx.currentTrafBox(); t != nil { - m = t.moof - } - - d.FieldU8("version") - sampleCompositionTimeOffsetsPresent := false - sampleFlagsPresent := false - sampleSizePresent := false - sampleDurationPresent := false - firstSampleFlagsPresent := false - dataOffsetPresent := false - d.FieldStruct("flags", func(d *decode.D) { - d.FieldU12("unused0") - sampleCompositionTimeOffsetsPresent = d.FieldBool("sample_composition_time_offsets_present") - sampleFlagsPresent = d.FieldBool("sample_flags_present") - sampleSizePresent = d.FieldBool("sample_size_present") - sampleDurationPresent = d.FieldBool("sample_duration_present") - d.FieldU5("unused1") - firstSampleFlagsPresent = d.FieldBool("first_sample_flags_present") - d.FieldU1("unused2") - dataOffsetPresent = d.FieldBool("data_offset_present") - }) - sampleCount := d.FieldU32("sample_count") - dataOffset := int64(0) - if dataOffsetPresent { - dataOffset = d.FieldS32("data_offset") - } - if firstSampleFlagsPresent { - d.FieldStruct("first_sample_flags", decodeSampleFlags) - } - - if sampleCount > maxSampleEntryCount { - d.Errorf("too many sample trun entries %d > %d", sampleCount, maxSampleEntryCount) - } - - t := trun{ - dataOffset: dataOffset, - } - d.FieldArray("samples", func(d *decode.D) { - for i := uint64(0); i < sampleCount; i++ { - sampleSize := m.defaultSampleSize - d.FieldStruct("sample", func(d *decode.D) { - if sampleDurationPresent { - d.FieldU32("sample_duration") - } - if sampleSizePresent { - sampleSize = int64(d.FieldU32("sample_size")) - } - if sampleFlagsPresent { - d.FieldStruct("sample_flags", decodeSampleFlags) - } - if sampleCompositionTimeOffsetsPresent { - d.FieldU32("sample_composition_time_offset") - } - }) - - t.samplesSizes = append(t.samplesSizes, sampleSize) - } - }) - - m.truns = append(m.truns, t) - }, - "tfdt": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - if version == 1 { - d.FieldU64("start_time") - } else { - d.FieldU32("start_time") - } - }, - "mvex": decodeBoxes, - "trex": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("track_id") - d.FieldU32("default_sample_description_index") + baseDataOffset := int64(0) + if baseDataOffsetPresent { + baseDataOffset = int64(d.FieldU64("base_data_offset")) + } + if sampleDescriptionIndexPresent { + m.defaultSampleDescriptionIndex = int(d.FieldU32("sample_description_index")) + } + if defaultSampleDurationPresent { d.FieldU32("default_sample_duration") - d.FieldU32("default_sample_size") + } + if defaultSampleSizePresent { + m.defaultSampleSize = int64(d.FieldU32("default_sample_size")) + } + if defaultSampleFlagsPresent { d.FieldStruct("default_sample_flags", decodeSampleFlags) - }, - "mfra": decodeBoxes, - "tfra": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("track_id") - d.FieldU26("reserved") - lengthSizeOfTrafNum := d.FieldU2("length_size_of_traf_num") - sampleLengthSizeOfTrunNum := d.FieldU2("sample_length_size_of_trun_num") - lengthSizeOfSampleNum := d.FieldU2("length_size_of_sample_num") - entryCount := d.FieldU32("entry_count") - d.FieldArray("entries", func(d *decode.D) { - for i := uint64(0); i < entryCount; i++ { - d.FieldStruct("entry", func(d *decode.D) { - if version == 1 { - d.FieldU64("time") - d.FieldU64("moof_offset") - } else { - d.FieldU32("time") - d.FieldU32("moof_offset") - } - d.FieldU("traf_number", int(lengthSizeOfTrafNum+1)*8) - d.FieldU("trun_number", int(sampleLengthSizeOfTrunNum+1)*8) - d.FieldU("sample_number", int(lengthSizeOfSampleNum+1)*8) - }) - } - }) - }, - "mfro": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("mfra_size") - }, + } + + if t := ctx.currentTrafBox(); t != nil { + t.trackID = trackID + t.moof = m + t.baseDataOffset = baseDataOffset + } + if t := ctx.currentTrack(); t != nil { + t.moofs = append(t.moofs, m) + } + // Track Fragment Run + case "trun": + m := &moof{} + if t := ctx.currentTrafBox(); t != nil { + m = t.moof + } + + d.FieldU8("version") + sampleCompositionTimeOffsetsPresent := false + sampleFlagsPresent := false + sampleSizePresent := false + sampleDurationPresent := false + firstSampleFlagsPresent := false + dataOffsetPresent := false + d.FieldStruct("flags", func(d *decode.D) { + d.FieldU12("unused0") + sampleCompositionTimeOffsetsPresent = d.FieldBool("sample_composition_time_offsets_present") + sampleFlagsPresent = d.FieldBool("sample_flags_present") + sampleSizePresent = d.FieldBool("sample_size_present") + sampleDurationPresent = d.FieldBool("sample_duration_present") + d.FieldU5("unused1") + firstSampleFlagsPresent = d.FieldBool("first_sample_flags_present") + d.FieldU1("unused2") + dataOffsetPresent = d.FieldBool("data_offset_present") + }) + sampleCount := d.FieldU32("sample_count") + dataOffset := int64(0) + if dataOffsetPresent { + dataOffset = d.FieldS32("data_offset") + } + if firstSampleFlagsPresent { + d.FieldStruct("first_sample_flags", decodeSampleFlags) + } + + if sampleCount > maxSampleEntryCount { + d.Errorf("too many sample trun entries %d > %d", sampleCount, maxSampleEntryCount) + } + + t := trun{ + dataOffset: dataOffset, + } + d.FieldArray("samples", func(d *decode.D) { + for i := uint64(0); i < sampleCount; i++ { + sampleSize := m.defaultSampleSize + d.FieldStruct("sample", func(d *decode.D) { + if sampleDurationPresent { + d.FieldU32("sample_duration") + } + if sampleSizePresent { + sampleSize = int64(d.FieldU32("sample_size")) + } + if sampleFlagsPresent { + d.FieldStruct("sample_flags", decodeSampleFlags) + } + if sampleCompositionTimeOffsetsPresent { + d.FieldU32("sample_composition_time_offset") + } + }) + + t.samplesSizes = append(t.samplesSizes, sampleSize) + } + }) + + m.truns = append(m.truns, t) + case "tfdt": + version := d.FieldU8("version") + d.FieldU24("flags") + if version == 1 { + d.FieldU64("start_time") + } else { + d.FieldU32("start_time") + } + case "mvex": + decodeBoxes(ctx, d) + case "trex": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("track_id") + d.FieldU32("default_sample_description_index") + d.FieldU32("default_sample_duration") + d.FieldU32("default_sample_size") + d.FieldStruct("default_sample_flags", decodeSampleFlags) + case "mfra": + decodeBoxes(ctx, d) + case "tfra": + version := d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("track_id") + d.FieldU26("reserved") + lengthSizeOfTrafNum := d.FieldU2("length_size_of_traf_num") + sampleLengthSizeOfTrunNum := d.FieldU2("sample_length_size_of_trun_num") + lengthSizeOfSampleNum := d.FieldU2("length_size_of_sample_num") + entryCount := d.FieldU32("entry_count") + d.FieldArray("entries", func(d *decode.D) { + for i := uint64(0); i < entryCount; i++ { + d.FieldStruct("entry", func(d *decode.D) { + if version == 1 { + d.FieldU64("time") + d.FieldU64("moof_offset") + } else { + d.FieldU32("time") + d.FieldU32("moof_offset") + } + d.FieldU("traf_number", int(lengthSizeOfTrafNum+1)*8) + d.FieldU("trun_number", int(sampleLengthSizeOfTrunNum+1)*8) + d.FieldU("sample_number", int(lengthSizeOfSampleNum+1)*8) + }) + } + }) + case "mfro": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("mfra_size") // TODO: item location // HEIC image - "iloc": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") + case "iloc": + version := d.FieldU8("version") + d.FieldU24("flags") - offsetSize := d.FieldU4("offset_size") - lengthSize := d.FieldU4("length_size") - baseOffsetSize := d.FieldU4("base_offset_size") - var indexSize uint64 - switch version { - case 1, 2: - indexSize = d.FieldU4("index_size") - default: - d.FieldU4("reserved") - } - var itemCount uint64 - if version < 2 { - itemCount = d.FieldU16("item_count") - } else { - itemCount = d.FieldU32("item_count") - } - d.FieldArray("items", func(d *decode.D) { - for i := uint64(0); i < itemCount; i++ { - d.FieldStruct("item", func(d *decode.D) { - switch version { - case 0, 1: - d.FieldU16("id") - case 2: - d.FieldU32("id") + offsetSize := d.FieldU4("offset_size") + lengthSize := d.FieldU4("length_size") + baseOffsetSize := d.FieldU4("base_offset_size") + var indexSize uint64 + switch version { + case 1, 2: + indexSize = d.FieldU4("index_size") + default: + d.FieldU4("reserved") + } + var itemCount uint64 + if version < 2 { + itemCount = d.FieldU16("item_count") + } else { + itemCount = d.FieldU32("item_count") + } + d.FieldArray("items", func(d *decode.D) { + for i := uint64(0); i < itemCount; i++ { + d.FieldStruct("item", func(d *decode.D) { + switch version { + case 0, 1: + d.FieldU16("id") + case 2: + d.FieldU32("id") + } + switch version { + case 1, 2: + d.FieldU12("reserved") + d.FieldU4("construction_method") + } + d.FieldU16("data_reference_index") + d.FieldU("base_offset", int(baseOffsetSize)*8) + extentCount := d.FieldU16("extent_count") + d.FieldArray("extends", func(d *decode.D) { + for i := uint64(0); i < extentCount; i++ { + d.FieldStruct("extent", func(d *decode.D) { + if (version == 1 || version == 2) && indexSize > 0 { + d.FieldU("index", int(offsetSize)*8) + } + d.FieldU("offset", int(offsetSize)*8) + d.FieldU("length", int(lengthSize)*8) + }) } - switch version { - case 1, 2: - d.FieldU12("reserved") - d.FieldU4("construction_method") - } - d.FieldU16("data_reference_index") - d.FieldU("base_offset", int(baseOffsetSize)*8) - extentCount := d.FieldU16("extent_count") - d.FieldArray("extends", func(d *decode.D) { - for i := uint64(0); i < extentCount; i++ { - d.FieldStruct("extent", func(d *decode.D) { - if (version == 1 || version == 2) && indexSize > 0 { - d.FieldU("index", int(offsetSize)*8) - } - d.FieldU("offset", int(offsetSize)*8) - d.FieldU("length", int(lengthSize)*8) + }) + }) + } + }) + case "infe": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU16("id") + d.FieldU16("protection_index") + d.FieldUTF8Null("item_name") + // TODO: really optional? seems so + if d.NotEnd() { + d.FieldUTF8Null("content_type") + } + if d.NotEnd() { + d.FieldUTF8Null("content_encoding") + } + case "iinf": + version := d.FieldU8("version") + d.FieldU24("flags") + if version == 0 { + _ = d.FieldU16("entry_count") + decodeBoxes(ctx, d) + } else { + d.FieldRawLen("data", d.BitsLeft()) + } + case "iprp": + decodeBoxes(ctx, d) + case "ipco": + decodeBoxes(ctx, d) + case "ID32": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU1("pad") + // ISO-639-2/T as 3*5 bit integers - 0x60 + d.FieldStrFn("language", func(d *decode.D) string { + s := "" + for i := 0; i < 3; i++ { + s += fmt.Sprintf("%c", int(d.U5())+0x60) + } + return s + }) + d.FieldFormat("data", id3v2Format, nil) + case "mehd": + version := d.FieldU8("version") + d.FieldU24("flags") + switch version { + case 0: + d.FieldU32("fragment_duration") + case 1: + d.FieldU64("fragment_duration") + } + case "pssh": + var ( + systemIDCommon = [16]byte{0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b} + systemIDWidevine = [16]byte{0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed} + systemIDPlayReady = [16]byte{0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95} + systemIDFairPlay = [16]byte{0x94, 0xce, 0x86, 0xfb, 0x07, 0xff, 0x4f, 0x43, 0xad, 0xb8, 0x93, 0xd2, 0xfa, 0x96, 0x8c, 0xa2} + ) + systemIDNames := scalar.BytesToScalar{ + {Bytes: systemIDCommon[:], Scalar: scalar.S{Sym: "common"}}, + {Bytes: systemIDWidevine[:], Scalar: scalar.S{Sym: "widevine"}}, + {Bytes: systemIDPlayReady[:], Scalar: scalar.S{Sym: "playready"}}, + {Bytes: systemIDFairPlay[:], Scalar: scalar.S{Sym: "fairplay"}}, + } + + version := d.FieldU8("version") + d.FieldU24("flags") + systemIDBR := d.FieldRawLen("system_id", 16*8, systemIDNames) + // TODO: make nicer + systemID := d.ReadAllBits(systemIDBR) + switch version { + case 0: + case 1: + kidCount := d.FieldU32("kid_count") + d.FieldArray("kids", func(d *decode.D) { + for i := uint64(0); i < kidCount; i++ { + d.FieldRawLen("kid", 16*8) + } + }) + } + dataLen := d.FieldU32("data_size") + + switch { + case bytes.Equal(systemID, systemIDWidevine[:]): + d.FieldFormatLen("data", int64(dataLen)*8, protoBufWidevineFormat, nil) + case bytes.Equal(systemID, systemIDPlayReady[:]): + d.FieldFormatLen("data", int64(dataLen)*8, psshPlayreadyFormat, nil) + case systemID == nil: + fallthrough + default: + d.FieldRawLen("data", int64(dataLen)*8) + } + case "sinf": + decodeBoxes(ctx, d) + case "frma": + format := d.FieldUTF8("format", 4) + + // set to original data format + // TODO: how to handle multiple descriptors? track current? + if t := ctx.currentTrack(); t != nil && len(t.sampleDescriptions) > 0 { + t.sampleDescriptions[0].originalFormat = format + } + case "schm": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldUTF8("encryption_type", 4) + d.FieldU16("encryption_version") + if d.BitsLeft() > 0 { + d.FieldUTF8("uri", int(d.BitsLeft())/8) + } + case "schi": + decodeBoxes(ctx, d) + case "btrt": + d.FieldU32("decoding_buffer_size") + d.FieldU32("max_bitrate") + d.FieldU32("avg_bitrate") + case "pasp": + d.FieldU32("h_spacing") + d.FieldU32("v_spacing") + case "uuid": + d.FieldRawLen("uuid", 16*8, scalar.RawUUID, uuidNames) + d.FieldRawLen("data", d.BitsLeft()) + case "keys": + mb := ctx.currentMetaBox() + var kb *keysBox + if mb != nil { + kb = &keysBox{} + mb.keys = kb + } + + d.FieldU8("version") + d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + d.FieldArray("entries", func(d *decode.D) { + for i := uint64(0); i < entryCount; i++ { + d.FieldStruct("entry", func(d *decode.D) { + keySize := d.FieldU32("key_size") + namespace := d.FieldUTF8("key_namespace", 4) + name := d.FieldUTF8("key_name", int(keySize)-8) + if kb != nil { + kb.keys = append(kb.keys, keysBoxKey{ + namespace: namespace, + name: name, + }) + } + }) + } + }) + case "wave": + decodeBoxes(ctx, d) + case "saiz": + d.FieldU8("version") + flags := d.FieldU24("flags") + if flags&0b1 != 0 { + d.FieldU32("aux_info_type") + d.FieldU32("aux_info_type_parameter") + } + defaultSampleInfoSize := d.FieldU8("default_sample_info_size") + sampleCount := d.FieldU32("sample_count") + if defaultSampleInfoSize == 0 { + d.FieldArray("sample_size_info_table", func(d *decode.D) { + for i := uint64(0); i < sampleCount; i++ { + d.FieldU8("sample_size") + } + }) + } + case "sgpd": + version := d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("grouping_type") + var defaultLength uint64 + if version == 1 { + defaultLength = d.FieldU32("default_length") + } + if version >= 2 { + d.FieldU32("default_sample_description_index") + } + entryCount := d.FieldU32("entry_count") + d.FieldArray("entries", func(d *decode.D) { + for i := uint64(0); i < entryCount; i++ { + entryLen := defaultLength + if version == 1 { + if defaultLength == 0 { + entryLen = d.FieldU32("description_length") + } else if entryLen == 0 { + d.Fatalf("sgpd groups entry len <= 0 version 1") + } + } else if entryLen == 0 { + d.Fatalf("sgpd groups entry len <= 0") + } + d.FieldRawLen("data", int64(entryLen)*8) + } + }) + case "sbgp": + version := d.FieldU8("version") + d.FieldU24("flags") + + d.FieldU32("grouping_type") + if version == 1 { + d.FieldU32("grouping_type_parameter") + } + entryCount := d.FieldU32("entry_count") + d.FieldArray("entries", func(d *decode.D) { + for i := uint64(0); i < entryCount; i++ { + d.FieldStruct("entry", func(d *decode.D) { + d.FieldU32("sample_count") + d.FieldU32("group_description_index") + }) + } + }) + case "saio": + version := d.FieldU8("version") + flags := d.FieldU24("flags") + + if flags&0b1 != 0 { + d.FieldU32("aux_info_type") + d.FieldU32("aux_info_type_parameter") + } + entryCount := d.FieldU32("entry_count") + d.FieldArray("entries", func(d *decode.D) { + for i := uint64(0); i < entryCount; i++ { + if version == 0 { + d.FieldU32("offset") + } else { + d.FieldU64("offset") + } + } + }) + case "senc": + d.FieldU8("version") + flags := d.FieldU24("flags") + + t := ctx.currentTrack() + if t == nil { + // need to know iv size + return + } + m := &moof{} + if t := ctx.currentTrafBox(); t != nil { + m = t.moof + } + + s := senc{} + sampleCount := d.FieldU32("sample_count") + d.FieldArray("samples", func(d *decode.D) { + for i := uint64(0); i < sampleCount; i++ { + d.FieldStruct("entry", func(d *decode.D) { + if t.defaultIVSize != 0 { + d.FieldRawLen("iv", int64(t.defaultIVSize*8)) + } + if flags&0b10 != 0 { + subSampleCount := d.FieldU16("subsample_count") + d.FieldArray("subsamples", func(d *decode.D) { + for i := uint64(0); i < subSampleCount; i++ { + d.FieldStruct("entry", func(d *decode.D) { + d.FieldU16("bytes_of_clean_data") + d.FieldU32("bytes_of_encrypted_data") }) } }) - }) - } - }) - }, - "infe": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU16("id") - d.FieldU16("protection_index") - d.FieldUTF8Null("item_name") - // TODO: really optional? seems so - if d.NotEnd() { - d.FieldUTF8Null("content_type") - } - if d.NotEnd() { - d.FieldUTF8Null("content_encoding") - } - }, - "iinf": func(ctx *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - if version == 0 { - _ = d.FieldU16("entry_count") - decodeBoxes(ctx, d) - } else { - d.FieldRawLen("data", d.BitsLeft()) - } - }, - "iprp": decodeBoxes, - "ipco": decodeBoxes, - "ID32": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU1("pad") - // ISO-639-2/T as 3*5 bit integers - 0x60 - d.FieldStrFn("language", func(d *decode.D) string { - s := "" - for i := 0; i < 3; i++ { - s += fmt.Sprintf("%c", int(d.U5())+0x60) - } - return s - }) - d.FieldFormat("data", id3v2Format, nil) - }, - "mehd": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - switch version { - case 0: - d.FieldU32("fragment_duration") - case 1: - d.FieldU64("fragment_duration") - } - }, - "pssh": func(_ *decodeContext, d *decode.D) { - var ( - systemIDCommon = [16]byte{0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b} - systemIDWidevine = [16]byte{0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed} - systemIDPlayReady = [16]byte{0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95} - systemIDFairPlay = [16]byte{0x94, 0xce, 0x86, 0xfb, 0x07, 0xff, 0x4f, 0x43, 0xad, 0xb8, 0x93, 0xd2, 0xfa, 0x96, 0x8c, 0xa2} - ) - systemIDNames := scalar.BytesToScalar{ - {Bytes: systemIDCommon[:], Scalar: scalar.S{Sym: "common"}}, - {Bytes: systemIDWidevine[:], Scalar: scalar.S{Sym: "widevine"}}, - {Bytes: systemIDPlayReady[:], Scalar: scalar.S{Sym: "playready"}}, - {Bytes: systemIDFairPlay[:], Scalar: scalar.S{Sym: "fairplay"}}, - } - - version := d.FieldU8("version") - d.FieldU24("flags") - systemIDBR := d.FieldRawLen("system_id", 16*8, systemIDNames) - // TODO: make nicer - systemID := d.ReadAllBits(systemIDBR) - switch version { - case 0: - case 1: - kidCount := d.FieldU32("kid_count") - d.FieldArray("kids", func(d *decode.D) { - for i := uint64(0); i < kidCount; i++ { - d.FieldRawLen("kid", 16*8) } }) - } - dataLen := d.FieldU32("data_size") - switch { - case bytes.Equal(systemID, systemIDWidevine[:]): - d.FieldFormatLen("data", int64(dataLen)*8, protoBufWidevineFormat, nil) - case bytes.Equal(systemID, systemIDPlayReady[:]): - d.FieldFormatLen("data", int64(dataLen)*8, psshPlayreadyFormat, nil) - case systemID == nil: - fallthrough - default: - d.FieldRawLen("data", int64(dataLen)*8) + // TODO: add iv etc + s.entries = append(s.entries, struct{}{}) } - }, - "sinf": decodeBoxes, - "frma": func(ctx *decodeContext, d *decode.D) { - format := d.FieldUTF8("format", 4) + }) + m.sencs = append(m.sencs, s) + case "tenc": + version := d.FieldU8("version") + d.FieldU24("flags") - // set to original data format - // TODO: how to handle multiple descriptors? track current? - if t := ctx.currentTrack(); t != nil && len(t.sampleDescriptions) > 0 { - t.sampleDescriptions[0].originalFormat = format + d.FieldU8("reserved0") + switch version { + case 0: + d.FieldU8("reserved1") + default: + d.FieldU4("default_crypto_bytes") + d.FieldU4("default_skip_bytes") + } + + defaultIsEncrypted := d.FieldU8("default_is_encrypted") + defaultIVSize := d.FieldU8("default_iv_size") + d.FieldRawLen("default_kid", 8*16) + + if defaultIsEncrypted != 0 && defaultIVSize == 0 { + defaultConstantIVSize := d.FieldU8("default_constant_iv_size") + d.FieldRawLen("default_constant_iv", int64(defaultConstantIVSize)*8) + } + if t := ctx.currentTrack(); t != nil { + t.defaultIVSize = int(defaultIVSize) + } + case "covr": + decodeBoxes(ctx, d) + case "dec3": + d.FieldU13("data_rate") + d.FieldU3("num_ind_sub") + d.FieldU2("fscod") + d.FieldU5("bsid") + d.FieldU5("bsmod") + d.FieldU3("acmod") + d.FieldU1("lfeon") + d.FieldU3("reserved0") + numDepSub := d.FieldU4("num_dep_sub") + if numDepSub > 0 { + d.FieldU9("chan_loc") + } else { + d.FieldU1("reserved1") + } + + if d.BitsLeft() >= 16 { + d.FieldU7("reserved2") + ec3JocFlag := d.FieldBool("ec3_job_flag") + if ec3JocFlag { + d.FieldU1("ec3_job_complexity") } - }, - "schm": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldUTF8("encryption_type", 4) - d.FieldU16("encryption_version") - if d.BitsLeft() > 0 { - d.FieldUTF8("uri", int(d.BitsLeft())/8) + } + case "dac4": + d.FieldU3("ac4_dsi_version") + bitstreamVersion := d.FieldU7("bitstream_version") + d.FieldU1("fs_index") + d.FieldU4("frame_rate_index") + d.FieldU9("n_presentation") + + if bitstreamVersion > 1 { + hasProgramID := d.FieldBool("has_program_id") + if hasProgramID { + d.FieldU16("short_program_id") + hasUUID := d.FieldBool("has_uuid") + if hasUUID { + d.FieldRawLen("uuid", 16*8) + } } - }, - "schi": decodeBoxes, - "btrt": func(_ *decodeContext, d *decode.D) { - d.FieldU32("decoding_buffer_size") - d.FieldU32("max_bitrate") - d.FieldU32("avg_bitrate") - }, - "pasp": func(_ *decodeContext, d *decode.D) { - d.FieldU32("h_spacing") - d.FieldU32("v_spacing") - }, - "uuid": func(_ *decodeContext, d *decode.D) { - d.FieldRawLen("uuid", 16*8, scalar.RawUUID, uuidNames) + } + + // if ac4DsiVersion == 1 { + // d.FieldU2("bit_rate_mode") + // d.FieldU32("bit_rate") + // d.FieldU32("bit_rate_precision") + // } + + // if ac4DsiVersion == 1 { + + // d.FieldArray("presentations", func(d *decode.D) { + // for i := uint64(0); i < nPresentation; i++ { + // d.FieldStruct("presentation", func(d *decode.D) { + // d.FieldU8("presentation_version") + // presBytes := d.FieldUFn("pres_bytes", func() (uint64, decode.DisplayFormat, string) { + // n := d.U8() + // if n == 0x0ff { + // n += d.U16() + // } + // return n, decode.NumberDecimal, "" + // }) + // d.FieldRawLen("data", int64(presBytes)*8) + // }) + // } + // }) + // } + + if d.BitsLeft() > 0 { d.FieldRawLen("data", d.BitsLeft()) - }, - "keys": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - d.FieldArray("entries", func(d *decode.D) { - for i := uint64(0); i < entryCount; i++ { - d.FieldStruct("entry", func(d *decode.D) { - keySize := d.FieldU32("key_size") - d.FieldUTF8("key_namespace", 4) - d.FieldUTF8("key_name", int(keySize)-8) - }) - } - }) - }, - "wave": decodeBoxes, - "saiz": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - flags := d.FieldU24("flags") - if flags&0b1 != 0 { - d.FieldU32("aux_info_type") - d.FieldU32("aux_info_type_parameter") - } - defaultSampleInfoSize := d.FieldU8("default_sample_info_size") - sampleCount := d.FieldU32("sample_count") - if defaultSampleInfoSize == 0 { - d.FieldArray("sample_size_info_table", func(d *decode.D) { - for i := uint64(0); i < sampleCount; i++ { - d.FieldU8("sample_size") - } - }) - } - }, - "sgpd": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("grouping_type") - var defaultLength uint64 - if version == 1 { - defaultLength = d.FieldU32("default_length") - } - if version >= 2 { - d.FieldU32("default_sample_description_index") - } - entryCount := d.FieldU32("entry_count") - d.FieldArray("entries", func(d *decode.D) { - for i := uint64(0); i < entryCount; i++ { - entryLen := defaultLength - if version == 1 { - if defaultLength == 0 { - entryLen = d.FieldU32("description_length") - } else if entryLen == 0 { - d.Fatalf("sgpd groups entry len <= 0 version 1") - } - } else if entryLen == 0 { - d.Fatalf("sgpd groups entry len <= 0") - } - d.FieldRawLen("data", int64(entryLen)*8) - } - }) - }, - "sbgp": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") + } + case "tapt": + decodeBoxes(ctx, d) + case "clef": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldFP32("width") + d.FieldFP32("height") + case "prof": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldFP32("width") + d.FieldFP32("height") + case "enof": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldFP32("width") + d.FieldFP32("height") + case "clap": + d.FieldU32("aperture_width_n") + d.FieldU32("aperture_width_d") + d.FieldU32("aperture_height_n") + d.FieldU32("aperture_height_d") + d.FieldU32("horiz_off_n") + d.FieldU32("horiz_off_d") + d.FieldU32("vert_off_n") + d.FieldU32("vert_off_d") + case "smhd": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldFP16("balance") + d.FieldU16("reserved") + case "colr": + parameterType := d.FieldUTF8("parameter_type", 4) - d.FieldU32("grouping_type") - if version == 1 { - d.FieldU32("grouping_type_parameter") + switch parameterType { + case "nclx", "nclc": + d.FieldU16("primaries_index", format.ISO_23091_2_ColourPrimariesMap) + d.FieldU16("transfer_function_index", format.ISO_23091_2_TransferCharacteristicMap) + d.FieldU16("matrix_index", format.ISO_23091_2_MatrixCoefficients) + switch parameterType { + case "nclx": + d.FieldU8("color_range") } - entryCount := d.FieldU32("entry_count") - d.FieldArray("entries", func(d *decode.D) { - for i := uint64(0); i < entryCount; i++ { - d.FieldStruct("entry", func(d *decode.D) { - d.FieldU32("sample_count") - d.FieldU32("group_description_index") - }) - } - }) - }, - "saio": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - flags := d.FieldU24("flags") - - if flags&0b1 != 0 { - d.FieldU32("aux_info_type") - d.FieldU32("aux_info_type_parameter") - } - entryCount := d.FieldU32("entry_count") - d.FieldArray("entries", func(d *decode.D) { - for i := uint64(0); i < entryCount; i++ { - if version == 0 { - d.FieldU32("offset") + case "prof": + d.FieldFormat("profile", iccProfileFormat, nil) + default: + d.FieldRawLen("data", d.BitsLeft()) + } + case "ispe": + d.FieldU8("version") + d.FieldU24("flags") + d.FieldU32("image_width") + d.FieldU32("image_height") + case "ipma": + version := d.FieldU8("version") + flags := d.FieldU24("flags") + entryCount := d.FieldU32("entry_count") + d.FieldArray("entries", func(d *decode.D) { + for i := uint64(0); i < entryCount; i++ { + d.FieldStruct("entry", func(d *decode.D) { + if version < 1 { + d.FieldU16("item_id") } else { - d.FieldU64("offset") + d.FieldU32("item_id") } - } - }) - }, - "senc": func(ctx *decodeContext, d *decode.D) { - d.FieldU8("version") - flags := d.FieldU24("flags") - - t := ctx.currentTrack() - if t == nil { - // need to know iv size - return - } - m := &moof{} - if t := ctx.currentTrafBox(); t != nil { - m = t.moof - } - - s := senc{} - sampleCount := d.FieldU32("sample_count") - d.FieldArray("samples", func(d *decode.D) { - for i := uint64(0); i < sampleCount; i++ { - d.FieldStruct("entry", func(d *decode.D) { - if t.defaultIVSize != 0 { - d.FieldRawLen("iv", int64(t.defaultIVSize*8)) - } - if flags&0b10 != 0 { - subSampleCount := d.FieldU16("subsample_count") - d.FieldArray("subsamples", func(d *decode.D) { - for i := uint64(0); i < subSampleCount; i++ { - d.FieldStruct("entry", func(d *decode.D) { - d.FieldU16("bytes_of_clean_data") - d.FieldU32("bytes_of_encrypted_data") - }) + associationCount := d.FieldU8("association_count") + d.FieldArray("associations", func(d *decode.D) { + for j := uint64(0); j < associationCount; j++ { + d.FieldStruct("association", func(d *decode.D) { + d.FieldBool("essential") + if flags&0b1 != 0 { + d.FieldU15("property_index") + } else { + d.FieldU7("item_id") } }) } }) - - // TODO: add iv etc - s.entries = append(s.entries, struct{}{}) - } - }) - m.sencs = append(m.sencs, s) - }, - "tenc": func(ctx *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - - d.FieldU8("reserved0") - switch version { - case 0: - d.FieldU8("reserved1") - default: - d.FieldU4("default_crypto_bytes") - d.FieldU4("default_skip_bytes") + }) } - - defaultIsEncrypted := d.FieldU8("default_is_encrypted") - defaultIVSize := d.FieldU8("default_iv_size") - d.FieldRawLen("default_kid", 8*16) - - if defaultIsEncrypted != 0 && defaultIVSize == 0 { - defaultConstantIVSize := d.FieldU8("default_constant_iv_size") - d.FieldRawLen("default_constant_iv", int64(defaultConstantIVSize)*8) - } - if t := ctx.currentTrack(); t != nil { - t.defaultIVSize = int(defaultIVSize) - } - }, - "covr": decodeBoxes, - "dec3": func(_ *decodeContext, d *decode.D) { - d.FieldU13("data_rate") - d.FieldU3("num_ind_sub") - d.FieldU2("fscod") - d.FieldU5("bsid") - d.FieldU5("bsmod") - d.FieldU3("acmod") - d.FieldU1("lfeon") - d.FieldU3("reserved0") - numDepSub := d.FieldU4("num_dep_sub") - if numDepSub > 0 { - d.FieldU9("chan_loc") - } else { - d.FieldU1("reserved1") - } - - if d.BitsLeft() >= 16 { - d.FieldU7("reserved2") - ec3JocFlag := d.FieldBool("ec3_job_flag") - if ec3JocFlag { - d.FieldU1("ec3_job_complexity") - } - } - }, - "dac4": func(_ *decodeContext, d *decode.D) { - d.FieldU3("ac4_dsi_version") - bitstreamVersion := d.FieldU7("bitstream_version") - d.FieldU1("fs_index") - d.FieldU4("frame_rate_index") - d.FieldU9("n_presentation") - - if bitstreamVersion > 1 { - hasProgramID := d.FieldBool("has_program_id") - if hasProgramID { - d.FieldU16("short_program_id") - hasUUID := d.FieldBool("has_uuid") - if hasUUID { - d.FieldRawLen("uuid", 16*8) - } - } - } - - // if ac4DsiVersion == 1 { - // d.FieldU2("bit_rate_mode") - // d.FieldU32("bit_rate") - // d.FieldU32("bit_rate_precision") - // } - - // if ac4DsiVersion == 1 { - - // d.FieldArray("presentations", func(d *decode.D) { - // for i := uint64(0); i < nPresentation; i++ { - // d.FieldStruct("presentation", func(d *decode.D) { - // d.FieldU8("presentation_version") - // presBytes := d.FieldUFn("pres_bytes", func() (uint64, decode.DisplayFormat, string) { - // n := d.U8() - // if n == 0x0ff { - // n += d.U16() - // } - // return n, decode.NumberDecimal, "" - // }) - // d.FieldRawLen("data", int64(presBytes)*8) - // }) - // } - // }) - // } - - if d.BitsLeft() > 0 { - d.FieldRawLen("data", d.BitsLeft()) - } - }, - "tapt": decodeBoxes, - "clef": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldFP32("width") - d.FieldFP32("height") - }, - "prof": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldFP32("width") - d.FieldFP32("height") - }, - "enof": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldFP32("width") - d.FieldFP32("height") - }, - "clap": func(_ *decodeContext, d *decode.D) { - d.FieldU32("aperture_width_n") - d.FieldU32("aperture_width_d") - d.FieldU32("aperture_height_n") - d.FieldU32("aperture_height_d") - d.FieldU32("horiz_off_n") - d.FieldU32("horiz_off_d") - d.FieldU32("vert_off_n") - d.FieldU32("vert_off_d") - }, - "smhd": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldFP16("balance") - d.FieldU16("reserved") - }, - "colr": func(_ *decodeContext, d *decode.D) { - parameterType := d.FieldUTF8("parameter_type", 4) - - switch parameterType { - case "nclx", "nclc": - d.FieldU16("primaries_index", format.ISO_23091_2_ColourPrimariesMap) - d.FieldU16("transfer_function_index", format.ISO_23091_2_TransferCharacteristicMap) - d.FieldU16("matrix_index", format.ISO_23091_2_MatrixCoefficients) - switch parameterType { - case "nclx": - d.FieldU8("color_range") - } - case "prof": - d.FieldFormat("profile", iccProfileFormat, nil) - default: - d.FieldRawLen("data", d.BitsLeft()) - } - }, - "ispe": func(_ *decodeContext, d *decode.D) { - d.FieldU8("version") - d.FieldU24("flags") - d.FieldU32("image_width") - d.FieldU32("image_height") - }, - "ipma": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - flags := d.FieldU24("flags") - entryCount := d.FieldU32("entry_count") - d.FieldArray("entries", func(d *decode.D) { - for i := uint64(0); i < entryCount; i++ { - d.FieldStruct("entry", func(d *decode.D) { - if version < 1 { - d.FieldU16("item_id") - } else { - d.FieldU32("item_id") - } - associationCount := d.FieldU8("association_count") - d.FieldArray("associations", func(d *decode.D) { - for j := uint64(0); j < associationCount; j++ { - d.FieldStruct("association", func(d *decode.D) { - d.FieldBool("essential") - if flags&0b1 != 0 { - d.FieldU15("property_index") - } else { - d.FieldU7("item_id") - } - }) - } - }) - }) - } - }) - }, - "pitm": func(_ *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - if version < 1 { - d.FieldU16("item_id") - } else { - d.FieldU32("item_id") - } - }, - "iref": func(ctx *decodeContext, d *decode.D) { - version := d.FieldU8("version") - d.FieldU24("flags") - decodeBoxesWithParentData(ctx, d, &irefBox{version: int(version)}) - }, - "dimg": irefEntryDecode, - "thmb": irefEntryDecode, - "cdsc": irefEntryDecode, - "irot": func(_ *decodeContext, d *decode.D) { - d.FieldU8("rotation", scalar.UToSymU{ - 0: 0, - 1: 90, - 2: 180, - 3: 270, - }) - }, + }) + case "pitm": + version := d.FieldU8("version") + d.FieldU24("flags") + if version < 1 { + d.FieldU16("item_id") + } else { + d.FieldU32("item_id") + } + case "iref": + version := d.FieldU8("version") + d.FieldU24("flags") + decodeBoxesWithParentData(ctx, d, &irefBox{version: int(version)}) + case "dimg": + decodeBoxIrefEntry(ctx, d) + case "thmb": + decodeBoxIrefEntry(ctx, d) + case "cdsc": + decodeBoxIrefEntry(ctx, d) + case "irot": + d.FieldU8("rotation", scalar.UToSymU{ + 0: 0, + 1: 90, + 2: 180, + 3: 270, + }) + default: + if mb := ctx.currentMetaBox(); mb != nil && ctx.parent().typ == "ilst" { + // unknown type inside a metadata box with ilst as parent, decode item boxes + decodeBoxes(ctx, d) + } else { + d.FieldRawLen("data", d.BitsLeft()) + } } } diff --git a/format/mp4/desc.go b/format/mp4/desc.go index 86c68629..8fc48fdf 100644 --- a/format/mp4/desc.go +++ b/format/mp4/desc.go @@ -263,5 +263,26 @@ var boxDescriptions = scalar.StrToDescription{ "xml ": "XML container", "yrrc": "Year when media was recorded", + // from https://wiki.multimedia.cx/index.php/FFmpeg_Metadata + "©nam": "Title", + "©ART": "Author", + "aART": "Album_artist", + "©alb": "Album", + "©grp": "Grouping", + "©wrt": "Composer", + "©day": "Year", + "trkn": "Track", + "©cmt": "Comment", + "©gen": "Genre", + "©cpy": "Copyright", + "desc": "Description", + "ldes": "Synopsis", + "tvsh": "Show", + "tven": "Episode_id", + "tvnn": "Network", + "©lyr": "Lyrics", + + "©too": "Encoder", + "\x00\x00\x00\x00": "Terminator Atom", } diff --git a/format/mp4/mp4.go b/format/mp4/mp4.go index 1a02f34f..d5a8cb39 100644 --- a/format/mp4/mp4.go +++ b/format/mp4/mp4.go @@ -197,6 +197,11 @@ func (ctx *decodeContext) currentMoofBox() *moofBox { return t } +func (ctx *decodeContext) currentMetaBox() *metaBox { + t, _ := ctx.findParent("meta").(*metaBox) + return t +} + func (ctx *decodeContext) currentTrack() *track { if t := ctx.currentTrakBox(); t != nil { return ctx.lookupTrack(t.trackID) diff --git a/format/mp4/testdata/aac.fqtest b/format/mp4/testdata/aac.fqtest index d5d8b5c8..df82bb6d 100644 --- a/format/mp4/testdata/aac.fqtest +++ b/format/mp4/testdata/aac.fqtest @@ -346,7 +346,7 @@ $ fq -d mp4 dv aac.mp4 | | | boxes[0:1]: 0x578-0x59c.7 (37) | | | [0]{}: box 0x578-0x59c.7 (37) 0x570| 00 00 00 25 | ...% | size: 37 0x578-0x57b.7 (4) -0x570| a9 74 6f 6f| .too| type: "�too" 0x57c-0x57f.7 (4) +0x570| a9 74 6f 6f| .too| type: "©too" (Encoder) 0x57c-0x57f.7 (4) | | | boxes[0:1]: 0x580-0x59c.7 (29) | | | [0]{}: box 0x580-0x59c.7 (29) 0x580|00 00 00 1d |.... | size: 29 0x580-0x583.7 (4) diff --git a/format/mp4/testdata/av1.fqtest b/format/mp4/testdata/av1.fqtest index 54059440..83e5b9c0 100644 --- a/format/mp4/testdata/av1.fqtest +++ b/format/mp4/testdata/av1.fqtest @@ -295,7 +295,7 @@ $ fq -d mp4 dv av1.mp4 | | | [0]{}: box 0x148d-0x14b1.7 (37) 0x1480| 00 00 00| ...| size: 37 0x148d-0x1490.7 (4) 0x1490|25 |% | -0x1490| a9 74 6f 6f | .too | type: "�too" 0x1491-0x1494.7 (4) +0x1490| a9 74 6f 6f | .too | type: "©too" (Encoder) 0x1491-0x1494.7 (4) | | | boxes[0:1]: 0x1495-0x14b1.7 (29) | | | [0]{}: box 0x1495-0x14b1.7 (29) 0x1490| 00 00 00 1d | .... | size: 29 0x1495-0x1498.7 (4) diff --git a/format/mp4/testdata/avc.fqtest b/format/mp4/testdata/avc.fqtest index df8ab21e..4cbe93c5 100644 --- a/format/mp4/testdata/avc.fqtest +++ b/format/mp4/testdata/avc.fqtest @@ -439,7 +439,7 @@ $ fq -d mp4 dv avc.mp4 | | | boxes[0:1]: 0x10bb-0x10df.7 (37) | | | [0]{}: box 0x10bb-0x10df.7 (37) 0x010b0| 00 00 00 25 | ...% | size: 37 0x10bb-0x10be.7 (4) -0x010b0| a9| .| type: "�too" 0x10bf-0x10c2.7 (4) +0x010b0| a9| .| type: "©too" (Encoder) 0x10bf-0x10c2.7 (4) 0x010c0|74 6f 6f |too | | | | boxes[0:1]: 0x10c3-0x10df.7 (29) | | | [0]{}: box 0x10c3-0x10df.7 (29) diff --git a/format/mp4/testdata/flac.fqtest b/format/mp4/testdata/flac.fqtest index a31eed5c..bf2c92f9 100644 --- a/format/mp4/testdata/flac.fqtest +++ b/format/mp4/testdata/flac.fqtest @@ -300,7 +300,7 @@ $ fq -d mp4 dv flac.mp4 | | | [0]{}: box 0x51e-0x542.7 (37) 0x510| 00 00| ..| size: 37 0x51e-0x521.7 (4) 0x520|00 25 |.% | -0x520| a9 74 6f 6f | .too | type: "�too" 0x522-0x525.7 (4) +0x520| a9 74 6f 6f | .too | type: "©too" (Encoder) 0x522-0x525.7 (4) | | | boxes[0:1]: 0x526-0x542.7 (29) | | | [0]{}: box 0x526-0x542.7 (29) 0x520| 00 00 00 1d | .... | size: 29 0x526-0x529.7 (4) diff --git a/format/mp4/testdata/fragmented.fqtest b/format/mp4/testdata/fragmented.fqtest index a78330d5..2c846802 100644 --- a/format/mp4/testdata/fragmented.fqtest +++ b/format/mp4/testdata/fragmented.fqtest @@ -574,7 +574,7 @@ $ fq -d mp4 dv fragmented.mp4 | | | boxes[0:1]: 0x4c8-0x4ec.7 (37) | | | [0]{}: box 0x4c8-0x4ec.7 (37) 0x004c0| 00 00 00 25 | ...% | size: 37 0x4c8-0x4cb.7 (4) -0x004c0| a9 74 6f 6f| .too| type: "�too" 0x4cc-0x4cf.7 (4) +0x004c0| a9 74 6f 6f| .too| type: "©too" (Encoder) 0x4cc-0x4cf.7 (4) | | | boxes[0:1]: 0x4d0-0x4ec.7 (29) | | | [0]{}: box 0x4d0-0x4ec.7 (29) 0x004d0|00 00 00 1d |.... | size: 29 0x4d0-0x4d3.7 (4) diff --git a/format/mp4/testdata/hevc.fqtest b/format/mp4/testdata/hevc.fqtest index 9909c963..3b4487c8 100644 --- a/format/mp4/testdata/hevc.fqtest +++ b/format/mp4/testdata/hevc.fqtest @@ -610,7 +610,7 @@ $ fq -d mp4 dv hevc.mp4 | | | boxes[0:1]: 0x1476-0x149a.7 (37) | | | [0]{}: box 0x1476-0x149a.7 (37) 0x1470| 00 00 00 25 | ...% | size: 37 0x1476-0x1479.7 (4) -0x1470| a9 74 6f 6f | .too | type: "�too" 0x147a-0x147d.7 (4) +0x1470| a9 74 6f 6f | .too | type: "©too" (Encoder) 0x147a-0x147d.7 (4) | | | boxes[0:1]: 0x147e-0x149a.7 (29) | | | [0]{}: box 0x147e-0x149a.7 (29) 0x1470| 00 00| ..| size: 29 0x147e-0x1481.7 (4) diff --git a/format/mp4/testdata/in24.fqtest b/format/mp4/testdata/in24.fqtest index a952d4e3..63cfdb6f 100644 --- a/format/mp4/testdata/in24.fqtest +++ b/format/mp4/testdata/in24.fqtest @@ -274,7 +274,7 @@ $ fq dv in24.mp4 | | | boxes[0:1]: 0x3d3-0x3eb.7 (25) | | | [0]{}: box 0x3d3-0x3eb.7 (25) 0x3d0| 00 00 00 19 | .... | size: 25 0x3d3-0x3d6.7 (4) -0x3d0| a9 73 77 72 | .swr | type: "�swr" 0x3d7-0x3da.7 (4) +0x3d0| a9 73 77 72 | .swr | type: "©swr" 0x3d7-0x3da.7 (4) 0x3d0| 00 0d 55 c4 4c| ..U.L| data: raw bits 0x3db-0x3eb.7 (17) 0x3e0|61 76 66 35 38 2e 37 36 2e 31 30 30| |avf58.76.100| | | | | tracks[0:1]: 0x3ec-NA (0) diff --git a/format/mp4/testdata/lpcm.fqtest b/format/mp4/testdata/lpcm.fqtest index 072bfade..0506dd53 100644 --- a/format/mp4/testdata/lpcm.fqtest +++ b/format/mp4/testdata/lpcm.fqtest @@ -264,7 +264,7 @@ $ fq dv lpcm.mp4 | | | boxes[0:1]: 0x4f9-0x511.7 (25) | | | [0]{}: box 0x4f9-0x511.7 (25) 0x4f0| 00 00 00 19 | .... | size: 25 0x4f9-0x4fc.7 (4) -0x4f0| a9 73 77| .sw| type: "�swr" 0x4fd-0x500.7 (4) +0x4f0| a9 73 77| .sw| type: "©swr" 0x4fd-0x500.7 (4) 0x500|72 |r | 0x500| 00 0d 55 c4 4c 61 76 66 35 38 2e 37 36 2e 31| ..U.Lavf58.76.1| data: raw bits 0x501-0x511.7 (17) 0x510|30 30| |00| | diff --git a/format/mp4/testdata/mdir_tags.mp4 b/format/mp4/testdata/mdir_tags.mp4 new file mode 100644 index 00000000..41868488 Binary files /dev/null and b/format/mp4/testdata/mdir_tags.mp4 differ diff --git a/format/mp4/testdata/mdir_tags.mp4.fqtest b/format/mp4/testdata/mdir_tags.mp4.fqtest new file mode 100644 index 00000000..19b87a37 --- /dev/null +++ b/format/mp4/testdata/mdir_tags.mp4.fqtest @@ -0,0 +1,51 @@ +# ffmpeg -y -f lavfi -i sine -metadata title=test -t 10ms mdir_tags.mp4 +$ fq 'grep_by(.type=="meta") | dv' mdir_tags.mp4 + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.boxes[3].boxes[2].boxes[0]{}: box 0x4c9-0x53e.7 (118) +0x4c0| 00 00 00 76 | ...v | size: 118 0x4c9-0x4cc.7 (4) +0x4c0| 6d 65 74| met| type: "meta" (Metadata container) 0x4cd-0x4d0.7 (4) +0x4d0|61 |a | +0x4d0| 00 00 00 00 | .... | maybe_flags: 0 0x4d1-0x4d4.7 (4) + | | | boxes[0:2]: 0x4d5-0x53e.7 (106) + | | | [0]{}: box 0x4d5-0x4f5.7 (33) +0x4d0| 00 00 00 21 | ...! | size: 33 0x4d5-0x4d8.7 (4) +0x4d0| 68 64 6c 72 | hdlr | type: "hdlr" (Handler, declares the media (handler) type) 0x4d9-0x4dc.7 (4) +0x4d0| 00 | . | version: 0 0x4dd-0x4dd.7 (1) +0x4d0| 00 00| ..| flags: 0 0x4de-0x4e0.7 (3) +0x4e0|00 |. | +0x4e0| 00 00 00 00 | .... | component_type: "" 0x4e1-0x4e4.7 (4) +0x4e0| 6d 64 69 72 | mdir | component_subtype: "mdir" (Metadata) 0x4e5-0x4e8.7 (4) +0x4e0| 61 70 70 6c | appl | component_manufacturer: "appl" 0x4e9-0x4ec.7 (4) +0x4e0| 00 00 00| ...| component_flags: 0 0x4ed-0x4f0.7 (4) +0x4f0|00 |. | +0x4f0| 00 00 00 00 | .... | component_flags_mask: 0 0x4f1-0x4f4.7 (4) +0x4f0| 00 | . | component_name: "" 0x4f5-0x4f5.7 (1) + | | | [1]{}: box 0x4f6-0x53e.7 (73) +0x4f0| 00 00 00 49 | ...I | size: 73 0x4f6-0x4f9.7 (4) +0x4f0| 69 6c 73 74 | ilst | type: "ilst" 0x4fa-0x4fd.7 (4) + | | | boxes[0:2]: 0x4fe-0x53e.7 (65) + | | | [0]{}: box 0x4fe-0x519.7 (28) +0x4f0| 00 00| ..| size: 28 0x4fe-0x501.7 (4) +0x500|00 1c |.. | +0x500| a9 6e 61 6d | .nam | type: "©nam" (Title) 0x502-0x505.7 (4) + | | | boxes[0:1]: 0x506-0x519.7 (20) + | | | [0]{}: box 0x506-0x519.7 (20) +0x500| 00 00 00 14 | .... | size: 20 0x506-0x509.7 (4) +0x500| 64 61 74 61 | data | type: "data" 0x50a-0x50d.7 (4) +0x500| 00 | . | version: 0 0x50e-0x50e.7 (1) +0x500| 00| .| flags: 1 0x50f-0x511.7 (3) +0x510|00 01 |.. | +0x510| 00 00 00 00 | .... | reserved: 0 0x512-0x515.7 (4) +0x510| 74 65 73 74 | test | data: "test" 0x516-0x519.7 (4) + | | | [1]{}: box 0x51a-0x53e.7 (37) +0x510| 00 00 00 25 | ...% | size: 37 0x51a-0x51d.7 (4) +0x510| a9 74| .t| type: "©too" (Encoder) 0x51e-0x521.7 (4) +0x520|6f 6f |oo | + | | | boxes[0:1]: 0x522-0x53e.7 (29) + | | | [0]{}: box 0x522-0x53e.7 (29) +0x520| 00 00 00 1d | .... | size: 29 0x522-0x525.7 (4) +0x520| 64 61 74 61 | data | type: "data" 0x526-0x529.7 (4) +0x520| 00 | . | version: 0 0x52a-0x52a.7 (1) +0x520| 00 00 01 | ... | flags: 1 0x52b-0x52d.7 (3) +0x520| 00 00| ..| reserved: 0 0x52e-0x531.7 (4) +0x530|00 00 |.. | +0x530| 4c 61 76 66 35 39 2e 32 37 2e 31 30 30| | Lavf59.27.100|| data: "Lavf59.27.100" 0x532-0x53e.7 (13) diff --git a/format/mp4/testdata/mdta_tags.mp4 b/format/mp4/testdata/mdta_tags.mp4 new file mode 100644 index 00000000..182b099c Binary files /dev/null and b/format/mp4/testdata/mdta_tags.mp4 differ diff --git a/format/mp4/testdata/mdta_tags.mp4.fqtest b/format/mp4/testdata/mdta_tags.mp4.fqtest new file mode 100644 index 00000000..1b21ad98 --- /dev/null +++ b/format/mp4/testdata/mdta_tags.mp4.fqtest @@ -0,0 +1,69 @@ +# ffmpeg -y -f lavfi -i sine -movflags use_metadata_tags -metadata title=test -t 10ms mdta_tags.mp4 +$ fq 'grep_by(.type=="meta") | dv' mdta_tags.mp4 + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.boxes[3].boxes[2].boxes[0]{}: box 0x4c9-0x56a.7 (162) +0x4c0| 00 00 00 a2 | .... | size: 162 0x4c9-0x4cc.7 (4) +0x4c0| 6d 65 74| met| type: "meta" (Metadata container) 0x4cd-0x4d0.7 (4) +0x4d0|61 |a | +0x4d0| 00 00 00 00 | .... | maybe_flags: 0 0x4d1-0x4d4.7 (4) + | | | boxes[0:3]: 0x4d5-0x56a.7 (150) + | | | [0]{}: box 0x4d5-0x4f5.7 (33) +0x4d0| 00 00 00 21 | ...! | size: 33 0x4d5-0x4d8.7 (4) +0x4d0| 68 64 6c 72 | hdlr | type: "hdlr" (Handler, declares the media (handler) type) 0x4d9-0x4dc.7 (4) +0x4d0| 00 | . | version: 0 0x4dd-0x4dd.7 (1) +0x4d0| 00 00| ..| flags: 0 0x4de-0x4e0.7 (3) +0x4e0|00 |. | +0x4e0| 00 00 00 00 | .... | component_type: "" 0x4e1-0x4e4.7 (4) +0x4e0| 6d 64 74 61 | mdta | component_subtype: "mdta" (Metadata Tags) 0x4e5-0x4e8.7 (4) +0x4e0| 00 00 00 00 | .... | component_manufacturer: "" 0x4e9-0x4ec.7 (4) +0x4e0| 00 00 00| ...| component_flags: 0 0x4ed-0x4f0.7 (4) +0x4f0|00 |. | +0x4f0| 00 00 00 00 | .... | component_flags_mask: 0 0x4f1-0x4f4.7 (4) +0x4f0| 00 | . | component_name: "" 0x4f5-0x4f5.7 (1) + | | | [1]{}: box 0x4f6-0x521.7 (44) +0x4f0| 00 00 00 2c | ..., | size: 44 0x4f6-0x4f9.7 (4) +0x4f0| 6b 65 79 73 | keys | type: "keys" 0x4fa-0x4fd.7 (4) +0x4f0| 00 | . | version: 0 0x4fe-0x4fe.7 (1) +0x4f0| 00| .| flags: 0 0x4ff-0x501.7 (3) +0x500|00 00 |.. | +0x500| 00 00 00 02 | .... | entry_count: 2 0x502-0x505.7 (4) + | | | entries[0:2]: 0x506-0x521.7 (28) + | | | [0]{}: entry 0x506-0x512.7 (13) +0x500| 00 00 00 0d | .... | key_size: 13 0x506-0x509.7 (4) +0x500| 6d 64 74 61 | mdta | key_namespace: "mdta" 0x50a-0x50d.7 (4) +0x500| 74 69| ti| key_name: "title" 0x50e-0x512.7 (5) +0x510|74 6c 65 |tle | + | | | [1]{}: entry 0x513-0x521.7 (15) +0x510| 00 00 00 0f | .... | key_size: 15 0x513-0x516.7 (4) +0x510| 6d 64 74 61 | mdta | key_namespace: "mdta" 0x517-0x51a.7 (4) +0x510| 65 6e 63 6f 64| encod| key_name: "encoder" 0x51b-0x521.7 (7) +0x520|65 72 |er | + | | | [2]{}: box 0x522-0x56a.7 (73) +0x520| 00 00 00 49 | ...I | size: 73 0x522-0x525.7 (4) +0x520| 69 6c 73 74 | ilst | type: "ilst" 0x526-0x529.7 (4) + | | | boxes[0:2]: 0x52a-0x56a.7 (65) + | | | [0]{}: box 0x52a-0x545.7 (28) +0x520| 00 00 00 1c | .... | size: 28 0x52a-0x52d.7 (4) +0x520| 00 00| ..| type: "title" ("\x00\x00\x00\x01") 0x52e-0x531.7 (4) +0x530|00 01 |.. | + | | | boxes[0:1]: 0x532-0x545.7 (20) + | | | [0]{}: box 0x532-0x545.7 (20) +0x530| 00 00 00 14 | .... | size: 20 0x532-0x535.7 (4) +0x530| 64 61 74 61 | data | type: "data" 0x536-0x539.7 (4) +0x530| 00 | . | version: 0 0x53a-0x53a.7 (1) +0x530| 00 00 01 | ... | flags: 1 0x53b-0x53d.7 (3) +0x530| 00 00| ..| reserved: 0 0x53e-0x541.7 (4) +0x540|00 00 |.. | +0x540| 74 65 73 74 | test | data: "test" 0x542-0x545.7 (4) + | | | [1]{}: box 0x546-0x56a.7 (37) +0x540| 00 00 00 25 | ...% | size: 37 0x546-0x549.7 (4) +0x540| 00 00 00 02 | .... | type: "encoder" ("\x00\x00\x00\x02") 0x54a-0x54d.7 (4) + | | | boxes[0:1]: 0x54e-0x56a.7 (29) + | | | [0]{}: box 0x54e-0x56a.7 (29) +0x540| 00 00| ..| size: 29 0x54e-0x551.7 (4) +0x550|00 1d |.. | +0x550| 64 61 74 61 | data | type: "data" 0x552-0x555.7 (4) +0x550| 00 | . | version: 0 0x556-0x556.7 (1) +0x550| 00 00 01 | ... | flags: 1 0x557-0x559.7 (3) +0x550| 00 00 00 00 | .... | reserved: 0 0x55a-0x55d.7 (4) +0x550| 4c 61| La| data: "Lavf59.27.100" 0x55e-0x56a.7 (13) +0x560|76 66 35 39 2e 32 37 2e 31 30 30| |vf59.27.100| | diff --git a/format/mp4/testdata/mp3.fqtest b/format/mp4/testdata/mp3.fqtest index bcb18def..9d9336f8 100644 --- a/format/mp4/testdata/mp3.fqtest +++ b/format/mp4/testdata/mp3.fqtest @@ -312,7 +312,7 @@ $ fq -d mp4 dv mp3.mp4 | | | boxes[0:1]: 0x540-0x564.7 (37) | | | [0]{}: box 0x540-0x564.7 (37) 0x540|00 00 00 25 |...% | size: 37 0x540-0x543.7 (4) -0x540| a9 74 6f 6f | .too | type: "�too" 0x544-0x547.7 (4) +0x540| a9 74 6f 6f | .too | type: "©too" (Encoder) 0x544-0x547.7 (4) | | | boxes[0:1]: 0x548-0x564.7 (29) | | | [0]{}: box 0x548-0x564.7 (29) 0x540| 00 00 00 1d | .... | size: 29 0x548-0x54b.7 (4) diff --git a/format/mp4/testdata/mpeg2.fqtest b/format/mp4/testdata/mpeg2.fqtest index aae827c8..477cc9ae 100644 --- a/format/mp4/testdata/mpeg2.fqtest +++ b/format/mp4/testdata/mpeg2.fqtest @@ -326,7 +326,7 @@ $ fq -d mp4 dv mpeg2.mp4 | | | boxes[0:1]: 0x2284-0x22a8.7 (37) | | | [0]{}: box 0x2284-0x22a8.7 (37) 0x2280| 00 00 00 25 | ...% | size: 37 0x2284-0x2287.7 (4) -0x2280| a9 74 6f 6f | .too | type: "�too" 0x2288-0x228b.7 (4) +0x2280| a9 74 6f 6f | .too | type: "©too" (Encoder) 0x2288-0x228b.7 (4) | | | boxes[0:1]: 0x228c-0x22a8.7 (29) | | | [0]{}: box 0x228c-0x22a8.7 (29) 0x2280| 00 00 00 1d| ....| size: 29 0x228c-0x228f.7 (4) diff --git a/format/mp4/testdata/opus.fqtest b/format/mp4/testdata/opus.fqtest index c5df9328..f1d8375e 100644 --- a/format/mp4/testdata/opus.fqtest +++ b/format/mp4/testdata/opus.fqtest @@ -291,7 +291,7 @@ $ fq -d mp4 dv opus.mp4 | | | boxes[0:1]: 0x414-0x438.7 (37) | | | [0]{}: box 0x414-0x438.7 (37) 0x410| 00 00 00 25 | ...% | size: 37 0x414-0x417.7 (4) -0x410| a9 74 6f 6f | .too | type: "�too" 0x418-0x41b.7 (4) +0x410| a9 74 6f 6f | .too | type: "©too" (Encoder) 0x418-0x41b.7 (4) | | | boxes[0:1]: 0x41c-0x438.7 (29) | | | [0]{}: box 0x41c-0x438.7 (29) 0x410| 00 00 00 1d| ....| size: 29 0x41c-0x41f.7 (4) diff --git a/format/mp4/testdata/png.mp4.fqtest b/format/mp4/testdata/png.mp4.fqtest index da98dc54..dbe774cb 100644 --- a/format/mp4/testdata/png.mp4.fqtest +++ b/format/mp4/testdata/png.mp4.fqtest @@ -335,7 +335,7 @@ $ fq dv png.mp4 | | | boxes[0:1]: 0x37b-0x39f.7 (37) | | | [0]{}: box 0x37b-0x39f.7 (37) 0x370| 00 00 00 25 | ...% | size: 37 0x37b-0x37e.7 (4) -0x370| a9| .| type: "�too" 0x37f-0x382.7 (4) +0x370| a9| .| type: "©too" (Encoder) 0x37f-0x382.7 (4) 0x380|74 6f 6f |too | | | | boxes[0:1]: 0x383-0x39f.7 (29) | | | [0]{}: box 0x383-0x39f.7 (29) diff --git a/format/mp4/testdata/png_no_hdlr.mp4.fqtest b/format/mp4/testdata/png_no_hdlr.mp4.fqtest index 528faaa4..60341b8f 100644 --- a/format/mp4/testdata/png_no_hdlr.mp4.fqtest +++ b/format/mp4/testdata/png_no_hdlr.mp4.fqtest @@ -281,7 +281,7 @@ $ fq dv png_no_hdlr.mp4 | | | boxes[0:1]: 0x2e9-0x30d.7 (37) | | | [0]{}: box 0x2e9-0x30d.7 (37) 0x2e0| 00 00 00 25 | ...% | size: 37 0x2e9-0x2ec.7 (4) -0x2e0| a9 74 6f| .to| type: "�too" 0x2ed-0x2f0.7 (4) +0x2e0| a9 74 6f| .to| type: "©too" (Encoder) 0x2ed-0x2f0.7 (4) 0x2f0|6f |o | | | | boxes[0:1]: 0x2f1-0x30d.7 (29) | | | [0]{}: box 0x2f1-0x30d.7 (29) diff --git a/format/mp4/testdata/vorbis.fqtest b/format/mp4/testdata/vorbis.fqtest index de9d4e94..842b1171 100644 --- a/format/mp4/testdata/vorbis.fqtest +++ b/format/mp4/testdata/vorbis.fqtest @@ -354,7 +354,7 @@ $ fq -d mp4 dv vorbis.mp4 | | | boxes[0:1]: 0x1164-0x1188.7 (37) | | | [0]{}: box 0x1164-0x1188.7 (37) 0x1160| 00 00 00 25 | ...% | size: 37 0x1164-0x1167.7 (4) -0x1160| a9 74 6f 6f | .too | type: "�too" 0x1168-0x116b.7 (4) +0x1160| a9 74 6f 6f | .too | type: "©too" (Encoder) 0x1168-0x116b.7 (4) | | | boxes[0:1]: 0x116c-0x1188.7 (29) | | | [0]{}: box 0x116c-0x1188.7 (29) 0x1160| 00 00 00 1d| ....| size: 29 0x116c-0x116f.7 (4) diff --git a/format/mp4/testdata/vp9.fqtest b/format/mp4/testdata/vp9.fqtest index 571914be..f344f8ff 100644 --- a/format/mp4/testdata/vp9.fqtest +++ b/format/mp4/testdata/vp9.fqtest @@ -298,7 +298,7 @@ $ fq -d mp4 dv vp9.mp4 | | | boxes[0:1]: 0x182a-0x184e.7 (37) | | | [0]{}: box 0x182a-0x184e.7 (37) 0x1820| 00 00 00 25 | ...% | size: 37 0x182a-0x182d.7 (4) -0x1820| a9 74| .t| type: "�too" 0x182e-0x1831.7 (4) +0x1820| a9 74| .t| type: "©too" (Encoder) 0x182e-0x1831.7 (4) 0x1830|6f 6f |oo | | | | boxes[0:1]: 0x1832-0x184e.7 (29) | | | [0]{}: box 0x1832-0x184e.7 (29) diff --git a/format/prores/testdata/prores_frame.fqtest b/format/prores/testdata/prores_frame.fqtest index 55ecd6b6..cde61638 100644 --- a/format/prores/testdata/prores_frame.fqtest +++ b/format/prores/testdata/prores_frame.fqtest @@ -289,7 +289,7 @@ $ fq -d mp4 dv prores_frame.mov | | | boxes[0:1]: 0x6ee6-0x6efe.7 (25) | | | [0]{}: box 0x6ee6-0x6efe.7 (25) 0x6ee0| 00 00 00 19 | .... | size: 25 0x6ee6-0x6ee9.7 (4) -0x6ee0| a9 73 77 72 | .swr | type: "�swr" 0x6eea-0x6eed.7 (4) +0x6ee0| a9 73 77 72 | .swr | type: "©swr" 0x6eea-0x6eed.7 (4) 0x6ee0| 00 0d| ..| data: raw bits 0x6eee-0x6efe.7 (17) 0x6ef0|55 c4 4c 61 76 66 35 39 2e 31 36 2e 31 30 30| |U.Lavf59.16.100|| | | | tracks[0:1]: 0x24-0x6efe.7 (28379) diff --git a/pkg/decode/decode_gen.go b/pkg/decode/decode_gen.go index 6dae7b41..c9bd7ce5 100644 --- a/pkg/decode/decode_gen.go +++ b/pkg/decode/decode_gen.go @@ -7,6 +7,7 @@ import ( "github.com/wader/fq/pkg/bitio" "github.com/wader/fq/pkg/scalar" + "golang.org/x/text/encoding" ) // Type BigInt @@ -19860,3 +19861,50 @@ func (d *D) TryFieldUTF8NullFixedLen(name string, fixedBytes int, sms ...scalar. func (d *D) FieldUTF8NullFixedLen(name string, fixedBytes int, sms ...scalar.Mapper) string { return d.FieldScalarUTF8NullFixedLen(name, fixedBytes, sms...).ActualStr() } + +// Reader Str + +// TryStr tries to read nBytes bytes using encoding e +func (d *D) TryStr(nBytes int, e encoding.Encoding) (string, error) { return d.tryText(nBytes, e) } + +// Str reads nBytes bytes using encoding e +func (d *D) Str(nBytes int, e encoding.Encoding) string { + v, err := d.tryText(nBytes, e) + if err != nil { + panic(IOError{Err: err, Op: "Str", Pos: d.Pos()}) + } + return v +} + +// TryFieldScalarStr tries to add a field and read nBytes bytes using encoding e +func (d *D) TryFieldScalarStr(name string, nBytes int, e encoding.Encoding, sms ...scalar.Mapper) (*scalar.S, error) { + s, err := d.TryFieldScalarFn(name, func(s scalar.S) (scalar.S, error) { + v, err := d.tryText(nBytes, e) + s.Actual = v + return s, err + }, sms...) + if err != nil { + return nil, err + } + return s, err +} + +// FieldScalarStr adds a field and reads nBytes bytes using encoding e +func (d *D) FieldScalarStr(name string, nBytes int, e encoding.Encoding, sms ...scalar.Mapper) *scalar.S { + s, err := d.TryFieldScalarStr(name, nBytes, e, sms...) + if err != nil { + panic(IOError{Err: err, Name: name, Op: "Str", Pos: d.Pos()}) + } + return s +} + +// TryFieldStr tries to add a field and read nBytes bytes using encoding e +func (d *D) TryFieldStr(name string, nBytes int, e encoding.Encoding, sms ...scalar.Mapper) (string, error) { + s, err := d.TryFieldScalarStr(name, nBytes, e, sms...) + return s.ActualStr(), err +} + +// FieldStr adds a field and reads nBytes bytes using encoding e +func (d *D) FieldStr(name string, nBytes int, e encoding.Encoding, sms ...scalar.Mapper) string { + return d.FieldScalarStr(name, nBytes, e, sms...).ActualStr() +} diff --git a/pkg/decode/decode_gen.go.tmpl b/pkg/decode/decode_gen.go.tmpl index 52d8fbc7..fa515d29 100644 --- a/pkg/decode/decode_gen.go.tmpl +++ b/pkg/decode/decode_gen.go.tmpl @@ -7,6 +7,7 @@ import ( "github.com/wader/fq/pkg/bitio" "github.com/wader/fq/pkg/scalar" + "golang.org/x/text/encoding" ) {{- range $name, $t := $.types }} diff --git a/pkg/decode/types.json b/pkg/decode/types.json index 6d20f995..b3900a10 100644 --- a/pkg/decode/types.json +++ b/pkg/decode/types.json @@ -234,6 +234,13 @@ "doc": "fixedBytes bytes long null terminated UTF8 string" } ] + }, + { + "name": "Str", + "type": "Str", + "variants": [ + {"name": "", "args": "nBytes, e", "params": "nBytes int, e encoding.Encoding", "call": "d.tryText(nBytes, e)", "doc": "nBytes bytes using encoding e"} + ] } ] } \ No newline at end of file