From c888274a856f995d46ec6c8857926978c4e06960 Mon Sep 17 00:00:00 2001 From: monyone Date: Tue, 7 Jan 2025 23:46:16 +0900 Subject: [PATCH] Feat: add ISO 14496-30 WebVTT Box --- box_types_iso14496_30.go | 127 ++++++++++++++++++++++++++++ box_types_iso14496_30_test.go | 155 ++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 box_types_iso14496_30.go create mode 100644 box_types_iso14496_30_test.go diff --git a/box_types_iso14496_30.go b/box_types_iso14496_30.go new file mode 100644 index 0000000..0228b4e --- /dev/null +++ b/box_types_iso14496_30.go @@ -0,0 +1,127 @@ +package mp4 + +/*********************** WebVTT Sample Entry ****************************/ + +func BoxTypeVttC() BoxType { return StrToBoxType("vttC") } +func BoxTypeVlab() BoxType { return StrToBoxType("vlab") } +func BoxTypeWvtt() BoxType { return StrToBoxType("wvtt") } + +func init() { + AddBoxDef(&WebVTTConfigurationBox{}) + AddBoxDef(&WebVTTSourceLabelBox{}) + AddAnyTypeBoxDef(&WVTTSampleEntry{}, BoxTypeWvtt()) +} + +type WebVTTConfigurationBox struct { + Box + Config string `mp4:"0,string"` +} + +func (WebVTTConfigurationBox) GetType() BoxType { + return BoxTypeVttC() +} + +type WebVTTSourceLabelBox struct { + Box + SourceLabel string `mp4:"0,string"` +} + +func (WebVTTSourceLabelBox) GetType() BoxType { + return BoxTypeVlab() +} + +type WVTTSampleEntry struct { + SampleEntry `mp4:"0,extend"` +} + +/*********************** WebVTT Sample Format ****************************/ + +func BoxTypeVttc() BoxType { return StrToBoxType("vttc") } +func BoxTypeVsid() BoxType { return StrToBoxType("vsid") } +func BoxTypeCtim() BoxType { return StrToBoxType("ctim") } +func BoxTypeIden() BoxType { return StrToBoxType("iden") } +func BoxTypeSttg() BoxType { return StrToBoxType("sttg") } +func BoxTypePayl() BoxType { return StrToBoxType("payl") } +func BoxTypeVtte() BoxType { return StrToBoxType("vtte") } +func BoxTypeVtta() BoxType { return StrToBoxType("vtta") } + +func init() { + AddBoxDef(&VTTCueBox{}) + AddBoxDef(&CueSourceIDBox{}) + AddBoxDef(&CueTimeBox{}) + AddBoxDef(&CueIDBox{}) + AddBoxDef(&CueSettingsBox{}) + AddBoxDef(&CuePayloadBox{}) + AddBoxDef(&VTTEmptyCueBox{}) + AddBoxDef(&VTTAdditionalTextBox{}) +} + +type VTTCueBox struct { + Box +} + +func (VTTCueBox) GetType() BoxType { + return BoxTypeVttc() +} + +type CueSourceIDBox struct { + Box + SourceId uint32 `mp4:"0,size=32"` +} + +func (CueSourceIDBox) GetType() BoxType { + return BoxTypeVsid() +} + +type CueTimeBox struct { + Box + CueCurrentTime string `mp4:"0,string"` +} + +func (CueTimeBox) GetType() BoxType { + return BoxTypeCtim() +} + +type CueIDBox struct { + Box + CueId string `mp4:"0,string"` +} + +func (CueIDBox) GetType() BoxType { + return BoxTypeIden() +} + +type CueSettingsBox struct { + Box + Settings string `mp4:"0,string"` +} + +func (CueSettingsBox) GetType() BoxType { + return BoxTypeSttg() +} + +type CuePayloadBox struct { + Box + CueText string `mp4:"0,string"` +} + +func (CuePayloadBox) GetType() BoxType { + return BoxTypePayl() +} + +type VTTEmptyCueBox struct { + Box +} + +func (VTTEmptyCueBox) GetType() BoxType { + return BoxTypeVtte() +} + +type VTTAdditionalTextBox struct { + Box + CueAdditionalText string `mp4:"0,string"` +} + +func (VTTAdditionalTextBox) GetType() BoxType { + return BoxTypeVtta() +} \ No newline at end of file diff --git a/box_types_iso14496_30_test.go b/box_types_iso14496_30_test.go new file mode 100644 index 0000000..5dc5cfa --- /dev/null +++ b/box_types_iso14496_30_test.go @@ -0,0 +1,155 @@ +package mp4 + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBoxTypesISO14496_30(t *testing.T) { + testCases := []struct { + name string + src IImmutableBox + dst IBox + bin []byte + str string + ctx Context + }{ + { + name: "vttC", + src: &WebVTTConfigurationBox{ + Config: "WEBVTT\n", + }, + dst: &WebVTTConfigurationBox{}, + bin: []byte{'W', 'E', 'B', 'V', 'T', 'T', '\n', 0 }, + str: `Config="WEBVTT."`, + }, + { + name: "vlab", + src: &WebVTTSourceLabelBox{ + SourceLabel: "Source", + }, + dst: &WebVTTSourceLabelBox{}, + bin: []byte{'S', 'o', 'u', 'r', 'c', 'e', 0 }, + str: `SourceLabel="Source"`, + }, + { + name: "wvtt", + src: &WVTTSampleEntry{ + SampleEntry: SampleEntry{ + AnyTypeBox: AnyTypeBox{Type: StrToBoxType("wvtt")}, + DataReferenceIndex: 0x1234, + }, + }, + dst: &WVTTSampleEntry{SampleEntry: SampleEntry{AnyTypeBox: AnyTypeBox{Type: StrToBoxType("wvtt")}}}, + bin: []byte{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x34 }, + str: `DataReferenceIndex=4660`, + }, + { + name: "vttc", + src: &VTTCueBox{}, + dst: &VTTCueBox{}, + bin: []byte(nil), + str: ``, + }, + { + name: "vsid", + src: &CueSourceIDBox{ + SourceId: 0, + }, + dst: &CueSourceIDBox{}, + bin: []byte{ 0, 0, 0, 0 }, + str: `SourceId=0`, + }, + { + name: "ctim", + src: &CueTimeBox{ + CueCurrentTime: "00:00:00.000", + }, + dst: &CueTimeBox{}, + bin: []byte{'0', '0', ':', '0', '0', ':', '0', '0', '.', '0', '0', '0', 0 }, + str: `CueCurrentTime="00:00:00.000"`, + }, + { + name: "iden", + src: &CueIDBox{ + CueId: "example_id", + }, + dst: &CueIDBox{}, + bin: []byte{'e', 'x', 'a', 'm', 'p', 'l', 'e', '_', 'i', 'd', 0 }, + str: `CueId="example_id"`, + }, + { + name: "sttg", + src: &CueSettingsBox{ + Settings: "line=0", + }, + dst: &CueSettingsBox{}, + bin: []byte{'l', 'i', 'n', 'e', '=', '0', 0 }, + str: `Settings="line=0"`, + }, + { + name: "payl", + src: &CuePayloadBox{ + CueText: "sample", + }, + dst: &CuePayloadBox{}, + bin: []byte{'s', 'a', 'm', 'p', 'l', 'e', 0 }, + str: `CueText="sample"`, + }, + { + name: "vtte", + src: &VTTEmptyCueBox{}, + dst: &VTTEmptyCueBox{}, + bin: []byte(nil), + str: ``, + }, + { + name: "vtta", + src: &VTTAdditionalTextBox{ + CueAdditionalText: "test", + }, + dst: &VTTAdditionalTextBox{}, + bin: []byte{'t', 'e', 's', 't', 0}, + str: `CueAdditionalText="test"`, + }, + } + + 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) + }) + } +}