stash/vendor/github.com/zencoder/go-dash/v3/mpd/mpd.go
2023-03-07 12:57:27 +11:00

1168 lines
40 KiB
Go

package mpd
import (
"encoding/base64"
"encoding/hex"
"encoding/xml"
"errors"
"strings"
"time"
. "github.com/zencoder/go-dash/v3/helpers/ptrs"
)
// Type definition for DASH profiles
type DashProfile string
// Constants for supported DASH profiles
const (
// Live Profile
DASH_PROFILE_LIVE DashProfile = "urn:mpeg:dash:profile:isoff-live:2011"
// On Demand Profile
DASH_PROFILE_ONDEMAND DashProfile = "urn:mpeg:dash:profile:isoff-on-demand:2011"
// HbbTV Profile
DASH_PROFILE_HBBTV_1_5_LIVE DashProfile = "urn:hbbtv:dash:profile:isoff-live:2012,urn:mpeg:dash:profile:isoff-live:2011"
)
type AudioChannelConfigurationScheme string
const (
// Scheme for non-Dolby Audio
AUDIO_CHANNEL_CONFIGURATION_MPEG_DASH AudioChannelConfigurationScheme = "urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
// Scheme for Dolby Audio
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"
DASH_MIME_TYPE_AUDIO_MP4 string = "audio/mp4"
DASH_MIME_TYPE_SUBTITLE_VTT string = "text/vtt"
DASH_MIME_TYPE_SUBTITLE_TTML string = "application/ttaf+xml"
DASH_MIME_TYPE_SUBTITLE_SRT string = "application/x-subrip"
DASH_MIME_TYPE_SUBTITLE_DFXP string = "application/ttaf+xml"
DASH_MIME_TYPE_IMAGE_JPEG string = "image/jpeg"
DASH_CONTENT_TYPE_IMAGE string = "image"
)
// Known error variables
var (
ErrNoDASHProfileSet error = errors.New("No DASH profile set")
ErrAdaptationSetNil = errors.New("Adaptation Set nil")
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")
ErrAudioChannelConfigurationNil = errors.New("Audio Channel Configuration nil")
ErrInvalidDefaultKID = errors.New("Invalid Default KID string, should be 32 characters")
ErrPROEmpty = errors.New("PlayReady PRO empty")
ErrContentProtectionNil = errors.New("Content Protection nil")
)
type MPD struct {
XMLNs *string `xml:"xmlns,attr"`
Profiles *string `xml:"profiles,attr"`
Type *string `xml:"type,attr"`
MediaPresentationDuration *string `xml:"mediaPresentationDuration,attr"`
MinBufferTime *string `xml:"minBufferTime,attr"`
AvailabilityStartTime *string `xml:"availabilityStartTime,attr,omitempty"`
MinimumUpdatePeriod *string `xml:"minimumUpdatePeriod,attr"`
PublishTime *string `xml:"publishTime,attr"`
TimeShiftBufferDepth *string `xml:"timeShiftBufferDepth,attr"`
SuggestedPresentationDelay *Duration `xml:"suggestedPresentationDelay,attr,omitempty"`
BaseURL string `xml:"BaseURL,omitempty"`
Location string `xml:"Location,omitempty"`
period *Period
Periods []*Period `xml:"Period,omitempty"`
UTCTiming *DescriptorType `xml:"UTCTiming,omitempty"`
}
type Period struct {
ID string `xml:"id,attr,omitempty"`
Duration Duration `xml:"duration,attr,omitempty"`
Start *Duration `xml:"start,attr,omitempty"`
BaseURL string `xml:"BaseURL,omitempty"`
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"`
AdaptationSets []*AdaptationSet `xml:"AdaptationSet,omitempty"`
EventStreams []EventStream `xml:"EventStream,omitempty"`
}
type DescriptorType struct {
SchemeIDURI *string `xml:"schemeIdUri,attr"`
Value *string `xml:"value,attr"`
ID *string `xml:"id,attr"`
}
// ISO 23009-1-2014 5.3.7
type CommonAttributesAndElements struct {
Profiles *string `xml:"profiles,attr"`
Width *string `xml:"width,attr"`
Height *string `xml:"height,attr"`
Sar *string `xml:"sar,attr"`
FrameRate *string `xml:"frameRate,attr"`
AudioSamplingRate *string `xml:"audioSamplingRate,attr"`
MimeType *string `xml:"mimeType,attr"`
SegmentProfiles *string `xml:"segmentProfiles,attr"`
Codecs *string `xml:"codecs,attr"`
MaximumSAPPeriod *string `xml:"maximumSAPPeriod,attr"`
StartWithSAP *int64 `xml:"startWithSAP,attr"`
MaxPlayoutRate *string `xml:"maxPlayoutRate,attr"`
ScanType *string `xml:"scanType,attr"`
FramePacking []DescriptorType `xml:"FramePacking,omitempty"`
AudioChannelConfiguration []DescriptorType `xml:"AudioChannelConfiguration,omitempty"`
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"`
EssentialProperty []DescriptorType `xml:"EssentialProperty,omitempty"`
SupplementalProperty []DescriptorType `xml:"SupplementalProperty,omitempty"`
InbandEventStream *DescriptorType `xml:"inbandEventStream,attr"`
}
type contentProtections []ContentProtectioner
func (as *contentProtections) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var scheme string
for _, a := range start.Attr {
if a.Name.Local == "schemeIdUri" {
scheme = a.Value
break
}
}
var target ContentProtectioner
switch scheme {
case CONTENT_PROTECTION_ROOT_SCHEME_ID_URI:
target = &CENCContentProtection{}
case CONTENT_PROTECTION_PLAYREADY_SCHEME_ID:
target = &PlayreadyContentProtection{}
case CONTENT_PROTECTION_WIDEVINE_SCHEME_ID:
target = &WidevineContentProtection{}
default:
target = &ContentProtection{}
}
if err := d.DecodeElement(target, &start); err != nil {
return err
}
*as = append(*as, target)
return nil
}
// wrappedAdaptationSet provides the default xml unmarshal
// to take care of the majority of our unmarshalling
type wrappedAdaptationSet AdaptationSet
// dtoAdaptationSet parses the items out of AdaptationSet
// that give us trouble:
// * Content Protection interface
type dtoAdaptationSet struct {
wrappedAdaptationSet
ContentProtection contentProtections `xml:"ContentProtection,omitempty"`
}
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"`
MinHeight *string `xml:"minHeight,attr"`
MaxHeight *string `xml:"maxHeight,attr"`
ContentType *string `xml:"contentType,attr"`
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 {
var n dtoAdaptationSet
if err := d.DecodeElement(&n, &start); err != nil {
return err
}
*as = AdaptationSet(n.wrappedAdaptationSet)
as.ContentProtection = make([]ContentProtectioner, len(n.ContentProtection))
for i := range n.ContentProtection {
as.ContentProtection[i] = n.ContentProtection[i]
}
return nil
}
// Constants for DRM / ContentProtection
const (
CONTENT_PROTECTION_ROOT_SCHEME_ID_URI = "urn:mpeg:dash:mp4protection:2011"
CONTENT_PROTECTION_ROOT_VALUE = "cenc"
CENC_XMLNS = "urn:mpeg:cenc:2013"
CONTENT_PROTECTION_WIDEVINE_SCHEME_ID = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
CONTENT_PROTECTION_WIDEVINE_SCHEME_HEX = "edef8ba979d64acea3c827dcd51d21ed"
CONTENT_PROTECTION_PLAYREADY_SCHEME_ID = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"
CONTENT_PROTECTION_PLAYREADY_SCHEME_HEX = "9a04f07998404286ab92e65be0885f95"
CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID = "urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95"
CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_HEX = "79f0049a40988642ab92e65be0885f95"
CONTENT_PROTECTION_PLAYREADY_XMLNS = "urn:microsoft:playready"
)
type ContentProtectioner interface {
ContentProtected()
}
type ContentProtection struct {
AdaptationSet *AdaptationSet `xml:"-"`
XMLName xml.Name `xml:"ContentProtection"`
SchemeIDURI *string `xml:"schemeIdUri,attr"` // Default: urn:mpeg:dash:mp4protection:2011
XMLNS *string `xml:"cenc,attr"` // Default: urn:mpeg:cenc:2013
Attrs []*xml.Attr `xml:",any,attr"`
}
type CENCContentProtection struct {
ContentProtection
DefaultKID *string `xml:"default_KID,attr"`
Value *string `xml:"value,attr"` // Default: cenc
}
type PlayreadyContentProtection struct {
ContentProtection
PlayreadyXMLNS *string `xml:"mspr,attr,omitempty"`
PRO *string `xml:"pro,omitempty"`
PSSH *string `xml:"pssh,omitempty"`
}
type WidevineContentProtection struct {
ContentProtection
PSSH *string `xml:"pssh,omitempty"`
}
type ContentProtectionMarshal struct {
AdaptationSet *AdaptationSet `xml:"-"`
XMLName xml.Name `xml:"ContentProtection"`
SchemeIDURI *string `xml:"schemeIdUri,attr"` // Default: urn:mpeg:dash:mp4protection:2011
XMLNS *string `xml:"xmlns:cenc,attr"` // Default: urn:mpeg:cenc:2013
Attrs []*xml.Attr `xml:",any,attr"`
}
type CENCContentProtectionMarshal struct {
ContentProtectionMarshal
DefaultKID *string `xml:"cenc:default_KID,attr"`
Value *string `xml:"value,attr"` // Default: cenc
}
type PlayreadyContentProtectionMarshal struct {
ContentProtectionMarshal
PlayreadyXMLNS *string `xml:"xmlns:mspr,attr,omitempty"`
PRO *string `xml:"mspr:pro,omitempty"`
PSSH *string `xml:"cenc:pssh,omitempty"`
}
type WidevineContentProtectionMarshal struct {
ContentProtectionMarshal
PSSH *string `xml:"cenc:pssh,omitempty"`
}
func (s ContentProtection) ContentProtected() {}
func (s ContentProtection) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
err := e.Encode(&ContentProtectionMarshal{
s.AdaptationSet,
s.XMLName,
s.SchemeIDURI,
s.XMLNS,
s.Attrs,
})
if err != nil {
return err
}
return nil
}
func (s CENCContentProtection) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
err := e.Encode(&CENCContentProtectionMarshal{
ContentProtectionMarshal{
s.AdaptationSet,
s.XMLName,
s.SchemeIDURI,
s.XMLNS,
s.Attrs,
},
s.DefaultKID,
s.Value,
})
if err != nil {
return err
}
return nil
}
func (s PlayreadyContentProtection) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
err := e.Encode(&PlayreadyContentProtectionMarshal{
ContentProtectionMarshal{
s.AdaptationSet,
s.XMLName,
s.SchemeIDURI,
s.XMLNS,
s.Attrs,
},
s.PlayreadyXMLNS,
s.PRO,
s.PSSH,
})
if err != nil {
return err
}
return nil
}
func (s WidevineContentProtection) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
err := e.Encode(&WidevineContentProtectionMarshal{
ContentProtectionMarshal{
s.AdaptationSet,
s.XMLName,
s.SchemeIDURI,
s.XMLNS,
s.Attrs,
},
s.PSSH,
})
if err != nil {
return err
}
return nil
}
type Role struct {
AdaptationSet *AdaptationSet `xml:"-"`
SchemeIDURI *string `xml:"schemeIdUri,attr"`
Value *string `xml:"value,attr"`
}
// Segment Template is for Live Profile Only
type SegmentTemplate struct {
AdaptationSet *AdaptationSet `xml:"-"`
SegmentTimeline *SegmentTimeline `xml:"SegmentTimeline,omitempty"`
PresentationTimeOffset *uint64 `xml:"presentationTimeOffset,attr,omitempty"`
Duration *int64 `xml:"duration,attr"`
Initialization *string `xml:"initialization,attr"`
Media *string `xml:"media,attr"`
StartNumber *int64 `xml:"startNumber,attr"`
Timescale *int64 `xml:"timescale,attr"`
}
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
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
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
Value *string `xml:"value,attr"`
}
// Creates a new static MPD object.
// profile - DASH Profile (Live or OnDemand).
// mediaPresentationDuration - Media Presentation Duration (i.e. PT6M16S).
// minBufferTime - Min Buffer Time (i.e. PT1.97S).
// attributes - Other attributes (optional).
func NewMPD(profile DashProfile, mediaPresentationDuration, minBufferTime string, attributes ...AttrMPD) *MPD {
period := &Period{}
mpd := &MPD{
XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
Profiles: Strptr((string)(profile)),
Type: Strptr("static"),
MediaPresentationDuration: Strptr(mediaPresentationDuration),
MinBufferTime: Strptr(minBufferTime),
period: period,
Periods: []*Period{period},
}
for i := range attributes {
switch attr := attributes[i].(type) {
case *attrAvailabilityStartTime:
mpd.AvailabilityStartTime = attr.GetStrptr()
}
}
return mpd
}
// Creates a new dynamic MPD object.
// profile - DASH Profile (Live or OnDemand).
// availabilityStartTime - anchor for the computation of the earliest availability time (in UTC).
// minBufferTime - Min Buffer Time (i.e. PT1.97S).
// attributes - Other attributes (optional).
func NewDynamicMPD(profile DashProfile, availabilityStartTime, minBufferTime string, attributes ...AttrMPD) *MPD {
period := &Period{}
mpd := &MPD{
XMLNs: Strptr("urn:mpeg:dash:schema:mpd:2011"),
Profiles: Strptr((string)(profile)),
Type: Strptr("dynamic"),
AvailabilityStartTime: Strptr(availabilityStartTime),
MinBufferTime: Strptr(minBufferTime),
period: period,
Periods: []*Period{period},
UTCTiming: &DescriptorType{},
}
for i := range attributes {
switch attr := attributes[i].(type) {
case *attrMinimumUpdatePeriod:
mpd.MinimumUpdatePeriod = attr.GetStrptr()
case *attrMediaPresentationDuration:
mpd.MediaPresentationDuration = attr.GetStrptr()
case *attrPublishTime:
mpd.PublishTime = attr.GetStrptr()
}
}
return mpd
}
// AddNewPeriod creates a new Period and make it the currently active one.
func (m *MPD) AddNewPeriod() *Period {
if m.period != nil && m.period.ID == "" && m.period.AdaptationSets == nil {
return m.GetCurrentPeriod()
}
period := &Period{}
m.Periods = append(m.Periods, period)
m.period = period
return period
}
// GetCurrentPeriod returns the current Period.
func (m *MPD) GetCurrentPeriod() *Period {
return m.period
}
func (period *Period) SetDuration(d time.Duration) {
period.Duration = Duration(d)
}
// Create a new Adaptation Set for thumbnails.
// mimeType - e.g. (image/jpeg)
func (m *MPD) AddNewAdaptationSetThumbnails(mimeType string) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetThumbnails(mimeType)
}
func (period *Period) AddNewAdaptationSetThumbnails(mimeType string) (*AdaptationSet, error) {
as := &AdaptationSet{
ContentType: Strptr(DASH_CONTENT_TYPE_IMAGE),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
func (m *MPD) AddNewAdaptationSetThumbnailsWithID(id, mimeType string) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetThumbnailsWithID(id, mimeType)
}
func (period *Period) AddNewAdaptationSetThumbnailsWithID(id, mimeType string) (*AdaptationSet, error) {
as := &AdaptationSet{
ID: Strptr(id),
ContentType: Strptr(DASH_CONTENT_TYPE_IMAGE),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
// Create a new Adaptation Set for Audio Assets.
// mimeType - MIME Type (i.e. audio/mp4).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
// lang - Language (i.e. en).
func (m *MPD) AddNewAdaptationSetAudio(mimeType string, segmentAlignment bool, startWithSAP int64, lang string) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetAudio(mimeType, segmentAlignment, startWithSAP, lang)
}
// Create a new Adaptation Set for Audio Assets.
// mimeType - MIME Type (i.e. audio/mp4).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
// lang - Language (i.e. en).
func (m *MPD) AddNewAdaptationSetAudioWithID(id string, mimeType string, segmentAlignment bool, startWithSAP int64, lang string) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetAudioWithID(id, mimeType, segmentAlignment, startWithSAP, lang)
}
// Create a new Adaptation Set for Audio Assets.
// mimeType - MIME Type (i.e. audio/mp4).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
// lang - Language (i.e. en).
func (period *Period) AddNewAdaptationSetAudio(mimeType string, segmentAlignment bool, startWithSAP int64, lang string) (*AdaptationSet, error) {
as := &AdaptationSet{
SegmentAlignment: Boolptr(segmentAlignment),
Lang: Strptr(lang),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
StartWithSAP: Int64ptr(startWithSAP),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
// Create a new Adaptation Set for Audio Assets.
// mimeType - MIME Type (i.e. audio/mp4).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
// lang - Language (i.e. en).
func (period *Period) AddNewAdaptationSetAudioWithID(id string, mimeType string, segmentAlignment bool, startWithSAP int64, lang string) (*AdaptationSet, error) {
as := &AdaptationSet{
ID: Strptr(id),
SegmentAlignment: Boolptr(segmentAlignment),
Lang: Strptr(lang),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
StartWithSAP: Int64ptr(startWithSAP),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
// Create a new Adaptation Set for Video Assets.
// mimeType - MIME Type (i.e. video/mp4).
// scanType - Scan Type (i.e.progressive).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
func (m *MPD) AddNewAdaptationSetVideo(mimeType string, scanType string, segmentAlignment bool, startWithSAP int64) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetVideo(mimeType, scanType, segmentAlignment, startWithSAP)
}
// Create a new Adaptation Set for Video Assets.
// mimeType - MIME Type (i.e. video/mp4).
// scanType - Scan Type (i.e.progressive).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
func (m *MPD) AddNewAdaptationSetVideoWithID(id string, mimeType string, scanType string, segmentAlignment bool, startWithSAP int64) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetVideoWithID(id, mimeType, scanType, segmentAlignment, startWithSAP)
}
// Create a new Adaptation Set for Video Assets.
// mimeType - MIME Type (i.e. video/mp4).
// scanType - Scan Type (i.e.progressive).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
func (period *Period) AddNewAdaptationSetVideo(mimeType string, scanType string, segmentAlignment bool, startWithSAP int64) (*AdaptationSet, error) {
as := &AdaptationSet{
SegmentAlignment: Boolptr(segmentAlignment),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
StartWithSAP: Int64ptr(startWithSAP),
ScanType: Strptr(scanType),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
// Create a new Adaptation Set for Video Assets.
// mimeType - MIME Type (i.e. video/mp4).
// scanType - Scan Type (i.e.progressive).
// segmentAlignment - Segment Alignment(i.e. true).
// startWithSAP - Starts With SAP (i.e. 1).
func (period *Period) AddNewAdaptationSetVideoWithID(id string, mimeType string, scanType string, segmentAlignment bool, startWithSAP int64) (*AdaptationSet, error) {
as := &AdaptationSet{
SegmentAlignment: Boolptr(segmentAlignment),
ID: Strptr(id),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
StartWithSAP: Int64ptr(startWithSAP),
ScanType: Strptr(scanType),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
// Create a new Adaptation Set for Subtitle Assets.
// mimeType - MIME Type (i.e. text/vtt).
// lang - Language (i.e. en).
func (m *MPD) AddNewAdaptationSetSubtitle(mimeType string, lang string) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetSubtitle(mimeType, lang)
}
// Create a new Adaptation Set for Subtitle Assets.
// mimeType - MIME Type (i.e. text/vtt).
// lang - Language (i.e. en).
func (m *MPD) AddNewAdaptationSetSubtitleWithID(id string, mimeType string, lang string) (*AdaptationSet, error) {
return m.period.AddNewAdaptationSetSubtitleWithID(id, mimeType, lang)
}
// Create a new Adaptation Set for Subtitle Assets.
// mimeType - MIME Type (i.e. text/vtt).
// lang - Language (i.e. en).
func (period *Period) AddNewAdaptationSetSubtitle(mimeType string, lang string) (*AdaptationSet, error) {
as := &AdaptationSet{
Lang: Strptr(lang),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
// Create a new Adaptation Set for Subtitle Assets.
// mimeType - MIME Type (i.e. text/vtt).
// lang - Language (i.e. en).
func (period *Period) AddNewAdaptationSetSubtitleWithID(id string, mimeType string, lang string) (*AdaptationSet, error) {
as := &AdaptationSet{
ID: Strptr(id),
Lang: Strptr(lang),
CommonAttributesAndElements: CommonAttributesAndElements{
MimeType: Strptr(mimeType),
},
}
err := period.addAdaptationSet(as)
if err != nil {
return nil, err
}
return as, nil
}
// Internal helper method for adding a AdapatationSet.
func (period *Period) addAdaptationSet(as *AdaptationSet) error {
if as == nil {
return ErrAdaptationSetNil
}
period.AdaptationSets = append(period.AdaptationSets, as)
return nil
}
// Adds a ContentProtection tag at the root level of an AdaptationSet.
// This ContentProtection tag does not include signaling for any particular DRM scheme.
// defaultKIDHex - Default Key ID as a Hex String.
//
// NOTE: this is only here for Legacy purposes. This will create an invalid UUID.
func (as *AdaptationSet) AddNewContentProtectionRootLegacyUUID(defaultKIDHex string) (*CENCContentProtection, error) {
if len(defaultKIDHex) != 32 || defaultKIDHex == "" {
return nil, ErrInvalidDefaultKID
}
// Convert the KID into the correct format
defaultKID := strings.ToLower(defaultKIDHex[0:8] + "-" + defaultKIDHex[8:12] + "-" + defaultKIDHex[12:16] + "-" + defaultKIDHex[16:32])
cp := &CENCContentProtection{
DefaultKID: Strptr(defaultKID),
Value: Strptr(CONTENT_PROTECTION_ROOT_VALUE),
}
cp.SchemeIDURI = Strptr(CONTENT_PROTECTION_ROOT_SCHEME_ID_URI)
cp.XMLNS = Strptr(CENC_XMLNS)
err := as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
// Adds a ContentProtection tag at the root level of an AdaptationSet.
// This ContentProtection tag does not include signaling for any particular DRM scheme.
// defaultKIDHex - Default Key ID as a Hex String.
func (as *AdaptationSet) AddNewContentProtectionRoot(defaultKIDHex string) (*CENCContentProtection, error) {
if len(defaultKIDHex) != 32 || defaultKIDHex == "" {
return nil, ErrInvalidDefaultKID
}
// Convert the KID into the correct format
defaultKID := strings.ToLower(defaultKIDHex[0:8] + "-" + defaultKIDHex[8:12] + "-" + defaultKIDHex[12:16] + "-" + defaultKIDHex[16:20] + "-" + defaultKIDHex[20:32])
cp := &CENCContentProtection{
DefaultKID: Strptr(defaultKID),
Value: Strptr(CONTENT_PROTECTION_ROOT_VALUE),
}
cp.SchemeIDURI = Strptr(CONTENT_PROTECTION_ROOT_SCHEME_ID_URI)
cp.XMLNS = Strptr(CENC_XMLNS)
err := as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
// AddNewContentProtectionSchemeWidevine adds a new content protection scheme for Widevine DRM to the adaptation set. With
// a <cenc:pssh> element that contains a Base64 encoded PSSH box
// wvHeader - binary representation of Widevine Header
// !!! Note: this function will accept any byte slice as a wvHeader value !!!
func (as *AdaptationSet) AddNewContentProtectionSchemeWidevineWithPSSH(wvHeader []byte) (*WidevineContentProtection, error) {
cp, err := NewWidevineContentProtection(wvHeader)
if err != nil {
return nil, err
}
err = as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
// AddNewContentProtectionSchemeWidevine adds a new content protection scheme for Widevine DRM to the adaptation set.
func (as *AdaptationSet) AddNewContentProtectionSchemeWidevine() (*WidevineContentProtection, error) {
cp, err := NewWidevineContentProtection(nil)
if err != nil {
return nil, err
}
err = as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
func NewWidevineContentProtection(wvHeader []byte) (*WidevineContentProtection, error) {
cp := &WidevineContentProtection{}
cp.SchemeIDURI = Strptr(CONTENT_PROTECTION_WIDEVINE_SCHEME_ID)
if len(wvHeader) > 0 {
cp.XMLNS = Strptr(CENC_XMLNS)
wvSystemID, err := hex.DecodeString(CONTENT_PROTECTION_WIDEVINE_SCHEME_HEX)
if err != nil {
panic(err.Error())
}
psshBox, err := MakePSSHBox(wvSystemID, wvHeader)
if err != nil {
return nil, err
}
psshB64 := base64.StdEncoding.EncodeToString(psshBox)
cp.PSSH = &psshB64
}
return cp, nil
}
// AddNewContentProtectionSchemePlayready adds a new content protection scheme for PlayReady DRM.
// pro - PlayReady Object Header, as a Base64 encoded string.
func (as *AdaptationSet) AddNewContentProtectionSchemePlayready(pro string) (*PlayreadyContentProtection, error) {
cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_ID)
if err != nil {
return nil, err
}
err = as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
// AddNewContentProtectionSchemePlayreadyV10 adds a new content protection scheme for PlayReady v1.0 DRM.
// pro - PlayReady Object Header, as a Base64 encoded string.
func (as *AdaptationSet) AddNewContentProtectionSchemePlayreadyV10(pro string) (*PlayreadyContentProtection, error) {
cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID)
if err != nil {
return nil, err
}
err = as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
func newPlayreadyContentProtection(pro string, schemeIDURI string) (*PlayreadyContentProtection, error) {
if pro == "" {
return nil, ErrPROEmpty
}
cp := &PlayreadyContentProtection{
PlayreadyXMLNS: Strptr(CONTENT_PROTECTION_PLAYREADY_XMLNS),
PRO: Strptr(pro),
}
cp.SchemeIDURI = Strptr(schemeIDURI)
return cp, nil
}
// AddNewContentProtectionSchemePlayreadyWithPSSH adds a new content protection scheme for PlayReady DRM. The scheme
// will include both ms:pro and cenc:pssh subelements
// pro - PlayReady Object Header, as a Base64 encoded string.
func (as *AdaptationSet) AddNewContentProtectionSchemePlayreadyWithPSSH(pro string) (*PlayreadyContentProtection, error) {
cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_ID)
if err != nil {
return nil, err
}
cp.XMLNS = Strptr(CENC_XMLNS)
prSystemID, err := hex.DecodeString(CONTENT_PROTECTION_PLAYREADY_SCHEME_HEX)
if err != nil {
panic(err.Error())
}
proBin, err := base64.StdEncoding.DecodeString(pro)
if err != nil {
return nil, err
}
psshBox, err := MakePSSHBox(prSystemID, proBin)
if err != nil {
return nil, err
}
cp.PSSH = Strptr(base64.StdEncoding.EncodeToString(psshBox))
err = as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
// AddNewContentProtectionSchemePlayreadyV10WithPSSH adds a new content protection scheme for PlayReady v1.0 DRM. The scheme
// will include both ms:pro and cenc:pssh subelements
// pro - PlayReady Object Header, as a Base64 encoded string.
func (as *AdaptationSet) AddNewContentProtectionSchemePlayreadyV10WithPSSH(pro string) (*PlayreadyContentProtection, error) {
cp, err := newPlayreadyContentProtection(pro, CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_ID)
if err != nil {
return nil, err
}
cp.XMLNS = Strptr(CENC_XMLNS)
prSystemID, err := hex.DecodeString(CONTENT_PROTECTION_PLAYREADY_SCHEME_V10_HEX)
if err != nil {
panic(err.Error())
}
proBin, err := base64.StdEncoding.DecodeString(pro)
if err != nil {
return nil, err
}
psshBox, err := MakePSSHBox(prSystemID, proBin)
if err != nil {
return nil, err
}
cp.PSSH = Strptr(base64.StdEncoding.EncodeToString(psshBox))
err = as.AddContentProtection(cp)
if err != nil {
return nil, err
}
return cp, nil
}
// Internal helper method for adding a ContentProtection to an AdaptationSet.
func (as *AdaptationSet) AddContentProtection(cp ContentProtectioner) error {
if cp == nil {
return ErrContentProtectionNil
}
as.ContentProtection = append(as.ContentProtection, cp)
return nil
}
// Sets up a new SegmentTemplate for an AdaptationSet.
// duration - relative to timescale (i.e. 2000).
// init - template string for init segment (i.e. $RepresentationID$/audio/en/init.mp4).
// media - template string for media segments.
// startNumber - the number to start segments from ($Number$) (i.e. 0).
// timescale - sets the timescale for duration (i.e. 1000, represents milliseconds).
func (as *AdaptationSet) SetNewSegmentTemplate(duration int64, init string, media string, startNumber int64, timescale int64) (*SegmentTemplate, error) {
st := &SegmentTemplate{
Duration: Int64ptr(duration),
Initialization: Strptr(init),
Media: Strptr(media),
StartNumber: Int64ptr(startNumber),
Timescale: Int64ptr(timescale),
}
err := as.setSegmentTemplate(st)
if err != nil {
return nil, err
}
return st, nil
}
// Internal helper method for setting the Segment Template on an AdaptationSet.
func (as *AdaptationSet) setSegmentTemplate(st *SegmentTemplate) error {
if st == nil {
return ErrSegmentTemplateNil
}
st.AdaptationSet = as
as.SegmentTemplate = st
return nil
}
// Adds a new SegmentTemplate to a thumbnail AdaptationSet
// duration - relative to timescale (i.e. 2000).
// media - template string for media segments.
// startNumber - the number to start segments from ($Number$) (i.e. 0).
// timescale - sets the timescale for duration (i.e. 1000, represents milliseconds).
func (as *AdaptationSet) SetNewSegmentTemplateThumbnails(duration int64, media string, startNumber int64, timescale int64) (*SegmentTemplate, error) {
st := &SegmentTemplate{
Duration: Int64ptr(duration),
Media: Strptr(media),
StartNumber: Int64ptr(startNumber),
Timescale: Int64ptr(timescale),
}
err := as.setSegmentTemplate(st)
if err != nil {
return nil, err
}
return st, nil
}
// Adds a new Thumbnail representation to an AdaptationSet.
// bandwidth - in Bits/s (i.e. 1518664).
// id - ID for this representation, will get used as $RepresentationID$ in template strings.
// width - width of the video (i.e. 1280).
// height - height of the video (i.e 720).
// uri -
func (as *AdaptationSet) AddNewRepresentationThumbnails(id, val, uri string, bandwidth, width, height int64) (*Representation, error) {
r := &Representation{
Bandwidth: Int64ptr(bandwidth),
ID: Strptr(id),
Width: Int64ptr(width),
Height: Int64ptr(height),
CommonAttributesAndElements: CommonAttributesAndElements{
EssentialProperty: []DescriptorType{
{
SchemeIDURI: Strptr(uri),
Value: Strptr(val),
},
},
},
}
err := as.addRepresentation(r)
if err != nil {
return nil, err
}
return r, nil
}
// Adds a new Audio representation to an AdaptationSet.
// samplingRate - in Hz (i.e. 44100).
// bandwidth - in Bits/s (i.e. 67095).
// codecs - codec string for Audio Only (in RFC6381, https://tools.ietf.org/html/rfc6381) (i.e. mp4a.40.2).
// id - ID for this representation, will get used as $RepresentationID$ in template strings.
func (as *AdaptationSet) AddNewRepresentationAudio(samplingRate int64, bandwidth int64, codecs string, id string) (*Representation, error) {
r := &Representation{
AudioSamplingRate: Int64ptr(samplingRate),
Bandwidth: Int64ptr(bandwidth),
Codecs: Strptr(codecs),
ID: Strptr(id),
}
err := as.addRepresentation(r)
if err != nil {
return nil, err
}
return r, nil
}
// Adds a new Video representation to an AdaptationSet.
// bandwidth - in Bits/s (i.e. 1518664).
// codecs - codec string for Audio Only (in RFC6381, https://tools.ietf.org/html/rfc6381) (i.e. avc1.4d401f).
// id - ID for this representation, will get used as $RepresentationID$ in template strings.
// frameRate - video frame rate (as a fraction) (i.e. 30000/1001).
// width - width of the video (i.e. 1280).
// height - height of the video (i.e 720).
func (as *AdaptationSet) AddNewRepresentationVideo(bandwidth int64, codecs string, id string, frameRate string, width int64, height int64) (*Representation, error) {
r := &Representation{
Bandwidth: Int64ptr(bandwidth),
Codecs: Strptr(codecs),
ID: Strptr(id),
FrameRate: Strptr(frameRate),
Width: Int64ptr(width),
Height: Int64ptr(height),
}
err := as.addRepresentation(r)
if err != nil {
return nil, err
}
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.
func (as *AdaptationSet) AddNewRepresentationSubtitle(bandwidth int64, id string) (*Representation, error) {
r := &Representation{
Bandwidth: Int64ptr(bandwidth),
ID: Strptr(id),
}
err := as.addRepresentation(r)
if err != nil {
return nil, err
}
return r, nil
}
// Internal helper method for adding a Representation to an AdaptationSet.
func (as *AdaptationSet) addRepresentation(r *Representation) error {
if r == nil {
return ErrRepresentationNil
}
r.AdaptationSet = as
as.Representations = append(as.Representations, r)
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)
func (as *AdaptationSet) AddNewRole(schemeIDURI string, value string) (*Role, error) {
r := &Role{
SchemeIDURI: Strptr(schemeIDURI),
Value: Strptr(value),
}
r.AdaptationSet = as
as.Roles = append(as.Roles, r)
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 {
if baseURL == "" {
return ErrBaseURLEmpty
}
r.BaseURL = Strptr(baseURL)
return nil
}
// Sets a new SegmentBase on a Representation.
// This is for On Demand profile.
// indexRange - Byte range to the index (sidx)atom.
// init - Byte range to the init atoms (ftyp+moov).
func (r *Representation) AddNewSegmentBase(indexRange string, initRange string) (*SegmentBase, error) {
sb := &SegmentBase{
IndexRange: Strptr(indexRange),
Initialization: &URL{Range: Strptr(initRange)},
}
err := r.setSegmentBase(sb)
if err != nil {
return nil, err
}
return sb, nil
}
// Internal helper method for setting the SegmentBase on a Representation.
func (r *Representation) setSegmentBase(sb *SegmentBase) error {
if r.AdaptationSet == nil {
return ErrNoDASHProfileSet
}
if sb == nil {
return ErrSegmentBaseNil
}
r.SegmentBase = sb
return nil
}
// Sets a new AudioChannelConfiguration on a Representation.
// This is required for the HbbTV profile.
// scheme - One of the two AudioConfigurationSchemes.
// channelConfiguration - string that represents the channel configuration.
func (r *Representation) AddNewAudioChannelConfiguration(scheme AudioChannelConfigurationScheme, channelConfiguration string) (*AudioChannelConfiguration, error) {
acc := &AudioChannelConfiguration{
SchemeIDURI: Strptr((string)(scheme)),
Value: Strptr(channelConfiguration),
}
err := r.setAudioChannelConfiguration(acc)
if err != nil {
return nil, err
}
return acc, nil
}
// Internal helper method for setting the SegmentBase on a Representation.
func (r *Representation) setAudioChannelConfiguration(acc *AudioChannelConfiguration) error {
if acc == nil {
return ErrAudioChannelConfigurationNil
}
r.AudioChannelConfiguration = acc
return nil
}