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 022cb45..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 (
@@ -240,8 +250,14 @@ 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")
+ return fmt.Errorf("unrecognized element in AdaptationSet %q", tt.Name.Local)
}
case xml.EndElement:
if tt == start.End() {
@@ -435,6 +451,12 @@ type Representation struct {
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"`
}
+type Accessibility struct {
+ AdaptationSet *AdaptationSet `xml:"-"`
+ SchemeIdUri *string `xml:"schemeIdUri,attr,omitempty"`
+ Value *string `xml:"value,attr,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
@@ -1019,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)
@@ -1032,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..6358c0f 100644
--- a/mpd/mpd_test.go
+++ b/mpd/mpd_test.go
@@ -457,3 +457,30 @@ 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)
+ require.EqualStringPtr(t, Strptr("1"), elem.Value)
+}