diff --git a/box_types_3gpp.go b/box_types_3gpp.go new file mode 100644 index 0000000..d19640c --- /dev/null +++ b/box_types_3gpp.go @@ -0,0 +1,24 @@ +package mp4 + +var udta3GppMetaBoxTypes = []BoxType{ + StrToBoxType("titl"), + StrToBoxType("dscp"), + StrToBoxType("cprt"), + StrToBoxType("perf"), + StrToBoxType("auth"), + StrToBoxType("gnre"), +} + +func init() { + for _, bt := range udta3GppMetaBoxTypes { + AddAnyTypeBoxDefEx(&Udta3GppString{}, bt, isUnderUdta, 0) + } +} + +type Udta3GppString struct { + AnyTypeBox + FullBox `mp4:"0,extend"` + Pad bool `mp4:"1,size=1,hidden"` + Language [3]byte `mp4:"2,size=5,iso639-2"` // ISO-639-2/T language code + Data []byte `mp4:"3,size=8,string"` +} diff --git a/box_types_3gpp_test.go b/box_types_3gpp_test.go new file mode 100644 index 0000000..783ede9 --- /dev/null +++ b/box_types_3gpp_test.go @@ -0,0 +1,75 @@ +package mp4 + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBoxTypes3GPP(t *testing.T) { + testCases := []struct { + name string + src IImmutableBox + dst IBox + bin []byte + str string + ctx Context + }{ + { + name: "udta 3gpp string", + src: &Udta3GppString{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("titl")}, + Language: [3]byte{0x5, 0xe, 0x7}, + Data: []byte("SING"), + }, + dst: &Udta3GppString{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("titl")}, + }, + bin: []byte{ + 0, // version + 0x00, 0x00, 0x00, // flags + 0x15, 0xc7, // language + 0x53, 0x49, 0x4e, 0x47, // data + }, + str: `Version=0 Flags=0x000000 Language="eng" Data="SING"`, + ctx: Context{UnderUdta: true}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Marshal + buf := bytes.NewBuffer(nil) + n, err := Marshal(buf, tc.src, tc.ctx) + 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, tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, tc.dst) + s, err := r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // UnmarshalAny + dst, n, err := UnmarshalAny(bytes.NewReader(tc.bin), tc.src.GetType(), uint64(len(tc.bin)), tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, dst) + s, err = r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // Stringify + str, err := Stringify(tc.src, tc.ctx) + require.NoError(t, err) + assert.Equal(t, tc.str, str) + }) + } +} diff --git a/box_types.go b/box_types_iso14496_12.go similarity index 85% rename from box_types.go rename to box_types_iso14496_12.go index b077946..b3e46fd 100644 --- a/box_types.go +++ b/box_types_iso14496_12.go @@ -1,14 +1,12 @@ package mp4 import ( - "bytes" "errors" "fmt" "io" "github.com/abema/go-mp4/bitio" "github.com/abema/go-mp4/util" - "github.com/google/uuid" ) /*************************** btrt ****************************/ @@ -468,129 +466,6 @@ func (*Emsg) GetType() BoxType { return BoxTypeEmsg() } -/*************************** esds ****************************/ - -// https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html - -func BoxTypeEsds() BoxType { return StrToBoxType("esds") } - -func init() { - AddBoxDef(&Esds{}, 0) -} - -const ( - ESDescrTag = 0x03 - DecoderConfigDescrTag = 0x04 - DecSpecificInfoTag = 0x05 - SLConfigDescrTag = 0x06 -) - -// Esds is ES descripter box -type Esds struct { - FullBox `mp4:"0,extend"` - Descriptors []Descriptor `mp4:"1,array"` -} - -// GetType returns the BoxType -func (*Esds) GetType() BoxType { - return BoxTypeEsds() -} - -type Descriptor struct { - BaseCustomFieldObject - Tag int8 `mp4:"0,size=8"` // must be 0x03 - Size uint32 `mp4:"1,varint"` - ESDescriptor *ESDescriptor `mp4:"2,extend,opt=dynamic"` - DecoderConfigDescriptor *DecoderConfigDescriptor `mp4:"3,extend,opt=dynamic"` - Data []byte `mp4:"4,size=8,opt=dynamic,len=dynamic"` -} - -// GetFieldLength returns length of dynamic field -func (ds *Descriptor) GetFieldLength(name string, ctx Context) uint { - switch name { - case "Data": - return uint(ds.Size) - } - panic(fmt.Errorf("invalid name of dynamic-length field: boxType=esds fieldName=%s", name)) -} - -func (ds *Descriptor) IsOptFieldEnabled(name string, ctx Context) bool { - switch ds.Tag { - case ESDescrTag: - return name == "ESDescriptor" - case DecoderConfigDescrTag: - return name == "DecoderConfigDescriptor" - default: - return name == "Data" - } -} - -// StringifyField returns field value as string -func (ds *Descriptor) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { - switch name { - case "Tag": - switch ds.Tag { - case ESDescrTag: - return "ESDescr", true - case DecoderConfigDescrTag: - return "DecoderConfigDescr", true - case DecSpecificInfoTag: - return "DecSpecificInfo", true - case SLConfigDescrTag: - return "SLConfigDescr", true - default: - return "", false - } - default: - return "", false - } -} - -type ESDescriptor struct { - BaseCustomFieldObject - ESID uint16 `mp4:"0,size=16"` - StreamDependenceFlag bool `mp4:"1,size=1"` - UrlFlag bool `mp4:"2,size=1"` - OcrStreamFlag bool `mp4:"3,size=1"` - StreamPriority int8 `mp4:"4,size=5"` - DependsOnESID uint16 `mp4:"5,size=16,opt=dynamic"` - URLLength uint8 `mp4:"6,size=8,opt=dynamic"` - URLString []byte `mp4:"7,size=8,len=dynamic,opt=dynamic,string"` - OCRESID uint16 `mp4:"8,size=16,opt=dynamic"` -} - -func (esds *ESDescriptor) GetFieldLength(name string, ctx Context) uint { - switch name { - case "URLString": - return uint(esds.URLLength) - } - panic(fmt.Errorf("invalid name of dynamic-length field: boxType=ESDescriptor fieldName=%s", name)) -} - -func (esds *ESDescriptor) IsOptFieldEnabled(name string, ctx Context) bool { - switch name { - case "DependsOnESID": - return esds.StreamDependenceFlag - case "URLLength", "URLString": - return esds.UrlFlag - case "OCRESID": - return esds.OcrStreamFlag - default: - return false - } -} - -type DecoderConfigDescriptor struct { - BaseCustomFieldObject - ObjectTypeIndication byte `mp4:"0,size=8"` - StreamType int8 `mp4:"1,size=6"` - UpStream bool `mp4:"2,size=1"` - Reserved bool `mp4:"3,size=1"` - BufferSizeDB uint32 `mp4:"4,size=24"` - MaxBitrate uint32 `mp4:"5,size=32"` - AvgBitrate uint32 `mp4:"6,size=32"` -} - /*************************** fiel ****************************/ func BoxTypeFiel() BoxType { return StrToBoxType("fiel") } @@ -865,171 +740,6 @@ func (hvcc HvcC) GetFieldLength(name string, ctx Context) uint { return 0 } -/*************************** ilst ****************************/ - -func BoxTypeIlst() BoxType { return StrToBoxType("ilst") } -func BoxTypeData() BoxType { return StrToBoxType("data") } - -var ilstMetaBoxTypes = []BoxType{ - StrToBoxType("----"), - StrToBoxType("aART"), - StrToBoxType("akID"), - StrToBoxType("apID"), - StrToBoxType("atID"), - StrToBoxType("cmID"), - StrToBoxType("cnID"), - StrToBoxType("covr"), - StrToBoxType("cpil"), - StrToBoxType("cprt"), - StrToBoxType("desc"), - StrToBoxType("disk"), - StrToBoxType("egid"), - StrToBoxType("geID"), - StrToBoxType("gnre"), - StrToBoxType("pcst"), - StrToBoxType("pgap"), - StrToBoxType("plID"), - StrToBoxType("purd"), - StrToBoxType("purl"), - StrToBoxType("rtng"), - StrToBoxType("sfID"), - StrToBoxType("soaa"), - StrToBoxType("soal"), - StrToBoxType("soar"), - StrToBoxType("soco"), - StrToBoxType("sonm"), - StrToBoxType("sosn"), - StrToBoxType("stik"), - StrToBoxType("tmpo"), - StrToBoxType("trkn"), - StrToBoxType("tven"), - StrToBoxType("tves"), - StrToBoxType("tvnn"), - StrToBoxType("tvsh"), - StrToBoxType("tvsn"), - {0xA9, 'A', 'R', 'T'}, - {0xA9, 'a', 'l', 'b'}, - {0xA9, 'c', 'm', 't'}, - {0xA9, 'c', 'o', 'm'}, - {0xA9, 'd', 'a', 'y'}, - {0xA9, 'g', 'e', 'n'}, - {0xA9, 'g', 'r', 'p'}, - {0xA9, 'n', 'a', 'm'}, - {0xA9, 't', 'o', 'o'}, - {0xA9, 'w', 'r', 't'}, -} - -func IsIlstMetaBoxType(boxType BoxType) bool { - for _, bt := range ilstMetaBoxTypes { - if boxType == bt { - return true - } - } - return false -} - -func init() { - AddBoxDef(&Ilst{}) - AddBoxDefEx(&Data{}, isUnderIlstMeta) - for _, bt := range ilstMetaBoxTypes { - AddAnyTypeBoxDefEx(&IlstMetaContainer{}, bt, isIlstMetaContainer) - } - AddAnyTypeBoxDefEx(&StringData{}, StrToBoxType("mean"), isUnderIlstFreeFormat) - AddAnyTypeBoxDefEx(&StringData{}, StrToBoxType("name"), isUnderIlstFreeFormat) -} - -type Ilst struct { - Box -} - -// GetType returns the BoxType -func (*Ilst) GetType() BoxType { - return BoxTypeIlst() -} - -type IlstMetaContainer struct { - AnyTypeBox -} - -func isIlstMetaContainer(ctx Context) bool { - return ctx.UnderIlst && !ctx.UnderIlstMeta -} - -const ( - DataTypeBinary = 0 - DataTypeStringUTF8 = 1 - DataTypeStringUTF16 = 2 - DataTypeStringMac = 3 - DataTypeStringJPEG = 14 - DataTypeSignedIntBigEndian = 21 - DataTypeFloat32BigEndian = 22 - DataTypeFloat64BigEndian = 23 -) - -type Data struct { - Box - DataType uint32 `mp4:"0,size=32"` - DataLang uint32 `mp4:"1,size=32"` - Data []byte `mp4:"2,size=8"` -} - -// GetType returns the BoxType -func (*Data) GetType() BoxType { - return BoxTypeData() -} - -func isUnderIlstMeta(ctx Context) bool { - return ctx.UnderIlstMeta -} - -// StringifyField returns field value as string -func (data *Data) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { - switch name { - case "DataType": - switch data.DataType { - case DataTypeBinary: - return "BINARY", true - case DataTypeStringUTF8: - return "UTF8", true - case DataTypeStringUTF16: - return "UTF16", true - case DataTypeStringMac: - return "MAC_STR", true - case DataTypeStringJPEG: - return "JPEG", true - case DataTypeSignedIntBigEndian: - return "INT", true - case DataTypeFloat32BigEndian: - return "FLOAT32", true - case DataTypeFloat64BigEndian: - return "FLOAT64", true - } - case "Data": - switch data.DataType { - case DataTypeStringUTF8: - return fmt.Sprintf("\"%s\"", util.EscapeUnprintables(string(data.Data))), true - } - } - return "", false -} - -type StringData struct { - AnyTypeBox - Data []byte `mp4:"0,size=8"` -} - -// StringifyField returns field value as string -func (sd *StringData) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { - if name == "Data" { - return fmt.Sprintf("\"%s\"", util.EscapeUnprintables(string(sd.Data))), true - } - return "", false -} - -func isUnderIlstFreeFormat(ctx Context) bool { - return ctx.UnderIlstFreeMeta -} - /*************************** mdat ****************************/ func BoxTypeMdat() BoxType { return StrToBoxType("mdat") } @@ -1408,64 +1118,6 @@ func (mvhd *Mvhd) GetRateInt() int16 { return int16(mvhd.Rate >> 16) } -/*************************** pssh ****************************/ - -func BoxTypePssh() BoxType { return StrToBoxType("pssh") } - -func init() { - AddBoxDef(&Pssh{}, 0, 1) -} - -// Pssh is ISOBMFF pssh box type -type Pssh struct { - FullBox `mp4:"0,extend"` - SystemID [16]byte `mp4:"1,size=8,uuid"` - KIDCount uint32 `mp4:"2,size=32,nver=0"` - KIDs []PsshKID `mp4:"3,nver=0,len=dynamic,size=128"` - DataSize int32 `mp4:"4,size=32"` - Data []byte `mp4:"5,size=8,len=dynamic"` -} - -type PsshKID struct { - KID [16]byte `mp4:"0,size=8,uuid"` -} - -// GetFieldLength returns length of dynamic field -func (pssh *Pssh) GetFieldLength(name string, ctx Context) uint { - switch name { - case "KIDs": - return uint(pssh.KIDCount) - case "Data": - return uint(pssh.DataSize) - } - panic(fmt.Errorf("invalid name of dynamic-length field: boxType=pssh fieldName=%s", name)) -} - -// StringifyField returns field value as string -func (pssh *Pssh) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { - switch name { - case "KIDs": - buf := bytes.NewBuffer(nil) - buf.WriteString("[") - for i, e := range pssh.KIDs { - if i != 0 { - buf.WriteString(", ") - } - buf.WriteString(uuid.UUID(e.KID).String()) - } - buf.WriteString("]") - return buf.String(), true - - default: - return "", false - } -} - -// GetType returns the BoxType -func (*Pssh) GetType() BoxType { - return BoxTypePssh() -} - /*************************** saio ****************************/ func BoxTypeSaio() BoxType { return StrToBoxType("saio") } @@ -2325,48 +1977,6 @@ func (*Styp) GetType() BoxType { return BoxTypeStyp() } -/*************************** tenc ****************************/ - -func BoxTypeTenc() BoxType { return StrToBoxType("tenc") } - -func init() { - AddBoxDef(&Tenc{}, 0, 1) -} - -// Tenc is ISOBMFF tenc box type -type Tenc struct { - FullBox `mp4:"0,extend"` - Reserved uint8 `mp4:"1,size=8,dec"` - DefaultCryptByteBlock uint8 `mp4:"2,size=4,dec"` // always 0 on version 0 - DefaultSkipByteBlock uint8 `mp4:"3,size=4,dec"` // always 0 on version 0 - DefaultIsProtected uint8 `mp4:"4,size=8,dec"` - DefaultPerSampleIVSize uint8 `mp4:"5,size=8,dec"` - DefaultKID [16]byte `mp4:"6,size=8,uuid"` - DefaultConstantIVSize uint8 `mp4:"7,size=8,opt=dynamic,dec"` - DefaultConstantIV []byte `mp4:"8,size=8,opt=dynamic,len=dynamic"` -} - -func (tenc *Tenc) IsOptFieldEnabled(name string, ctx Context) bool { - switch name { - case "DefaultConstantIVSize", "DefaultConstantIV": - return tenc.DefaultIsProtected == 1 && tenc.DefaultPerSampleIVSize == 0 - } - return false -} - -func (tenc *Tenc) GetFieldLength(name string, ctx Context) uint { - switch name { - case "DefaultConstantIV": - return uint(tenc.DefaultConstantIVSize) - } - panic(fmt.Errorf("invalid name of dynamic-length field: boxType=tenc fieldName=%s", name)) -} - -// GetType returns the BoxType -func (*Tenc) GetType() BoxType { - return BoxTypeTenc() -} - /*************************** tfdt ****************************/ func BoxTypeTfdt() BoxType { return StrToBoxType("tfdt") } @@ -2787,20 +2397,8 @@ func (trun *Trun) GetSampleCompositionTimeOffset(index int) int64 { func BoxTypeUdta() BoxType { return StrToBoxType("udta") } -var udta3GppMetaBoxTypes = []BoxType{ - StrToBoxType("titl"), - StrToBoxType("dscp"), - StrToBoxType("cprt"), - StrToBoxType("perf"), - StrToBoxType("auth"), - StrToBoxType("gnre"), -} - func init() { AddBoxDef(&Udta{}) - for _, bt := range udta3GppMetaBoxTypes { - AddAnyTypeBoxDefEx(&Udta3GppString{}, bt, isUnderUdta, 0) - } } // Udta is ISOBMFF udta box type @@ -2813,14 +2411,6 @@ func (*Udta) GetType() BoxType { return BoxTypeUdta() } -type Udta3GppString struct { - AnyTypeBox - FullBox `mp4:"0,extend"` - Pad bool `mp4:"1,size=1,hidden"` - Language [3]byte `mp4:"2,size=5,iso639-2"` // ISO-639-2/T language code - Data []byte `mp4:"3,size=8,string"` -} - func isUnderUdta(ctx Context) bool { return ctx.UnderUdta } diff --git a/box_types_test.go b/box_types_iso14496_12_test.go similarity index 87% rename from box_types_test.go rename to box_types_iso14496_12_test.go index 1ce35f5..1973f3a 100644 --- a/box_types_test.go +++ b/box_types_iso14496_12_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestBoxTypes(t *testing.T) { +func TestBoxTypesISO14496_12(t *testing.T) { testCases := []struct { name string src IImmutableBox @@ -372,107 +372,6 @@ func TestBoxTypes(t *testing.T) { `Id=43981 ` + `MessageData="abema"`, }, - { - name: "esds", - src: &Esds{ - FullBox: FullBox{ - Version: 0, - Flags: [3]byte{0x00, 0x00, 0x00}, - }, - Descriptors: []Descriptor{ - { - Tag: ESDescrTag, - Size: 0x1234567, - ESDescriptor: &ESDescriptor{ - ESID: 0x1234, - StreamDependenceFlag: true, - UrlFlag: false, - OcrStreamFlag: true, - StreamPriority: 0x03, - DependsOnESID: 0x2345, - OCRESID: 0x3456, - }, - }, - { - Tag: ESDescrTag, - Size: 0x1234567, - ESDescriptor: &ESDescriptor{ - ESID: 0x1234, - StreamDependenceFlag: false, - UrlFlag: true, - OcrStreamFlag: false, - StreamPriority: 0x03, - URLLength: 11, - URLString: []byte("http://hoge"), - }, - }, - { - Tag: DecoderConfigDescrTag, - Size: 0x1234567, - DecoderConfigDescriptor: &DecoderConfigDescriptor{ - ObjectTypeIndication: 0x12, - StreamType: 0x15, - UpStream: true, - Reserved: false, - BufferSizeDB: 0x123456, - MaxBitrate: 0x12345678, - AvgBitrate: 0x23456789, - }, - }, - { - Tag: DecSpecificInfoTag, - Size: 0x03, - Data: []byte{0x11, 0x22, 0x33}, - }, - { - Tag: SLConfigDescrTag, - Size: 0x05, - Data: []byte{0x11, 0x22, 0x33, 0x44, 0x55}, - }, - }, - }, - dst: &Esds{}, - bin: []byte{ - 0, // version - 0x00, 0x00, 0x00, // flags - // - 0x03, // tag - 0x89, 0x8d, 0x8a, 0x67, // size (varint) - 0x12, 0x34, // esid - 0xa3, // flags & streamPriority - 0x23, 0x45, // dependsOnESID - 0x34, 0x56, // ocresid - // - 0x03, // tag - 0x89, 0x8d, 0x8a, 0x67, // size (varint) - 0x12, 0x34, // esid - 0x43, // flags & streamPriority - 11, // urlLength - 'h', 't', 't', 'p', ':', '/', '/', 'h', 'o', 'g', 'e', // urlString - // - 0x04, // tag - 0x89, 0x8d, 0x8a, 0x67, // size (varint) - 0x12, // objectTypeIndication - 0x56, // streamType & upStream & reserved - 0x12, 0x34, 0x56, // bufferSizeDB - 0x12, 0x34, 0x56, 0x78, // maxBitrate - 0x23, 0x45, 0x67, 0x89, // avgBitrate - // - 0x05, // tag - 0x80, 0x80, 0x80, 0x03, // size (varint) - 0x11, 0x22, 0x33, // data - // - 0x06, // tag - 0x80, 0x80, 0x80, 0x05, // size (varint) - 0x11, 0x22, 0x33, 0x44, 0x55, // data - }, - str: `Version=0 Flags=0x000000 Descriptors=[` + - `{Tag=ESDescr Size=19088743 ESID=4660 StreamDependenceFlag=true UrlFlag=false OcrStreamFlag=true StreamPriority=3 DependsOnESID=9029 OCRESID=13398}, ` + - `{Tag=ESDescr Size=19088743 ESID=4660 StreamDependenceFlag=false UrlFlag=true OcrStreamFlag=false StreamPriority=3 URLLength=0xb URLString="http://hoge"}, ` + - `{Tag=DecoderConfigDescr Size=19088743 ObjectTypeIndication=0x12 StreamType=21 UpStream=true Reserved=false BufferSizeDB=1193046 MaxBitrate=305419896 AvgBitrate=591751049}, ` + - "{Tag=DecSpecificInfo Size=3 Data=[0x11, 0x22, 0x33]}, " + - "{Tag=SLConfigDescr Size=5 Data=[0x11, 0x22, 0x33, 0x44, 0x55]}]", - }, { name: "fiel", src: &Fiel{ @@ -665,148 +564,6 @@ func TestBoxTypes(t *testing.T) { `{Completeness=false Reserved=false NaluType=0x27 NumNalus=1 Nalus=[{Length=11 NALUnit=[0x4e, 0x1, 0x5, 0xff, 0xff, 0xff, ` + `0xa6, 0x2c, 0xa2, 0xde, 0x9]}]}]`, }, - { - name: "ilst", - src: &Ilst{}, - dst: &Ilst{}, - bin: nil, - str: ``, - }, - { - name: "ilst meta container", - src: &IlstMetaContainer{ - AnyTypeBox: AnyTypeBox{Type: StrToBoxType("----")}, - }, - dst: &IlstMetaContainer{ - AnyTypeBox: AnyTypeBox{Type: StrToBoxType("----")}, - }, - bin: nil, - str: ``, - ctx: Context{UnderIlst: true}, - }, - { - name: "ilst data (binary)", - src: &Data{DataType: 0, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x00, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=BINARY DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (utf8)", - src: &Data{DataType: 1, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x01, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=UTF8 DataLang=305419896 Data="foo"`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (utf8 escaped)", - src: &Data{DataType: 1, DataLang: 0x12345678, Data: []byte{0x00, 'f', 'o', 'o'}}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x01, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x00, 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=UTF8 DataLang=305419896 Data=".foo"`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (utf16)", - src: &Data{DataType: 2, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x02, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=UTF16 DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (mac string)", - src: &Data{DataType: 3, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x03, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=MAC_STR DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (jpsg)", - src: &Data{DataType: 14, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x0e, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=JPEG DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (int)", - src: &Data{DataType: 21, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x15, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=INT DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (float32)", - src: &Data{DataType: 22, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x16, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=FLOAT32 DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (float64)", - src: &Data{DataType: 23, DataLang: 0x12345678, Data: []byte("foo")}, - dst: &Data{}, - bin: []byte{ - 0x00, 0x00, 0x00, 0x17, // data type - 0x12, 0x34, 0x56, 0x78, // data lang - 0x66, 0x6f, 0x6f, // data - }, - str: `DataType=FLOAT64 DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, - ctx: Context{UnderIlstMeta: true}, - }, - { - name: "ilst data (string)", - src: &StringData{ - AnyTypeBox: AnyTypeBox{Type: StrToBoxType("mean")}, - Data: []byte{0x00, 'f', 'o', 'o'}, - }, - dst: &StringData{ - AnyTypeBox: AnyTypeBox{Type: StrToBoxType("mean")}, - }, - bin: []byte{ - 0x00, 0x66, 0x6f, 0x6f, // data - }, - str: `Data=".foo"`, - ctx: Context{UnderIlstFreeMeta: true}, - }, { name: "mdat", src: &Mdat{ @@ -1105,67 +862,6 @@ func TestBoxTypes(t *testing.T) { `PreDefined=[0, 0, 0, 0, 0, 0] ` + `NextTrackID=2882400001`, }, - { - name: "pssh: version 0: no KIDs", - src: &Pssh{ - FullBox: FullBox{ - Version: 0, - Flags: [3]byte{0x00, 0x00, 0x00}, - }, - SystemID: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, - DataSize: 5, - Data: []byte{0x21, 0x22, 0x23, 0x24, 0x25}, - }, - dst: &Pssh{}, - bin: []byte{ - 0, // version - 0x00, 0x00, 0x00, // flags - // system ID - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x00, 0x00, 0x00, 0x05, // data size - 0x21, 0x22, 0x23, 0x24, 0x25, // data - }, - str: `Version=0 Flags=0x000000 ` + - `SystemID=01020304-0506-0708-090a-0b0c0d0e0f10 ` + - `DataSize=5 ` + - `Data=[0x21, 0x22, 0x23, 0x24, 0x25]`, - }, - { - name: "pssh: version 1: with KIDs", - src: &Pssh{ - FullBox: FullBox{ - Version: 1, - Flags: [3]byte{0x00, 0x00, 0x00}, - }, - SystemID: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, - KIDCount: 2, - KIDs: []PsshKID{ - {KID: [16]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10}}, - {KID: [16]byte{0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20}}, - }, - DataSize: 5, - Data: []byte{0x21, 0x22, 0x23, 0x24, 0x25}, - }, - dst: &Pssh{}, - bin: []byte{ - 1, // version - 0x00, 0x00, 0x00, // flags - // system ID - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x00, 0x00, 0x00, 0x02, // KID count - // KIDs - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20, - 0x00, 0x00, 0x00, 0x05, // data size - 0x21, 0x22, 0x23, 0x24, 0x25, // data - }, - str: `Version=1 Flags=0x000000 ` + - `SystemID=01020304-0506-0708-090a-0b0c0d0e0f10 ` + - `KIDCount=2 ` + - `KIDs=[11121314-1516-1718-191a-1b1c1d1e1f10, 21222324-2526-2728-292a-2b2c2d2e2f20] ` + - `DataSize=5 ` + - `Data=[0x21, 0x22, 0x23, 0x24, 0x25]`, - }, { name: "saio: version 0: no aux info type", src: &Saio{ @@ -2518,117 +2214,6 @@ func TestBoxTypes(t *testing.T) { }, str: `MajorBrand="abem" MinorVersion=305419896 CompatibleBrands=[{CompatibleBrand="abcd"}, {CompatibleBrand="efgh"}]`, }, - { - name: "tenc: DefaultIsProtected=1 DefaultPerSampleIVSize=0", - src: &Tenc{ - FullBox: FullBox{ - Version: 1, - Flags: [3]byte{0x00, 0x00, 0x00}, - }, - Reserved: 0x00, - DefaultCryptByteBlock: 0x0a, - DefaultSkipByteBlock: 0x0b, - DefaultIsProtected: 1, - DefaultPerSampleIVSize: 0, - DefaultKID: [16]byte{ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - }, - DefaultConstantIVSize: 4, - DefaultConstantIV: []byte{0x01, 0x23, 0x45, 0x67}, - }, - dst: &Tenc{}, - bin: []byte{ - 1, // version - 0x00, 0x00, 0x00, // flags - 0x00, // reserved - 0xab, // default crypt byte block / default skip byte block - 0x01, 0x00, // default is protected / default per sample iv size - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // default kid - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x04, // default constant iv size - 0x01, 0x23, 0x45, 0x67, // default constant iv - }, - str: `Version=1 Flags=0x000000 ` + - `Reserved=0 ` + - `DefaultCryptByteBlock=10 ` + - `DefaultSkipByteBlock=11 ` + - `DefaultIsProtected=1 ` + - `DefaultPerSampleIVSize=0 ` + - `DefaultKID=01234567-89ab-cdef-0123-456789abcdef ` + - `DefaultConstantIVSize=4 ` + - `DefaultConstantIV=[0x1, 0x23, 0x45, 0x67]`, - }, - { - name: "tenc: DefaultIsProtected=0 DefaultPerSampleIVSize=0", - src: &Tenc{ - FullBox: FullBox{ - Version: 1, - Flags: [3]byte{0x00, 0x00, 0x00}, - }, - Reserved: 0x00, - DefaultCryptByteBlock: 0x0a, - DefaultSkipByteBlock: 0x0b, - DefaultIsProtected: 0, - DefaultPerSampleIVSize: 0, - DefaultKID: [16]byte{ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - }, - }, - dst: &Tenc{}, - bin: []byte{ - 1, // version - 0x00, 0x00, 0x00, // flags - 0x00, // reserved - 0xab, // default crypt byte block / default skip byte block - 0x00, 0x00, // default is protected / default per sample iv size - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // default kid - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - }, - str: `Version=1 Flags=0x000000 ` + - `Reserved=0 ` + - `DefaultCryptByteBlock=10 ` + - `DefaultSkipByteBlock=11 ` + - `DefaultIsProtected=0 ` + - `DefaultPerSampleIVSize=0 ` + - `DefaultKID=01234567-89ab-cdef-0123-456789abcdef`, - }, - { - name: "tenc: DefaultIsProtected=1 DefaultPerSampleIVSize=1", - src: &Tenc{ - FullBox: FullBox{ - Version: 1, - Flags: [3]byte{0x00, 0x00, 0x00}, - }, - Reserved: 0x00, - DefaultCryptByteBlock: 0x0a, - DefaultSkipByteBlock: 0x0b, - DefaultIsProtected: 1, - DefaultPerSampleIVSize: 1, - DefaultKID: [16]byte{ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - }, - }, - dst: &Tenc{}, - bin: []byte{ - 1, // version - 0x00, 0x00, 0x00, // flags - 0x00, // reserved - 0xab, // default crypt byte block / default skip byte block - 0x01, 0x01, // default is protected / default per sample iv size - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // default kid - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - }, - str: `Version=1 Flags=0x000000 ` + - `Reserved=0 ` + - `DefaultCryptByteBlock=10 ` + - `DefaultSkipByteBlock=11 ` + - `DefaultIsProtected=1 ` + - `DefaultPerSampleIVSize=1 ` + - `DefaultKID=01234567-89ab-cdef-0123-456789abcdef`, - }, { name: "tfdt: version 0", src: &Tfdt{ @@ -3108,25 +2693,6 @@ func TestBoxTypes(t *testing.T) { bin: nil, str: ``, }, - { - name: "udta 3gpp string", - src: &Udta3GppString{ - AnyTypeBox: AnyTypeBox{Type: StrToBoxType("titl")}, - Language: [3]byte{0x5, 0xe, 0x7}, - Data: []byte("SING"), - }, - dst: &Udta3GppString{ - AnyTypeBox: AnyTypeBox{Type: StrToBoxType("titl")}, - }, - bin: []byte{ - 0, // version - 0x00, 0x00, 0x00, // flags - 0x15, 0xc7, // language - 0x53, 0x49, 0x4e, 0x47, // data - }, - str: `Version=0 Flags=0x000000 Language="eng" Data="SING"`, - ctx: Context{UnderUdta: true}, - }, { name: "vmhd", src: &Vmhd{ diff --git a/box_types_iso14496_14.go b/box_types_iso14496_14.go new file mode 100644 index 0000000..fe9880c --- /dev/null +++ b/box_types_iso14496_14.go @@ -0,0 +1,126 @@ +package mp4 + +import "fmt" + +/*************************** esds ****************************/ + +// https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html + +func BoxTypeEsds() BoxType { return StrToBoxType("esds") } + +func init() { + AddBoxDef(&Esds{}, 0) +} + +const ( + ESDescrTag = 0x03 + DecoderConfigDescrTag = 0x04 + DecSpecificInfoTag = 0x05 + SLConfigDescrTag = 0x06 +) + +// Esds is ES descripter box +type Esds struct { + FullBox `mp4:"0,extend"` + Descriptors []Descriptor `mp4:"1,array"` +} + +// GetType returns the BoxType +func (*Esds) GetType() BoxType { + return BoxTypeEsds() +} + +type Descriptor struct { + BaseCustomFieldObject + Tag int8 `mp4:"0,size=8"` // must be 0x03 + Size uint32 `mp4:"1,varint"` + ESDescriptor *ESDescriptor `mp4:"2,extend,opt=dynamic"` + DecoderConfigDescriptor *DecoderConfigDescriptor `mp4:"3,extend,opt=dynamic"` + Data []byte `mp4:"4,size=8,opt=dynamic,len=dynamic"` +} + +// GetFieldLength returns length of dynamic field +func (ds *Descriptor) GetFieldLength(name string, ctx Context) uint { + switch name { + case "Data": + return uint(ds.Size) + } + panic(fmt.Errorf("invalid name of dynamic-length field: boxType=esds fieldName=%s", name)) +} + +func (ds *Descriptor) IsOptFieldEnabled(name string, ctx Context) bool { + switch ds.Tag { + case ESDescrTag: + return name == "ESDescriptor" + case DecoderConfigDescrTag: + return name == "DecoderConfigDescriptor" + default: + return name == "Data" + } +} + +// StringifyField returns field value as string +func (ds *Descriptor) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { + switch name { + case "Tag": + switch ds.Tag { + case ESDescrTag: + return "ESDescr", true + case DecoderConfigDescrTag: + return "DecoderConfigDescr", true + case DecSpecificInfoTag: + return "DecSpecificInfo", true + case SLConfigDescrTag: + return "SLConfigDescr", true + default: + return "", false + } + default: + return "", false + } +} + +type ESDescriptor struct { + BaseCustomFieldObject + ESID uint16 `mp4:"0,size=16"` + StreamDependenceFlag bool `mp4:"1,size=1"` + UrlFlag bool `mp4:"2,size=1"` + OcrStreamFlag bool `mp4:"3,size=1"` + StreamPriority int8 `mp4:"4,size=5"` + DependsOnESID uint16 `mp4:"5,size=16,opt=dynamic"` + URLLength uint8 `mp4:"6,size=8,opt=dynamic"` + URLString []byte `mp4:"7,size=8,len=dynamic,opt=dynamic,string"` + OCRESID uint16 `mp4:"8,size=16,opt=dynamic"` +} + +func (esds *ESDescriptor) GetFieldLength(name string, ctx Context) uint { + switch name { + case "URLString": + return uint(esds.URLLength) + } + panic(fmt.Errorf("invalid name of dynamic-length field: boxType=ESDescriptor fieldName=%s", name)) +} + +func (esds *ESDescriptor) IsOptFieldEnabled(name string, ctx Context) bool { + switch name { + case "DependsOnESID": + return esds.StreamDependenceFlag + case "URLLength", "URLString": + return esds.UrlFlag + case "OCRESID": + return esds.OcrStreamFlag + default: + return false + } +} + +type DecoderConfigDescriptor struct { + BaseCustomFieldObject + ObjectTypeIndication byte `mp4:"0,size=8"` + StreamType int8 `mp4:"1,size=6"` + UpStream bool `mp4:"2,size=1"` + Reserved bool `mp4:"3,size=1"` + BufferSizeDB uint32 `mp4:"4,size=24"` + MaxBitrate uint32 `mp4:"5,size=32"` + AvgBitrate uint32 `mp4:"6,size=32"` +} diff --git a/box_types_iso14496_14_test.go b/box_types_iso14496_14_test.go new file mode 100644 index 0000000..51e2ba1 --- /dev/null +++ b/box_types_iso14496_14_test.go @@ -0,0 +1,157 @@ +package mp4 + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBoxTypesISO14496_14(t *testing.T) { + testCases := []struct { + name string + src IImmutableBox + dst IBox + bin []byte + str string + ctx Context + }{ + { + name: "esds", + src: &Esds{ + FullBox: FullBox{ + Version: 0, + Flags: [3]byte{0x00, 0x00, 0x00}, + }, + Descriptors: []Descriptor{ + { + Tag: ESDescrTag, + Size: 0x1234567, + ESDescriptor: &ESDescriptor{ + ESID: 0x1234, + StreamDependenceFlag: true, + UrlFlag: false, + OcrStreamFlag: true, + StreamPriority: 0x03, + DependsOnESID: 0x2345, + OCRESID: 0x3456, + }, + }, + { + Tag: ESDescrTag, + Size: 0x1234567, + ESDescriptor: &ESDescriptor{ + ESID: 0x1234, + StreamDependenceFlag: false, + UrlFlag: true, + OcrStreamFlag: false, + StreamPriority: 0x03, + URLLength: 11, + URLString: []byte("http://hoge"), + }, + }, + { + Tag: DecoderConfigDescrTag, + Size: 0x1234567, + DecoderConfigDescriptor: &DecoderConfigDescriptor{ + ObjectTypeIndication: 0x12, + StreamType: 0x15, + UpStream: true, + Reserved: false, + BufferSizeDB: 0x123456, + MaxBitrate: 0x12345678, + AvgBitrate: 0x23456789, + }, + }, + { + Tag: DecSpecificInfoTag, + Size: 0x03, + Data: []byte{0x11, 0x22, 0x33}, + }, + { + Tag: SLConfigDescrTag, + Size: 0x05, + Data: []byte{0x11, 0x22, 0x33, 0x44, 0x55}, + }, + }, + }, + dst: &Esds{}, + bin: []byte{ + 0, // version + 0x00, 0x00, 0x00, // flags + // + 0x03, // tag + 0x89, 0x8d, 0x8a, 0x67, // size (varint) + 0x12, 0x34, // esid + 0xa3, // flags & streamPriority + 0x23, 0x45, // dependsOnESID + 0x34, 0x56, // ocresid + // + 0x03, // tag + 0x89, 0x8d, 0x8a, 0x67, // size (varint) + 0x12, 0x34, // esid + 0x43, // flags & streamPriority + 11, // urlLength + 'h', 't', 't', 'p', ':', '/', '/', 'h', 'o', 'g', 'e', // urlString + // + 0x04, // tag + 0x89, 0x8d, 0x8a, 0x67, // size (varint) + 0x12, // objectTypeIndication + 0x56, // streamType & upStream & reserved + 0x12, 0x34, 0x56, // bufferSizeDB + 0x12, 0x34, 0x56, 0x78, // maxBitrate + 0x23, 0x45, 0x67, 0x89, // avgBitrate + // + 0x05, // tag + 0x80, 0x80, 0x80, 0x03, // size (varint) + 0x11, 0x22, 0x33, // data + // + 0x06, // tag + 0x80, 0x80, 0x80, 0x05, // size (varint) + 0x11, 0x22, 0x33, 0x44, 0x55, // data + }, + str: `Version=0 Flags=0x000000 Descriptors=[` + + `{Tag=ESDescr Size=19088743 ESID=4660 StreamDependenceFlag=true UrlFlag=false OcrStreamFlag=true StreamPriority=3 DependsOnESID=9029 OCRESID=13398}, ` + + `{Tag=ESDescr Size=19088743 ESID=4660 StreamDependenceFlag=false UrlFlag=true OcrStreamFlag=false StreamPriority=3 URLLength=0xb URLString="http://hoge"}, ` + + `{Tag=DecoderConfigDescr Size=19088743 ObjectTypeIndication=0x12 StreamType=21 UpStream=true Reserved=false BufferSizeDB=1193046 MaxBitrate=305419896 AvgBitrate=591751049}, ` + + "{Tag=DecSpecificInfo Size=3 Data=[0x11, 0x22, 0x33]}, " + + "{Tag=SLConfigDescr Size=5 Data=[0x11, 0x22, 0x33, 0x44, 0x55]}]", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Marshal + buf := bytes.NewBuffer(nil) + n, err := Marshal(buf, tc.src, tc.ctx) + 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, tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, tc.dst) + s, err := r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // UnmarshalAny + dst, n, err := UnmarshalAny(bytes.NewReader(tc.bin), tc.src.GetType(), uint64(len(tc.bin)), tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, dst) + s, err = r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // Stringify + str, err := Stringify(tc.src, tc.ctx) + require.NoError(t, err) + assert.Equal(t, tc.str, str) + }) + } +} diff --git a/box_types_iso23001_7.go b/box_types_iso23001_7.go new file mode 100644 index 0000000..766c348 --- /dev/null +++ b/box_types_iso23001_7.go @@ -0,0 +1,108 @@ +package mp4 + +import ( + "bytes" + "fmt" + + "github.com/google/uuid" +) + +/*************************** pssh ****************************/ + +func BoxTypePssh() BoxType { return StrToBoxType("pssh") } + +func init() { + AddBoxDef(&Pssh{}, 0, 1) +} + +// Pssh is ISOBMFF pssh box type +type Pssh struct { + FullBox `mp4:"0,extend"` + SystemID [16]byte `mp4:"1,size=8,uuid"` + KIDCount uint32 `mp4:"2,size=32,nver=0"` + KIDs []PsshKID `mp4:"3,nver=0,len=dynamic,size=128"` + DataSize int32 `mp4:"4,size=32"` + Data []byte `mp4:"5,size=8,len=dynamic"` +} + +type PsshKID struct { + KID [16]byte `mp4:"0,size=8,uuid"` +} + +// GetFieldLength returns length of dynamic field +func (pssh *Pssh) GetFieldLength(name string, ctx Context) uint { + switch name { + case "KIDs": + return uint(pssh.KIDCount) + case "Data": + return uint(pssh.DataSize) + } + panic(fmt.Errorf("invalid name of dynamic-length field: boxType=pssh fieldName=%s", name)) +} + +// StringifyField returns field value as string +func (pssh *Pssh) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { + switch name { + case "KIDs": + buf := bytes.NewBuffer(nil) + buf.WriteString("[") + for i, e := range pssh.KIDs { + if i != 0 { + buf.WriteString(", ") + } + buf.WriteString(uuid.UUID(e.KID).String()) + } + buf.WriteString("]") + return buf.String(), true + + default: + return "", false + } +} + +// GetType returns the BoxType +func (*Pssh) GetType() BoxType { + return BoxTypePssh() +} + +/*************************** tenc ****************************/ + +func BoxTypeTenc() BoxType { return StrToBoxType("tenc") } + +func init() { + AddBoxDef(&Tenc{}, 0, 1) +} + +// Tenc is ISOBMFF tenc box type +type Tenc struct { + FullBox `mp4:"0,extend"` + Reserved uint8 `mp4:"1,size=8,dec"` + DefaultCryptByteBlock uint8 `mp4:"2,size=4,dec"` // always 0 on version 0 + DefaultSkipByteBlock uint8 `mp4:"3,size=4,dec"` // always 0 on version 0 + DefaultIsProtected uint8 `mp4:"4,size=8,dec"` + DefaultPerSampleIVSize uint8 `mp4:"5,size=8,dec"` + DefaultKID [16]byte `mp4:"6,size=8,uuid"` + DefaultConstantIVSize uint8 `mp4:"7,size=8,opt=dynamic,dec"` + DefaultConstantIV []byte `mp4:"8,size=8,opt=dynamic,len=dynamic"` +} + +func (tenc *Tenc) IsOptFieldEnabled(name string, ctx Context) bool { + switch name { + case "DefaultConstantIVSize", "DefaultConstantIV": + return tenc.DefaultIsProtected == 1 && tenc.DefaultPerSampleIVSize == 0 + } + return false +} + +func (tenc *Tenc) GetFieldLength(name string, ctx Context) uint { + switch name { + case "DefaultConstantIV": + return uint(tenc.DefaultConstantIVSize) + } + panic(fmt.Errorf("invalid name of dynamic-length field: boxType=tenc fieldName=%s", name)) +} + +// GetType returns the BoxType +func (*Tenc) GetType() BoxType { + return BoxTypeTenc() +} diff --git a/box_types_iso23001_7_test.go b/box_types_iso23001_7_test.go new file mode 100644 index 0000000..7270e09 --- /dev/null +++ b/box_types_iso23001_7_test.go @@ -0,0 +1,228 @@ +package mp4 + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBoxTypesISO23001_7(t *testing.T) { + testCases := []struct { + name string + src IImmutableBox + dst IBox + bin []byte + str string + ctx Context + }{ + { + name: "pssh: version 0: no KIDs", + src: &Pssh{ + FullBox: FullBox{ + Version: 0, + Flags: [3]byte{0x00, 0x00, 0x00}, + }, + SystemID: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, + DataSize: 5, + Data: []byte{0x21, 0x22, 0x23, 0x24, 0x25}, + }, + dst: &Pssh{}, + bin: []byte{ + 0, // version + 0x00, 0x00, 0x00, // flags + // system ID + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x00, 0x00, 0x00, 0x05, // data size + 0x21, 0x22, 0x23, 0x24, 0x25, // data + }, + str: `Version=0 Flags=0x000000 ` + + `SystemID=01020304-0506-0708-090a-0b0c0d0e0f10 ` + + `DataSize=5 ` + + `Data=[0x21, 0x22, 0x23, 0x24, 0x25]`, + }, + { + name: "pssh: version 1: with KIDs", + src: &Pssh{ + FullBox: FullBox{ + Version: 1, + Flags: [3]byte{0x00, 0x00, 0x00}, + }, + SystemID: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, + KIDCount: 2, + KIDs: []PsshKID{ + {KID: [16]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10}}, + {KID: [16]byte{0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20}}, + }, + DataSize: 5, + Data: []byte{0x21, 0x22, 0x23, 0x24, 0x25}, + }, + dst: &Pssh{}, + bin: []byte{ + 1, // version + 0x00, 0x00, 0x00, // flags + // system ID + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x00, 0x00, 0x00, 0x02, // KID count + // KIDs + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20, + 0x00, 0x00, 0x00, 0x05, // data size + 0x21, 0x22, 0x23, 0x24, 0x25, // data + }, + str: `Version=1 Flags=0x000000 ` + + `SystemID=01020304-0506-0708-090a-0b0c0d0e0f10 ` + + `KIDCount=2 ` + + `KIDs=[11121314-1516-1718-191a-1b1c1d1e1f10, 21222324-2526-2728-292a-2b2c2d2e2f20] ` + + `DataSize=5 ` + + `Data=[0x21, 0x22, 0x23, 0x24, 0x25]`, + }, + { + name: "tenc: DefaultIsProtected=1 DefaultPerSampleIVSize=0", + src: &Tenc{ + FullBox: FullBox{ + Version: 1, + Flags: [3]byte{0x00, 0x00, 0x00}, + }, + Reserved: 0x00, + DefaultCryptByteBlock: 0x0a, + DefaultSkipByteBlock: 0x0b, + DefaultIsProtected: 1, + DefaultPerSampleIVSize: 0, + DefaultKID: [16]byte{ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + }, + DefaultConstantIVSize: 4, + DefaultConstantIV: []byte{0x01, 0x23, 0x45, 0x67}, + }, + dst: &Tenc{}, + bin: []byte{ + 1, // version + 0x00, 0x00, 0x00, // flags + 0x00, // reserved + 0xab, // default crypt byte block / default skip byte block + 0x01, 0x00, // default is protected / default per sample iv size + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // default kid + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x04, // default constant iv size + 0x01, 0x23, 0x45, 0x67, // default constant iv + }, + str: `Version=1 Flags=0x000000 ` + + `Reserved=0 ` + + `DefaultCryptByteBlock=10 ` + + `DefaultSkipByteBlock=11 ` + + `DefaultIsProtected=1 ` + + `DefaultPerSampleIVSize=0 ` + + `DefaultKID=01234567-89ab-cdef-0123-456789abcdef ` + + `DefaultConstantIVSize=4 ` + + `DefaultConstantIV=[0x1, 0x23, 0x45, 0x67]`, + }, + { + name: "tenc: DefaultIsProtected=0 DefaultPerSampleIVSize=0", + src: &Tenc{ + FullBox: FullBox{ + Version: 1, + Flags: [3]byte{0x00, 0x00, 0x00}, + }, + Reserved: 0x00, + DefaultCryptByteBlock: 0x0a, + DefaultSkipByteBlock: 0x0b, + DefaultIsProtected: 0, + DefaultPerSampleIVSize: 0, + DefaultKID: [16]byte{ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + }, + }, + dst: &Tenc{}, + bin: []byte{ + 1, // version + 0x00, 0x00, 0x00, // flags + 0x00, // reserved + 0xab, // default crypt byte block / default skip byte block + 0x00, 0x00, // default is protected / default per sample iv size + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // default kid + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + }, + str: `Version=1 Flags=0x000000 ` + + `Reserved=0 ` + + `DefaultCryptByteBlock=10 ` + + `DefaultSkipByteBlock=11 ` + + `DefaultIsProtected=0 ` + + `DefaultPerSampleIVSize=0 ` + + `DefaultKID=01234567-89ab-cdef-0123-456789abcdef`, + }, + { + name: "tenc: DefaultIsProtected=1 DefaultPerSampleIVSize=1", + src: &Tenc{ + FullBox: FullBox{ + Version: 1, + Flags: [3]byte{0x00, 0x00, 0x00}, + }, + Reserved: 0x00, + DefaultCryptByteBlock: 0x0a, + DefaultSkipByteBlock: 0x0b, + DefaultIsProtected: 1, + DefaultPerSampleIVSize: 1, + DefaultKID: [16]byte{ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + }, + }, + dst: &Tenc{}, + bin: []byte{ + 1, // version + 0x00, 0x00, 0x00, // flags + 0x00, // reserved + 0xab, // default crypt byte block / default skip byte block + 0x01, 0x01, // default is protected / default per sample iv size + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // default kid + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + }, + str: `Version=1 Flags=0x000000 ` + + `Reserved=0 ` + + `DefaultCryptByteBlock=10 ` + + `DefaultSkipByteBlock=11 ` + + `DefaultIsProtected=1 ` + + `DefaultPerSampleIVSize=1 ` + + `DefaultKID=01234567-89ab-cdef-0123-456789abcdef`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Marshal + buf := bytes.NewBuffer(nil) + n, err := Marshal(buf, tc.src, tc.ctx) + 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, tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, tc.dst) + s, err := r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // UnmarshalAny + dst, n, err := UnmarshalAny(bytes.NewReader(tc.bin), tc.src.GetType(), uint64(len(tc.bin)), tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, dst) + s, err = r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // Stringify + str, err := Stringify(tc.src, tc.ctx) + require.NoError(t, err) + assert.Equal(t, tc.str, str) + }) + } +} diff --git a/box_types_metadata.go b/box_types_metadata.go new file mode 100644 index 0000000..a56145e --- /dev/null +++ b/box_types_metadata.go @@ -0,0 +1,172 @@ +package mp4 + +import ( + "fmt" + + "github.com/abema/go-mp4/util" +) + +/*************************** ilst ****************************/ + +func BoxTypeIlst() BoxType { return StrToBoxType("ilst") } +func BoxTypeData() BoxType { return StrToBoxType("data") } + +var ilstMetaBoxTypes = []BoxType{ + StrToBoxType("----"), + StrToBoxType("aART"), + StrToBoxType("akID"), + StrToBoxType("apID"), + StrToBoxType("atID"), + StrToBoxType("cmID"), + StrToBoxType("cnID"), + StrToBoxType("covr"), + StrToBoxType("cpil"), + StrToBoxType("cprt"), + StrToBoxType("desc"), + StrToBoxType("disk"), + StrToBoxType("egid"), + StrToBoxType("geID"), + StrToBoxType("gnre"), + StrToBoxType("pcst"), + StrToBoxType("pgap"), + StrToBoxType("plID"), + StrToBoxType("purd"), + StrToBoxType("purl"), + StrToBoxType("rtng"), + StrToBoxType("sfID"), + StrToBoxType("soaa"), + StrToBoxType("soal"), + StrToBoxType("soar"), + StrToBoxType("soco"), + StrToBoxType("sonm"), + StrToBoxType("sosn"), + StrToBoxType("stik"), + StrToBoxType("tmpo"), + StrToBoxType("trkn"), + StrToBoxType("tven"), + StrToBoxType("tves"), + StrToBoxType("tvnn"), + StrToBoxType("tvsh"), + StrToBoxType("tvsn"), + {0xA9, 'A', 'R', 'T'}, + {0xA9, 'a', 'l', 'b'}, + {0xA9, 'c', 'm', 't'}, + {0xA9, 'c', 'o', 'm'}, + {0xA9, 'd', 'a', 'y'}, + {0xA9, 'g', 'e', 'n'}, + {0xA9, 'g', 'r', 'p'}, + {0xA9, 'n', 'a', 'm'}, + {0xA9, 't', 'o', 'o'}, + {0xA9, 'w', 'r', 't'}, +} + +func IsIlstMetaBoxType(boxType BoxType) bool { + for _, bt := range ilstMetaBoxTypes { + if boxType == bt { + return true + } + } + return false +} + +func init() { + AddBoxDef(&Ilst{}) + AddBoxDefEx(&Data{}, isUnderIlstMeta) + for _, bt := range ilstMetaBoxTypes { + AddAnyTypeBoxDefEx(&IlstMetaContainer{}, bt, isIlstMetaContainer) + } + AddAnyTypeBoxDefEx(&StringData{}, StrToBoxType("mean"), isUnderIlstFreeFormat) + AddAnyTypeBoxDefEx(&StringData{}, StrToBoxType("name"), isUnderIlstFreeFormat) +} + +type Ilst struct { + Box +} + +// GetType returns the BoxType +func (*Ilst) GetType() BoxType { + return BoxTypeIlst() +} + +type IlstMetaContainer struct { + AnyTypeBox +} + +func isIlstMetaContainer(ctx Context) bool { + return ctx.UnderIlst && !ctx.UnderIlstMeta +} + +const ( + DataTypeBinary = 0 + DataTypeStringUTF8 = 1 + DataTypeStringUTF16 = 2 + DataTypeStringMac = 3 + DataTypeStringJPEG = 14 + DataTypeSignedIntBigEndian = 21 + DataTypeFloat32BigEndian = 22 + DataTypeFloat64BigEndian = 23 +) + +type Data struct { + Box + DataType uint32 `mp4:"0,size=32"` + DataLang uint32 `mp4:"1,size=32"` + Data []byte `mp4:"2,size=8"` +} + +// GetType returns the BoxType +func (*Data) GetType() BoxType { + return BoxTypeData() +} + +func isUnderIlstMeta(ctx Context) bool { + return ctx.UnderIlstMeta +} + +// StringifyField returns field value as string +func (data *Data) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { + switch name { + case "DataType": + switch data.DataType { + case DataTypeBinary: + return "BINARY", true + case DataTypeStringUTF8: + return "UTF8", true + case DataTypeStringUTF16: + return "UTF16", true + case DataTypeStringMac: + return "MAC_STR", true + case DataTypeStringJPEG: + return "JPEG", true + case DataTypeSignedIntBigEndian: + return "INT", true + case DataTypeFloat32BigEndian: + return "FLOAT32", true + case DataTypeFloat64BigEndian: + return "FLOAT64", true + } + case "Data": + switch data.DataType { + case DataTypeStringUTF8: + return fmt.Sprintf("\"%s\"", util.EscapeUnprintables(string(data.Data))), true + } + } + return "", false +} + +type StringData struct { + AnyTypeBox + Data []byte `mp4:"0,size=8"` +} + +// StringifyField returns field value as string +func (sd *StringData) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { + if name == "Data" { + return fmt.Sprintf("\"%s\"", util.EscapeUnprintables(string(sd.Data))), true + } + return "", false +} + +func isUnderIlstFreeFormat(ctx Context) bool { + return ctx.UnderIlstFreeMeta +} diff --git a/box_types_metadata_test.go b/box_types_metadata_test.go new file mode 100644 index 0000000..ebce386 --- /dev/null +++ b/box_types_metadata_test.go @@ -0,0 +1,198 @@ +package mp4 + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBoxTypesMetadata(t *testing.T) { + testCases := []struct { + name string + src IImmutableBox + dst IBox + bin []byte + str string + ctx Context + }{ + { + name: "ilst", + src: &Ilst{}, + dst: &Ilst{}, + bin: nil, + str: ``, + }, + { + name: "ilst meta container", + src: &IlstMetaContainer{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("----")}, + }, + dst: &IlstMetaContainer{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("----")}, + }, + bin: nil, + str: ``, + ctx: Context{UnderIlst: true}, + }, + { + name: "ilst data (binary)", + src: &Data{DataType: 0, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x00, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=BINARY DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (utf8)", + src: &Data{DataType: 1, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x01, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=UTF8 DataLang=305419896 Data="foo"`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (utf8 escaped)", + src: &Data{DataType: 1, DataLang: 0x12345678, Data: []byte{0x00, 'f', 'o', 'o'}}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x01, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x00, 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=UTF8 DataLang=305419896 Data=".foo"`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (utf16)", + src: &Data{DataType: 2, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x02, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=UTF16 DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (mac string)", + src: &Data{DataType: 3, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x03, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=MAC_STR DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (jpsg)", + src: &Data{DataType: 14, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x0e, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=JPEG DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (int)", + src: &Data{DataType: 21, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x15, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=INT DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (float32)", + src: &Data{DataType: 22, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x16, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=FLOAT32 DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (float64)", + src: &Data{DataType: 23, DataLang: 0x12345678, Data: []byte("foo")}, + dst: &Data{}, + bin: []byte{ + 0x00, 0x00, 0x00, 0x17, // data type + 0x12, 0x34, 0x56, 0x78, // data lang + 0x66, 0x6f, 0x6f, // data + }, + str: `DataType=FLOAT64 DataLang=305419896 Data=[0x66, 0x6f, 0x6f]`, + ctx: Context{UnderIlstMeta: true}, + }, + { + name: "ilst data (string)", + src: &StringData{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("mean")}, + Data: []byte{0x00, 'f', 'o', 'o'}, + }, + dst: &StringData{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("mean")}, + }, + bin: []byte{ + 0x00, 0x66, 0x6f, 0x6f, // data + }, + str: `Data=".foo"`, + ctx: Context{UnderIlstFreeMeta: true}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Marshal + buf := bytes.NewBuffer(nil) + n, err := Marshal(buf, tc.src, tc.ctx) + 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, tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, tc.dst) + s, err := r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // UnmarshalAny + dst, n, err := UnmarshalAny(bytes.NewReader(tc.bin), tc.src.GetType(), uint64(len(tc.bin)), tc.ctx) + require.NoError(t, err) + assert.Equal(t, uint64(buf.Len()), n) + assert.Equal(t, tc.src, dst) + s, err = r.Seek(0, io.SeekCurrent) + require.NoError(t, err) + assert.Equal(t, int64(buf.Len()), s) + + // Stringify + str, err := Stringify(tc.src, tc.ctx) + require.NoError(t, err) + assert.Equal(t, tc.str, str) + }) + } +}