cdm-tools-downey/cdm/pssh.go

116 lines
4.3 KiB
Go

package widevine
import (
"bytes"
"encoding/base64"
"encoding/xml"
"errors"
"io"
"net/http"
)
// This function retrieves the PSSH/Init Data from a given MPD file reader.
// Example file: https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd
func InitDataFromMPD(r io.Reader) ([]byte, error) {
type mpd struct {
XMLName xml.Name `xml:"MPD"`
Text string `xml:",chardata"`
ID string `xml:"id,attr"`
Profiles string `xml:"profiles,attr"`
Type string `xml:"type,attr"`
AvailabilityStartTime string `xml:"availabilityStartTime,attr"`
PublishTime string `xml:"publishTime,attr"`
MediaPresentationDuration string `xml:"mediaPresentationDuration,attr"`
MinBufferTime string `xml:"minBufferTime,attr"`
Version string `xml:"version,attr"`
Ns2 string `xml:"ns2,attr"`
Xmlns string `xml:"xmlns,attr"`
Bitmovin string `xml:"bitmovin,attr"`
Period struct {
Text string `xml:",chardata"`
AdaptationSet []struct {
Text string `xml:",chardata"`
MimeType string `xml:"mimeType,attr"`
Codecs string `xml:"codecs,attr"`
Lang string `xml:"lang,attr"`
Label string `xml:"label,attr"`
SegmentTemplate struct {
Text string `xml:",chardata"`
Media string `xml:"media,attr"`
Initialization string `xml:"initialization,attr"`
Duration string `xml:"duration,attr"`
StartNumber string `xml:"startNumber,attr"`
Timescale string `xml:"timescale,attr"`
} `xml:"SegmentTemplate"`
ContentProtection []struct {
Text string `xml:",chardata"`
SchemeIdUri string `xml:"schemeIdUri,attr"`
Value string `xml:"value,attr"`
DefaultKID string `xml:"default_KID,attr"`
Pssh string `xml:"pssh"`
} `xml:"ContentProtection"`
Representation []struct {
Text string `xml:",chardata"`
ID string `xml:"id,attr"`
Bandwidth string `xml:"bandwidth,attr"`
Width string `xml:"width,attr"`
Height string `xml:"height,attr"`
FrameRate string `xml:"frameRate,attr"`
AudioSamplingRate string `xml:"audioSamplingRate,attr"`
ContentProtection []struct {
Text string `xml:",chardata"`
SchemeIdUri string `xml:"schemeIdUri,attr"`
Value string `xml:"value,attr"`
DefaultKID string `xml:"default_KID,attr"`
Cenc string `xml:"cenc,attr"`
Pssh struct {
Text string `xml:",chardata"`
Cenc string `xml:"cenc,attr"`
} `xml:"pssh"`
} `xml:"ContentProtection"`
} `xml:"Representation"`
AudioChannelConfiguration struct {
Text string `xml:",chardata"`
SchemeIdUri string `xml:"schemeIdUri,attr"`
Value string `xml:"value,attr"`
} `xml:"AudioChannelConfiguration"`
} `xml:"AdaptationSet"`
} `xml:"Period"`
}
var mpdPlaylist mpd
if err := xml.NewDecoder(r).Decode(&mpdPlaylist); err != nil {
return nil, err
}
const widevineSchemeIdURI = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
for _, adaptionSet := range mpdPlaylist.Period.AdaptationSet {
for _, protection := range adaptionSet.ContentProtection {
if protection.SchemeIdUri == widevineSchemeIdURI && len(protection.Pssh) > 0 {
return base64.StdEncoding.DecodeString(protection.Pssh)
}
}
}
for _, adaptionSet := range mpdPlaylist.Period.AdaptationSet {
for _, representation := range adaptionSet.Representation {
for _, protection := range representation.ContentProtection {
if protection.SchemeIdUri == widevineSchemeIdURI && len(protection.Pssh.Text) > 0 {
return base64.StdEncoding.DecodeString(protection.Pssh.Text)
}
}
}
}
return nil, errors.New("no init data found")
}
// This function retrieves certificate data from a given license server.
func GetCertData(client *http.Client, licenseURL string) ([]byte, error) {
response, err := client.Post(licenseURL, "application/x-www-form-urlencoded", bytes.NewReader([]byte{0x08, 0x04}))
if err != nil {
return nil, err
}
defer response.Body.Close()
return io.ReadAll(response.Body)
}