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:
404errordeveloper 2026-10-06 00:25:35 +02:00
commit 2fa8f2435f
121 changed files with 17802 additions and 0 deletions

154
apps/pkg/app/app.go Normal file
View file

@ -0,0 +1,154 @@
package app
import (
"flag"
"fmt"
"sort"
"sync"
"drmdecryption/adb"
"drmdecryption/capture"
"drmdecryption/mpd"
"drmdecryption/session"
)
// Channel is a named live target inside an app (e.g. rteone).
type Channel struct {
ID string
Label string
}
// App is a per-streaming-app plugin, compiled in from apps/modules/<name>. The
// capture core stays generic: an app supplies its package name, UI automation,
// capture hints, key mode and optional MPD rewrite.
type App interface {
Name() string
Package() string
LicenseURL() string
Launch(c *adb.Client) error
AutoPlay(c *adb.Client, channel string) error
CaptureHints() capture.Hints
MPDRewriter() mpd.Rewriter
Channels() []Channel
HasChannel(id string) bool
// ProxyBin is the on-device MITM binary to push.
ProxyBin() string
// CAHash is the MITM CA subject hash to reinject.
CAHash() string
// KeyMode selects the wvkey mode: "modulardrm" or "raw".
KeyMode() string
}
// Catalog is implemented by apps whose channels can be resolved without a phone
// (a public playback catalog). Drives the `catalog` subcommand.
type Catalog interface {
// Resolve returns streamd-ready credentials for a channel id.
Resolve(channel string) (session.Stream, error)
// EnrichWithKeys fills PSSH + KID:KEY using the local CDM.
EnrichWithKeys(info *session.Stream, python, wvd string) error
}
// CatalogRow is one channel in a catalog listing.
type CatalogRow struct {
ID string
CatalogID string
Label string
}
// CatalogLister is the optional richer listing a catalog app may provide: each
// channel's resolved catalog id plus whatever feature flags the provider
// publishes alongside it.
type CatalogLister interface {
ListChannels() (rows []CatalogRow, flags map[string]bool, err error)
}
// StreamDefaults is implemented by apps that need particular downloader settings.
// It replaces name-sniffing in the media supervisor.
type StreamDefaults interface {
// VideoSelect / AudioSelect are N_m3u8DL-RE selector expressions.
VideoSelect() string
AudioSelect() string
// RewriterName is the registered mpd rewriter to run, or "none".
RewriterName() string
// LiveWaitSeconds is the downloader's live refresh wait.
LiveWaitSeconds() int
// TSReadyBytes is how much muxed output to buffer before probing.
TSReadyBytes() int64
}
// TransparentMITM is implemented by apps that must use iptables REDIRECT capture
// (no Wi‑Fi http_proxy) so a phone VPN (e.g. NordVPN UK for BBC) can stay on.
type TransparentMITM interface {
UseTransparentMITM() bool
}
var (
mu sync.RWMutex
registry = map[string]func() (App, error){}
)
// Register adds a constructor for --app <name>. Called from each module's init().
func Register(name string, ctor func() (App, error)) {
mu.Lock()
defer mu.Unlock()
registry[name] = ctor
}
// Get constructs a registered app by name.
func Get(name string) (App, error) {
mu.RLock()
ctor, ok := registry[name]
mu.RUnlock()
if !ok {
return nil, fmt.Errorf("unknown app %q — compiled-in apps: %v", name, Names())
}
return ctor()
}
// Names lists registered app ids, sorted.
func Names() []string {
mu.RLock()
defer mu.RUnlock()
out := make([]string, 0, len(registry))
for k := range registry {
out = append(out, k)
}
sort.Strings(out)
return out
}
// All constructs every registered app, skipping those that fail to build.
func All() []App {
out := []App{}
for _, n := range Names() {
a, err := Get(n)
if err != nil {
continue
}
out = append(out, a)
}
return out
}
// flagHooks are registered from each module's init() so overrides can be bound
// to the module's config holder BEFORE any app is constructed.
var flagHooks []func(*flag.FlagSet)
// RegisterFlags records a module's flag binder. Called from init().
func RegisterFlags(bind func(*flag.FlagSet)) {
mu.Lock()
defer mu.Unlock()
flagHooks = append(flagHooks, bind)
}
// BindFlags lets every compiled-in module register its value overrides on fs.
// Call this before fs.Parse, and construct apps only afterwards.
func BindFlags(fs *flag.FlagSet) {
mu.RLock()
hooks := append([]func(*flag.FlagSet){}, flagHooks...)
mu.RUnlock()
for _, h := range hooks {
h(fs)
}
}