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
181
apps/cli/catalogcmd/catalogcmd.go
Normal file
181
apps/cli/catalogcmd/catalogcmd.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// Package catalogcmd resolves a channel's stream credentials from its provider's
|
||||
// public catalog, with no phone involved. It is provider-agnostic: any app module
|
||||
// implementing app.Catalog works here.
|
||||
package catalogcmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"drmdecryption/app"
|
||||
"drmdecryption/repo"
|
||||
"drmdecryption/session"
|
||||
)
|
||||
|
||||
// Run performs a catalog lookup for one channel.
|
||||
func Run(args []string) error {
|
||||
fs := flag.NewFlagSet("catalog", flag.ExitOnError)
|
||||
appName := fs.String("app", "", "app module to query")
|
||||
channel := fs.String("channel", "", "channel id")
|
||||
asJSON := fs.Bool("json", false, "print streamd-ready JSON only")
|
||||
list := fs.Bool("list", false, "list the app's channels and their catalog ids")
|
||||
writeOut := fs.Bool("out", false, "write outputs/<app>/<stamp>/session.json")
|
||||
withKeys := fs.Bool("keys", false, "also fetch PSSH and resolve KID:KEY via the local CDM")
|
||||
python := fs.String("python", "", "python for wvkey.py (default: .venv)")
|
||||
wvd := fs.String("wvd", "", "path to .wvd device file (required with --keys)")
|
||||
// Each module contributes --<app>.<field> overrides for its local values.
|
||||
app.BindFlags(fs)
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintf(fs.Output(), "catalog — resolve a channel from its public catalog (no phone)\n\nUsage:\n catalog --app <%s> --channel ID [--keys] [--json]\n\nFlags:\n", strings.Join(catalogApps(), "|"))
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
available := catalogApps()
|
||||
if strings.TrimSpace(*appName) == "" {
|
||||
if len(available) != 1 {
|
||||
return fmt.Errorf("--app required (apps with a catalog: %s)", strings.Join(available, ", "))
|
||||
}
|
||||
*appName = available[0]
|
||||
}
|
||||
a, err := app.Get(*appName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cat, ok := a.(app.Catalog)
|
||||
if !ok {
|
||||
return fmt.Errorf("app %q has no catalog — capture it from the phone instead: drm capture --app %s", a.Name(), a.Name())
|
||||
}
|
||||
|
||||
if *list {
|
||||
return listChannels(a, cat)
|
||||
}
|
||||
if strings.TrimSpace(*channel) == "" {
|
||||
return fmt.Errorf("--channel required (see: drm catalog --app %s --list)", a.Name())
|
||||
}
|
||||
|
||||
info, err := cat.Resolve(*channel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *withKeys {
|
||||
fmt.Fprintln(os.Stderr, "[*] fetching PSSH + Widevine keys…")
|
||||
if err := cat.EnrichWithKeys(&info, *python, *wvd); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[+] key %s (%d total)\n", info.Key, len(info.Keys))
|
||||
}
|
||||
if *writeOut || *withKeys {
|
||||
dir, err := session.WriteStream(repo.Root(), a.Name(), info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[+] wrote %s\n", dir)
|
||||
}
|
||||
if *asJSON {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(info)
|
||||
}
|
||||
printInfo(a.Name(), info)
|
||||
return nil
|
||||
}
|
||||
|
||||
// catalogApps lists compiled-in modules that can answer catalog lookups.
|
||||
func catalogApps() []string {
|
||||
out := []string{}
|
||||
for _, a := range app.All() {
|
||||
if _, ok := a.(app.Catalog); ok {
|
||||
out = append(out, a.Name())
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func listChannels(a app.App, cat app.Catalog) error {
|
||||
if l, ok := cat.(app.CatalogLister); ok {
|
||||
rows, flags, err := l.ListChannels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%-10s %-16s %s\n", "CHANNEL", "CATALOG ID", "LABEL")
|
||||
sort.Slice(rows, func(i, j int) bool { return rows[i].ID < rows[j].ID })
|
||||
for _, r := range rows {
|
||||
id := r.CatalogID
|
||||
if id == "" {
|
||||
id = "-"
|
||||
}
|
||||
fmt.Printf("%-10s %-16s %s\n", r.ID, id, r.Label)
|
||||
}
|
||||
if len(flags) > 0 {
|
||||
keys := make([]string, 0, len(flags))
|
||||
for k := range flags {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
parts := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
parts = append(parts, fmt.Sprintf("%s=%v", k, flags[k]))
|
||||
}
|
||||
fmt.Printf("\nprovider flags: %s\n", strings.Join(parts, " "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Fall back to the plain channel list every app can produce.
|
||||
fmt.Printf("%-10s %s\n", "CHANNEL", "LABEL")
|
||||
for _, ch := range a.Channels() {
|
||||
fmt.Printf("%-10s %s\n", ch.ID, ch.Label)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printInfo(appName string, info session.Stream) {
|
||||
fmt.Printf("name: %s\n", info.Name)
|
||||
fmt.Printf("title: %s\n", info.Title)
|
||||
fmt.Printf("app/channel: %s / %s\n", info.App, info.Channel)
|
||||
if info.VideoID != "" {
|
||||
fmt.Printf("catalog id: %s\n", info.VideoID)
|
||||
}
|
||||
fmt.Printf("manifest: %s\n", info.MPD)
|
||||
fmt.Printf("manifest type: %s\n", info.ManifestType)
|
||||
fmt.Printf("license_url: %s\n", truncate(info.LicenseURL, 100))
|
||||
if info.PSSH != "" {
|
||||
fmt.Printf("pssh: %s\n", truncate(info.PSSH, 64))
|
||||
}
|
||||
if info.Key != "" {
|
||||
fmt.Printf("key: %s\n", info.Key)
|
||||
}
|
||||
for i, k := range info.Keys {
|
||||
if k == info.Key {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("key[%d]: %s\n", i, k)
|
||||
}
|
||||
body, _ := json.MarshalIndent(map[string]any{
|
||||
"name": info.Name,
|
||||
"title": info.Title,
|
||||
"app": info.App,
|
||||
"channel": info.Channel,
|
||||
"mpd": info.MPD,
|
||||
"key": info.Key,
|
||||
"headers_json": info.HeadersJSON,
|
||||
"rewriter": info.Rewriter,
|
||||
}, "", " ")
|
||||
fmt.Printf("\nstreamd create body:\n%s\n", body)
|
||||
fmt.Printf("\nTip: drm catalog --app %s --channel %s --keys --wvd path/to/device.wvd --json\n",
|
||||
appName, info.Channel)
|
||||
}
|
||||
|
||||
func truncate(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