diff --git a/_examples/sample_qt.mp4 b/_examples/sample_qt.mp4 new file mode 100644 index 0000000..3ec33ce Binary files /dev/null and b/_examples/sample_qt.mp4 differ diff --git a/box.go b/box.go index 06401c3..beac0fd 100644 --- a/box.go +++ b/box.go @@ -3,68 +3,71 @@ package mp4 import ( "errors" "io" + "math" "github.com/abema/go-mp4/bitio" ) +const LengthUnlimited = math.MaxUint32 + type ICustomFieldObject interface { // GetFieldSize returns size of dynamic field - GetFieldSize(string) uint + GetFieldSize(name string, bss BoxStructureStatus) uint // GetFieldLength returns length of dynamic field - GetFieldLength(string) uint + GetFieldLength(name string, bss BoxStructureStatus) uint // IsOptFieldEnabled check whether if the optional field is enabled - IsOptFieldEnabled(string) bool + IsOptFieldEnabled(name string, bss BoxStructureStatus) bool // StringifyField returns field value as string - StringifyField(string, string, int) (string, bool) + StringifyField(name string, indent string, depth int, bss BoxStructureStatus) (string, bool) - IsPString(name string, bytes []byte, remainingSize uint64) bool + IsPString(name string, bytes []byte, remainingSize uint64, bss BoxStructureStatus) bool - BeforeUnmarshal(r io.ReadSeeker, size uint64) (n uint64, override bool, err error) + BeforeUnmarshal(r io.ReadSeeker, size uint64, bss BoxStructureStatus) (n uint64, override bool, err error) - OnReadField(name string, r bitio.ReadSeeker, leftBits uint64) (rbits uint64, override bool, err error) + OnReadField(name string, r bitio.ReadSeeker, leftBits uint64, bss BoxStructureStatus) (rbits uint64, override bool, err error) - OnWriteField(name string, w bitio.Writer) (wbits uint64, override bool, err error) + OnWriteField(name string, w bitio.Writer, bss BoxStructureStatus) (wbits uint64, override bool, err error) } type BaseCustomFieldObject struct { } // GetFieldSize returns size of dynamic field -func (box *BaseCustomFieldObject) GetFieldSize(string) uint { +func (box *BaseCustomFieldObject) GetFieldSize(string, BoxStructureStatus) uint { panic(errors.New("GetFieldSize not implemented")) } // GetFieldLength returns length of dynamic field -func (box *BaseCustomFieldObject) GetFieldLength(string) uint { +func (box *BaseCustomFieldObject) GetFieldLength(string, BoxStructureStatus) uint { panic(errors.New("GetFieldLength not implemented")) } // IsOptFieldEnabled check whether if the optional field is enabled -func (box *BaseCustomFieldObject) IsOptFieldEnabled(string) bool { +func (box *BaseCustomFieldObject) IsOptFieldEnabled(string, BoxStructureStatus) bool { return false } // StringifyField returns field value as string -func (box *BaseCustomFieldObject) StringifyField(string, string, int) (string, bool) { +func (box *BaseCustomFieldObject) StringifyField(string, string, int, BoxStructureStatus) (string, bool) { return "", false } -func (*BaseCustomFieldObject) IsPString(name string, bytes []byte, remainingSize uint64) bool { +func (*BaseCustomFieldObject) IsPString(name string, bytes []byte, remainingSize uint64, bss BoxStructureStatus) bool { return true } -func (*BaseCustomFieldObject) BeforeUnmarshal(io.ReadSeeker, uint64) (uint64, bool, error) { +func (*BaseCustomFieldObject) BeforeUnmarshal(io.ReadSeeker, uint64, BoxStructureStatus) (uint64, bool, error) { return 0, false, nil } -func (*BaseCustomFieldObject) OnReadField(string, bitio.ReadSeeker, uint64) (uint64, bool, error) { +func (*BaseCustomFieldObject) OnReadField(string, bitio.ReadSeeker, uint64, BoxStructureStatus) (uint64, bool, error) { return 0, false, nil } -func (*BaseCustomFieldObject) OnWriteField(string, bitio.Writer) (uint64, bool, error) { +func (*BaseCustomFieldObject) OnWriteField(string, bitio.Writer, BoxStructureStatus) (uint64, bool, error) { return 0, false, nil } diff --git a/box_info.go b/box_info.go index 5ce7db2..7152c76 100644 --- a/box_info.go +++ b/box_info.go @@ -7,6 +7,14 @@ import ( "math" ) +type BoxStructureStatus struct { + // IsQuickTimeCompatible represents whether ftyp.compatible_brands contains "qt ". + IsQuickTimeCompatible bool + + // UnderWave represents whether current box is under the wave box. + UnderWave bool +} + // BoxInfo has common infomations of box type BoxInfo struct { // Offset specifies an offset of the box in a file. @@ -23,6 +31,9 @@ type BoxInfo struct { // ExtendToEOF is set true when Box.size is zero. It means that end of box equals to end of file. ExtendToEOF bool + + // BoxStructureStatus would be set by ReadBoxStructure, not ReadBoxInfo. + BoxStructureStatus } const ( diff --git a/box_types.go b/box_types.go index 7e2dab8..e4d8d9e 100644 --- a/box_types.go +++ b/box_types.go @@ -31,7 +31,7 @@ func (*Co64) GetType() BoxType { } // GetFieldLength returns length of dynamic field -func (co64 *Co64) GetFieldLength(name string) uint { +func (co64 *Co64) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "ChunkOffset": return uint(co64.EntryCount) @@ -59,7 +59,7 @@ type Colr struct { Unknown []byte `mp4:"size=8,opt=dynamic"` } -func (colr *Colr) IsOptFieldEnabled(name string) bool { +func (colr *Colr) IsOptFieldEnabled(name string, bss BoxStructureStatus) bool { switch colr.ColourType { case [4]byte{'n', 'c', 'l', 'x'}: switch name { @@ -111,7 +111,7 @@ func (*Ctts) GetType() BoxType { } // GetFieldLength returns length of dynamic field -func (ctts *Ctts) GetFieldLength(name string) uint { +func (ctts *Ctts) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Entries": return uint(ctts.EntryCount) @@ -231,7 +231,7 @@ func (*Elst) GetType() BoxType { } // GetFieldSize returns size of dynamic field -func (elst *Elst) GetFieldSize(name string) uint { +func (elst *Elst) GetFieldSize(name string, bss BoxStructureStatus) uint { switch name { case "Entries": switch elst.GetVersion() { @@ -253,7 +253,7 @@ func (elst *Elst) GetFieldSize(name string) uint { } // GetFieldLength returns length of dynamic field -func (elst *Elst) GetFieldLength(name string) uint { +func (elst *Elst) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Entries": return uint(elst.EntryCount) @@ -282,7 +282,7 @@ type Emsg struct { MessageData []byte `mp4:"size=8,string"` } -func (emsg *Emsg) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64) (rbits uint64, override bool, err error) { +func (emsg *Emsg) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64, bss BoxStructureStatus) (rbits uint64, override bool, err error) { if emsg.GetVersion() == 0 { return } @@ -306,7 +306,7 @@ func (emsg *Emsg) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64) } } -func (emsg *Emsg) OnWriteField(name string, w bitio.Writer) (wbits uint64, override bool, err error) { +func (emsg *Emsg) OnWriteField(name string, w bitio.Writer, bss BoxStructureStatus) (wbits uint64, override bool, err error) { if emsg.GetVersion() == 0 { return } @@ -371,7 +371,7 @@ type Descriptor struct { } // GetFieldLength returns length of dynamic field -func (ds *Descriptor) GetFieldLength(name string) uint { +func (ds *Descriptor) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Data": return uint(ds.Size) @@ -379,7 +379,7 @@ func (ds *Descriptor) GetFieldLength(name string) uint { panic(fmt.Errorf("invalid name of dynamic-length field: boxType=esds fieldName=%s", name)) } -func (ds *Descriptor) IsOptFieldEnabled(name string) bool { +func (ds *Descriptor) IsOptFieldEnabled(name string, bss BoxStructureStatus) bool { switch ds.Tag { case ESDescrTag: return name == "ESDescriptor" @@ -391,7 +391,7 @@ func (ds *Descriptor) IsOptFieldEnabled(name string) bool { } // StringifyField returns field value as string -func (ds *Descriptor) StringifyField(name string, indent string, depth int) (string, bool) { +func (ds *Descriptor) StringifyField(name string, indent string, depth int, bss BoxStructureStatus) (string, bool) { switch name { case "Tag": switch ds.Tag { @@ -424,7 +424,7 @@ type ESDescriptor struct { OCRESID uint16 `mp4:"size=16,opt=dynamic"` } -func (esds *ESDescriptor) GetFieldLength(name string) uint { +func (esds *ESDescriptor) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "URLString": return uint(esds.URLLength) @@ -432,7 +432,7 @@ func (esds *ESDescriptor) GetFieldLength(name string) uint { panic(fmt.Errorf("invalid name of dynamic-length field: boxType=ESDescriptor fieldName=%s", name)) } -func (esds *ESDescriptor) IsOptFieldEnabled(name string) bool { +func (esds *ESDescriptor) IsOptFieldEnabled(name string, bss BoxStructureStatus) bool { switch name { case "DependsOnESID": return esds.StreamDependenceFlag @@ -491,6 +491,8 @@ func init() { AddBoxDef(&Ftyp{}) } +func CompatibleBrandQT() [4]byte { return [4]byte{'q', 't', ' ', ' '} } + // Ftyp is ISOBMFF ftyp box type type Ftyp struct { Box @@ -534,7 +536,7 @@ func (*Hdlr) GetType() BoxType { return BoxTypeHdlr() } -func (hdlr *Hdlr) IsPString(name string, bytes []byte, remainingSize uint64) bool { +func (hdlr *Hdlr) IsPString(name string, bytes []byte, remainingSize uint64, bss BoxStructureStatus) bool { switch name { case "Name": return remainingSize == 0 && hdlr.PreDefined != 0 @@ -664,7 +666,7 @@ func (*Meta) GetType() BoxType { return BoxTypeMeta() } -func (meta *Meta) BeforeUnmarshal(r io.ReadSeeker, size uint64) (n uint64, override bool, err error) { +func (meta *Meta) BeforeUnmarshal(r io.ReadSeeker, size uint64, bss BoxStructureStatus) (n uint64, override bool, err error) { // for Apple Quick Time buf := make([]byte, 4) if _, err := io.ReadFull(r, buf); err != nil { @@ -842,7 +844,7 @@ func (*Mvhd) GetType() BoxType { } // StringifyField returns field value as string -func (mvhd *Mvhd) StringifyField(name string, indent string, depth int) (string, bool) { +func (mvhd *Mvhd) StringifyField(name string, indent string, depth int, bss BoxStructureStatus) (string, bool) { switch name { case "Rate": return strconv.FormatFloat(mvhd.GetRate(), 'f', -1, 32), true @@ -884,7 +886,7 @@ type PsshKID struct { } // GetFieldLength returns length of dynamic field -func (pssh *Pssh) GetFieldLength(name string) uint { +func (pssh *Pssh) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "KIDs": return uint(pssh.KIDCount) @@ -895,7 +897,7 @@ func (pssh *Pssh) GetFieldLength(name string) uint { } // StringifyField returns field value as string -func (pssh *Pssh) StringifyField(name string, indent string, depth int) (string, bool) { +func (pssh *Pssh) StringifyField(name string, indent string, depth int, bss BoxStructureStatus) (string, bool) { switch name { case "SystemID": buf := bytes.NewBuffer(nil) @@ -967,7 +969,7 @@ type VisualSampleEntry struct { } // StringifyField returns field value as string -func (vse *VisualSampleEntry) StringifyField(name string, indent string, depth int) (string, bool) { +func (vse *VisualSampleEntry) StringifyField(name string, indent string, depth int, bss BoxStructureStatus) (string, bool) { switch name { case "Compressorname": if vse.Compressorname[0] <= 31 { @@ -980,14 +982,38 @@ func (vse *VisualSampleEntry) StringifyField(name string, indent string, depth i } type AudioSampleEntry struct { - SampleEntry `mp4:"extend"` - EntryVersion uint16 `mp4:"size=16"` - Reserved [3]uint16 `mp4:"size=16,const=0"` - ChannelCount uint16 `mp4:"size=16"` - SampleSize uint16 `mp4:"size=16"` - PreDefined uint16 `mp4:"size=16"` - Reserved2 uint16 `mp4:"size=16,const=0"` - SampleRate uint32 `mp4:"size=32"` + SampleEntry `mp4:"extend,opt=dynamic"` + EntryVersion uint16 `mp4:"size=16,opt=dynamic"` + Reserved [3]uint16 `mp4:"size=16,opt=dynamic,const=0"` + ChannelCount uint16 `mp4:"size=16,opt=dynamic"` + SampleSize uint16 `mp4:"size=16,opt=dynamic"` + PreDefined uint16 `mp4:"size=16,opt=dynamic"` + Reserved2 uint16 `mp4:"size=16,opt=dynamic,const=0"` + SampleRate uint32 `mp4:"size=32,opt=dynamic"` + QuickTimeData []byte `mp4:"size=8,opt=dynamic,len=dynamic"` +} + +func (ase *AudioSampleEntry) IsOptFieldEnabled(name string, bss BoxStructureStatus) bool { + if name == "QuickTimeData" { + return bss.IsQuickTimeCompatible && (bss.UnderWave || ase.EntryVersion == 1 || ase.EntryVersion == 2) + } + if bss.IsQuickTimeCompatible && bss.UnderWave { + return false + } + return true +} + +func (ase *AudioSampleEntry) GetFieldLength(name string, bss BoxStructureStatus) uint { + if name == "QuickTimeData" && bss.IsQuickTimeCompatible { + if bss.UnderWave { + return LengthUnlimited + } else if ase.EntryVersion == 1 { + return 16 + } else if ase.EntryVersion == 2 { + return 36 + } + } + return 0 } const ( @@ -1023,7 +1049,7 @@ type AVCDecoderConfiguration struct { SequenceParameterSetsExt []AVCParameterSet `mp4:"len=dynamic,opt=dynamic"` } -func (avcc *AVCDecoderConfiguration) GetFieldLength(name string) uint { +func (avcc *AVCDecoderConfiguration) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "SequenceParameterSets": return uint(avcc.NumOfSequenceParameterSets) @@ -1035,7 +1061,7 @@ func (avcc *AVCDecoderConfiguration) GetFieldLength(name string) uint { return 0 } -func (avcc *AVCDecoderConfiguration) IsOptFieldEnabled(name string) bool { +func (avcc *AVCDecoderConfiguration) IsOptFieldEnabled(name string, bss BoxStructureStatus) bool { switch name { case "Reserved3", "ChromaFormat", @@ -1050,7 +1076,7 @@ func (avcc *AVCDecoderConfiguration) IsOptFieldEnabled(name string) bool { return false } -func (avcc *AVCDecoderConfiguration) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64) (rbits uint64, override bool, err error) { +func (avcc *AVCDecoderConfiguration) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64, bss BoxStructureStatus) (rbits uint64, override bool, err error) { if name == "HighProfileFieldsEnabled" { avcc.HighProfileFieldsEnabled = leftBits >= 32 && (avcc.Profile == AVCHighProfile || @@ -1062,7 +1088,7 @@ func (avcc *AVCDecoderConfiguration) OnReadField(name string, r bitio.ReadSeeker return 0, false, nil } -func (avcc *AVCDecoderConfiguration) OnWriteField(name string, w bitio.Writer) (wbits uint64, override bool, err error) { +func (avcc *AVCDecoderConfiguration) OnWriteField(name string, w bitio.Writer, bss BoxStructureStatus) (wbits uint64, override bool, err error) { if name == "HighProfileFieldsEnabled" { if avcc.HighProfileFieldsEnabled && avcc.Profile != AVCHighProfile && @@ -1082,7 +1108,7 @@ type AVCParameterSet struct { NALUnit []byte `mp4:"size=8,len=dynamic"` } -func (s *AVCParameterSet) GetFieldLength(name string) uint { +func (s *AVCParameterSet) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "NALUnit": return uint(s.Length) @@ -1117,7 +1143,7 @@ type SbgpEntry struct { GroupDescriptionIndex uint32 `mp4:"size=32"` } -func (sbgp *Sbgp) GetFieldLength(name string) uint { +func (sbgp *Sbgp) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Entries": return uint(sbgp.EntryCount) @@ -1213,7 +1239,7 @@ type TemporalLevelEntryL struct { TemporalLevelEntry `mp4:"extend"` } -func (sgpd *Sgpd) GetFieldSize(name string) uint { +func (sgpd *Sgpd) GetFieldSize(name string, bss BoxStructureStatus) uint { switch name { case "AlternativeStartupEntries": return uint(sgpd.DefaultLength * 8) @@ -1221,7 +1247,7 @@ func (sgpd *Sgpd) GetFieldSize(name string) uint { return 0 } -func (sgpd *Sgpd) GetFieldLength(name string) uint { +func (sgpd *Sgpd) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "RollDistances", "RollDistancesL", "AlternativeStartupEntries", "AlternativeStartupEntriesL", @@ -1232,7 +1258,7 @@ func (sgpd *Sgpd) GetFieldLength(name string) uint { return 0 } -func (sgpd *Sgpd) IsOptFieldEnabled(name string) bool { +func (sgpd *Sgpd) IsOptFieldEnabled(name string, bss BoxStructureStatus) bool { noDefaultLength := sgpd.Version == 1 && sgpd.DefaultLength == 0 rollDistances := sgpd.GroupingType == [4]byte{'r', 'o', 'l', 'l'} || sgpd.GroupingType == [4]byte{'p', 'r', 'o', 'l'} @@ -1270,7 +1296,7 @@ func (*Sgpd) GetType() BoxType { return BoxTypeSgpd() } -func (entry *AlternativeStartupEntry) GetFieldLength(name string) uint { +func (entry *AlternativeStartupEntry) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "SampleOffset": return uint(entry.RollCount) @@ -1278,7 +1304,7 @@ func (entry *AlternativeStartupEntry) GetFieldLength(name string) uint { return 0 } -func (entry *AlternativeStartupEntryL) GetFieldSize(name string) uint { +func (entry *AlternativeStartupEntryL) GetFieldSize(name string, bss BoxStructureStatus) uint { switch name { case "AlternativeStartupEntry": return uint(entry.DescriptionLength * 8) @@ -1320,7 +1346,7 @@ func (*Sidx) GetType() BoxType { return BoxTypeSidx() } -func (sidx *Sidx) GetFieldLength(name string) uint { +func (sidx *Sidx) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "References": return uint(sidx.ReferenceCount) @@ -1363,7 +1389,7 @@ func (*Smhd) GetType() BoxType { } // StringifyField returns field value as string -func (smhd *Smhd) StringifyField(name string, indent string, depth int) (string, bool) { +func (smhd *Smhd) StringifyField(name string, indent string, depth int, bss BoxStructureStatus) (string, bool) { switch name { case "Balance": return strconv.FormatFloat(float64(smhd.GetBalance()), 'f', -1, 32), true @@ -1421,7 +1447,7 @@ func (*Stco) GetType() BoxType { } // GetFieldLength returns length of dynamic field -func (stco *Stco) GetFieldLength(name string) uint { +func (stco *Stco) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "ChunkOffset": return uint(stco.EntryCount) @@ -1456,7 +1482,7 @@ func (*Stsc) GetType() BoxType { } // GetFieldLength returns length of dynamic field -func (stsc *Stsc) GetFieldLength(name string) uint { +func (stsc *Stsc) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Entries": return uint(stsc.EntryCount) @@ -1503,7 +1529,7 @@ func (*Stss) GetType() BoxType { } // GetFieldLength returns length of dynamic field -func (stss *Stss) GetFieldLength(name string) uint { +func (stss *Stss) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "SampleNumber": return uint(stss.EntryCount) @@ -1533,7 +1559,7 @@ func (*Stsz) GetType() BoxType { } // GetFieldLength returns length of dynamic field -func (stsz *Stsz) GetFieldLength(name string) uint { +func (stsz *Stsz) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "EntrySize": if stsz.SampleSize == 0 { @@ -1571,7 +1597,7 @@ func (*Stts) GetType() BoxType { } // GetFieldLength returns length of dynamic field -func (stts *Stts) GetFieldLength(name string) uint { +func (stts *Stts) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Entries": return uint(stts.EntryCount) @@ -1690,7 +1716,7 @@ func (*Tfra) GetType() BoxType { } // GetFieldSize returns size of dynamic field -func (tfra *Tfra) GetFieldSize(name string) uint { +func (tfra *Tfra) GetFieldSize(name string, bss BoxStructureStatus) uint { switch name { case "TrafNumber": return (uint(tfra.LengthSizeOfTrafNum) + 1) * 8 @@ -1720,7 +1746,7 @@ func (tfra *Tfra) GetFieldSize(name string) uint { } // GetFieldLength returns length of dynamic field -func (tfra *Tfra) GetFieldLength(name string) uint { +func (tfra *Tfra) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Entries": return uint(tfra.NumberOfEntry) @@ -1768,7 +1794,7 @@ func (*Tkhd) GetType() BoxType { } // StringifyField returns field value as string -func (tkhd *Tkhd) StringifyField(name string, indent string, depth int) (string, bool) { +func (tkhd *Tkhd) StringifyField(name string, indent string, depth int, bss BoxStructureStatus) (string, bool) { switch name { case "Width": return strconv.FormatFloat(tkhd.GetWidth(), 'f', -1, 32), true @@ -1891,7 +1917,7 @@ func (*Trun) GetType() BoxType { } // GetFieldSize returns size of dynamic field -func (trun *Trun) GetFieldSize(name string) uint { +func (trun *Trun) GetFieldSize(name string, bss BoxStructureStatus) uint { switch name { case "Entries": var size uint @@ -1914,7 +1940,7 @@ func (trun *Trun) GetFieldSize(name string) uint { } // GetFieldLength returns length of dynamic field -func (trun *Trun) GetFieldLength(name string) uint { +func (trun *Trun) GetFieldLength(name string, bss BoxStructureStatus) uint { switch name { case "Entries": return uint(trun.SampleCount) @@ -1959,3 +1985,21 @@ type Vmhd struct { func (*Vmhd) GetType() BoxType { return BoxTypeVmhd() } + +/*************************** wave ****************************/ + +func BoxTypeWave() BoxType { return StrToBoxType("wave") } + +func init() { + AddBoxDef(&Wave{}) +} + +// Wave is QuickTime wave box +type Wave struct { + Box +} + +// GetType returns the BoxType +func (*Wave) GetType() BoxType { + return BoxTypeWave() +} diff --git a/box_types_test.go b/box_types_test.go index 84fa8e1..0836e2e 100644 --- a/box_types_test.go +++ b/box_types_test.go @@ -16,6 +16,7 @@ func TestBoxTypes(t *testing.T) { dst IBox bin []byte str string + bss BoxStructureStatus }{ { name: "co64", @@ -946,6 +947,152 @@ func TestBoxTypes(t *testing.T) { `PreDefined=26505 ` + `SampleRate=19088743`, }, + { + name: "AudioSampleEntry", + src: &AudioSampleEntry{ + SampleEntry: SampleEntry{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}, + DataReferenceIndex: 0x1234, + }, + EntryVersion: 0x0123, + ChannelCount: 0x2345, + SampleSize: 0x4567, + PreDefined: 0x6789, + SampleRate: 0x01234567, + }, + dst: &AudioSampleEntry{SampleEntry: SampleEntry{AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}}}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x12, 0x34, // data reference index + 0x01, 0x23, // entry version + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x23, 0x45, // channel count + 0x45, 0x67, // sample size + 0x67, 0x89, // pre-defined + 0x00, 0x00, // reserved + 0x01, 0x23, 0x45, 0x67, // sample rate + }, + str: `DataReferenceIndex=4660 ` + + `EntryVersion=291 ` + + `ChannelCount=9029 ` + + `SampleSize=17767 ` + + `PreDefined=26505 ` + + `SampleRate=19088743`, + }, + { + name: "AudioSampleEntry (QuickTime v0)", + src: &AudioSampleEntry{ + SampleEntry: SampleEntry{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}, + DataReferenceIndex: 0x1234, + }, + EntryVersion: 0, + ChannelCount: 0x2345, + SampleSize: 0x4567, + PreDefined: 0x6789, + SampleRate: 0x01234567, + }, + dst: &AudioSampleEntry{SampleEntry: SampleEntry{AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}}}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x12, 0x34, // data reference index + 0x00, 0x00, // entry version + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x23, 0x45, // channel count + 0x45, 0x67, // sample size + 0x67, 0x89, // pre-defined + 0x00, 0x00, // reserved + 0x01, 0x23, 0x45, 0x67, // sample rate + }, + str: `DataReferenceIndex=4660 ` + + `EntryVersion=0 ` + + `ChannelCount=9029 ` + + `SampleSize=17767 ` + + `PreDefined=26505 ` + + `SampleRate=19088743`, + bss: BoxStructureStatus{IsQuickTimeCompatible: true}, + }, + { + name: "AudioSampleEntry (QuickTime v1)", + src: &AudioSampleEntry{ + SampleEntry: SampleEntry{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}, + DataReferenceIndex: 0x1234, + }, + EntryVersion: 1, + ChannelCount: 0x2345, + SampleSize: 0x4567, + PreDefined: 0x6789, + SampleRate: 0x01234567, + QuickTimeData: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + }, + dst: &AudioSampleEntry{SampleEntry: SampleEntry{AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}}}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x12, 0x34, // data reference index + 0x00, 0x01, // entry version + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x23, 0x45, // channel count + 0x45, 0x67, // sample size + 0x67, 0x89, // pre-defined + 0x00, 0x00, // reserved + 0x01, 0x23, 0x45, 0x67, // sample rate + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + }, + str: `DataReferenceIndex=4660 ` + + `EntryVersion=1 ` + + `ChannelCount=9029 ` + + `SampleSize=17767 ` + + `PreDefined=26505 ` + + `SampleRate=19088743 ` + + `QuickTimeData=[0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]`, + bss: BoxStructureStatus{IsQuickTimeCompatible: true}, + }, + { + name: "AudioSampleEntry (QuickTime v2)", + src: &AudioSampleEntry{ + SampleEntry: SampleEntry{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}, + DataReferenceIndex: 0x1234, + }, + EntryVersion: 2, + ChannelCount: 0x2345, + SampleSize: 0x4567, + PreDefined: 0x6789, + SampleRate: 0x01234567, + QuickTimeData: []byte{ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x00, 0x11, 0x22, 0x33, + }, + }, + dst: &AudioSampleEntry{SampleEntry: SampleEntry{AnyTypeBox: AnyTypeBox{Type: StrToBoxType("enca")}}}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x12, 0x34, // data reference index + 0x00, 0x02, // entry version + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0x23, 0x45, // channel count + 0x45, 0x67, // sample size + 0x67, 0x89, // pre-defined + 0x00, 0x00, // reserved + 0x01, 0x23, 0x45, 0x67, // sample rate + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x00, 0x11, 0x22, 0x33, + }, + str: `DataReferenceIndex=4660 ` + + `EntryVersion=2 ` + + `ChannelCount=9029 ` + + `SampleSize=17767 ` + + `PreDefined=26505 ` + + `SampleRate=19088743 ` + + `QuickTimeData=[` + + `0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, ` + + `0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, ` + + `0x0, 0x11, 0x22, 0x33]`, + bss: BoxStructureStatus{IsQuickTimeCompatible: true}, + }, { name: "AVCDecoderConfiguration main profile", src: &AVCDecoderConfiguration{ @@ -2290,14 +2437,14 @@ func TestBoxTypes(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Marshal buf := bytes.NewBuffer(nil) - n, err := Marshal(buf, tc.src) + n, err := Marshal(buf, tc.src, tc.bss) require.NoError(t, err) assert.Equal(t, uint64(len(tc.bin)), n) assert.Equal(t, tc.bin, buf.Bytes()) // Unmarshal r := bytes.NewReader(tc.bin) - n, err = Unmarshal(r, uint64(len(tc.bin)), tc.dst) + n, err = Unmarshal(r, uint64(len(tc.bin)), tc.dst, tc.bss) require.NoError(t, err) assert.Equal(t, uint64(buf.Len()), n) assert.Equal(t, tc.src, tc.dst) @@ -2306,7 +2453,7 @@ func TestBoxTypes(t *testing.T) { assert.Equal(t, int64(buf.Len()), s) // UnmarshalAny - dst, n, err := UnmarshalAny(bytes.NewReader(tc.bin), tc.src.GetType(), uint64(len(tc.bin))) + dst, n, err := UnmarshalAny(bytes.NewReader(tc.bin), tc.src.GetType(), uint64(len(tc.bin)), tc.bss) require.NoError(t, err) assert.Equal(t, uint64(buf.Len()), n) assert.Equal(t, tc.src, dst) @@ -2315,7 +2462,7 @@ func TestBoxTypes(t *testing.T) { assert.Equal(t, int64(buf.Len()), s) // Stringify - str, err := Stringify(tc.src) + str, err := Stringify(tc.src, tc.bss) require.NoError(t, err) assert.Equal(t, tc.str, str) }) @@ -2387,7 +2534,7 @@ func TestHdlrUnmarshalHandlerName(t *testing.T) { // unmarshal dst := Hdlr{} r := bytes.NewReader(bin) - n, err := Unmarshal(r, uint64(len(bin)), &dst) + n, err := Unmarshal(r, uint64(len(bin)), &dst, BoxStructureStatus{}) assert.NoError(t, err) assert.Equal(t, uint64(len(bin)), n) assert.Equal(t, [4]byte{'v', 'i', 'd', 'e'}, dst.HandlerType) @@ -2408,7 +2555,7 @@ func TestMetaMarshalAppleQuickTime(t *testing.T) { // unmarshal dst := Meta{} r := bytes.NewReader(bin) - n, err := Unmarshal(r, uint64(len(bin)), &dst) + n, err := Unmarshal(r, uint64(len(bin)), &dst, BoxStructureStatus{}) assert.NoError(t, err) assert.Equal(t, uint64(0), n) s, _ := r.Seek(0, io.SeekCurrent) @@ -2451,7 +2598,7 @@ func TestAvcCInconsistentError(t *testing.T) { }, } buf := bytes.NewBuffer(nil) - _, err := Marshal(buf, avcc) + _, err := Marshal(buf, avcc, BoxStructureStatus{}) require.Error(t, err) assert.Equal(t, "each values of Profile and HighProfileFieldsEnabled are inconsistent", err.Error()) } diff --git a/extract.go b/extract.go index a850efa..5e3001b 100644 --- a/extract.go +++ b/extract.go @@ -26,7 +26,11 @@ func ExtractBoxesWithPayload(r io.ReadSeeker, parent *BoxInfo, paths []BoxPath) return nil, err } - box, _, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize) + var bss BoxStructureStatus + if parent != nil { + bss = parent.BoxStructureStatus + } + box, _, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize, bss) if err != nil { return nil, err } diff --git a/marshaller.go b/marshaller.go index e9ed662..1045bd6 100644 --- a/marshaller.go +++ b/marshaller.go @@ -14,8 +14,7 @@ import ( ) const ( - anyVersion = math.MaxUint8 - lengthUnlimited = math.MaxUint32 + anyVersion = math.MaxUint8 ) var ErrUnsupportedBoxVersion = errors.New("unsupported box version") @@ -24,15 +23,17 @@ type marshaller struct { writer bitio.Writer wbits uint64 src IImmutableBox + bss BoxStructureStatus } -func Marshal(w io.Writer, src IImmutableBox) (n uint64, err error) { +func Marshal(w io.Writer, src IImmutableBox, bss BoxStructureStatus) (n uint64, err error) { t := reflect.TypeOf(src).Elem() v := reflect.ValueOf(src).Elem() m := &marshaller{ writer: bitio.NewWriter(w), src: src, + bss: bss, } if err := m.marshalStruct(t, v); err != nil { @@ -83,16 +84,16 @@ func (m *marshaller) marshalStruct(t reflect.Type, v reflect.Value) error { if !ok { continue } - config, err := readFieldConfig(m.src, v, f.Name, parseFieldTag(tagStr)) + config, err := readFieldConfig(m.src, v, f.Name, parseFieldTag(tagStr), m.bss) if err != nil { return err } - if !isTargetField(m.src, config) { + if !isTargetField(m.src, config, m.bss) { continue } - wbits, override, err := config.cfo.OnWriteField(f.Name, m.writer) + wbits, override, err := config.cfo.OnWriteField(f.Name, m.writer, m.bss) if err != nil { return err } @@ -124,7 +125,7 @@ func (m *marshaller) marshalArray(t reflect.Type, v reflect.Value, config fieldC func (m *marshaller) marshalSlice(t reflect.Type, v reflect.Value, config fieldConfig) error { length := uint64(v.Len()) - if config.length != lengthUnlimited { + if config.length != LengthUnlimited { if length < uint64(config.length) { return fmt.Errorf("the slice has too few elements: required=%d actual=%d", config.length, length) } @@ -258,18 +259,19 @@ type unmarshaller struct { dst IBox size uint64 rbits uint64 + bss BoxStructureStatus } -func UnmarshalAny(r io.ReadSeeker, boxType BoxType, payloadSize uint64) (box IBox, n uint64, err error) { +func UnmarshalAny(r io.ReadSeeker, boxType BoxType, payloadSize uint64, bss BoxStructureStatus) (box IBox, n uint64, err error) { if dst, err := boxType.New(); err != nil { return nil, 0, err } else { - n, err := Unmarshal(r, payloadSize, dst) + n, err := Unmarshal(r, payloadSize, dst, bss) return dst, n, err } } -func Unmarshal(r io.ReadSeeker, payloadSize uint64, dst IBox) (n uint64, err error) { +func Unmarshal(r io.ReadSeeker, payloadSize uint64, dst IBox, bss BoxStructureStatus) (n uint64, err error) { t := reflect.TypeOf(dst).Elem() v := reflect.ValueOf(dst).Elem() @@ -279,9 +281,10 @@ func Unmarshal(r io.ReadSeeker, payloadSize uint64, dst IBox) (n uint64, err err reader: bitio.NewReadSeeker(r), dst: dst, size: payloadSize, + bss: bss, } - if n, override, err := dst.BeforeUnmarshal(r, payloadSize); err != nil { + if n, override, err := dst.BeforeUnmarshal(r, payloadSize, u.bss); err != nil { return 0, err } else if override { return n, nil @@ -370,16 +373,16 @@ func (u *unmarshaller) unmarshalStruct(t reflect.Type, v reflect.Value) error { if !ok { continue } - config, err := readFieldConfig(u.dst, v, f.Name, parseFieldTag(tagStr)) + config, err := readFieldConfig(u.dst, v, f.Name, parseFieldTag(tagStr), u.bss) if err != nil { return err } - if !isTargetField(u.dst, config) { + if !isTargetField(u.dst, config, u.bss) { continue } - rbits, override, err := config.cfo.OnReadField(f.Name, u.reader, u.size*8-u.rbits) + rbits, override, err := config.cfo.OnReadField(f.Name, u.reader, u.size*8-u.rbits, u.bss) if err != nil { return err } @@ -418,7 +421,7 @@ func (u *unmarshaller) unmarshalSlice(t reflect.Type, v reflect.Value, config fi elemType := t.Elem() length := uint64(config.length) - if config.length == lengthUnlimited { + if config.length == LengthUnlimited { if config.size != 0 { left := (u.size)*8 - u.rbits if left%uint64(config.size) != 0 { @@ -446,11 +449,11 @@ func (u *unmarshaller) unmarshalSlice(t reflect.Type, v reflect.Value, config fi } else { slice = reflect.MakeSlice(t, 0, int(length)) for i := 0; ; i++ { - if config.length != lengthUnlimited && uint(i) >= config.length { + if config.length != LengthUnlimited && uint(i) >= config.length { break } - if config.length == lengthUnlimited && u.rbits >= u.size*8 { + if config.length == LengthUnlimited && u.rbits >= u.size*8 { break } @@ -632,7 +635,7 @@ func (u *unmarshaller) tryReadPString(t reflect.Type, v reflect.Value, config fi return false, err } remainingSize -= uint64(plen) - if config.cfo.IsPString(config.name, buf, remainingSize) { + if config.cfo.IsPString(config.name, buf, remainingSize, u.bss) { u.rbits += uint64(len(buf)+1) * 8 v.SetString(string(buf)) return true, nil @@ -684,7 +687,7 @@ type fieldConfig struct { hidden bool } -func readFieldConfig(box IImmutableBox, parent reflect.Value, fieldName string, tag fieldTag) (config fieldConfig, err error) { +func readFieldConfig(box IImmutableBox, parent reflect.Value, fieldName string, tag fieldTag, bss BoxStructureStatus) (config fieldConfig, err error) { config.name = fieldName cfo, ok := parent.Addr().Interface().(ICustomFieldObject) if ok { @@ -695,7 +698,7 @@ func readFieldConfig(box IImmutableBox, parent reflect.Value, fieldName string, if val, contained := tag["size"]; contained { if val == "dynamic" { - config.size = config.cfo.GetFieldSize(fieldName) + config.size = config.cfo.GetFieldSize(fieldName, bss) } else { var size uint64 size, err = strconv.ParseUint(val, 10, 32) @@ -706,10 +709,10 @@ func readFieldConfig(box IImmutableBox, parent reflect.Value, fieldName string, } } - config.length = lengthUnlimited + config.length = LengthUnlimited if val, contained := tag["len"]; contained { if val == "dynamic" { - config.length = config.cfo.GetFieldLength(fieldName) + config.length = config.cfo.GetFieldLength(fieldName, bss) } else { var l uint64 l, err = strconv.ParseUint(val, 10, 32) @@ -822,7 +825,7 @@ func parseFieldTag(str string) fieldTag { return tag } -func isTargetField(box IImmutableBox, config fieldConfig) bool { +func isTargetField(box IImmutableBox, config fieldConfig, bss BoxStructureStatus) bool { if box.GetVersion() != anyVersion { if config.version != anyVersion && box.GetVersion() != config.version { return false @@ -841,7 +844,7 @@ func isTargetField(box IImmutableBox, config fieldConfig) bool { return false } - if config.optDynamic && !config.cfo.IsOptFieldEnabled(config.name) { + if config.optDynamic && !config.cfo.IsOptFieldEnabled(config.name, bss) { return false } diff --git a/marshaller_test.go b/marshaller_test.go index 27ed4eb..7d04bee 100644 --- a/marshaller_test.go +++ b/marshaller_test.go @@ -21,7 +21,7 @@ func (m *mockBox) GetType() BoxType { return m.Type } -func (m *mockBox) GetFieldSize(n string) uint { +func (m *mockBox) GetFieldSize(n string, bss BoxStructureStatus) uint { if s, ok := m.DynSizeMap[n]; !ok { panic(fmt.Errorf("invalid name of dynamic-size field: %s", n)) } else { @@ -29,7 +29,7 @@ func (m *mockBox) GetFieldSize(n string) uint { } } -func (m *mockBox) GetFieldLength(n string) uint { +func (m *mockBox) GetFieldLength(n string, bss BoxStructureStatus) uint { if l, ok := m.DynLenMap[n]; !ok { panic(fmt.Errorf("invalid name of dynamic-length field: %s", n)) } else { @@ -180,14 +180,14 @@ func TestMarshal(t *testing.T) { // marshal buf := &bytes.Buffer{} - n, err := Marshal(buf, &src) + n, err := Marshal(buf, &src, BoxStructureStatus{}) require.NoError(t, err) assert.Equal(t, uint64(len(bin)), n) assert.Equal(t, bin, buf.Bytes()) // unmarshal dst := testBox{mockBox: mb} - n, err = Unmarshal(bytes.NewReader(bin), uint64(len(bin)+8), &dst) + n, err = Unmarshal(bytes.NewReader(bin), uint64(len(bin)+8), &dst, BoxStructureStatus{}) assert.NoError(t, err) assert.Equal(t, uint64(len(bin)), n) assert.Equal(t, src, dst) @@ -229,7 +229,7 @@ func TestUnsupportedBoxVersionErr(t *testing.T) { } dst := testBox{mockBox: mb} - n, err := Unmarshal(bytes.NewReader(bin), uint64(len(bin)+8), &dst) + n, err := Unmarshal(bytes.NewReader(bin), uint64(len(bin)+8), &dst, BoxStructureStatus{}) if e.enabled { assert.NoError(t, err, "version=%d", e.version) @@ -308,7 +308,7 @@ func TestReadFieldConfig(t *testing.T) { name: "ByteArray", cfo: box, size: 8, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, }, @@ -329,7 +329,7 @@ func TestReadFieldConfig(t *testing.T) { name: "ByteArray", cfo: box, size: 3, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, }, @@ -378,7 +378,7 @@ func TestReadFieldConfig(t *testing.T) { name: "Int", cfo: box, size: 13, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, varint: true, @@ -393,7 +393,7 @@ func TestReadFieldConfig(t *testing.T) { name: "Int", cfo: box, size: 32, - length: lengthUnlimited, + length: LengthUnlimited, version: 0, nVersion: anyVersion, }, @@ -407,7 +407,7 @@ func TestReadFieldConfig(t *testing.T) { name: "Int", cfo: box, size: 32, - length: lengthUnlimited, + length: LengthUnlimited, version: 1, nVersion: anyVersion, }, @@ -428,7 +428,7 @@ func TestReadFieldConfig(t *testing.T) { name: "Int", cfo: box, size: 32, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: 0, }, @@ -442,7 +442,7 @@ func TestReadFieldConfig(t *testing.T) { name: "Int", cfo: box, size: 32, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: 1, }, @@ -462,7 +462,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, optDynamic: true, @@ -476,7 +476,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, optFlag: 0x0100, @@ -490,7 +490,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, optFlag: 0x0020, @@ -511,7 +511,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, nOptFlag: 0x0100, @@ -525,7 +525,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, nOptFlag: 0x0020, @@ -547,7 +547,7 @@ func TestReadFieldConfig(t *testing.T) { name: "Int", cfo: box, size: 32, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, cnst: "0", @@ -561,7 +561,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "FullBox", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, extend: true, @@ -576,7 +576,7 @@ func TestReadFieldConfig(t *testing.T) { name: "Int", cfo: box, size: 32, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, hex: true, @@ -590,7 +590,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, str: true, @@ -605,7 +605,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, str: true, @@ -620,7 +620,7 @@ func TestReadFieldConfig(t *testing.T) { expected: fieldConfig{ name: "String", cfo: box, - length: lengthUnlimited, + length: LengthUnlimited, version: anyVersion, nVersion: anyVersion, iso639_2: true, @@ -631,7 +631,7 @@ func TestReadFieldConfig(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { v := reflect.ValueOf(tc.box).Elem() - config, err := readFieldConfig(tc.box, v, tc.fieldName, tc.fieldTag) + config, err := readFieldConfig(tc.box, v, tc.fieldName, tc.fieldTag, BoxStructureStatus{}) if tc.err { assert.Error(t, err) return diff --git a/mp4tool/mp4dump/mp4dump.go b/mp4tool/mp4dump/mp4dump.go index 32867db..ea8c1fa 100644 --- a/mp4tool/mp4dump/mp4dump.go +++ b/mp4tool/mp4dump/mp4dump.go @@ -132,7 +132,7 @@ func (m *mp4dump) dump(r io.ReadSeeker) error { return nil, err } - str, err := mp4.Stringify(box) + str, err := mp4.Stringify(box, h.BoxInfo.BoxStructureStatus) if err != nil { return nil, err } diff --git a/mp4tool/mp4edit/mp4edit.go b/mp4tool/mp4edit/mp4edit.go index c054e25..1b0034c 100644 --- a/mp4tool/mp4edit/mp4edit.go +++ b/mp4tool/mp4edit/mp4edit.go @@ -108,7 +108,7 @@ func editFile(inputPath, outputPath string) error { } } - n, err := mp4.Marshal(outputFile, box) + n, err := mp4.Marshal(outputFile, box, bi.BoxStructureStatus) if err != nil { return nil, err } diff --git a/mp4tool/psshdump/psshdump.go b/mp4tool/psshdump/psshdump.go index de29952..f90203b 100644 --- a/mp4tool/psshdump/psshdump.go +++ b/mp4tool/psshdump/psshdump.go @@ -36,7 +36,7 @@ func dump(inputFilePath string) error { for i := range bs { pssh := bs[i].Payload.(*mp4.Pssh) - sysid, _ := pssh.StringifyField("SystemID", "", 0) + sysid, _ := pssh.StringifyField("SystemID", "", 0, bs[i].Info.BoxStructureStatus) if _, err := bs[i].Info.SeekToStart(inputFile); err != nil { return err diff --git a/probe.go b/probe.go index 2e69f50..51cf297 100644 --- a/probe.go +++ b/probe.go @@ -106,7 +106,7 @@ func probeTkhd(r io.ReadSeeker, bi *BoxInfo, info *TrackInfo) error { } tkhd := Tkhd{} - _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tkhd) + _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tkhd, bi.BoxStructureStatus) if err != nil { return err } @@ -126,7 +126,7 @@ func probeMdhd(r io.ReadSeeker, bi *BoxInfo, info *TrackInfo) error { } mdhd := Mdhd{} - _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &mdhd) + _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &mdhd, bi.BoxStructureStatus) if err != nil { return err } @@ -183,7 +183,7 @@ func probeTfhd(r io.ReadSeeker, bi *BoxInfo, segment *SegmentInfo) error { } tfhd := Tfhd{} - _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tfhd) + _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tfhd, bi.BoxStructureStatus) if err != nil { return err } @@ -200,7 +200,7 @@ func probeTfdt(r io.ReadSeeker, bi *BoxInfo, segment *SegmentInfo) error { } tfdt := Tfdt{} - _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tfdt) + _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tfdt, bi.BoxStructureStatus) if err != nil { return err } @@ -220,7 +220,7 @@ func probeTrun(r io.ReadSeeker, bi *BoxInfo, segment *SegmentInfo) error { } trun := Trun{} - _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &trun) + _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &trun, bi.BoxStructureStatus) if err != nil { return err } @@ -263,7 +263,7 @@ func probeTfra(r io.ReadSeeker, bi *BoxInfo, info *FraProbeInfo) error { } tfra := Tfra{} - _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tfra) + _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &tfra, bi.BoxStructureStatus) if err != nil { return err } diff --git a/read.go b/read.go index de652bf..0065691 100644 --- a/read.go +++ b/read.go @@ -39,7 +39,7 @@ func ReadBoxStructure(r io.ReadSeeker, handler ReadHandler, params ...interface{ if _, err := r.Seek(0, io.SeekStart); err != nil { return nil, err } - return readBoxStructure(r, 0, true, nil, handler, params) + return readBoxStructure(r, 0, true, nil, BoxStructureStatus{}, handler, params) } func ReadBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, handler ReadHandler, params ...interface{}) (interface{}, error) { @@ -47,10 +47,30 @@ func ReadBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, handler ReadHand } func readBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, path BoxPath, handler ReadHandler, params []interface{}) (interface{}, error) { - if _, err := r.Seek(int64(bi.Offset+bi.HeaderSize), io.SeekCurrent); err != nil { + if _, err := bi.SeekToPayload(r); err != nil { return nil, err } + // check comatible-brands + if len(path) == 0 && bi.Type == BoxTypeFtyp() { + var ftyp Ftyp + if _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &ftyp, bi.BoxStructureStatus); err != nil { + return nil, err + } + for _, cb := range ftyp.CompatibleBrands { + if cb.CompatibleBrand == CompatibleBrandQT() { + bi.IsQuickTimeCompatible = true + } + } + if _, err := bi.SeekToPayload(r); err != nil { + return nil, err + } + } + + if bi.Type == BoxTypeWave() { + bi.UnderWave = true + } + newPath := make(BoxPath, len(path)+1) copy(newPath, path) newPath[len(path)] = bi.Type @@ -68,7 +88,7 @@ func readBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, path BoxPath, ha return nil, 0, err } - if box, n, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize); err != nil { + if box, n, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize, bi.BoxStructureStatus); err != nil { return nil, 0, err } else { childrenOffset = bi.Offset + bi.HeaderSize + n @@ -94,7 +114,7 @@ func readBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, path BoxPath, ha return nil, err } - if _, n, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize); err != nil { + if _, n, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize, bi.BoxStructureStatus); err != nil { return nil, err } else { childrenOffset = bi.Offset + bi.HeaderSize + n @@ -106,7 +126,7 @@ func readBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, path BoxPath, ha } childrenSize := bi.Offset + bi.Size - childrenOffset - return readBoxStructure(r, childrenSize, false, newPath, handler, params) + return readBoxStructure(r, childrenSize, false, newPath, bi.BoxStructureStatus, handler, params) } if val, err := handler(h); err != nil { @@ -118,7 +138,7 @@ func readBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, path BoxPath, ha } } -func readBoxStructure(r io.ReadSeeker, totalSize uint64, isRoot bool, path BoxPath, handler ReadHandler, params []interface{}) ([]interface{}, error) { +func readBoxStructure(r io.ReadSeeker, totalSize uint64, isRoot bool, path BoxPath, bss BoxStructureStatus, handler ReadHandler, params []interface{}) ([]interface{}, error) { vals := make([]interface{}, 0, 8) for isRoot || totalSize != 0 { @@ -134,11 +154,17 @@ func readBoxStructure(r io.ReadSeeker, totalSize uint64, isRoot bool, path BoxPa } totalSize -= bi.Size + bi.BoxStructureStatus = bss + val, err := readBoxStructureFromInternal(r, bi, path, handler, params) if err != nil { return nil, err } vals = append(vals, val) + + if bi.IsQuickTimeCompatible { + bss.IsQuickTimeCompatible = true + } } if totalSize != 0 { diff --git a/read_test.go b/read_test.go index 51c8d6e..6949875 100644 --- a/read_test.go +++ b/read_test.go @@ -121,3 +121,99 @@ func TestReadBoxStructure(t *testing.T) { // 54 [ilst] Size=45 // 55 [0xa9746f6f] (unsupported box type) Size=37 Data=[...] (use "-full 0xa9746f6f" to show all) // 56 [loci] (unsupported box type) Size=35 Data=[...] (use "-full loci" to show all) + +func TestReadBoxStructureQT(t *testing.T) { + f, err := os.Open("./_examples/sample_qt.mp4") + require.NoError(t, err) + defer f.Close() + + var n int + _, err = ReadBoxStructure(f, func(h *ReadHandle) (interface{}, error) { + n++ + switch n { + case 5, 42, 45: // unsupported + require.False(t, h.BoxInfo.Type.IsSupported()) + buf := bytes.NewBuffer(nil) + n, err := h.ReadData(buf) + require.NoError(t, err) + require.Equal(t, h.BoxInfo.Size-h.BoxInfo.HeaderSize, n) + assert.Len(t, buf.Bytes(), int(n)) + case 40: // mp4a + require.True(t, h.BoxInfo.Type.IsSupported()) + require.Equal(t, StrToBoxType("mp4a"), h.BoxInfo.Type) + box, n, err := h.ReadPayload() + require.NoError(t, err) + require.Equal(t, uint64(44), n) + assert.Equal(t, []byte{0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, box.(*AudioSampleEntry).QuickTimeData) + _, err = h.Expand() + require.NoError(t, err) + case 43: // mp4a + require.True(t, h.BoxInfo.Type.IsSupported()) + require.Equal(t, StrToBoxType("mp4a"), h.BoxInfo.Type) + box, n, err := h.ReadPayload() + require.NoError(t, err) + require.Equal(t, uint64(4), n) + assert.Equal(t, []byte{0x0, 0x0, 0x0, 0x0}, box.(*AudioSampleEntry).QuickTimeData) + _, err = h.Expand() + require.NoError(t, err) + default: // otherwise + require.True(t, h.BoxInfo.Type.IsSupported()) + _, err = h.Expand() + require.NoError(t, err) + } + return nil, nil + }) + require.NoError(t, err) + assert.Equal(t, 49, n) +} + +// > mp4tool dump -full mp4a sample_qt.mp4 | cat -n +// 1 [ftyp] Size=20 MajorBrand="qt " MinorVersion=512 CompatibleBrands=[{CompatibleBrand="qt "}] +// 2 [free] Size=42 Data=[...] (use "-full free" to show all) +// 3 [moov] Size=340232 +// 4 [udta] Size=31 +// 5 [(c)enc] (unsupported box type) Size=23 Data=[...] (use "-full (c)enc" to show all) +// 6 [mvhd] Size=108 ... (use "-full mvhd" to show all) +// 7 [trak] Size=115889 +// 8 [tkhd] Size=92 ... (use "-full tkhd" to show all) +// 9 [mdia] Size=115789 +// 10 [mdhd] Size=32 Version=0 Flags=0x000000 CreationTimeV0=2082844800 ModificationTimeV0=2082844800 Timescale=24 DurationV0=14315 Pad=false Language="```" PreDefined=0 +// 11 [hdlr] Size=45 Version=0 Flags=0x000000 PreDefined=1835560050 HandlerType="vide" Name="VideoHandler" +// 12 [minf] Size=115704 +// 13 [hdlr] Size=44 Version=0 Flags=0x000000 PreDefined=1684565106 HandlerType="url " Name="DataHandler" +// 14 [vmhd] Size=20 Version=0 Flags=0x000001 Graphicsmode=0 Opcolor=[0, 0, 0] +// 15 [dinf] Size=36 +// 16 [dref] Size=28 Version=0 Flags=0x000000 EntryCount=1 +// 17 [url ] Size=12 Version=0 Flags=0x000001 +// 18 [stbl] Size=115596 +// 19 [stsd] Size=148 Version=0 Flags=0x000000 EntryCount=1 +// 20 [avc1] Size=132 ... (use "-full avc1" to show all) +// 21 [avcC] Size=46 ... (use "-full avcC" to show all) +// 22 [stts] Size=24 Version=0 Flags=0x000000 EntryCount=1 Entries=[{SampleCount=14315 SampleDelta=1}] +// 23 [stss] Size=832 ... (use "-full stss" to show all) +// 24 [stsc] Size=28 Version=0 Flags=0x000000 EntryCount=1 Entries=[{FirstChunk=1 SamplesPerChunk=1 SampleDescriptionIndex=1}] +// 25 [stsz] Size=57280 ... (use "-full stsz" to show all) +// 26 [stco] Size=57276 ... (use "-full stco" to show all) +// 27 [trak] Size=224196 +// 28 [tkhd] Size=92 ... (use "-full tkhd" to show all) +// 29 [mdia] Size=224096 +// 30 [mdhd] Size=32 Version=0 Flags=0x000000 CreationTimeV0=2082844800 ModificationTimeV0=2082844800 Timescale=48000 DurationV0=28628992 Pad=false Language="```" PreDefined=0 +// 31 [hdlr] Size=45 Version=0 Flags=0x000000 PreDefined=1835560050 HandlerType="soun" Name="SoundHandler" +// 32 [minf] Size=224011 +// 33 [hdlr] Size=44 Version=0 Flags=0x000000 PreDefined=1684565106 HandlerType="url " Name="DataHandler" +// 34 [smhd] Size=16 Version=0 Flags=0x000000 Balance=0 +// 35 [dinf] Size=36 +// 36 [dref] Size=28 Version=0 Flags=0x000000 EntryCount=1 +// 37 [url ] Size=12 Version=0 Flags=0x000001 +// 38 [stbl] Size=223907 +// 39 [stsd] Size=147 Version=0 Flags=0x000000 EntryCount=1 +// 40 [mp4a] Size=131 DataReferenceIndex=1 EntryVersion=1 ChannelCount=2 SampleSize=16 PreDefined=65534 SampleRate=3145728000 QuickTimeData=[0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2] +// 41 [wave] Size=79 +// 42 [frma] (unsupported box type) Size=12 Data=[...] (use "-full frma" to show all) +// 43 [mp4a] Size=12 QuickTimeData=[0x0, 0x0, 0x0, 0x0] +// 44 [esds] Size=39 ... (use "-full esds" to show all) +// 45 [0x00000000] (unsupported box type) Size=8 Data=[...] (use "-full 0x00000000" to show all) +// 46 [stts] Size=24 Version=0 Flags=0x000000 EntryCount=1 Entries=[{SampleCount=27958 SampleDelta=1024}] +// 47 [stsc] Size=28 Version=0 Flags=0x000000 EntryCount=1 Entries=[{FirstChunk=1 SamplesPerChunk=1 SampleDescriptionIndex=1}] +// 48 [stsz] Size=111852 ... (use "-full stsz" to show all) +// 49 [stco] Size=111848 ... (use "-full stco" to show all) diff --git a/string.go b/string.go index a14d5a2..0370ce0 100644 --- a/string.go +++ b/string.go @@ -12,13 +12,14 @@ type stringfier struct { buf *bytes.Buffer src IImmutableBox indent string + bss BoxStructureStatus } -func Stringify(src IImmutableBox) (string, error) { - return StringifyWithIndent(src, "") +func Stringify(src IImmutableBox, bss BoxStructureStatus) (string, error) { + return StringifyWithIndent(src, "", bss) } -func StringifyWithIndent(src IImmutableBox, indent string) (string, error) { +func StringifyWithIndent(src IImmutableBox, indent string, bss BoxStructureStatus) (string, error) { t := reflect.TypeOf(src).Elem() v := reflect.ValueOf(src).Elem() @@ -26,6 +27,7 @@ func StringifyWithIndent(src IImmutableBox, indent string) (string, error) { buf: bytes.NewBuffer(nil), src: src, indent: indent, + bss: bss, } err := m.stringifyStruct(t, v, 0, true) @@ -81,12 +83,12 @@ func (m *stringfier) stringifyStruct(t reflect.Type, v reflect.Value, depth int, if !ok { continue } - config, err := readFieldConfig(m.src, v, f.Name, parseFieldTag(tagStr)) + config, err := readFieldConfig(m.src, v, f.Name, parseFieldTag(tagStr), m.bss) if err != nil { return err } - if !isTargetField(m.src, config) { + if !isTargetField(m.src, config, m.bss) { continue } @@ -104,7 +106,7 @@ func (m *stringfier) stringifyStruct(t reflect.Type, v reflect.Value, depth int, m.buf.WriteString("=") } - str, ok := config.cfo.StringifyField(f.Name, m.indent, depth+1) + str, ok := config.cfo.StringifyField(f.Name, m.indent, depth+1, m.bss) if ok { m.buf.WriteString(str) if !config.extend && m.indent != "" { @@ -174,7 +176,7 @@ func (m *stringfier) stringifySlice(t reflect.Type, v reflect.Value, config fiel m.buf.WriteString(begin) for i := 0; i < v.Len(); i++ { - if config.length != lengthUnlimited && uint(i) >= config.length { + if config.length != LengthUnlimited && uint(i) >= config.length { break } diff --git a/string_test.go b/string_test.go index d425ed9..01ee5eb 100644 --- a/string_test.go +++ b/string_test.go @@ -54,7 +54,7 @@ func TestEmsgStringify(t *testing.T) { Bool: true, } - str, err := StringifyWithIndent(&box, " ") + str, err := StringifyWithIndent(&box, " ", BoxStructureStatus{}) require.NoError(t, err) assert.Equal(t, ` Version=0`+"\n"+ ` Flags=0x000000`+"\n"+ @@ -74,7 +74,7 @@ func TestEmsgStringify(t *testing.T) { ` Array="hoge"`+"\n"+ ` Bool=true`+"\n", str) - str, err = Stringify(&box) + str, err = Stringify(&box, BoxStructureStatus{}) require.NoError(t, err) assert.Equal(t, `Version=0 Flags=0x000000 String="abema.tv" Int32=-1234567890 Int32Hex=0x12345678 Uint32=1234567890 Bytes="abema" Ptr={Uint64=0x1234567890} Uint64=0x1234567890 Struct={Uint64=0x1234567890} Uint64=0x1234567890 Array="hoge" Bool=true`, str) }