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.
252 lines
6.6 KiB
Markdown
252 lines
6.6 KiB
Markdown
# 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` |
|