mirror of
https://github.com/NohamR/dashdrm-mp4ff.git
synced 2026-07-11 19:10:04 +00:00
Initial commit: dashdrm-mp4ff plugin
Add a Streamlink plugin (plugin/dashdrm_mp4ff.py) to support DRM-protected MPEG-DASH streams with optional mp4ff-decrypt integration. Include README with usage and installation notes, an interactive helper (gen.py) that reads vault.json and prints Streamlink commands, a vault.json example, pyproject.toml (declares inquirerpy dependency), .python-version, and .gitignore. This provides the plugin, packaging metadata, and tooling needed to run and test DASH-DRM streams locally.
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
vault.json
|
||||||
|
.DS_Store
|
||||||
|
*__pycache__
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.11
|
||||||
75
README.md
Normal file
75
README.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# dashdrm-mp4ff
|
||||||
|
|
||||||
|
Custom Streamlink plugin for DRM-protected MPEG-DASH streams.
|
||||||
|
|
||||||
|
This project is based on [titus-au/streamlink-plugin-dashdrm](https://github.com/titus-au/streamlink-plugin-dashdrm), which itself is based on Streamlink's [`dash.py`](https://github.com/streamlink/streamlink/blob/master/src/streamlink/stream/dash/dash.py).
|
||||||
|
|
||||||
|
## Install `mp4ff-decrypt`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/Eyevinn/mp4ff/cmd/mp4ff-decrypt@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure the Go bin directory is in your `PATH`, then verify:
|
||||||
|
```bash
|
||||||
|
mp4ff-decrypt -h
|
||||||
|
```
|
||||||
|
|
||||||
|
If you do not want to use `mp4ff-decrypt`, you can still use the plugin without `--dashdrm-mp4ff-use-mp4ff`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run Streamlink with the local plugin directory:
|
||||||
|
```bash
|
||||||
|
streamlink --plugin-dirs "plugin" "dashdrm://https://example.com/live.mpd" best
|
||||||
|
```
|
||||||
|
|
||||||
|
With `mp4ff-decrypt` and `kid:key` pairs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
streamlink \
|
||||||
|
--plugin-dirs "plugin" \
|
||||||
|
--dashdrm-mp4ff-use-mp4ff \
|
||||||
|
--dashdrm-mp4ff-decryption-key "kid1:key1,kid2:key2,kid3:key3" \
|
||||||
|
"dashdrm://https://example.com/live.mpd" \
|
||||||
|
best
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Keys can be provided as raw keys or as `kid:key`.
|
||||||
|
- When `kid:key` pairs are provided, the plugin parses the manifest and matches each track with the correct key.
|
||||||
|
- The plugin file is [`plugin/dashdrm_mp4ff.py`](./plugin/dashdrm_mp4ff.py).
|
||||||
|
|
||||||
|
## `gen.py`
|
||||||
|
|
||||||
|
[`gen.py`](./gen.py) is a small helper that reads channels from `vault.json`, lets you pick one interactively, and prints the full Streamlink command.
|
||||||
|
|
||||||
|
Install the helper dependency:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
Create your own `vault.json` from [`vault.json.example`](./vault.json.example), then run:
|
||||||
|
```bash
|
||||||
|
python gen.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The expected structure is:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"id": "channel-id",
|
||||||
|
"number": "1",
|
||||||
|
"name": "Channel name",
|
||||||
|
"mpd_url": "https://example.com/live.mpd",
|
||||||
|
"keys": [
|
||||||
|
"kid1:key1",
|
||||||
|
"kid2:key2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
46
gen.py
Normal file
46
gen.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import json
|
||||||
|
from InquirerPy import prompt
|
||||||
|
from InquirerPy.base.control import Choice
|
||||||
|
|
||||||
|
VAULT_FILE = "vault.json"
|
||||||
|
|
||||||
|
def load_vault():
|
||||||
|
try:
|
||||||
|
with open(VAULT_FILE, "r") as f:
|
||||||
|
return json.load(f)["channels"]
|
||||||
|
except FileNotFoundError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def main():
|
||||||
|
channels = load_vault()
|
||||||
|
|
||||||
|
if not channels:
|
||||||
|
print("No channels found in vault.json")
|
||||||
|
return
|
||||||
|
|
||||||
|
choices = [
|
||||||
|
Choice(value=channel, name=f"{channel.get('number', '?')} - {channel.get('name', 'Unknown')}")
|
||||||
|
for channel in channels
|
||||||
|
]
|
||||||
|
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "list",
|
||||||
|
"name": "channel",
|
||||||
|
"message": "Select a channel to generate the streamlink command for:",
|
||||||
|
"choices": choices,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
result = prompt(questions)
|
||||||
|
selected_channel = result.get("channel")
|
||||||
|
|
||||||
|
if selected_channel:
|
||||||
|
mpd_url = selected_channel.get("mpd_url", "")
|
||||||
|
keys_list = selected_channel.get("keys", [])
|
||||||
|
keys_str = ",".join(keys_list)
|
||||||
|
command = f'streamlink -l debug --plugin-dirs "./plugin" --dashdrm-mp4ff-use-mp4ff --dashdrm-mp4ff-decryption-key "{keys_str}" "dashdrm://{mpd_url}" best'
|
||||||
|
print(command)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1075
plugin/dashdrm_mp4ff.py
Normal file
1075
plugin/dashdrm_mp4ff.py
Normal file
File diff suppressed because it is too large
Load Diff
9
pyproject.toml
Normal file
9
pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "dashdrm-mp4ff"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Plugin for Streamlink to support DASH-DRM protected streams using mp4ff in option."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"inquirerpy>=0.3.4",
|
||||||
|
]
|
||||||
57
uv.lock
generated
Normal file
57
uv.lock
generated
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dashdrm-mp4ff"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "inquirerpy" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "inquirerpy", specifier = ">=0.3.4" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inquirerpy"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pfzy" },
|
||||||
|
{ name = "prompt-toolkit" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/64/73/7570847b9da026e07053da3bbe2ac7ea6cde6bb2cbd3c7a5a950fa0ae40b/InquirerPy-0.3.4.tar.gz", hash = "sha256:89d2ada0111f337483cb41ae31073108b2ec1e618a49d7110b0d7ade89fc197e", size = 44431, upload-time = "2022-06-27T23:11:20.598Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/ff/3b59672c47c6284e8005b42e84ceba13864aa0f39f067c973d1af02f5d91/InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4", size = 67677, upload-time = "2022-06-27T23:11:17.723Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pfzy"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d9/5a/32b50c077c86bfccc7bed4881c5a2b823518f5450a30e639db5d3711952e/pfzy-0.3.4.tar.gz", hash = "sha256:717ea765dd10b63618e7298b2d98efd819e0b30cd5905c9707223dceeb94b3f1", size = 8396, upload-time = "2022-01-28T02:26:17.946Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/d7/8ff98376b1acc4503253b685ea09981697385ce344d4e3935c2af49e044d/pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96", size = 8537, upload-time = "2022-01-28T02:26:16.047Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prompt-toolkit"
|
||||||
|
version = "3.0.52"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "wcwidth" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wcwidth"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" },
|
||||||
|
]
|
||||||
24
vault.json.example
Normal file
24
vault.json.example
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"id": "example-channel-1",
|
||||||
|
"number": "1",
|
||||||
|
"name": "Example Channel 1",
|
||||||
|
"mpd_url": "https://example.com/playlist/v1/live/123/1/live.mpd",
|
||||||
|
"keys": [
|
||||||
|
"11111111111111111111111111111111:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"22222222222222222222222222222222:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "example-channel-2",
|
||||||
|
"number": "2",
|
||||||
|
"name": "Example Channel 2",
|
||||||
|
"mpd_url": "https://example.com/playlist/v1/live/456/1/live.mpd",
|
||||||
|
"keys": [
|
||||||
|
"33333333333333333333333333333333:cccccccccccccccccccccccccccccccc",
|
||||||
|
"44444444444444444444444444444444:dddddddddddddddddddddddddddddddd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user