Initial commit: Null DRM Official
Capture, decrypt, and restream toolkit with compiled-in app modules (RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www. BBC module.yaml is published (clear streams); other module values stay local.
This commit is contained in:
commit
2fa8f2435f
121 changed files with 17802 additions and 0 deletions
161
apps/pkg/brightcove/brightcove.go
Normal file
161
apps/pkg/brightcove/brightcove.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// Package brightcove talks to the Brightcove Playback API and reads Widevine
|
||||
// material out of an HLS master. Brightcove is a platform, not a provider: this
|
||||
// package holds no account, policy key, video id or channel knowledge.
|
||||
package brightcove
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EdgeAPI is the Playback API base. Overridable for tests or a proxy.
|
||||
var EdgeAPI = "https://edge.api.brightcove.com/playback/v1"
|
||||
|
||||
var (
|
||||
// SESSION-KEY / KEY lines put URI and KEYFORMAT in either order.
|
||||
reWidevinePSSH = regexp.MustCompile(`(?is)KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".*?URI="data:text/plain;base64,([A-Za-z0-9+/=]+)"`)
|
||||
reWidevinePSSH2 = regexp.MustCompile(`(?is)URI="data:text/plain;base64,([A-Za-z0-9+/=]+)".*?KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"`)
|
||||
reKeyID = regexp.MustCompile(`(?i)keyId=([0-9a-f]{32})`)
|
||||
)
|
||||
|
||||
// WidevineKeySystem is the key_systems map key for Widevine sources.
|
||||
const WidevineKeySystem = "com.widevine.alpha"
|
||||
|
||||
// Video is the subset of a Playback API response we use.
|
||||
type Video struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Sources []struct {
|
||||
Type string `json:"type"`
|
||||
Src string `json:"src"`
|
||||
KeySystems map[string]struct {
|
||||
LicenseURL string `json:"license_url"`
|
||||
} `json:"key_systems"`
|
||||
} `json:"sources"`
|
||||
}
|
||||
|
||||
// PlaybackURL is the canonical (token-free) Playback API URL for a video.
|
||||
func PlaybackURL(accountID, videoID string) string {
|
||||
return fmt.Sprintf("%s/accounts/%s/videos/%s", EdgeAPI, accountID, videoID)
|
||||
}
|
||||
|
||||
// FetchPlayback resolves a video through the Playback API. livePlaybackToken may
|
||||
// be empty for assets that do not need one.
|
||||
func FetchPlayback(accountID, videoID, livePlaybackToken, policyKey string) (Video, error) {
|
||||
var v Video
|
||||
if policyKey == "" {
|
||||
return v, fmt.Errorf("policy key required")
|
||||
}
|
||||
if accountID == "" {
|
||||
return v, fmt.Errorf("account id required")
|
||||
}
|
||||
u := PlaybackURL(accountID, videoID)
|
||||
if livePlaybackToken != "" {
|
||||
u += "?livePlaybackToken=" + livePlaybackToken
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/json;pk="+policyKey)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != 200 {
|
||||
return v, fmt.Errorf("brightcove playback HTTP %d: %s", resp.StatusCode, Trim(string(body), 200))
|
||||
}
|
||||
if err := json.Unmarshal(body, &v); err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// PickHLSAndLicense chooses the best HLS source: prefer a DVR playlist that
|
||||
// carries a Widevine license URL, then any Widevine source, then any DVR
|
||||
// playlist, then any HLS at all.
|
||||
func PickHLSAndLicense(v Video) (hls, license string) {
|
||||
var dvr, dvrWV, anyWV, anyHLS string
|
||||
var dvrLic, anyLic string
|
||||
for _, s := range v.Sources {
|
||||
if !strings.Contains(strings.ToLower(s.Type), "mpegurl") && !strings.Contains(strings.ToLower(s.Src), ".m3u8") {
|
||||
continue
|
||||
}
|
||||
src := s.Src
|
||||
lic := ""
|
||||
if ks, ok := s.KeySystems[WidevineKeySystem]; ok {
|
||||
lic = ks.LicenseURL
|
||||
}
|
||||
isDVR := strings.Contains(src, "playlist-hls-dvr.m3u8") || strings.Contains(src, "-dvr")
|
||||
switch {
|
||||
case isDVR && lic != "" && dvrWV == "":
|
||||
dvrWV, dvrLic = src, lic
|
||||
case isDVR && dvr == "":
|
||||
dvr = src
|
||||
case lic != "" && anyWV == "":
|
||||
anyWV, anyLic = src, lic
|
||||
case anyHLS == "":
|
||||
anyHLS = src
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case dvrWV != "":
|
||||
return dvrWV, dvrLic
|
||||
case anyWV != "":
|
||||
return anyWV, anyLic
|
||||
case dvr != "":
|
||||
return dvr, ""
|
||||
default:
|
||||
return anyHLS, ""
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractWidevinePSSH returns the base64 Widevine init data from an HLS master.
|
||||
func ExtractWidevinePSSH(master string) string {
|
||||
if m := reWidevinePSSH.FindStringSubmatch(master); len(m) == 2 {
|
||||
return m[1]
|
||||
}
|
||||
if m := reWidevinePSSH2.FindStringSubmatch(master); len(m) == 2 {
|
||||
return m[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PrimaryKID returns the first keyId= seen in an HLS master, lowercased.
|
||||
func PrimaryKID(master string) string {
|
||||
if kids := reKeyID.FindAllStringSubmatch(master, -1); len(kids) > 0 {
|
||||
return strings.ToLower(kids[0][1])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get fetches a URL as text (used for the HLS master).
|
||||
func Get(url string) (string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// Trim shortens a string for error messages.
|
||||
func Trim(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n] + "..."
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue