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
252
docs/quickstart.md
Normal file
252
docs/quickstart.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
# Quickstart
|
||||
|
||||
From an empty directory to a captured Widevine key.
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
| Need | Version | Needed for |
|
||||
|---|---|---|
|
||||
| Go | 1.25+ | everything |
|
||||
| Python | 3.10+ | the CDM helper (`apps/wvkey/wvkey.py`) |
|
||||
| adb | any recent platform-tools, on `PATH` | phone capture only |
|
||||
| `.wvd` device file | — | fetching content keys |
|
||||
| Rooted Android phone (Magisk) | — | phone capture only |
|
||||
|
||||
Check Go:
|
||||
|
||||
```bash
|
||||
go version
|
||||
```
|
||||
|
||||
If Go is installed but not on `PATH`, set `GOROOT` and prepend its `bin`:
|
||||
|
||||
```bash
|
||||
export GOROOT=/path/to/go
|
||||
export PATH="$GOROOT/bin:$PATH"
|
||||
```
|
||||
|
||||
## 2. Clone and build
|
||||
|
||||
```bash
|
||||
git clone https://git.nulldrm.com/nulldrm/Null-DRM-Official.git
|
||||
cd Null-DRM-Official
|
||||
|
||||
go -C apps/cli build -o ../../bin/drm . # Windows: -o ../../bin/drm.exe
|
||||
```
|
||||
|
||||
That single command builds the whole toolchain: capture, catalog, agent, the
|
||||
streamd control plane, the MITM host CLI, and every app module.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
./bin/drm help
|
||||
./bin/drm modules
|
||||
```
|
||||
|
||||
Expected on a fresh clone — modules exist, values do not:
|
||||
|
||||
```text
|
||||
APP PACKAGE KEY MODE REWRITER CHANNELS VALUES
|
||||
rte (not configured) modulardrm rte 0 missing: .../apps/modules/rte/module.yaml
|
||||
tg4 (not configured) raw none 0 missing: .../apps/modules/tg4/module.yaml
|
||||
```
|
||||
|
||||
## 3. The Python CDM helper
|
||||
|
||||
Only `wvkey.py` is Python; it turns a PSSH + license URL into `KID:KEY` lines.
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv/bin/pip install -r apps/wvkey/requirements.txt
|
||||
```
|
||||
|
||||
On Windows:
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.venv\Scripts\pip install -r apps\wvkey\requirements.txt
|
||||
```
|
||||
|
||||
The binary finds `.venv` automatically; override with `--python /path/to/python`.
|
||||
|
||||
Place your Widevine device file under `data/` (gitignored):
|
||||
|
||||
```text
|
||||
data/device.wvd
|
||||
```
|
||||
|
||||
## 4. Add values for a module
|
||||
|
||||
Create the module's values file. It is gitignored, so it never leaves your machine.
|
||||
|
||||
```bash
|
||||
$EDITOR apps/modules/tg4/module.yaml
|
||||
```
|
||||
|
||||
Minimum for TG4 — see [providers/tg4.md](providers/tg4.md) for where each comes from:
|
||||
|
||||
```yaml
|
||||
package: <android package id>
|
||||
account_id: "<brightcove account id>"
|
||||
policy_key: "BCpkAD..."
|
||||
playback_config_url: "https://.../playback_config.json"
|
||||
|
||||
channels:
|
||||
ioi:
|
||||
label: "Main channel"
|
||||
video_id: "<brightcove video id>"
|
||||
token_field: <field name in the playback config>
|
||||
live_card_index: 0
|
||||
```
|
||||
|
||||
Confirm it loaded:
|
||||
|
||||
```bash
|
||||
./bin/drm modules
|
||||
./bin/drm catalog --app tg4 --list
|
||||
```
|
||||
|
||||
Nothing stored? Pass values per run instead:
|
||||
|
||||
```bash
|
||||
./bin/drm catalog --app tg4 --channel ioi \
|
||||
--tg4.account-id 1234567890 --tg4.policy-key 'BCpkAD...'
|
||||
```
|
||||
|
||||
Or through the environment (`<APP>_<FIELD>`, upper snake case):
|
||||
|
||||
```bash
|
||||
export TG4_POLICY_KEY='BCpkAD...'
|
||||
```
|
||||
|
||||
Precedence is **flag → environment → module.yaml → built-in default**. Only
|
||||
mechanical defaults (timeouts, DASH timescales, card geometry) have built-ins.
|
||||
|
||||
## 5. Get keys without a phone
|
||||
|
||||
If the app has a public playback catalog:
|
||||
|
||||
```bash
|
||||
./bin/drm catalog --app tg4 --channel ioi --keys --wvd data/device.wvd
|
||||
```
|
||||
|
||||
```text
|
||||
manifest: https://.../playlist-hls-dvr.m3u8
|
||||
license_url: https://.../lic/wv?token=...
|
||||
pssh: AAAAYHBzc2gAAAAA7e+LqXnWSs6j...
|
||||
key: 27c38b955b983ac2827204f8d260d628:f31efbc58fc89526cce5e5dee50a7ebd
|
||||
key[0]: 5b9d58edf81f3b5b9c3cc61f3e81845e:688236976dcf8cdde5cdfac4a698f256
|
||||
...
|
||||
```
|
||||
|
||||
Add `--json` for a machine-readable object, `--out` to write a session directory
|
||||
without fetching keys.
|
||||
|
||||
## 6. Get keys from the phone
|
||||
|
||||
One-time device setup:
|
||||
|
||||
```bash
|
||||
adb devices # your phone must be listed and authorised
|
||||
./bin/drm proxy build # cross-compiles the on-device MITM (linux/arm64)
|
||||
./bin/drm proxy install-ca --reinject
|
||||
```
|
||||
|
||||
`install-ca` pushes the MITM CA and Magisk-mounts it into the system trust store.
|
||||
Without it apps fail TLS with `unknown certificate`. See [capture.md](capture.md)
|
||||
if the reinject does not report success.
|
||||
|
||||
Then capture:
|
||||
|
||||
```bash
|
||||
./bin/drm capture --app rte --channel rteone --auto-play --wvd data/device.wvd
|
||||
```
|
||||
|
||||
What it does: start the on-device MITM → point the phone's HTTP proxy at it →
|
||||
reinject the CA → launch the app → drive its UI to the channel → wait for the
|
||||
license/manifest traffic → run the CDM → write a session → close the app.
|
||||
|
||||
```text
|
||||
[*] Launching rte (...)
|
||||
[*] Auto-play RTE One…
|
||||
[*] tap_ui "Live"/"Live tab, 2 out of 5" @ 324,2253
|
||||
[*] tap Play @ 540,720
|
||||
[+] ... is PLAYING
|
||||
[+] Capture:
|
||||
pid: 8T8Ohq7XT5KN
|
||||
mpd: https://dai.google.com/linear/dash/pa/event/...
|
||||
[+] key: df1633821dddfdd5bec9822c1ec0f052:b6324f76d2bb4dfe63e4272f803274f4
|
||||
[+] session: outputs/rte/20261005-215448/session.json
|
||||
```
|
||||
|
||||
Keep the screen on for the whole run:
|
||||
|
||||
```bash
|
||||
adb shell svc power stayon true
|
||||
```
|
||||
|
||||
Drop `--auto-play` to launch the app and navigate by hand, or drop `--app`
|
||||
entirely for passive mode — the MITM records whatever you play.
|
||||
|
||||
## 7. Control plane
|
||||
|
||||
```bash
|
||||
./bin/drm serve --bind 127.0.0.1:8083 --data .cache/streamd --token SECRET
|
||||
```
|
||||
|
||||
Open <http://127.0.0.1:8083>. The app and channel pickers are populated from
|
||||
`GET /api/apps`, i.e. from the modules compiled into the binary.
|
||||
|
||||
Create a stream from a capture:
|
||||
|
||||
```bash
|
||||
./bin/drm catalog --app tg4 --channel ioi --keys --wvd data/device.wvd --json \
|
||||
> /tmp/s.json
|
||||
|
||||
curl -X POST http://127.0.0.1:8083/api/streams \
|
||||
-H 'Authorization: Bearer SECRET' -H 'Content-Type: application/json' \
|
||||
--data @/tmp/s.json
|
||||
```
|
||||
|
||||
## 8. Agent
|
||||
|
||||
The agent polls streamd, queues any enabled stream that is down or missing
|
||||
credentials, claims a free phone, re-captures it and POSTs the result back.
|
||||
|
||||
```bash
|
||||
export STREAMD_TOKEN=SECRET
|
||||
./bin/drm agent run --config apps/agent/agent.yaml
|
||||
|
||||
./bin/drm agent devices # which phones it can use
|
||||
./bin/drm agent status # recent jobs
|
||||
./bin/drm agent enqueue --stream tg4-ioi --reason manual
|
||||
```
|
||||
|
||||
Details in [streamd.md](streamd.md).
|
||||
|
||||
## 9. Tests
|
||||
|
||||
```bash
|
||||
go -C apps/pkg test ./...
|
||||
go -C apps/modules test ./...
|
||||
go -C apps/streamd test ./...
|
||||
```
|
||||
|
||||
Live-origin tests are tag-gated and need your local values:
|
||||
|
||||
```bash
|
||||
go -C apps/modules test -tags live ./rte/ -v
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Cause |
|
||||
|---|---|
|
||||
| `unknown app "x"` | not compiled in — check `apps/modules/all/all.go`, then rebuild |
|
||||
| `channels 0` in `drm modules` | no `module.yaml`, or `channels:` is empty |
|
||||
| `unknown channel "y"` | id not in `channels:` and not in `aliases:` |
|
||||
| `tls: unknown certificate` on the phone | CA not mounted — `drm proxy install-ca --reinject` |
|
||||
| `wvkey requires --wvd` | pass `--wvd path/to/device.wvd` |
|
||||
| `timed out waiting for capture` | playback never started, or the app used a path the MITM missed — see [capture.md](capture.md) |
|
||||
| `INVALID_POLICY_KEY` | wrong or stale `policy_key` |
|
||||
Loading…
Add table
Add a link
Reference in a new issue