mirror of
https://github.com/chris124567/hulu
synced 2024-11-01 06:17:30 +00:00
116 lines
4.3 KiB
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)
|
||
|
}
|