m3u8-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- m3u8_cli-0.1.0/LICENSE +21 -0
- m3u8_cli-0.1.0/PKG-INFO +343 -0
- m3u8_cli-0.1.0/README.md +330 -0
- m3u8_cli-0.1.0/chrome-extension/README.md +153 -0
- m3u8_cli-0.1.0/chrome-extension/background.js +660 -0
- m3u8_cli-0.1.0/chrome-extension/content.js +178 -0
- m3u8_cli-0.1.0/chrome-extension/lib.js +162 -0
- m3u8_cli-0.1.0/chrome-extension/lib.test.js +124 -0
- m3u8_cli-0.1.0/chrome-extension/manifest.json +24 -0
- m3u8_cli-0.1.0/chrome-extension/popup.css +471 -0
- m3u8_cli-0.1.0/chrome-extension/popup.html +101 -0
- m3u8_cli-0.1.0/chrome-extension/popup.js +583 -0
- m3u8_cli-0.1.0/pyproject.toml +33 -0
- m3u8_cli-0.1.0/pyproject.toml.orig +33 -0
- m3u8_cli-0.1.0/scripts/build_extension.py +176 -0
- m3u8_cli-0.1.0/src/m3u8_cli/__init__.py +3 -0
- m3u8_cli-0.1.0/src/m3u8_cli/__main__.py +6 -0
- m3u8_cli-0.1.0/src/m3u8_cli/cli.py +186 -0
- m3u8_cli-0.1.0/src/m3u8_cli/convert.py +277 -0
- m3u8_cli-0.1.0/src/m3u8_cli/download.py +193 -0
- m3u8_cli-0.1.0/src/m3u8_cli/downloader.py +105 -0
- m3u8_cli-0.1.0/src/m3u8_cli/errors.py +6 -0
- m3u8_cli-0.1.0/src/m3u8_cli/hls_export.py +151 -0
- m3u8_cli-0.1.0/src/m3u8_cli/merge.py +43 -0
- m3u8_cli-0.1.0/src/m3u8_cli/paths.py +30 -0
- m3u8_cli-0.1.0/src/m3u8_cli/playlist.py +165 -0
- m3u8_cli-0.1.0/src/m3u8_cli/serve.py +720 -0
m3u8_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 r1a
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
m3u8_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: m3u8-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HLS 视频下载与格式转换工具
|
|
5
|
+
Author: r1a
|
|
6
|
+
Author-email: r1a <m@zcq100.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: pycryptodome>=4.0.1
|
|
10
|
+
Requires-Dist: requests>=2.34.2
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# m3u8-cli
|
|
15
|
+
|
|
16
|
+
一个基于 Python 的 HLS(m3u8)视频下载与格式转换工具:并发下载分片、支持 AES-128 解密、本地缓存与断点续传,最后通过 FFmpeg 无损封装为 MP4;也可以把本地视频转换为 HLS 或其他常见格式。
|
|
17
|
+
|
|
18
|
+
## 特性
|
|
19
|
+
|
|
20
|
+
- **清晰度选择**:自动解析 master playlist,选择 `BANDWIDTH` 最高的变体。
|
|
21
|
+
- **并发下载**:多线程下载分片,默认 8 并发,可按需调整。
|
|
22
|
+
- **AES-128 解密**:支持标准 HLS AES-128 加密(`KEYFORMAT=identity`)。
|
|
23
|
+
- **断点续传**:分片保存在本地任务目录,中断后重新运行会复用已下载分片。
|
|
24
|
+
- **任务隔离**:最终视频与中间缓存集中放在以输出文件名命名的子目录中。
|
|
25
|
+
- **无损封装**:本地合并分片后一次性调用 FFmpeg,使用 `-c copy` 与 `+faststart` 封装为 MP4。
|
|
26
|
+
- **格式转换**:把本地视频转换为 HLS、MP4、MKV 或 WebM,支持 H.264、HEVC 与流复制。
|
|
27
|
+
|
|
28
|
+
## 环境要求
|
|
29
|
+
|
|
30
|
+
- Python 3.12+
|
|
31
|
+
- [FFmpeg](https://ffmpeg.org/)(需要能在 `PATH` 中找到 `ffmpeg`)
|
|
32
|
+
- [uv](https://docs.astral.sh/uv/) 或 pip(任选其一用于安装)
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
### 使用 uv(推荐)
|
|
37
|
+
|
|
38
|
+
从 PyPI 安装为全局命令:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv tool install m3u8-cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
升级:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv tool upgrade m3u8-cli
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
也可以克隆后从本地源码安装:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone <仓库地址>
|
|
54
|
+
cd m3u8-cli
|
|
55
|
+
uv tool install .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
安装后 `m3u8-cli` 已在 `PATH` 中,日常直接运行即可,不需要 `uv run`。
|
|
59
|
+
|
|
60
|
+
若参与开发,则在仓库目录中执行 `uv sync` 把依赖装进项目的 `.venv`,此时需要使用 `uv run` 前缀。
|
|
61
|
+
|
|
62
|
+
### 使用 pip
|
|
63
|
+
|
|
64
|
+
从 PyPI 安装:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install m3u8-cli
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
升级:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install --upgrade m3u8-cli
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
也可以克隆后从本地源码安装:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone <仓库地址>
|
|
80
|
+
cd m3u8-cli
|
|
81
|
+
pip install .
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
若系统的 Python 启用了 externally-managed 保护(如 Arch、Debian 的默认配置),请先创建并激活虚拟环境再执行上述命令。
|
|
85
|
+
|
|
86
|
+
## 使用
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
m3u8-cli download "<m3u8_url>"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
也可以以模块方式运行:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python -m m3u8_cli download "<m3u8_url>"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
转换同样支持模块方式:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m m3u8_cli convert movie.mp4 -o out
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 常用示例
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 默认输出到 output/output.mp4
|
|
108
|
+
m3u8-cli download "https://example.com/playlist.m3u8"
|
|
109
|
+
|
|
110
|
+
# 指定输出文件名与并发数
|
|
111
|
+
m3u8-cli download "https://example.com/playlist.m3u8" -o movie.mp4 -j 16
|
|
112
|
+
|
|
113
|
+
# 需要 Referer / Cookie 等请求头时
|
|
114
|
+
m3u8-cli download "https://example.com/playlist.m3u8" \
|
|
115
|
+
--referer "https://example.com/" \
|
|
116
|
+
--cookie "session=xxxx"
|
|
117
|
+
|
|
118
|
+
# 覆盖已存在的最终文件
|
|
119
|
+
m3u8-cli download "https://example.com/playlist.m3u8" -o movie.mp4 --overwrite
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 转换视频
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# 默认转换为 HLS:当前目录下生成 movie/playlist.m3u8 与 movie/ts/*.ts
|
|
126
|
+
m3u8-cli convert movie.mp4
|
|
127
|
+
|
|
128
|
+
# 指定输出目录、格式与编码
|
|
129
|
+
m3u8-cli convert movie.mp4 -f mkv -c hevc -o out
|
|
130
|
+
|
|
131
|
+
# 转换 HLS 并设置每片 10 秒
|
|
132
|
+
m3u8-cli convert movie.mp4 -f hls --segment-time 10
|
|
133
|
+
|
|
134
|
+
# 覆盖已有产物
|
|
135
|
+
m3u8-cli convert movie.mp4 -f mp4 --overwrite
|
|
136
|
+
|
|
137
|
+
# 加密 HLS 分片(生成 key.bin)
|
|
138
|
+
m3u8-cli convert movie.mp4 --encrypt
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| 参数 | 默认值 | 说明 |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| `input` | 必填 | 本地视频文件路径 |
|
|
144
|
+
| `-f, --format` | `hls` | `hls`、`mp4`、`mkv`、`webm` |
|
|
145
|
+
| `-c, --codec` | `h264` | `h264`、`hevc`、`copy` |
|
|
146
|
+
| `-o, --output-dir` | 输入文件 stem | 输出目录 |
|
|
147
|
+
| `--segment-time` | `6` | 仅 HLS 使用 |
|
|
148
|
+
| `--overwrite` | 关闭 | 覆盖已有产物 |
|
|
149
|
+
| `--encrypt` | 关闭 | 仅 HLS;生成 `key.bin` 并加密分片 |
|
|
150
|
+
|
|
151
|
+
HLS 输出固定为 `playlist.m3u8` 加 `ts/000000.ts` 起的分片;WebM 重编码时使用 VP9 + Opus,`-c copy` 则直接复制源编码并由 FFmpeg 判断兼容性。
|
|
152
|
+
|
|
153
|
+
- `mp4`/`mkv`/`webm` 输出为 `<输出目录>/<输入文件 stem>.<扩展名>`,例如 `m3u8-cli convert movie.mp4 -f mkv -o out` 生成 `out/movie.mkv`;单文件先写入 `.partial.*`,成功后原子替换。
|
|
154
|
+
- `-c copy` 时 HLS 分片边界由源关键帧决定,`--segment-time` 只是近似目标。
|
|
155
|
+
- `--encrypt` 仅用于 HLS:输出目录会额外生成 16 字节的 `key.bin`,播放列表通过相对 URI 引用;分发时必须与 `playlist.m3u8` 放在一起。
|
|
156
|
+
- FFmpeg/ffprobe 默认拒绝 `key.bin` 扩展名,本地播放或探测需加 `-allowed_extensions ALL`;浏览器播放器不受此限制。
|
|
157
|
+
- 每次 `--encrypt` 转换都会生成新密钥;未指定 `--overwrite` 时已有 `key.bin` 或 `.key_info.txt` 会阻止转换,指定后会替换旧密钥,需要重新分发 `key.bin`。
|
|
158
|
+
|
|
159
|
+
### 下载参数
|
|
160
|
+
|
|
161
|
+
| 参数 | 默认值 | 说明 |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| `url` | 必填 | m3u8 播放列表地址 |
|
|
164
|
+
| `-f, --format` | `mp4` | `mp4`(合并封装)或 `hls`(保留分片,不合并) |
|
|
165
|
+
| `-o, --output` | `output.mp4` / `output` | mp4 为最终文件名;hls 为任务目录(格式不同,默认值不同) |
|
|
166
|
+
| `-j, --jobs` | `8` | 并发下载线程数 |
|
|
167
|
+
| `--retries` | `3` | 单个分片下载失败后的重试次数 |
|
|
168
|
+
| `--timeout` | `30` | 网络请求超时(秒) |
|
|
169
|
+
| `--referer` | 无 | 自定义 `Referer` 请求头 |
|
|
170
|
+
| `--cookie` | 无 | 自定义 `Cookie` 请求头 |
|
|
171
|
+
| `--user-agent` | `Mozilla/5.0` | 自定义 `User-Agent` 请求头 |
|
|
172
|
+
| `--overwrite` | 关闭 | 覆盖已存在的最终文件 |
|
|
173
|
+
|
|
174
|
+
### 保留 HLS(不合并)
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# 输出 movie/playlist.m3u8 与 movie/000000.ts 等分片
|
|
178
|
+
m3u8-cli download "<m3u8_url>" -f hls -o movie
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- `-f hls` 不调用 FFmpeg;分片保持服务器返回的原始字节。
|
|
182
|
+
- 普通 TS 流输出 `playlist.m3u8` 与 6 位编号的 `.ts` 分片;fMP4 流输出 `init.mp4` 与 `.m4s` 分片。
|
|
183
|
+
- 源流为 AES-128 加密时保持密文,同时把密钥下载为 `key.bin`(多个不同密钥时为 `key_000.bin`、`key_001.bin`),播放列表的 `EXT-X-KEY` URI 重写为本地文件。
|
|
184
|
+
- 分片、`playlist.m3u8` 与密钥文件位于同一目录;`--overwrite` 只清理本工具生成的条目。
|
|
185
|
+
|
|
186
|
+
### 启动下载服务
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
m3u8-cli serve --host 0.0.0.0 --port 8787 --output-dir ./downloads
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
启动日志会打印监听地址、输出目录与 API token;省略 --token 时会自动生成。
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# 创建任务
|
|
196
|
+
curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
197
|
+
-H "Authorization: Bearer <token>" \
|
|
198
|
+
-H "Content-Type: application/json" \
|
|
199
|
+
-d '{
|
|
200
|
+
"url": "https://example.com/playlist.m3u8",
|
|
201
|
+
"format": "hls",
|
|
202
|
+
"output": "movie",
|
|
203
|
+
"headers": {"Referer": "https://example.com/"}
|
|
204
|
+
}'
|
|
205
|
+
|
|
206
|
+
# 任务列表 / 详情
|
|
207
|
+
curl http://127.0.0.1:8787/api/tasks -H "Authorization: Bearer <token>"
|
|
208
|
+
curl http://127.0.0.1:8787/api/tasks/<id> -H "Authorization: Bearer <token>"
|
|
209
|
+
|
|
210
|
+
# 取消任务
|
|
211
|
+
curl -X DELETE http://127.0.0.1:8787/api/tasks/<id> \
|
|
212
|
+
-H "Authorization: Bearer <token>"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- 全局最多 `--max-tasks` 个任务同时运行,其余 FIFO 排队;每个任务内部由 `jobs` 控制分片并发。
|
|
216
|
+
- 请求头通过 `headers` 对象传入,用于播放列表、分片与密钥请求,不会被持久化或返回。
|
|
217
|
+
- 状态文件默认 `<output-dir>/.m3u8-serve-state.json`;重启后未完成任务标记为 `interrupted`。
|
|
218
|
+
- 任务列表支持 `?state=running`、`?limit=50&offset=0`;`total` 为过滤后的数量。
|
|
219
|
+
- `--port 0` 时由系统分配端口,实际端口会打印在启动日志中。
|
|
220
|
+
|
|
221
|
+
## Chrome 扩展
|
|
222
|
+
|
|
223
|
+
`chrome-extension/` 提供一个 Manifest V3 扩展,用于识别页面中的 m3u8 链接:
|
|
224
|
+
|
|
225
|
+
- 监听页面网络请求并结合 DOM 扫描,列出当前标签页的 m3u8 地址;
|
|
226
|
+
- 复制链接,或生成带 `Referer`/`Cookie`/`User-Agent` 的 `m3u8-cli download` 命令;
|
|
227
|
+
- 一键提交到本地 `m3u8-cli serve` 下载,并在弹窗中查看进度、取消任务。
|
|
228
|
+
|
|
229
|
+
安装步骤:打开 `chrome://extensions`,开启“开发者模式”,点击“加载已解压的扩展程序”,选择仓库中的 `chrome-extension/` 目录。
|
|
230
|
+
|
|
231
|
+
建议以固定 token 和本机监听启动服务,然后在扩展设置中填入相同地址与 token:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
m3u8-cli serve --host 127.0.0.1 --port 8787 --token mytoken --output-dir ./downloads
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
权限、Cookie 处理与排障说明见源码仓库中的 `chrome-extension/README.md`。
|
|
238
|
+
|
|
239
|
+
发布或分发时,可生成只包含运行时文件的扩展 zip:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
uv run python scripts/build_extension.py
|
|
243
|
+
# 输出 dist/m3u8-cli-chrome-extension-<manifest 版本>.zip
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 下载输出结构
|
|
247
|
+
|
|
248
|
+
以 `-o movie.mp4` 为例:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
movie/
|
|
252
|
+
├── movie.mp4 # 最终产物
|
|
253
|
+
└── cache/
|
|
254
|
+
├── state.json # 播放列表指纹,用于缓存失效判断
|
|
255
|
+
├── segment_000000.bin # 已解密的媒体分片
|
|
256
|
+
├── map_0000.bin # fMP4 初始化片段(仅 fMP4 流)
|
|
257
|
+
├── combined.ts|mp4 # 本地合并的中间文件
|
|
258
|
+
└── movie.partial.mp4 # FFmpeg 临时输出
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- 任务目录由输出文件名自动派生;若输出路径已经是 `movie/movie.mp4` 形式,会复用该目录,不会重复嵌套。
|
|
262
|
+
- 最终文件生成成功后,缓存仍会保留在任务目录中。
|
|
263
|
+
|
|
264
|
+
## 缓存与续传
|
|
265
|
+
|
|
266
|
+
- 使用相同 `-o` 且任务目录仍在时,已存在且非空的分片会被直接复用。
|
|
267
|
+
- `cache/state.json` 记录播放列表原文的 SHA-256 指纹;播放列表变化时会清除旧分片。
|
|
268
|
+
- 下载时最终文件已存在且未指定 `--overwrite`,程序会直接退出。
|
|
269
|
+
- 需要彻底清理时,删除整个任务目录即可。
|
|
270
|
+
|
|
271
|
+
## 已知限制
|
|
272
|
+
|
|
273
|
+
- 不支持 `EXT-X-BYTERANGE`,也不支持带 BYTERANGE 的初始化片段。
|
|
274
|
+
- 只支持单个初始化片段(`EXT-X-MAP`)。
|
|
275
|
+
- 只支持 AES-128(`KEYFORMAT=identity`),不支持 SAMPLE-AES 或任何 DRM。
|
|
276
|
+
- 播放列表只抓取一次,不刷新直播或事件播放列表的滑动窗口。
|
|
277
|
+
- 不支持多音轨/多字幕轨道的选择性下载。
|
|
278
|
+
- `convert` 仅接受本地文件;`-c copy` 不做源编码兼容性预检。
|
|
279
|
+
- HLS 转换在 `--overwrite` 时会先删除旧 `playlist.m3u8` 和 `ts/` 中 6 位编号的分片,保留其他文件(`--encrypt` 时 `key.bin` 会被新密钥替换);转换失败将丢失旧产物,并可能留下部分新分片,新写入的 `key.bin` 也会被删除。
|
|
280
|
+
- HLS AES-128 加密不是 DRM;能访问 `key.bin` 的客户端即可解密。
|
|
281
|
+
- FFmpeg 的 HLS muxer 会写入固定 `IV=0`,所有分片共用同一密钥与 IV。
|
|
282
|
+
- 不支持自定义密钥、密钥 URI 或 IV。
|
|
283
|
+
- serve 不做 SSRF 内网地址屏蔽,依赖 token 与局域网部署。
|
|
284
|
+
- serve 不提供下载产物接口,文件留在服务器。
|
|
285
|
+
- serve 重启后任务标记为 `interrupted`,不会自动恢复。
|
|
286
|
+
- 请求头不落盘,重启后无法重放任务。
|
|
287
|
+
|
|
288
|
+
## 开发
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# 编译检查
|
|
292
|
+
uv run python -m compileall -q src
|
|
293
|
+
|
|
294
|
+
# 类型检查(使用 .venv)
|
|
295
|
+
pyright
|
|
296
|
+
|
|
297
|
+
# 单元测试
|
|
298
|
+
uv run python -m unittest discover -s tests -v
|
|
299
|
+
|
|
300
|
+
# Chrome 扩展:语法检查与单元测试
|
|
301
|
+
for f in chrome-extension/{lib,background,content,popup}.js; do node --check "$f"; done
|
|
302
|
+
node --test chrome-extension/lib.test.js
|
|
303
|
+
|
|
304
|
+
# 构建 Python 包(sdist 同时包含 chrome-extension/ 源码与打包脚本)
|
|
305
|
+
uv build
|
|
306
|
+
|
|
307
|
+
# 打包 Chrome 扩展 zip(输出到 dist/)
|
|
308
|
+
uv run python scripts/build_extension.py
|
|
309
|
+
|
|
310
|
+
# 或一次构建全部产物
|
|
311
|
+
uv build && uv run python scripts/build_extension.py
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
项目目前没有 lint 或 CI 配置,Python 单元测试使用标准库 unittest,Chrome 扩展的纯函数测试使用 Node 内置 test runner。
|
|
315
|
+
|
|
316
|
+
代码结构:
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
src/m3u8_cli/
|
|
320
|
+
├── cli.py # 命令行入口与流程编排
|
|
321
|
+
├── convert.py # 视频格式转换(FFmpeg)
|
|
322
|
+
├── playlist.py # m3u8 解析
|
|
323
|
+
├── download.py # 可复用下载核心(mp4/hls、进度、取消)
|
|
324
|
+
├── hls_export.py # 本地 HLS 导出
|
|
325
|
+
├── serve.py # REST API 与任务管理
|
|
326
|
+
├── downloader.py # 单分片下载、重试与 AES-128 解密
|
|
327
|
+
├── merge.py # 分片合并与 FFmpeg 封装
|
|
328
|
+
├── paths.py # 任务目录派生
|
|
329
|
+
├── errors.py # 共享异常
|
|
330
|
+
└── __main__.py # python -m m3u8_cli
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`chrome-extension/` 是配套的 Chrome 扩展(MV3、原生 JS、加载时无需构建),细节见其 README;`scripts/build_extension.py` 负责生成可分发的扩展 zip。
|
|
334
|
+
|
|
335
|
+
`reference/hls_downloader.py` 是早期把 m3u8 直接交给 FFmpeg 的轻量实现,仅作参考保留,不再维护。
|
|
336
|
+
|
|
337
|
+
## 许可证
|
|
338
|
+
|
|
339
|
+
本项目采用 [MIT](https://opensource.org/licenses/MIT) 许可证,详见仓库根目录的 `LICENSE` 文件。
|
|
340
|
+
|
|
341
|
+
## 免责声明
|
|
342
|
+
|
|
343
|
+
请仅下载你有权访问和保存的内容,并遵守相关网站的服务条款与版权要求。
|
m3u8_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# m3u8-cli
|
|
2
|
+
|
|
3
|
+
一个基于 Python 的 HLS(m3u8)视频下载与格式转换工具:并发下载分片、支持 AES-128 解密、本地缓存与断点续传,最后通过 FFmpeg 无损封装为 MP4;也可以把本地视频转换为 HLS 或其他常见格式。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **清晰度选择**:自动解析 master playlist,选择 `BANDWIDTH` 最高的变体。
|
|
8
|
+
- **并发下载**:多线程下载分片,默认 8 并发,可按需调整。
|
|
9
|
+
- **AES-128 解密**:支持标准 HLS AES-128 加密(`KEYFORMAT=identity`)。
|
|
10
|
+
- **断点续传**:分片保存在本地任务目录,中断后重新运行会复用已下载分片。
|
|
11
|
+
- **任务隔离**:最终视频与中间缓存集中放在以输出文件名命名的子目录中。
|
|
12
|
+
- **无损封装**:本地合并分片后一次性调用 FFmpeg,使用 `-c copy` 与 `+faststart` 封装为 MP4。
|
|
13
|
+
- **格式转换**:把本地视频转换为 HLS、MP4、MKV 或 WebM,支持 H.264、HEVC 与流复制。
|
|
14
|
+
|
|
15
|
+
## 环境要求
|
|
16
|
+
|
|
17
|
+
- Python 3.12+
|
|
18
|
+
- [FFmpeg](https://ffmpeg.org/)(需要能在 `PATH` 中找到 `ffmpeg`)
|
|
19
|
+
- [uv](https://docs.astral.sh/uv/) 或 pip(任选其一用于安装)
|
|
20
|
+
|
|
21
|
+
## 安装
|
|
22
|
+
|
|
23
|
+
### 使用 uv(推荐)
|
|
24
|
+
|
|
25
|
+
从 PyPI 安装为全局命令:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv tool install m3u8-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
升级:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool upgrade m3u8-cli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
也可以克隆后从本地源码安装:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone <仓库地址>
|
|
41
|
+
cd m3u8-cli
|
|
42
|
+
uv tool install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
安装后 `m3u8-cli` 已在 `PATH` 中,日常直接运行即可,不需要 `uv run`。
|
|
46
|
+
|
|
47
|
+
若参与开发,则在仓库目录中执行 `uv sync` 把依赖装进项目的 `.venv`,此时需要使用 `uv run` 前缀。
|
|
48
|
+
|
|
49
|
+
### 使用 pip
|
|
50
|
+
|
|
51
|
+
从 PyPI 安装:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install m3u8-cli
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
升级:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install --upgrade m3u8-cli
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
也可以克隆后从本地源码安装:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone <仓库地址>
|
|
67
|
+
cd m3u8-cli
|
|
68
|
+
pip install .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
若系统的 Python 启用了 externally-managed 保护(如 Arch、Debian 的默认配置),请先创建并激活虚拟环境再执行上述命令。
|
|
72
|
+
|
|
73
|
+
## 使用
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
m3u8-cli download "<m3u8_url>"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
也可以以模块方式运行:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python -m m3u8_cli download "<m3u8_url>"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
转换同样支持模块方式:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python -m m3u8_cli convert movie.mp4 -o out
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 常用示例
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# 默认输出到 output/output.mp4
|
|
95
|
+
m3u8-cli download "https://example.com/playlist.m3u8"
|
|
96
|
+
|
|
97
|
+
# 指定输出文件名与并发数
|
|
98
|
+
m3u8-cli download "https://example.com/playlist.m3u8" -o movie.mp4 -j 16
|
|
99
|
+
|
|
100
|
+
# 需要 Referer / Cookie 等请求头时
|
|
101
|
+
m3u8-cli download "https://example.com/playlist.m3u8" \
|
|
102
|
+
--referer "https://example.com/" \
|
|
103
|
+
--cookie "session=xxxx"
|
|
104
|
+
|
|
105
|
+
# 覆盖已存在的最终文件
|
|
106
|
+
m3u8-cli download "https://example.com/playlist.m3u8" -o movie.mp4 --overwrite
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 转换视频
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 默认转换为 HLS:当前目录下生成 movie/playlist.m3u8 与 movie/ts/*.ts
|
|
113
|
+
m3u8-cli convert movie.mp4
|
|
114
|
+
|
|
115
|
+
# 指定输出目录、格式与编码
|
|
116
|
+
m3u8-cli convert movie.mp4 -f mkv -c hevc -o out
|
|
117
|
+
|
|
118
|
+
# 转换 HLS 并设置每片 10 秒
|
|
119
|
+
m3u8-cli convert movie.mp4 -f hls --segment-time 10
|
|
120
|
+
|
|
121
|
+
# 覆盖已有产物
|
|
122
|
+
m3u8-cli convert movie.mp4 -f mp4 --overwrite
|
|
123
|
+
|
|
124
|
+
# 加密 HLS 分片(生成 key.bin)
|
|
125
|
+
m3u8-cli convert movie.mp4 --encrypt
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| 参数 | 默认值 | 说明 |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| `input` | 必填 | 本地视频文件路径 |
|
|
131
|
+
| `-f, --format` | `hls` | `hls`、`mp4`、`mkv`、`webm` |
|
|
132
|
+
| `-c, --codec` | `h264` | `h264`、`hevc`、`copy` |
|
|
133
|
+
| `-o, --output-dir` | 输入文件 stem | 输出目录 |
|
|
134
|
+
| `--segment-time` | `6` | 仅 HLS 使用 |
|
|
135
|
+
| `--overwrite` | 关闭 | 覆盖已有产物 |
|
|
136
|
+
| `--encrypt` | 关闭 | 仅 HLS;生成 `key.bin` 并加密分片 |
|
|
137
|
+
|
|
138
|
+
HLS 输出固定为 `playlist.m3u8` 加 `ts/000000.ts` 起的分片;WebM 重编码时使用 VP9 + Opus,`-c copy` 则直接复制源编码并由 FFmpeg 判断兼容性。
|
|
139
|
+
|
|
140
|
+
- `mp4`/`mkv`/`webm` 输出为 `<输出目录>/<输入文件 stem>.<扩展名>`,例如 `m3u8-cli convert movie.mp4 -f mkv -o out` 生成 `out/movie.mkv`;单文件先写入 `.partial.*`,成功后原子替换。
|
|
141
|
+
- `-c copy` 时 HLS 分片边界由源关键帧决定,`--segment-time` 只是近似目标。
|
|
142
|
+
- `--encrypt` 仅用于 HLS:输出目录会额外生成 16 字节的 `key.bin`,播放列表通过相对 URI 引用;分发时必须与 `playlist.m3u8` 放在一起。
|
|
143
|
+
- FFmpeg/ffprobe 默认拒绝 `key.bin` 扩展名,本地播放或探测需加 `-allowed_extensions ALL`;浏览器播放器不受此限制。
|
|
144
|
+
- 每次 `--encrypt` 转换都会生成新密钥;未指定 `--overwrite` 时已有 `key.bin` 或 `.key_info.txt` 会阻止转换,指定后会替换旧密钥,需要重新分发 `key.bin`。
|
|
145
|
+
|
|
146
|
+
### 下载参数
|
|
147
|
+
|
|
148
|
+
| 参数 | 默认值 | 说明 |
|
|
149
|
+
| --- | --- | --- |
|
|
150
|
+
| `url` | 必填 | m3u8 播放列表地址 |
|
|
151
|
+
| `-f, --format` | `mp4` | `mp4`(合并封装)或 `hls`(保留分片,不合并) |
|
|
152
|
+
| `-o, --output` | `output.mp4` / `output` | mp4 为最终文件名;hls 为任务目录(格式不同,默认值不同) |
|
|
153
|
+
| `-j, --jobs` | `8` | 并发下载线程数 |
|
|
154
|
+
| `--retries` | `3` | 单个分片下载失败后的重试次数 |
|
|
155
|
+
| `--timeout` | `30` | 网络请求超时(秒) |
|
|
156
|
+
| `--referer` | 无 | 自定义 `Referer` 请求头 |
|
|
157
|
+
| `--cookie` | 无 | 自定义 `Cookie` 请求头 |
|
|
158
|
+
| `--user-agent` | `Mozilla/5.0` | 自定义 `User-Agent` 请求头 |
|
|
159
|
+
| `--overwrite` | 关闭 | 覆盖已存在的最终文件 |
|
|
160
|
+
|
|
161
|
+
### 保留 HLS(不合并)
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# 输出 movie/playlist.m3u8 与 movie/000000.ts 等分片
|
|
165
|
+
m3u8-cli download "<m3u8_url>" -f hls -o movie
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- `-f hls` 不调用 FFmpeg;分片保持服务器返回的原始字节。
|
|
169
|
+
- 普通 TS 流输出 `playlist.m3u8` 与 6 位编号的 `.ts` 分片;fMP4 流输出 `init.mp4` 与 `.m4s` 分片。
|
|
170
|
+
- 源流为 AES-128 加密时保持密文,同时把密钥下载为 `key.bin`(多个不同密钥时为 `key_000.bin`、`key_001.bin`),播放列表的 `EXT-X-KEY` URI 重写为本地文件。
|
|
171
|
+
- 分片、`playlist.m3u8` 与密钥文件位于同一目录;`--overwrite` 只清理本工具生成的条目。
|
|
172
|
+
|
|
173
|
+
### 启动下载服务
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
m3u8-cli serve --host 0.0.0.0 --port 8787 --output-dir ./downloads
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
启动日志会打印监听地址、输出目录与 API token;省略 --token 时会自动生成。
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# 创建任务
|
|
183
|
+
curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
184
|
+
-H "Authorization: Bearer <token>" \
|
|
185
|
+
-H "Content-Type: application/json" \
|
|
186
|
+
-d '{
|
|
187
|
+
"url": "https://example.com/playlist.m3u8",
|
|
188
|
+
"format": "hls",
|
|
189
|
+
"output": "movie",
|
|
190
|
+
"headers": {"Referer": "https://example.com/"}
|
|
191
|
+
}'
|
|
192
|
+
|
|
193
|
+
# 任务列表 / 详情
|
|
194
|
+
curl http://127.0.0.1:8787/api/tasks -H "Authorization: Bearer <token>"
|
|
195
|
+
curl http://127.0.0.1:8787/api/tasks/<id> -H "Authorization: Bearer <token>"
|
|
196
|
+
|
|
197
|
+
# 取消任务
|
|
198
|
+
curl -X DELETE http://127.0.0.1:8787/api/tasks/<id> \
|
|
199
|
+
-H "Authorization: Bearer <token>"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- 全局最多 `--max-tasks` 个任务同时运行,其余 FIFO 排队;每个任务内部由 `jobs` 控制分片并发。
|
|
203
|
+
- 请求头通过 `headers` 对象传入,用于播放列表、分片与密钥请求,不会被持久化或返回。
|
|
204
|
+
- 状态文件默认 `<output-dir>/.m3u8-serve-state.json`;重启后未完成任务标记为 `interrupted`。
|
|
205
|
+
- 任务列表支持 `?state=running`、`?limit=50&offset=0`;`total` 为过滤后的数量。
|
|
206
|
+
- `--port 0` 时由系统分配端口,实际端口会打印在启动日志中。
|
|
207
|
+
|
|
208
|
+
## Chrome 扩展
|
|
209
|
+
|
|
210
|
+
`chrome-extension/` 提供一个 Manifest V3 扩展,用于识别页面中的 m3u8 链接:
|
|
211
|
+
|
|
212
|
+
- 监听页面网络请求并结合 DOM 扫描,列出当前标签页的 m3u8 地址;
|
|
213
|
+
- 复制链接,或生成带 `Referer`/`Cookie`/`User-Agent` 的 `m3u8-cli download` 命令;
|
|
214
|
+
- 一键提交到本地 `m3u8-cli serve` 下载,并在弹窗中查看进度、取消任务。
|
|
215
|
+
|
|
216
|
+
安装步骤:打开 `chrome://extensions`,开启“开发者模式”,点击“加载已解压的扩展程序”,选择仓库中的 `chrome-extension/` 目录。
|
|
217
|
+
|
|
218
|
+
建议以固定 token 和本机监听启动服务,然后在扩展设置中填入相同地址与 token:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
m3u8-cli serve --host 127.0.0.1 --port 8787 --token mytoken --output-dir ./downloads
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
权限、Cookie 处理与排障说明见源码仓库中的 `chrome-extension/README.md`。
|
|
225
|
+
|
|
226
|
+
发布或分发时,可生成只包含运行时文件的扩展 zip:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
uv run python scripts/build_extension.py
|
|
230
|
+
# 输出 dist/m3u8-cli-chrome-extension-<manifest 版本>.zip
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## 下载输出结构
|
|
234
|
+
|
|
235
|
+
以 `-o movie.mp4` 为例:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
movie/
|
|
239
|
+
├── movie.mp4 # 最终产物
|
|
240
|
+
└── cache/
|
|
241
|
+
├── state.json # 播放列表指纹,用于缓存失效判断
|
|
242
|
+
├── segment_000000.bin # 已解密的媒体分片
|
|
243
|
+
├── map_0000.bin # fMP4 初始化片段(仅 fMP4 流)
|
|
244
|
+
├── combined.ts|mp4 # 本地合并的中间文件
|
|
245
|
+
└── movie.partial.mp4 # FFmpeg 临时输出
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- 任务目录由输出文件名自动派生;若输出路径已经是 `movie/movie.mp4` 形式,会复用该目录,不会重复嵌套。
|
|
249
|
+
- 最终文件生成成功后,缓存仍会保留在任务目录中。
|
|
250
|
+
|
|
251
|
+
## 缓存与续传
|
|
252
|
+
|
|
253
|
+
- 使用相同 `-o` 且任务目录仍在时,已存在且非空的分片会被直接复用。
|
|
254
|
+
- `cache/state.json` 记录播放列表原文的 SHA-256 指纹;播放列表变化时会清除旧分片。
|
|
255
|
+
- 下载时最终文件已存在且未指定 `--overwrite`,程序会直接退出。
|
|
256
|
+
- 需要彻底清理时,删除整个任务目录即可。
|
|
257
|
+
|
|
258
|
+
## 已知限制
|
|
259
|
+
|
|
260
|
+
- 不支持 `EXT-X-BYTERANGE`,也不支持带 BYTERANGE 的初始化片段。
|
|
261
|
+
- 只支持单个初始化片段(`EXT-X-MAP`)。
|
|
262
|
+
- 只支持 AES-128(`KEYFORMAT=identity`),不支持 SAMPLE-AES 或任何 DRM。
|
|
263
|
+
- 播放列表只抓取一次,不刷新直播或事件播放列表的滑动窗口。
|
|
264
|
+
- 不支持多音轨/多字幕轨道的选择性下载。
|
|
265
|
+
- `convert` 仅接受本地文件;`-c copy` 不做源编码兼容性预检。
|
|
266
|
+
- HLS 转换在 `--overwrite` 时会先删除旧 `playlist.m3u8` 和 `ts/` 中 6 位编号的分片,保留其他文件(`--encrypt` 时 `key.bin` 会被新密钥替换);转换失败将丢失旧产物,并可能留下部分新分片,新写入的 `key.bin` 也会被删除。
|
|
267
|
+
- HLS AES-128 加密不是 DRM;能访问 `key.bin` 的客户端即可解密。
|
|
268
|
+
- FFmpeg 的 HLS muxer 会写入固定 `IV=0`,所有分片共用同一密钥与 IV。
|
|
269
|
+
- 不支持自定义密钥、密钥 URI 或 IV。
|
|
270
|
+
- serve 不做 SSRF 内网地址屏蔽,依赖 token 与局域网部署。
|
|
271
|
+
- serve 不提供下载产物接口,文件留在服务器。
|
|
272
|
+
- serve 重启后任务标记为 `interrupted`,不会自动恢复。
|
|
273
|
+
- 请求头不落盘,重启后无法重放任务。
|
|
274
|
+
|
|
275
|
+
## 开发
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# 编译检查
|
|
279
|
+
uv run python -m compileall -q src
|
|
280
|
+
|
|
281
|
+
# 类型检查(使用 .venv)
|
|
282
|
+
pyright
|
|
283
|
+
|
|
284
|
+
# 单元测试
|
|
285
|
+
uv run python -m unittest discover -s tests -v
|
|
286
|
+
|
|
287
|
+
# Chrome 扩展:语法检查与单元测试
|
|
288
|
+
for f in chrome-extension/{lib,background,content,popup}.js; do node --check "$f"; done
|
|
289
|
+
node --test chrome-extension/lib.test.js
|
|
290
|
+
|
|
291
|
+
# 构建 Python 包(sdist 同时包含 chrome-extension/ 源码与打包脚本)
|
|
292
|
+
uv build
|
|
293
|
+
|
|
294
|
+
# 打包 Chrome 扩展 zip(输出到 dist/)
|
|
295
|
+
uv run python scripts/build_extension.py
|
|
296
|
+
|
|
297
|
+
# 或一次构建全部产物
|
|
298
|
+
uv build && uv run python scripts/build_extension.py
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
项目目前没有 lint 或 CI 配置,Python 单元测试使用标准库 unittest,Chrome 扩展的纯函数测试使用 Node 内置 test runner。
|
|
302
|
+
|
|
303
|
+
代码结构:
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
src/m3u8_cli/
|
|
307
|
+
├── cli.py # 命令行入口与流程编排
|
|
308
|
+
├── convert.py # 视频格式转换(FFmpeg)
|
|
309
|
+
├── playlist.py # m3u8 解析
|
|
310
|
+
├── download.py # 可复用下载核心(mp4/hls、进度、取消)
|
|
311
|
+
├── hls_export.py # 本地 HLS 导出
|
|
312
|
+
├── serve.py # REST API 与任务管理
|
|
313
|
+
├── downloader.py # 单分片下载、重试与 AES-128 解密
|
|
314
|
+
├── merge.py # 分片合并与 FFmpeg 封装
|
|
315
|
+
├── paths.py # 任务目录派生
|
|
316
|
+
├── errors.py # 共享异常
|
|
317
|
+
└── __main__.py # python -m m3u8_cli
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`chrome-extension/` 是配套的 Chrome 扩展(MV3、原生 JS、加载时无需构建),细节见其 README;`scripts/build_extension.py` 负责生成可分发的扩展 zip。
|
|
321
|
+
|
|
322
|
+
`reference/hls_downloader.py` 是早期把 m3u8 直接交给 FFmpeg 的轻量实现,仅作参考保留,不再维护。
|
|
323
|
+
|
|
324
|
+
## 许可证
|
|
325
|
+
|
|
326
|
+
本项目采用 [MIT](https://opensource.org/licenses/MIT) 许可证,详见仓库根目录的 `LICENSE` 文件。
|
|
327
|
+
|
|
328
|
+
## 免责声明
|
|
329
|
+
|
|
330
|
+
请仅下载你有权访问和保存的内容,并遵守相关网站的服务条款与版权要求。
|