From 2d8665c31b9700d9012d2e18c7034fde632c16ad Mon Sep 17 00:00:00 2001 From: Vishal Shah Date: Mon, 1 Apr 2024 10:48:41 -0600 Subject: [PATCH 1/2] feat: Dovi8.1 profile support --- go.mod | 2 +- helpers/testfixtures/testfixtures.go | 7 +- mpd/fixtures/ondemand_withdolby.mpd | 95 +++++++++++++++++++++++ mpd/mpd.go | 49 +++++++++--- mpd/mpd_read_write_test.go | 108 +++++++++++++++++++++++++++ mpd/mpd_test.go | 26 +++++++ 6 files changed, 271 insertions(+), 16 deletions(-) create mode 100644 mpd/fixtures/ondemand_withdolby.mpd diff --git a/go.mod b/go.mod index d60a7c1..0d06d1f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/cbsinteractive/go-dash/v3 -go 1.16 +go 1.22 diff --git a/helpers/testfixtures/testfixtures.go b/helpers/testfixtures/testfixtures.go index d00a071..d0368d3 100644 --- a/helpers/testfixtures/testfixtures.go +++ b/helpers/testfixtures/testfixtures.go @@ -2,7 +2,6 @@ package testfixtures import ( "fmt" - "io/ioutil" "os" "testing" @@ -11,7 +10,7 @@ import ( // Load test fixture from path relative to fixtures directory func LoadFixture(path string) (js string) { - f, err := ioutil.ReadFile(path) + f, err := os.ReadFile(path) if err != nil { panic(fmt.Sprintf("LoadFixture Error. ioutil.ReadFile. path = %s, Err = %s", path, err.Error())) } @@ -22,8 +21,8 @@ func CompareFixture(t *testing.T, fixturePath string, actualContent string) { t.Helper() expectedContent := LoadFixture(fixturePath) if os.Getenv("GENERATE_FIXTURES") != "" { - _ = ioutil.WriteFile(fixturePath, []byte(actualContent), os.ModePerm) - fmt.Println("Wrote fixture: " + fixturePath) + _ = os.WriteFile(fixturePath, []byte(actualContent), os.ModePerm) + fmt.Println("GEN FIXTURES - Wrote fixture: " + fixturePath) return } require.EqualString(t, expectedContent, actualContent) diff --git a/mpd/fixtures/ondemand_withdolby.mpd b/mpd/fixtures/ondemand_withdolby.mpd new file mode 100644 index 0000000..7497836 --- /dev/null +++ b/mpd/fixtures/ondemand_withdolby.mpd @@ -0,0 +1,95 @@ + + + + + + + AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA== + + + BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + + + + 800k/output-audio-en-US.mp4 + + + + + + + + + + AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA== + + + BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + + + + 800k/output-audio-AD-en-US.mp4 + + + + + + + + + + + AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA== + + + BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + + + + 800k/output-video-1.mp4 + + + + + + 1200k/output-video-1.mp4 + + + + + + + + + AAAAYXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAEEIARIQWr3VL1VKTyq40GH3YUJRVRoIY2FzdGxhYnMiGFdyM1ZMMVZLVHlxNDBHSDNZVUpSVlE9PTIHZGVmYXVsdA== + + + BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATAA5AFcAOQBXAGsAcABWAEsAawArADQAMABHAEgAMwBZAFUASgBSAFYAUQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBJAEsAegBZADIASABaAEwAQQBsAEkAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBMADkAVwA5AFcAawBwAFYASwBrACsANAAwAEcASAAzAFkAVQBKAFIAVgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEkASwB6AFkAMgBIAFoATABBAGwASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + + + + 800k/output-video-1.mp4 + + + + + + 1200k/output-video-1.mp4 + + + + + + + + + + http://example.com/content/sintel/subtitles/subtitles_en.vtt + + + + diff --git a/mpd/mpd.go b/mpd/mpd.go index ccea914..67d41e6 100644 --- a/mpd/mpd.go +++ b/mpd/mpd.go @@ -73,6 +73,7 @@ var ( type MPD struct { XMLNs *string `xml:"xmlns,attr"` XMLNsDolby *string `xml:"xmlns:dolby,attr"` + XMLNsSCTE214 *string `xml:"xmlns:scte214,attr"` Scte35NS *Scte35NS `xml:"scte35,attr,omitempty"` XsiNS *XmlnsAttr `xml:"xsi,attr,omitempty"` XsiSchemaLocation *XsiSL `xml:"schemaLocation,attr,omitempty"` @@ -246,9 +247,17 @@ func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er return err } *as = AdaptationSet(n.wrappedAdaptationSet) + + if as.Roles != nil { + as.Roles = n.Roles + } + as.ContentProtection = make([]ContentProtectioner, len(n.ContentProtection)) - for i := range n.ContentProtection { - as.ContentProtection[i] = n.ContentProtection[i] + copy(as.ContentProtection, n.ContentProtection) + + for i := range as.Representations { + as.Representations[i].SupplementalCodecs = n.Representations[i].SupplementalCodecs + as.Representations[i].SupplementalProfiles = n.Representations[i].SupplementalProfiles } return nil } @@ -434,15 +443,17 @@ type Representation struct { CommonAttributesAndElements AdaptationSet *AdaptationSet `xml:"-"` AudioChannelConfiguration *AudioChannelConfiguration `xml:"AudioChannelConfiguration,omitempty"` - AudioSamplingRate *int64 `xml:"audioSamplingRate,attr"` // Audio - Bandwidth *int64 `xml:"bandwidth,attr"` // Audio + Video - Codecs *string `xml:"codecs,attr"` // Audio + Video - FrameRate *string `xml:"frameRate,attr,omitempty"` // Video - Height *int64 `xml:"height,attr"` // Video - ID *string `xml:"id,attr"` // Audio + Video - Width *int64 `xml:"width,attr"` // Video - BaseURL []string `xml:"BaseURL,omitempty"` // On-Demand Profile - SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` // On-Demand Profile + AudioSamplingRate *int64 `xml:"audioSamplingRate,attr"` // Audio + Bandwidth *int64 `xml:"bandwidth,attr"` // Audio + Video + Codecs *string `xml:"codecs,attr"` // Audio + Video + SupplementalCodecs *string `xml:"scte214:supplementalCodecs,attr,omitempty"` // Video + SupplementalProfiles *string `xml:"scte214:supplementalProfiles,attr,omitempty"` // Video + FrameRate *string `xml:"frameRate,attr,omitempty"` // Video + Height *int64 `xml:"height,attr"` // Video + ID *string `xml:"id,attr"` // Audio + Video + Width *int64 `xml:"width,attr"` // Video + BaseURL []string `xml:"BaseURL,omitempty"` // On-Demand Profile + SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` // On-Demand Profile SegmentList *SegmentList `xml:"SegmentList,omitempty"` SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` } @@ -463,6 +474,10 @@ func (m *MPD) SetDolbyXMLNs() { m.XMLNsDolby = Strptr("http://www.dolby.com/ns/online/DASH") } +func (m *MPD) SetScte214XMLNs() { + m.XMLNsSCTE214 = Strptr("urn:scte:dash:scte214-extensions") +} + // Creates a new static MPD object. // profile - DASH Profile (Live or OnDemand). // mediaPresentationDuration - Media Presentation Duration (i.e. PT6M16S). @@ -1114,6 +1129,18 @@ func (as *AdaptationSet) AddNewRepresentationVideo(bandwidth int64, codecs strin return r, nil } +// Adds supplementalCodecs and supplementalProfiles to a Representation +// supplementalCodecs - scte214:supplementalCodecs attribute for Dovi 8.1 signaling (optional). +// supplementalProfiles - scte214:supplementalProfiles attribute for Dovi 8.1 signaling (optional). +func (r *Representation) AddScte214VideoCodecProperties(supplementalCodecs string, supplementalProfiles string) (*Representation, error) { + // For Dovi 8.1 signaling both supplementalCodecs and supplementalProfiles should be added + if len(supplementalCodecs) > 0 && len(supplementalProfiles) > 0 { + r.SupplementalCodecs = Strptr(supplementalCodecs) + r.SupplementalProfiles = Strptr(supplementalProfiles) + } + return r, nil +} + // Adds a new Subtitle representation to an AdaptationSet. // bandwidth - in Bits/s (i.e. 256). // id - ID for this representation, will get used as $RepresentationID$ in template strings. diff --git a/mpd/mpd_read_write_test.go b/mpd/mpd_read_write_test.go index 6934faf..92d3a17 100644 --- a/mpd/mpd_read_write_test.go +++ b/mpd/mpd_read_write_test.go @@ -123,6 +123,21 @@ func TestNewMPDOnDemandWriteToString(t *testing.T) { require.EqualString(t, expectedXML, xmlStr) } +func TestNewMPDOnDemandwithDolbyWriteToString(t *testing.T) { + m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) + m.SetDolbyXMLNs() + m.SetScte214XMLNs() + + xmlStr, err := m.WriteToString() + require.NoError(t, err) + expectedXML := ` + + + +` + require.EqualString(t, expectedXML, xmlStr) +} + func TestAddNewAdaptationSetAudioWriteToString(t *testing.T) { m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) @@ -519,3 +534,96 @@ func TestWriteComment(t *testing.T) { ` require.EqualString(t, answer, s) } + +func OnDemandProfileWithDolby() *MPD { + m := NewMPD(DASH_PROFILE_ONDEMAND, "PT30S", VALID_MIN_BUFFER_TIME) + m.SetDolbyXMLNs() + m.SetScte214XMLNs() + + // Main audio adaptation set + audioAS, _ := m.AddNewAdaptationSetAudioWithID("1", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, "en-US") + _, _ = audioAS.AddNewRole("urn:mpeg:dash:role:2011", "main") + + _, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60") + _, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes()) + _, _ = audioAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO) + _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") + + audioRep, _ := audioAS.AddNewRepresentationAudio(44100, 128558, "mp4a.40.5", "800k/audio-en-US") + _ = audioRep.SetNewBaseURL("800k/output-audio-en-US.mp4") + _, _ = audioRep.AddNewSegmentBase("629-756", "0-628") + + // audio description adaptation set + audioAD, _ := m.AddNewAdaptationSetAudioWithID("2", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP, "en-US") + _, _ = audioAD.AddNewRole("urn:mpeg:dash:role:2011", "description") + _, _ = audioAD.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") + + _, _ = audioAD.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60") + _, _ = audioAD.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes()) + _, _ = audioAD.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO) + _, _ = audioAD.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") + + audioADRep, _ := audioAD.AddNewRepresentationAudio(44100, 128558, "mp4a.40.5", "800k/audio-AD-en-US") + _ = audioADRep.SetNewBaseURL("800k/output-audio-AD-en-US.mp4") + _, _ = audioADRep.AddNewSegmentBase("629-756", "0-628") + + // video avc adaptation set + videoAS, _ := m.AddNewAdaptationSetVideoWithID("3", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) + _, _ = videoAS.AddNewRole("urn:mpeg:dash:role:2011", "main") + + _, _ = videoAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60") + _, _ = videoAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes()) + _, _ = videoAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO) + + videoRep1, _ := videoAS.AddNewRepresentationVideo(1100690, "avc1.4d401e", "800k/video-1", "30000/1001", 640, 360) + _ = videoRep1.SetNewBaseURL("800k/output-video-1.mp4") + _, _ = videoRep1.AddNewSegmentBase("686-813", "0-685") + + videoRep2, _ := videoAS.AddNewRepresentationVideo(1633516, "avc1.4d401f", "1200k/video-1", "30000/1001", 960, 540) + _ = videoRep2.SetNewBaseURL("1200k/output-video-1.mp4") + _, _ = videoRep2.AddNewSegmentBase("686-813", "0-685") + + // video hevc adaptation set with Dolby Vision signaling + videoAS1, _ := m.AddNewAdaptationSetVideoWithID("4", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) + _, _ = videoAS1.AddNewRole("urn:mpeg:dash:role:2011", "main") + + _, _ = videoAS1.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60") + _, _ = videoAS1.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes()) + _, _ = videoAS1.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO) + + videoRep3, _ := videoAS1.AddNewRepresentationVideo(1100690, "hev1.2.4.L63.90", "800k/video-1", "30000/1001", 640, 360) + _, _ = videoRep3.AddScte214VideoCodecProperties("dvhe.08.07", "db1p") + _ = videoRep3.SetNewBaseURL("800k/output-video-1.mp4") + _, _ = videoRep3.AddNewSegmentBase("686-813", "0-685") + + videoRep4, _ := videoAS1.AddNewRepresentationVideo(1633516, "hev1.2.4.L93.90", "1200k/video-1", "30000/1001", 960, 540) + _, _ = videoRep4.AddScte214VideoCodecProperties("dvhe.08.07", "db4h") + _ = videoRep4.SetNewBaseURL("1200k/output-video-1.mp4") + _, _ = videoRep4.AddNewSegmentBase("686-813", "0-685") + + // subtitle adaptation set + subtitleAS, _ := m.AddNewAdaptationSetSubtitleWithID("5", DASH_MIME_TYPE_SUBTITLE_VTT, VALID_LANG, VALID_SUBTITLE_LABEL) + _, _ = subtitleAS.AddNewRole("urn:mpeg:dash:role:2011", "subtitle") + subtitleRep, _ := subtitleAS.AddNewRepresentationSubtitle(VALID_SUBTITLE_BANDWIDTH, VALID_SUBTITLE_ID) + _ = subtitleRep.SetNewBaseURL(VALID_SUBTITLE_URL) + + return m +} + +func TestOnDemandProfileWithDolbyWriteToString(t *testing.T) { + m := OnDemandProfileWithDolby() + require.NotNil(t, m) + xmlStr, err := m.WriteToString() + require.NoError(t, err) + testfixtures.CompareFixture(t, "fixtures/ondemand_withdolby.mpd", xmlStr) +} + +func TestOnDemandProfileWithDolbyWriteToFile(t *testing.T) { + m := OnDemandProfileWithDolby() + require.NotNil(t, m) + err := m.WriteToFile("test-withdolby-ondemand.mpd") + xmlStr := testfixtures.LoadFixture("test-withdolby-ondemand.mpd") + testfixtures.CompareFixture(t, "fixtures/ondemand_withdolby.mpd", xmlStr) + defer os.Remove("test-withdolby-ondemand.mpd") + require.NoError(t, err) +} diff --git a/mpd/mpd_test.go b/mpd/mpd_test.go index 39d4146..06815c6 100644 --- a/mpd/mpd_test.go +++ b/mpd/mpd_test.go @@ -243,6 +243,32 @@ func TestNewMPDOnDemand(t *testing.T) { require.EqualString(t, expectedString, actualString) } +func TestNewMPDOnDemandWithDolby(t *testing.T) { + m := NewMPD(DASH_PROFILE_ONDEMAND, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) + m.SetDolbyXMLNs() + m.SetScte214XMLNs() + + require.NotNil(t, m) + expectedMPD := &MPD{ + XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"), + XMLNsDolby: Strptr("http://www.dolby.com/ns/online/DASH"), + XMLNsSCTE214: Strptr("urn:scte:dash:scte214-extensions"), + Profiles: Strptr((string)(DASH_PROFILE_ONDEMAND)), + Type: Strptr("static"), + MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION), + MinBufferTime: Strptr(VALID_MIN_BUFFER_TIME), + period: &Period{}, + Periods: []*Period{{}}, + } + + expectedString, err := expectedMPD.WriteToString() + require.NoError(t, err) + actualString, err := m.WriteToString() + require.NoError(t, err) + + require.EqualString(t, expectedString, actualString) +} + func TestAddAdaptationSetErrorNil(t *testing.T) { m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) From ebe10ac0aa7dbf3eb2e4d04470ba47937bbc6e07 Mon Sep 17 00:00:00 2001 From: Vishal Shah Date: Mon, 1 Apr 2024 13:03:49 -0600 Subject: [PATCH 2/2] update xmlns parsing for scte214 --- mpd/mpd.go | 70 +++++++++++++++++++++++++++++++++++++------------ mpd/mpd_test.go | 13 ++++++--- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/mpd/mpd.go b/mpd/mpd.go index 67d41e6..2efb925 100644 --- a/mpd/mpd.go +++ b/mpd/mpd.go @@ -72,8 +72,8 @@ var ( type MPD struct { XMLNs *string `xml:"xmlns,attr"` - XMLNsDolby *string `xml:"xmlns:dolby,attr"` - XMLNsSCTE214 *string `xml:"xmlns:scte214,attr"` + XMLNsDolby *XmlnsAttr `xml:"dolby,attr"` + XMLNsSCTE214 *XmlnsAttr `xml:"scte214,attr"` Scte35NS *Scte35NS `xml:"scte35,attr,omitempty"` XsiNS *XmlnsAttr `xml:"xsi,attr,omitempty"` XsiSchemaLocation *XsiSL `xml:"schemaLocation,attr,omitempty"` @@ -109,9 +109,27 @@ func (s *XmlnsAttr) UnmarshalXMLAttr(attr xml.Attr) error { } func (s *XmlnsAttr) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + if s == nil { + return xml.Attr{}, nil + } return xml.Attr{Name: xml.Name{Local: fmt.Sprintf("xmlns:%s", s.XmlName.Local)}, Value: s.Value}, nil } +type Scte214Attr struct { + XmlName xml.Name + Value string +} + +func (s *Scte214Attr) UnmarshalXMLAttr(attr xml.Attr) error { + s.XmlName = attr.Name + s.Value = attr.Value + return nil +} + +func (s *Scte214Attr) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + return xml.Attr{Name: xml.Name{Local: fmt.Sprintf("scte214:%s", s.XmlName.Local)}, Value: s.Value}, nil +} + type XsiSL struct { XmlName xml.Name Value string @@ -443,17 +461,17 @@ type Representation struct { CommonAttributesAndElements AdaptationSet *AdaptationSet `xml:"-"` AudioChannelConfiguration *AudioChannelConfiguration `xml:"AudioChannelConfiguration,omitempty"` - AudioSamplingRate *int64 `xml:"audioSamplingRate,attr"` // Audio - Bandwidth *int64 `xml:"bandwidth,attr"` // Audio + Video - Codecs *string `xml:"codecs,attr"` // Audio + Video - SupplementalCodecs *string `xml:"scte214:supplementalCodecs,attr,omitempty"` // Video - SupplementalProfiles *string `xml:"scte214:supplementalProfiles,attr,omitempty"` // Video - FrameRate *string `xml:"frameRate,attr,omitempty"` // Video - Height *int64 `xml:"height,attr"` // Video - ID *string `xml:"id,attr"` // Audio + Video - Width *int64 `xml:"width,attr"` // Video - BaseURL []string `xml:"BaseURL,omitempty"` // On-Demand Profile - SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` // On-Demand Profile + AudioSamplingRate *int64 `xml:"audioSamplingRate,attr"` // Audio + Bandwidth *int64 `xml:"bandwidth,attr"` // Audio + Video + Codecs *string `xml:"codecs,attr"` // Audio + Video + SupplementalCodecs *Scte214Attr `xml:"supplementalCodecs,attr,omitempty"` // Video + SupplementalProfiles *Scte214Attr `xml:"supplementalProfiles,attr,omitempty"` // Video + FrameRate *string `xml:"frameRate,attr,omitempty"` // Video + Height *int64 `xml:"height,attr"` // Video + ID *string `xml:"id,attr"` // Audio + Video + Width *int64 `xml:"width,attr"` // Video + BaseURL []string `xml:"BaseURL,omitempty"` // On-Demand Profile + SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` // On-Demand Profile SegmentList *SegmentList `xml:"SegmentList,omitempty"` SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` } @@ -471,11 +489,17 @@ type AudioChannelConfiguration struct { } func (m *MPD) SetDolbyXMLNs() { - m.XMLNsDolby = Strptr("http://www.dolby.com/ns/online/DASH") + m.XMLNsDolby = &XmlnsAttr{ + XmlName: xml.Name{Space: "xmlns", Local: "dolby"}, + Value: "http://www.dolby.com/ns/online/DASH", + } } func (m *MPD) SetScte214XMLNs() { - m.XMLNsSCTE214 = Strptr("urn:scte:dash:scte214-extensions") + m.XMLNsSCTE214 = &XmlnsAttr{ + XmlName: xml.Name{Space: "xmlns", Local: "scte214"}, + Value: "urn:scte:dash:scte214-extensions", + } } // Creates a new static MPD object. @@ -1135,8 +1159,20 @@ func (as *AdaptationSet) AddNewRepresentationVideo(bandwidth int64, codecs strin func (r *Representation) AddScte214VideoCodecProperties(supplementalCodecs string, supplementalProfiles string) (*Representation, error) { // For Dovi 8.1 signaling both supplementalCodecs and supplementalProfiles should be added if len(supplementalCodecs) > 0 && len(supplementalProfiles) > 0 { - r.SupplementalCodecs = Strptr(supplementalCodecs) - r.SupplementalProfiles = Strptr(supplementalProfiles) + r.SupplementalCodecs = &Scte214Attr{ + XmlName: xml.Name{ + Space: "scte214", + Local: "supplementalCodecs", + }, + Value: supplementalCodecs, + } + r.SupplementalProfiles = &Scte214Attr{ + XmlName: xml.Name{ + Space: "scte214", + Local: "supplementalProfiles", + }, + Value: supplementalProfiles, + } } return r, nil } diff --git a/mpd/mpd_test.go b/mpd/mpd_test.go index 06815c6..9343cfa 100644 --- a/mpd/mpd_test.go +++ b/mpd/mpd_test.go @@ -2,6 +2,7 @@ package mpd import ( "encoding/base64" + "encoding/xml" "path/filepath" "strconv" "testing" @@ -250,9 +251,15 @@ func TestNewMPDOnDemandWithDolby(t *testing.T) { require.NotNil(t, m) expectedMPD := &MPD{ - XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"), - XMLNsDolby: Strptr("http://www.dolby.com/ns/online/DASH"), - XMLNsSCTE214: Strptr("urn:scte:dash:scte214-extensions"), + XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"), + XMLNsDolby: &XmlnsAttr{ + XmlName: xml.Name{Space: "xmlns", Local: "dolby"}, + Value: "http://www.dolby.com/ns/online/DASH", + }, + XMLNsSCTE214: &XmlnsAttr{ + XmlName: xml.Name{Space: "xmlns", Local: "scte214"}, + Value: "urn:scte:dash:scte214-extensions", + }, Profiles: Strptr((string)(DASH_PROFILE_ONDEMAND)), Type: Strptr("static"), MediaPresentationDuration: Strptr(VALID_MEDIA_PRESENTATION_DURATION),