From 54279504bb190031a73129e2c8e25cd24e0cd8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Ribeiro?= Date: Thu, 31 Oct 2019 19:42:33 -0400 Subject: [PATCH 1/4] return element name unrecognized in adaptation set --- mpd/mpd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpd/mpd.go b/mpd/mpd.go index e490abb..9fc11f2 100644 --- a/mpd/mpd.go +++ b/mpd/mpd.go @@ -236,7 +236,7 @@ func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er } representations = append(representations, rp) default: - return errors.New("Unrecognized element in AdaptationSet") + return errors.New("Unrecognized element in AdaptationSet: " + tt.Name.Local) } case xml.EndElement: if tt == start.End() { From b6bd25ebaa21ff6a5a42fc059decf0ce480aed03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Ribeiro?= Date: Thu, 31 Oct 2019 20:11:48 -0400 Subject: [PATCH 2/4] extend elements in adaptation set to cover accessibility elements --- mpd/mpd.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mpd/mpd.go b/mpd/mpd.go index 9fc11f2..ba5b670 100644 --- a/mpd/mpd.go +++ b/mpd/mpd.go @@ -235,6 +235,12 @@ func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er return err } representations = append(representations, rp) + case "Accessibility": + ac := new(Accessibility) + err = d.DecodeElement(ac, &tt) + if err != nil { + return err + } default: return errors.New("Unrecognized element in AdaptationSet: " + tt.Name.Local) } @@ -430,6 +436,11 @@ type Representation struct { SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` } +type Accessibility struct { + SchemeIdUri *string `xml:"schemeIdUri,omitempty"` + Value *int64 `xml:"value,omitempty"` +} + type AudioChannelConfiguration struct { SchemeIDURI *string `xml:"schemeIdUri,attr"` // Value will be an int for non-Dolby Schemes, and a hexstring for Dolby Schemes, hence we make it a string From b35789894eaf0a95ffa6868b453693b89378d5e4 Mon Sep 17 00:00:00 2001 From: Thomas Symborski Date: Tue, 7 Jan 2020 14:07:08 -0500 Subject: [PATCH 3/4] testing: add tests for the Accessibility element --- mpd/fixtures/hbbtv_profile.mpd | 1 + mpd/fixtures/live_profile.mpd | 1 + mpd/fixtures/live_profile_dynamic.mpd | 1 + mpd/fixtures/ondemand_profile.mpd | 1 + mpd/mpd.go | 112 +++++++++++++++++--------- mpd/mpd_read_write_test.go | 33 ++++++++ mpd/mpd_test.go | 26 ++++++ 7 files changed, 138 insertions(+), 37 deletions(-) diff --git a/mpd/fixtures/hbbtv_profile.mpd b/mpd/fixtures/hbbtv_profile.mpd index 94de68a..f7672e9 100644 --- a/mpd/fixtures/hbbtv_profile.mpd +++ b/mpd/fixtures/hbbtv_profile.mpd @@ -15,6 +15,7 @@ + diff --git a/mpd/fixtures/live_profile.mpd b/mpd/fixtures/live_profile.mpd index a0327da..20b4f44 100644 --- a/mpd/fixtures/live_profile.mpd +++ b/mpd/fixtures/live_profile.mpd @@ -13,6 +13,7 @@ + diff --git a/mpd/fixtures/live_profile_dynamic.mpd b/mpd/fixtures/live_profile_dynamic.mpd index a0b0acf..f06c190 100644 --- a/mpd/fixtures/live_profile_dynamic.mpd +++ b/mpd/fixtures/live_profile_dynamic.mpd @@ -13,6 +13,7 @@ + diff --git a/mpd/fixtures/ondemand_profile.mpd b/mpd/fixtures/ondemand_profile.mpd index 35752e0..47a7cc7 100644 --- a/mpd/fixtures/ondemand_profile.mpd +++ b/mpd/fixtures/ondemand_profile.mpd @@ -16,6 +16,7 @@ + diff --git a/mpd/mpd.go b/mpd/mpd.go index 42d5658..3847f5f 100644 --- a/mpd/mpd.go +++ b/mpd/mpd.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/xml" "errors" + "fmt" "strings" "time" @@ -33,6 +34,12 @@ const ( AUDIO_CHANNEL_CONFIGURATION_MPEG_DOLBY AudioChannelConfigurationScheme = "tag:dolby.com,2014:dash:audio_channel_configuration:2011" ) +// AccessibilityElementScheme is the scheme definition for an Accessibility element +type AccessibilityElementScheme string + +// Accessibility descriptor values for Audio Description +const ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO AccessibilityElementScheme = "urn:tva:metadata:cs:AudioPurposeCS:2007" + // Constants for some known MIME types, this is a limited list and others can be used. const ( DASH_MIME_TYPE_VIDEO_MP4 string = "video/mp4" @@ -50,6 +57,7 @@ var ( ErrSegmentTemplateLiveProfileOnly = errors.New("Segment template can only be used with Live Profile") ErrSegmentTemplateNil = errors.New("Segment Template nil ") ErrRepresentationNil = errors.New("Representation nil") + ErrAccessibilityNil = errors.New("Accessibility nil") ErrBaseURLEmpty = errors.New("Base URL empty") ErrSegmentBaseOnDemandProfileOnly = errors.New("Segment Base can only be used with On-Demand Profile") ErrSegmentBaseNil = errors.New("Segment Base nil") @@ -117,46 +125,48 @@ type CommonAttributesAndElements struct { type AdaptationSet struct { CommonAttributesAndElements - XMLName xml.Name `xml:"AdaptationSet"` - ID *string `xml:"id,attr"` - SegmentAlignment *bool `xml:"segmentAlignment,attr"` - Lang *string `xml:"lang,attr"` - Group *string `xml:"group,attr"` - PAR *string `xml:"par,attr"` - MinBandwidth *string `xml:"minBandwidth,attr"` - MaxBandwidth *string `xml:"maxBandwidth,attr"` - MinWidth *string `xml:"minWidth,attr"` - MaxWidth *string `xml:"maxWidth,attr"` - ContentType *string `xml:"contentType,attr"` - ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here - Roles []*Role `xml:"Role,omitempty"` - SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` - SegmentList *SegmentList `xml:"SegmentList,omitempty"` - SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only - Representations []*Representation `xml:"Representation,omitempty"` + XMLName xml.Name `xml:"AdaptationSet"` + ID *string `xml:"id,attr"` + SegmentAlignment *bool `xml:"segmentAlignment,attr"` + Lang *string `xml:"lang,attr"` + Group *string `xml:"group,attr"` + PAR *string `xml:"par,attr"` + MinBandwidth *string `xml:"minBandwidth,attr"` + MaxBandwidth *string `xml:"maxBandwidth,attr"` + MinWidth *string `xml:"minWidth,attr"` + MaxWidth *string `xml:"maxWidth,attr"` + ContentType *string `xml:"contentType,attr"` + ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here + Roles []*Role `xml:"Role,omitempty"` + SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` + SegmentList *SegmentList `xml:"SegmentList,omitempty"` + SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only + Representations []*Representation `xml:"Representation,omitempty"` + AccessibilityElems []*Accessibility `xml:"Accessibility,omitempty"` } func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { adaptationSet := struct { CommonAttributesAndElements - XMLName xml.Name `xml:"AdaptationSet"` - ID *string `xml:"id,attr"` - SegmentAlignment *bool `xml:"segmentAlignment,attr"` - Lang *string `xml:"lang,attr"` - Group *string `xml:"group,attr"` - PAR *string `xml:"par,attr"` - MinBandwidth *string `xml:"minBandwidth,attr"` - MaxBandwidth *string `xml:"maxBandwidth,attr"` - MinWidth *string `xml:"minWidth,attr"` - MaxWidth *string `xml:"maxWidth,attr"` - ContentType *string `xml:"contentType,attr"` - ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here - Roles []*Role `xml:"Role,omitempty"` - SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` - SegmentList *SegmentList `xml:"SegmentList,omitempty"` - SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only - Representations []*Representation `xml:"Representation,omitempty"` + XMLName xml.Name `xml:"AdaptationSet"` + ID *string `xml:"id,attr"` + SegmentAlignment *bool `xml:"segmentAlignment,attr"` + Lang *string `xml:"lang,attr"` + Group *string `xml:"group,attr"` + PAR *string `xml:"par,attr"` + MinBandwidth *string `xml:"minBandwidth,attr"` + MaxBandwidth *string `xml:"maxBandwidth,attr"` + MinWidth *string `xml:"minWidth,attr"` + MaxWidth *string `xml:"maxWidth,attr"` + ContentType *string `xml:"contentType,attr"` + ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here + Roles []*Role `xml:"Role,omitempty"` + SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"` + SegmentList *SegmentList `xml:"SegmentList,omitempty"` + SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only + Representations []*Representation `xml:"Representation,omitempty"` + AccessibilityElems []*Accessibility `xml:"Accessibility,omitempty"` }{} var ( @@ -247,7 +257,7 @@ func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er return err } default: - return errors.New("Unrecognized element in AdaptationSet: " + tt.Name.Local) + return fmt.Errorf("unrecognized element in AdaptationSet %q", tt.Name.Local) } case xml.EndElement: if tt == start.End() { @@ -442,8 +452,9 @@ type Representation struct { } type Accessibility struct { - SchemeIdUri *string `xml:"schemeIdUri,omitempty"` - Value *int64 `xml:"value,omitempty"` + AdaptationSet *AdaptationSet `xml:"-"` + SchemeIdUri *string `xml:"schemeIdUri,attr,omitempty"` + Value *string `xml:"value,attr,omitempty"` } type AudioChannelConfiguration struct { @@ -1030,6 +1041,16 @@ func (as *AdaptationSet) addRepresentation(r *Representation) error { return nil } +// Internal helper method for adding an Accessibility element to an AdaptationSet. +func (as *AdaptationSet) addAccessibility(a *Accessibility) error { + if a == nil { + return ErrAccessibilityNil + } + a.AdaptationSet = as + as.AccessibilityElems = append(as.AccessibilityElems, a) + return nil +} + // Adds a new Role to an AdaptationSet // schemeIdUri - Scheme ID URI string (i.e. urn:mpeg:dash:role:2011) // value - Value for this role, (i.e. caption, subtitle, main, alternate, supplementary, commentary, dub) @@ -1043,6 +1064,23 @@ func (as *AdaptationSet) AddNewRole(schemeIDURI string, value string) (*Role, er return r, nil } +// AddNewAccessibilityElement adds a new accessibility element to an adaptation set +// schemeIdUri - Scheme ID URI for the Accessibility element (i.e. urn:tva:metadata:cs:AudioPurposeCS:2007) +// value - specified value based on scheme +func (as *AdaptationSet) AddNewAccessibilityElement(scheme AccessibilityElementScheme, val string) (*Accessibility, error) { + accessibility := &Accessibility{ + SchemeIdUri: Strptr((string)(scheme)), + Value: Strptr(val), + } + + err := as.addAccessibility(accessibility) + if err != nil { + return nil, err + } + + return accessibility, nil +} + // Sets the BaseURL for a Representation. // baseURL - Base URL as a string (i.e. 800k/output-audio-und.mp4) func (r *Representation) SetNewBaseURL(baseURL string) error { diff --git a/mpd/mpd_read_write_test.go b/mpd/mpd_read_write_test.go index c0757b6..3df7fc3 100644 --- a/mpd/mpd_read_write_test.go +++ b/mpd/mpd_read_write_test.go @@ -180,6 +180,35 @@ func TestExampleAddNewPeriod(t *testing.T) { testfixtures.CompareFixture(t, "fixtures/newperiod.mpd", xmlStr) } +func TestAddNewAccessibilityElementWriteToString(t *testing.T) { + m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) + audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, + VALID_START_WITH_SAP, VALID_LANG) + if err != nil { + t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err) + return + } + + _, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") + if err != nil { + t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err) + return + } + + xmlStr, err := m.WriteToString() + require.NoError(t, err) + expectedXML := ` + + + + + + + +` + require.EqualString(t, expectedXML, xmlStr) +} + func LiveProfile() *MPD { m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME, AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME)) @@ -194,6 +223,7 @@ func LiveProfile() *MPD { _, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000) _, _ = audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800") + _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) @@ -250,6 +280,7 @@ func LiveProfileDynamic() *MPD { _, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000) _, _ = audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800") + _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) @@ -305,6 +336,7 @@ func HbbTVProfile() *MPD { _, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000) r, _ := audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800") _, _ = r.AddNewAudioChannelConfiguration(AUDIO_CHANNEL_CONFIGURATION_MPEG_DASH, "2") + _, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP) @@ -353,6 +385,7 @@ func OnDemandProfile() *MPD { _, _ = 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-und") _ = audioRep.SetNewBaseURL("800k/output-audio-und.mp4") diff --git a/mpd/mpd_test.go b/mpd/mpd_test.go index 14f1efb..388d9b9 100644 --- a/mpd/mpd_test.go +++ b/mpd/mpd_test.go @@ -457,3 +457,29 @@ func getValidWVHeaderBytes() []byte { } return wvHeader } + +func TestAddNewAccessibilityElement(t *testing.T) { + m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME) + audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT, + VALID_START_WITH_SAP, VALID_LANG) + if err != nil { + t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err) + return + } + + _, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1") + if err != nil { + t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err) + return + } + + if g, e := len(audioAS.AccessibilityElems), 1; g != e { + t.Errorf("AddNewAccessibilityElement() wrong number of accessibility elements, got: %d, expected: %d", + g, e) + return + } + + elem := audioAS.AccessibilityElems[0] + + require.EqualStringPtr(t, Strptr((string)(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO)), elem.SchemeIdUri) +} From 8356b3b1db96837be7c6900a6ede68b01ae58ca8 Mon Sep 17 00:00:00 2001 From: Thomas Symborski Date: Tue, 7 Jan 2020 14:49:42 -0500 Subject: [PATCH 4/4] testing: assert accessibility value in test --- mpd/mpd_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mpd/mpd_test.go b/mpd/mpd_test.go index 388d9b9..6358c0f 100644 --- a/mpd/mpd_test.go +++ b/mpd/mpd_test.go @@ -482,4 +482,5 @@ func TestAddNewAccessibilityElement(t *testing.T) { elem := audioAS.AccessibilityElems[0] require.EqualStringPtr(t, Strptr((string)(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO)), elem.SchemeIdUri) + require.EqualStringPtr(t, Strptr("1"), elem.Value) }