m3u8-cli 0.1.0__tar.gz → 0.2.1__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 → m3u8_cli-0.2.1}/PKG-INFO +64 -17
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/README.md +63 -16
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/README.md +28 -11
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/background.js +110 -33
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/content.js +109 -1
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/lib.js +72 -1
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/lib.test.js +74 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/manifest.json +1 -1
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/popup.css +189 -25
- m3u8_cli-0.2.1/chrome-extension/popup.html +293 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/chrome-extension/popup.js +186 -8
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/pyproject.toml +1 -1
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/pyproject.toml.orig +1 -1
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/__init__.py +1 -1
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/cli.py +13 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/download.py +114 -9
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/downloader.py +3 -1
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/hls_export.py +2 -2
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/merge.py +6 -3
- m3u8_cli-0.2.1/src/m3u8_cli/paths.py +98 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/playlist.py +6 -5
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/serve.py +60 -14
- m3u8_cli-0.1.0/chrome-extension/popup.html +0 -101
- m3u8_cli-0.1.0/src/m3u8_cli/paths.py +0 -30
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/LICENSE +0 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/scripts/build_extension.py +0 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/__main__.py +0 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/convert.py +0 -0
- {m3u8_cli-0.1.0 → m3u8_cli-0.2.1}/src/m3u8_cli/errors.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: m3u8-cli
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: HLS 视频下载与格式转换工具
|
|
5
5
|
Author: r1a
|
|
6
6
|
Author-email: r1a <m@zcq100.com>
|
|
@@ -101,10 +101,16 @@ python -m m3u8_cli download "<m3u8_url>"
|
|
|
101
101
|
python -m m3u8_cli convert movie.mp4 -o out
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
查看版本:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
m3u8-cli -v # 或 --version
|
|
108
|
+
```
|
|
109
|
+
|
|
104
110
|
### 常用示例
|
|
105
111
|
|
|
106
112
|
```bash
|
|
107
|
-
#
|
|
113
|
+
# 默认输出到当前目录的 output.mp4
|
|
108
114
|
m3u8-cli download "https://example.com/playlist.m3u8"
|
|
109
115
|
|
|
110
116
|
# 指定输出文件名与并发数
|
|
@@ -170,6 +176,7 @@ HLS 输出固定为 `playlist.m3u8` 加 `ts/000000.ts` 起的分片;WebM 重
|
|
|
170
176
|
| `--cookie` | 无 | 自定义 `Cookie` 请求头 |
|
|
171
177
|
| `--user-agent` | `Mozilla/5.0` | 自定义 `User-Agent` 请求头 |
|
|
172
178
|
| `--overwrite` | 关闭 | 覆盖已存在的最终文件 |
|
|
179
|
+
| `-k, --insecure` | 关闭 | 跳过 TLS 证书校验,仅用于自签名或内网服务 |
|
|
173
180
|
|
|
174
181
|
### 保留 HLS(不合并)
|
|
175
182
|
|
|
@@ -183,6 +190,8 @@ m3u8-cli download "<m3u8_url>" -f hls -o movie
|
|
|
183
190
|
- 源流为 AES-128 加密时保持密文,同时把密钥下载为 `key.bin`(多个不同密钥时为 `key_000.bin`、`key_001.bin`),播放列表的 `EXT-X-KEY` URI 重写为本地文件。
|
|
184
191
|
- 分片、`playlist.m3u8` 与密钥文件位于同一目录;`--overwrite` 只清理本工具生成的条目。
|
|
185
192
|
|
|
193
|
+
`--insecure` 会关闭播放列表、分片与密钥请求的证书和主机名校验,只应在信任的网络上对自签名服务使用;它不会影响元数据中记录的其他信息。
|
|
194
|
+
|
|
186
195
|
### 启动下载服务
|
|
187
196
|
|
|
188
197
|
```bash
|
|
@@ -191,6 +200,11 @@ m3u8-cli serve --host 0.0.0.0 --port 8787 --output-dir ./downloads
|
|
|
191
200
|
|
|
192
201
|
启动日志会打印监听地址、输出目录与 API token;省略 --token 时会自动生成。
|
|
193
202
|
|
|
203
|
+
来源为回环地址(`127.0.0.0/8`、`::1`,含 IPv4-mapped IPv6)的请求**免 token**,方便本机扩展/脚本直接使用;其他来源仍需 `Authorization: Bearer <token>`。注意:
|
|
204
|
+
|
|
205
|
+
- 这意味着同一台机器上的其他用户或进程也能免 token 访问;如果通过监听在本机的反向代理转发请求,所有流量看起来都来自回环地址。
|
|
206
|
+
- API 的 CORS 为 `*`,免 token 也意味着浏览器里的任意网页都能调用本机 serve(读取任务、提交下载)。如果在意这一点,请保留 Token(网页无法获取 Token),或自行收紧 CORS。
|
|
207
|
+
|
|
194
208
|
```bash
|
|
195
209
|
# 创建任务
|
|
196
210
|
curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
@@ -200,6 +214,7 @@ curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
|
200
214
|
"url": "https://example.com/playlist.m3u8",
|
|
201
215
|
"format": "hls",
|
|
202
216
|
"output": "movie",
|
|
217
|
+
"insecure": false,
|
|
203
218
|
"headers": {"Referer": "https://example.com/"}
|
|
204
219
|
}'
|
|
205
220
|
|
|
@@ -207,6 +222,9 @@ curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
|
207
222
|
curl http://127.0.0.1:8787/api/tasks -H "Authorization: Bearer <token>"
|
|
208
223
|
curl http://127.0.0.1:8787/api/tasks/<id> -H "Authorization: Bearer <token>"
|
|
209
224
|
|
|
225
|
+
# 服务版本
|
|
226
|
+
curl http://127.0.0.1:8787/api/version -H "Authorization: Bearer <token>"
|
|
227
|
+
|
|
210
228
|
# 取消任务
|
|
211
229
|
curl -X DELETE http://127.0.0.1:8787/api/tasks/<id> \
|
|
212
230
|
-H "Authorization: Bearer <token>"
|
|
@@ -223,8 +241,10 @@ curl -X DELETE http://127.0.0.1:8787/api/tasks/<id> \
|
|
|
223
241
|
`chrome-extension/` 提供一个 Manifest V3 扩展,用于识别页面中的 m3u8 链接:
|
|
224
242
|
|
|
225
243
|
- 监听页面网络请求并结合 DOM 扫描,列出当前标签页的 m3u8 地址;
|
|
244
|
+
- 采集页面标题(`og:title`、Twitter Card、JSON-LD、`document.title`)作为默认下载文件名,也可在设置中改用 URL 短 hash;
|
|
226
245
|
- 复制链接,或生成带 `Referer`/`Cookie`/`User-Agent` 的 `m3u8-cli download` 命令;
|
|
227
|
-
- 一键提交到本地 `m3u8-cli serve`
|
|
246
|
+
- 一键提交到本地 `m3u8-cli serve` 下载,并在弹窗中查看进度、取消任务;
|
|
247
|
+
- 设置页显示扩展版本与后端 `serve` 版本(依赖 `GET /api/version`)。
|
|
228
248
|
|
|
229
249
|
安装步骤:打开 `chrome://extensions`,开启“开发者模式”,点击“加载已解压的扩展程序”,选择仓库中的 `chrome-extension/` 目录。
|
|
230
250
|
|
|
@@ -245,28 +265,53 @@ uv run python scripts/build_extension.py
|
|
|
245
265
|
|
|
246
266
|
## 下载输出结构
|
|
247
267
|
|
|
248
|
-
|
|
268
|
+
### MP4(默认,合并封装)
|
|
269
|
+
|
|
270
|
+
以 `-o movie.mp4` 为例,输出目录下只有最终产物和元数据:
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
movie.mp4
|
|
274
|
+
movie.mp4.meta.json # ffprobe 的 format/streams + source 来源信息
|
|
275
|
+
movie.partial.mp4 # FFmpeg 临时输出(仅在合并过程中存在)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
分片缓存不落在输出目录,而是位于系统临时目录:
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
<TMP>/m3u8-cli-<uid>/<最终路径哈希>/
|
|
282
|
+
├── state.json # 播放列表指纹,用于缓存失效判断
|
|
283
|
+
├── segment_000000.bin # 已解密的媒体分片
|
|
284
|
+
├── map_0000.bin # fMP4 初始化片段(仅 fMP4 流)
|
|
285
|
+
└── combined.ts|mp4 # 本地合并的中间文件
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
- 合并成功后整个缓存目录会被删除;失败或取消时保留,便于续传。
|
|
289
|
+
- 同一 `-o` 对应同一个缓存目录;播放列表变化时 `state.json` 指纹会触发旧分片清理。
|
|
290
|
+
- 元数据文件由 ffprobe 读取产物生成:顶层 `format`/`streams` 为 `ffprobe -show_format -show_streams -of json` 的输出(已去掉 `filename` 路径),`source` 记录 URL、格式、分片数、指纹、时间与是否校验 TLS;不包含 `output` 字段,也不包含 `Referer`/`Cookie`/`User-Agent`。
|
|
291
|
+
- 未安装 ffprobe 时会在 stderr 打印警告,元数据仅保留 `source` 部分,下载仍继续进行。
|
|
292
|
+
|
|
293
|
+
### HLS(保留分片)
|
|
294
|
+
|
|
295
|
+
以 `-o movie` 为例:
|
|
249
296
|
|
|
250
297
|
```
|
|
251
298
|
movie/
|
|
252
|
-
├──
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
├── map_0000.bin # fMP4 初始化片段(仅 fMP4 流)
|
|
257
|
-
├── combined.ts|mp4 # 本地合并的中间文件
|
|
258
|
-
└── movie.partial.mp4 # FFmpeg 临时输出
|
|
299
|
+
├── playlist.m3u8 # 本地播放列表
|
|
300
|
+
├── 000000.ts # 分片(fMP4 为 .m4s,另有 init.mp4)
|
|
301
|
+
├── key.bin # 存在 AES-128 密钥时
|
|
302
|
+
└── meta.json # ffprobe 媒体信息 + source 来源信息
|
|
259
303
|
```
|
|
260
304
|
|
|
261
|
-
-
|
|
262
|
-
-
|
|
305
|
+
- 分片先下载到系统临时目录,导出成功后清理;输出目录只保留最终产物和元数据。
|
|
306
|
+
- 导出失败或取消时缓存保留,便于用相同 `-o` 续传。
|
|
263
307
|
|
|
264
308
|
## 缓存与续传
|
|
265
309
|
|
|
266
|
-
-
|
|
267
|
-
- `
|
|
310
|
+
- 两种格式的分片缓存都位于 `<系统临时目录>/m3u8-cli-<uid>/<最终路径 SHA-256 前 16 位>/`;成功下载后删除,失败/取消时保留。
|
|
311
|
+
- 使用相同 `-o` 且缓存仍在时,已存在且非空的分片会被直接复用。
|
|
312
|
+
- 缓存中的 `state.json` 记录播放列表原文的 SHA-256 指纹;播放列表变化时会清除旧分片。
|
|
268
313
|
- 下载时最终文件已存在且未指定 `--overwrite`,程序会直接退出。
|
|
269
|
-
-
|
|
314
|
+
- 操作系统清理临时目录后,失败任务需要重新下载;需要彻底清理时删除对应的临时缓存目录即可。
|
|
270
315
|
|
|
271
316
|
## 已知限制
|
|
272
317
|
|
|
@@ -280,7 +325,9 @@ movie/
|
|
|
280
325
|
- HLS AES-128 加密不是 DRM;能访问 `key.bin` 的客户端即可解密。
|
|
281
326
|
- FFmpeg 的 HLS muxer 会写入固定 `IV=0`,所有分片共用同一密钥与 IV。
|
|
282
327
|
- 不支持自定义密钥、密钥 URI 或 IV。
|
|
283
|
-
- serve 不做 SSRF
|
|
328
|
+
- serve 不做 SSRF 内网地址屏蔽;回环地址请求免 token,非本机来源仍依赖 token 与局域网部署。
|
|
329
|
+
- 回环免 token 对同一台机器上的所有用户生效;多用户主机或本机反向代理场景请谨慎,必要时只监听 `127.0.0.1`。
|
|
330
|
+
- Windows 默认 `MAX_PATH=260`:serve 会按输出目录长度截短过长的文件名(保留扩展名),任务响应中返回实际名字;CLI 的 `-o` 不做改写。
|
|
284
331
|
- serve 不提供下载产物接口,文件留在服务器。
|
|
285
332
|
- serve 重启后任务标记为 `interrupted`,不会自动恢复。
|
|
286
333
|
- 请求头不落盘,重启后无法重放任务。
|
|
@@ -88,10 +88,16 @@ python -m m3u8_cli download "<m3u8_url>"
|
|
|
88
88
|
python -m m3u8_cli convert movie.mp4 -o out
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
查看版本:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
m3u8-cli -v # 或 --version
|
|
95
|
+
```
|
|
96
|
+
|
|
91
97
|
### 常用示例
|
|
92
98
|
|
|
93
99
|
```bash
|
|
94
|
-
#
|
|
100
|
+
# 默认输出到当前目录的 output.mp4
|
|
95
101
|
m3u8-cli download "https://example.com/playlist.m3u8"
|
|
96
102
|
|
|
97
103
|
# 指定输出文件名与并发数
|
|
@@ -157,6 +163,7 @@ HLS 输出固定为 `playlist.m3u8` 加 `ts/000000.ts` 起的分片;WebM 重
|
|
|
157
163
|
| `--cookie` | 无 | 自定义 `Cookie` 请求头 |
|
|
158
164
|
| `--user-agent` | `Mozilla/5.0` | 自定义 `User-Agent` 请求头 |
|
|
159
165
|
| `--overwrite` | 关闭 | 覆盖已存在的最终文件 |
|
|
166
|
+
| `-k, --insecure` | 关闭 | 跳过 TLS 证书校验,仅用于自签名或内网服务 |
|
|
160
167
|
|
|
161
168
|
### 保留 HLS(不合并)
|
|
162
169
|
|
|
@@ -170,6 +177,8 @@ m3u8-cli download "<m3u8_url>" -f hls -o movie
|
|
|
170
177
|
- 源流为 AES-128 加密时保持密文,同时把密钥下载为 `key.bin`(多个不同密钥时为 `key_000.bin`、`key_001.bin`),播放列表的 `EXT-X-KEY` URI 重写为本地文件。
|
|
171
178
|
- 分片、`playlist.m3u8` 与密钥文件位于同一目录;`--overwrite` 只清理本工具生成的条目。
|
|
172
179
|
|
|
180
|
+
`--insecure` 会关闭播放列表、分片与密钥请求的证书和主机名校验,只应在信任的网络上对自签名服务使用;它不会影响元数据中记录的其他信息。
|
|
181
|
+
|
|
173
182
|
### 启动下载服务
|
|
174
183
|
|
|
175
184
|
```bash
|
|
@@ -178,6 +187,11 @@ m3u8-cli serve --host 0.0.0.0 --port 8787 --output-dir ./downloads
|
|
|
178
187
|
|
|
179
188
|
启动日志会打印监听地址、输出目录与 API token;省略 --token 时会自动生成。
|
|
180
189
|
|
|
190
|
+
来源为回环地址(`127.0.0.0/8`、`::1`,含 IPv4-mapped IPv6)的请求**免 token**,方便本机扩展/脚本直接使用;其他来源仍需 `Authorization: Bearer <token>`。注意:
|
|
191
|
+
|
|
192
|
+
- 这意味着同一台机器上的其他用户或进程也能免 token 访问;如果通过监听在本机的反向代理转发请求,所有流量看起来都来自回环地址。
|
|
193
|
+
- API 的 CORS 为 `*`,免 token 也意味着浏览器里的任意网页都能调用本机 serve(读取任务、提交下载)。如果在意这一点,请保留 Token(网页无法获取 Token),或自行收紧 CORS。
|
|
194
|
+
|
|
181
195
|
```bash
|
|
182
196
|
# 创建任务
|
|
183
197
|
curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
@@ -187,6 +201,7 @@ curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
|
187
201
|
"url": "https://example.com/playlist.m3u8",
|
|
188
202
|
"format": "hls",
|
|
189
203
|
"output": "movie",
|
|
204
|
+
"insecure": false,
|
|
190
205
|
"headers": {"Referer": "https://example.com/"}
|
|
191
206
|
}'
|
|
192
207
|
|
|
@@ -194,6 +209,9 @@ curl -X POST http://127.0.0.1:8787/api/tasks \
|
|
|
194
209
|
curl http://127.0.0.1:8787/api/tasks -H "Authorization: Bearer <token>"
|
|
195
210
|
curl http://127.0.0.1:8787/api/tasks/<id> -H "Authorization: Bearer <token>"
|
|
196
211
|
|
|
212
|
+
# 服务版本
|
|
213
|
+
curl http://127.0.0.1:8787/api/version -H "Authorization: Bearer <token>"
|
|
214
|
+
|
|
197
215
|
# 取消任务
|
|
198
216
|
curl -X DELETE http://127.0.0.1:8787/api/tasks/<id> \
|
|
199
217
|
-H "Authorization: Bearer <token>"
|
|
@@ -210,8 +228,10 @@ curl -X DELETE http://127.0.0.1:8787/api/tasks/<id> \
|
|
|
210
228
|
`chrome-extension/` 提供一个 Manifest V3 扩展,用于识别页面中的 m3u8 链接:
|
|
211
229
|
|
|
212
230
|
- 监听页面网络请求并结合 DOM 扫描,列出当前标签页的 m3u8 地址;
|
|
231
|
+
- 采集页面标题(`og:title`、Twitter Card、JSON-LD、`document.title`)作为默认下载文件名,也可在设置中改用 URL 短 hash;
|
|
213
232
|
- 复制链接,或生成带 `Referer`/`Cookie`/`User-Agent` 的 `m3u8-cli download` 命令;
|
|
214
|
-
- 一键提交到本地 `m3u8-cli serve`
|
|
233
|
+
- 一键提交到本地 `m3u8-cli serve` 下载,并在弹窗中查看进度、取消任务;
|
|
234
|
+
- 设置页显示扩展版本与后端 `serve` 版本(依赖 `GET /api/version`)。
|
|
215
235
|
|
|
216
236
|
安装步骤:打开 `chrome://extensions`,开启“开发者模式”,点击“加载已解压的扩展程序”,选择仓库中的 `chrome-extension/` 目录。
|
|
217
237
|
|
|
@@ -232,28 +252,53 @@ uv run python scripts/build_extension.py
|
|
|
232
252
|
|
|
233
253
|
## 下载输出结构
|
|
234
254
|
|
|
235
|
-
|
|
255
|
+
### MP4(默认,合并封装)
|
|
256
|
+
|
|
257
|
+
以 `-o movie.mp4` 为例,输出目录下只有最终产物和元数据:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
movie.mp4
|
|
261
|
+
movie.mp4.meta.json # ffprobe 的 format/streams + source 来源信息
|
|
262
|
+
movie.partial.mp4 # FFmpeg 临时输出(仅在合并过程中存在)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
分片缓存不落在输出目录,而是位于系统临时目录:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
<TMP>/m3u8-cli-<uid>/<最终路径哈希>/
|
|
269
|
+
├── state.json # 播放列表指纹,用于缓存失效判断
|
|
270
|
+
├── segment_000000.bin # 已解密的媒体分片
|
|
271
|
+
├── map_0000.bin # fMP4 初始化片段(仅 fMP4 流)
|
|
272
|
+
└── combined.ts|mp4 # 本地合并的中间文件
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
- 合并成功后整个缓存目录会被删除;失败或取消时保留,便于续传。
|
|
276
|
+
- 同一 `-o` 对应同一个缓存目录;播放列表变化时 `state.json` 指纹会触发旧分片清理。
|
|
277
|
+
- 元数据文件由 ffprobe 读取产物生成:顶层 `format`/`streams` 为 `ffprobe -show_format -show_streams -of json` 的输出(已去掉 `filename` 路径),`source` 记录 URL、格式、分片数、指纹、时间与是否校验 TLS;不包含 `output` 字段,也不包含 `Referer`/`Cookie`/`User-Agent`。
|
|
278
|
+
- 未安装 ffprobe 时会在 stderr 打印警告,元数据仅保留 `source` 部分,下载仍继续进行。
|
|
279
|
+
|
|
280
|
+
### HLS(保留分片)
|
|
281
|
+
|
|
282
|
+
以 `-o movie` 为例:
|
|
236
283
|
|
|
237
284
|
```
|
|
238
285
|
movie/
|
|
239
|
-
├──
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
├── map_0000.bin # fMP4 初始化片段(仅 fMP4 流)
|
|
244
|
-
├── combined.ts|mp4 # 本地合并的中间文件
|
|
245
|
-
└── movie.partial.mp4 # FFmpeg 临时输出
|
|
286
|
+
├── playlist.m3u8 # 本地播放列表
|
|
287
|
+
├── 000000.ts # 分片(fMP4 为 .m4s,另有 init.mp4)
|
|
288
|
+
├── key.bin # 存在 AES-128 密钥时
|
|
289
|
+
└── meta.json # ffprobe 媒体信息 + source 来源信息
|
|
246
290
|
```
|
|
247
291
|
|
|
248
|
-
-
|
|
249
|
-
-
|
|
292
|
+
- 分片先下载到系统临时目录,导出成功后清理;输出目录只保留最终产物和元数据。
|
|
293
|
+
- 导出失败或取消时缓存保留,便于用相同 `-o` 续传。
|
|
250
294
|
|
|
251
295
|
## 缓存与续传
|
|
252
296
|
|
|
253
|
-
-
|
|
254
|
-
- `
|
|
297
|
+
- 两种格式的分片缓存都位于 `<系统临时目录>/m3u8-cli-<uid>/<最终路径 SHA-256 前 16 位>/`;成功下载后删除,失败/取消时保留。
|
|
298
|
+
- 使用相同 `-o` 且缓存仍在时,已存在且非空的分片会被直接复用。
|
|
299
|
+
- 缓存中的 `state.json` 记录播放列表原文的 SHA-256 指纹;播放列表变化时会清除旧分片。
|
|
255
300
|
- 下载时最终文件已存在且未指定 `--overwrite`,程序会直接退出。
|
|
256
|
-
-
|
|
301
|
+
- 操作系统清理临时目录后,失败任务需要重新下载;需要彻底清理时删除对应的临时缓存目录即可。
|
|
257
302
|
|
|
258
303
|
## 已知限制
|
|
259
304
|
|
|
@@ -267,7 +312,9 @@ movie/
|
|
|
267
312
|
- HLS AES-128 加密不是 DRM;能访问 `key.bin` 的客户端即可解密。
|
|
268
313
|
- FFmpeg 的 HLS muxer 会写入固定 `IV=0`,所有分片共用同一密钥与 IV。
|
|
269
314
|
- 不支持自定义密钥、密钥 URI 或 IV。
|
|
270
|
-
- serve 不做 SSRF
|
|
315
|
+
- serve 不做 SSRF 内网地址屏蔽;回环地址请求免 token,非本机来源仍依赖 token 与局域网部署。
|
|
316
|
+
- 回环免 token 对同一台机器上的所有用户生效;多用户主机或本机反向代理场景请谨慎,必要时只监听 `127.0.0.1`。
|
|
317
|
+
- Windows 默认 `MAX_PATH=260`:serve 会按输出目录长度截短过长的文件名(保留扩展名),任务响应中返回实际名字;CLI 的 `-o` 不做改写。
|
|
271
318
|
- serve 不提供下载产物接口,文件留在服务器。
|
|
272
319
|
- serve 重启后任务标记为 `interrupted`,不会自动恢复。
|
|
273
320
|
- 请求头不落盘,重启后无法重放任务。
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
- 无构建步骤:原生 JavaScript,Chrome 直接加载目录即可。
|
|
6
6
|
- 网络请求 + DOM 扫描双来源识别,支持 iframe 中的播放器。
|
|
7
|
+
- 自动采集页面标题(`og:title`、Twitter Card、JSON-LD、`document.title`)作为默认下载文件名,标题缺失时回退到「host-文件名」;也可在设置中改为 URL 短 hash(12 位十六进制)。
|
|
7
8
|
- 一键下载时自动携带该 m3u8 请求自己的 `Referer`、`Cookie`、`User-Agent`。
|
|
8
9
|
- 弹窗内查看任务进度、取消任务;popup 关闭后仍由后台轮询。
|
|
9
10
|
|
|
@@ -47,20 +48,22 @@ uv run m3u8-cli serve --host 127.0.0.1 --port 8787 --token mytoken --output-dir
|
|
|
47
48
|
| 设置项 | 说明 |
|
|
48
49
|
| --- | --- |
|
|
49
50
|
| 服务地址 | 默认 `http://127.0.0.1:8787`,需与 `--host/--port` 一致 |
|
|
50
|
-
| Token | 与 `--token`
|
|
51
|
+
| Token | 与 `--token` 一致;本机 serve(回环地址)可留空,点击“测试连接”确认 |
|
|
51
52
|
| 下载格式 | `mp4`(合并封装)或 `hls`(保留分片) |
|
|
53
|
+
| 文件名 | `页面标题`(默认)或 `URL 短 hash`(12 位十六进制;URL 带临时 token 时每次可能不同) |
|
|
52
54
|
| 覆盖已存在的文件 | 对应 serve 的 `overwrite` 字段 |
|
|
53
55
|
| 发送捕获到的 Cookie | 默认开启,仅发送给上面配置的服务地址 |
|
|
54
56
|
| 复制 CLI 命令时包含 Cookie | 默认关闭;开启后命令会包含 Cookie 原文,注意 shell 历史 |
|
|
57
|
+
| 跳过 TLS 证书校验 | 默认关闭;开启后 serve 请求源站时不校验证书与主机名,仅用于自签名/内网服务 |
|
|
55
58
|
|
|
56
59
|
## 使用
|
|
57
60
|
|
|
58
61
|
1. 打开包含视频的页面并开始播放,或等待页面加载完成。
|
|
59
62
|
2. 扩展图标上的 badge 显示当前标签页识别到的 m3u8 数量。
|
|
60
|
-
3.
|
|
63
|
+
3. 点击扩展图标,弹窗分为「链接 / 任务 / 设置 / 关于」四个标签页;「关于」页分为「项目描述 / 使用帮助 / 项目信息」:使用帮助包含 `pip install m3u8-cli`、可折行展示的 serve 启动命令与扩展配置提示,项目信息包含 PyPI 主页、作者/邮箱与 MIT 许可证链接;「链接」中候选条目会显示页面标题和 m3u8 URL:
|
|
61
64
|
- **复制**:复制 m3u8 URL。
|
|
62
|
-
- **复制 CLI**:生成 `m3u8-cli download ...` 命令,按设置决定是否包含 `--referer`、`--cookie`、`--user-agent
|
|
63
|
-
- **下载**:提交到本地 serve
|
|
65
|
+
- **复制 CLI**:生成 `m3u8-cli download ...` 命令,按设置决定是否包含 `--referer`、`--cookie`、`--user-agent`;`-o` 默认使用页面标题。
|
|
66
|
+
- **下载**:提交到本地 serve 服务,默认文件名同样来自页面标题;若提示文件名冲突,可点击“换名重试”自动加时间戳。
|
|
64
67
|
4. “下载任务”区域显示排队中/下载中/已完成/失败/已取消/已中断等状态和分片进度;运行中的任务可以取消。
|
|
65
68
|
5. “重新扫描”会请求当前页面的所有 frame 重新执行 DOM 扫描;“清空”只会清除本标签页的识别结果。
|
|
66
69
|
|
|
@@ -71,6 +74,13 @@ uv run m3u8-cli serve --host 127.0.0.1 --port 8787 --token mytoken --output-dir
|
|
|
71
74
|
|
|
72
75
|
同一 URL 同时来自两个来源时会合并为一条,并优先保留网络请求捕获的请求头。
|
|
73
76
|
|
|
77
|
+
标题采集:
|
|
78
|
+
|
|
79
|
+
- content script 按 `og:title` → `twitter:title` → 视频类 JSON-LD → `document.title` 的顺序取标题;iframe 中的候选优先使用顶层标签页标题。
|
|
80
|
+
- 网络请求捕获的候选通过 `chrome.tabs.get()` 取标签页标题(`<all_urls>` host permission 已覆盖,无需 `tabs` 权限)。
|
|
81
|
+
- 标题会清洗控制字符、合并空白,输出名再经过非法字符替换和 200 字符截断;拿不到标题时回退到「host-文件名」。
|
|
82
|
+
- 选择「URL 短 hash」时直接对 m3u8 URL 做 12 位哈希,不依赖页面标题,适合标题过长或不想在文件名中暴露标题的场景。
|
|
83
|
+
|
|
74
84
|
## 权限说明
|
|
75
85
|
|
|
76
86
|
| 权限 | 用途 |
|
|
@@ -91,7 +101,9 @@ uv run m3u8-cli serve --host 127.0.0.1 --port 8787 --token mytoken --output-dir
|
|
|
91
101
|
- 服务地址不是本机(`127.0.0.1`/`localhost`/`::1`)且开启 Cookie 透传时,设置区会显示警告。
|
|
92
102
|
- Token 保存在 `storage.local`,请勿在不信任的共享 Chrome 配置中使用。
|
|
93
103
|
- serve 端请求头只保留在内存,不落盘、不在任务响应中返回;详见仓库 README。
|
|
104
|
+
- serve 对回环地址请求免 token:Token 留空时扩展默认只适合连接本机 serve;同一台机器上的其他用户/进程也能免 token 访问该服务。API 的 CORS 为 `*`,因此任意网页也能调用本机 serve;若在意请在扩展里保留 Token(网页拿不到它)。
|
|
94
105
|
- 扩展不会自动提交下载,必须由用户点击,避免页面借扩展触发 serve 请求。
|
|
106
|
+
- 「跳过 TLS 证书校验」会关闭 serve 到源站的证书与主机名校验,可能暴露下载内容给中间人;仅在确认源站可信(如自签名内网服务)时开启。
|
|
95
107
|
- CORS `*` 不是安全边界,真正的访问控制是 Bearer token。
|
|
96
108
|
|
|
97
109
|
## 排障
|
|
@@ -99,10 +111,13 @@ uv run m3u8-cli serve --host 127.0.0.1 --port 8787 --token mytoken --output-dir
|
|
|
99
111
|
| 现象 | 处理 |
|
|
100
112
|
| --- | --- |
|
|
101
113
|
| 弹窗提示“未检测到 m3u8” | 确认视频已开始播放;刚安装/重载扩展后刷新页面;尝试“重新扫描” |
|
|
102
|
-
|
|
|
103
|
-
|
|
|
114
|
+
| 提示“未配置 Token” | 在扩展设置中填写 serve 的 `--token` |
|
|
115
|
+
| “无法连接 http://...(Failed to fetch)” | serve 未启动、地址/端口错误,或协议不是 `http/https` |
|
|
116
|
+
| 提示“连接 ... 超时(8 秒)” | 服务可达性差或进程卡住;确认 serve 正常运行并检查网络/防火墙 |
|
|
117
|
+
| 401 / “未授权” | 非本机请求需要 Token;本机回环请求免 token,若仍 401 检查是否经过代理或填了错误 Token |
|
|
104
118
|
| 409 / 目标文件已存在 | 点击“换名重试”,或在设置中开启“覆盖已存在的文件” |
|
|
105
119
|
| 下载失败,提示 403 | 源站可能校验 `Referer`/`Cookie`/`User-Agent`;确认候选来自“网络”且页面仍处于登录状态 |
|
|
120
|
+
| 失败原因是 `CERTIFICATE_VERIFY_FAILED` / Hostname mismatch | 源站证书不匹配(自签名或内网域名);确认可信后开启「跳过 TLS 证书校验」 |
|
|
106
121
|
| 扩展重载后页面无反应 | 刷新页面,让 content script 重新注入 |
|
|
107
122
|
| badge 数量未变化 | 网络识别只在页面产生新请求时更新;静态链接由 DOM 扫描或“重新扫描”补充 |
|
|
108
123
|
|
|
@@ -127,7 +142,7 @@ uv run python scripts/build_extension.py
|
|
|
127
142
|
| 文件 | 职责 |
|
|
128
143
|
| --- | --- |
|
|
129
144
|
| `manifest.json` | MV3 配置、权限、content script 注入 |
|
|
130
|
-
| `lib.js` | 三端共享的纯函数:URL 判断、MIME
|
|
145
|
+
| `lib.js` | 三端共享的纯函数:URL 判断、MIME 判断、页面标题清洗、输出名清洗与派生、shell 转义、CLI 命令 |
|
|
131
146
|
| `lib.test.js` | `lib.js` 的单元测试 |
|
|
132
147
|
| `background.js` | 网络检测、候选与会话存储、badge、serve API 调用、任务轮询、消息路由 |
|
|
133
148
|
| `content.js` | DOM 扫描、MutationObserver、增量上报 |
|
|
@@ -135,18 +150,20 @@ uv run python scripts/build_extension.py
|
|
|
135
150
|
|
|
136
151
|
### 数据与消息
|
|
137
152
|
|
|
138
|
-
- `storage.session["tab:<tabId>"]`:`{pageUrl, items:[{id,url,source,hasCookie,...}]}`,最多 30 条,标签页关闭或导航时清理。
|
|
153
|
+
- `storage.session["tab:<tabId>"]`:`{pageUrl, pageTitle, items:[{id,url,source,pageTitle,hasCookie,...}]}`,最多 30 条,标签页关闭或导航时清理。
|
|
139
154
|
- `storage.session["hdr:<candidateId>"]`:`{cookie, referer, userAgent}`,仅 background 读取。
|
|
140
|
-
- `storage.local["settings"]`:服务地址、token
|
|
155
|
+
- `storage.local["settings"]`:服务地址、token、格式、`naming`(`title`/`hash`)、`insecure`(跳过源站 TLS 校验)等设置。
|
|
141
156
|
- `storage.local["tasks"]`:最近的任务摘要(最多 20 条,优先保留活动任务),不含请求头。
|
|
142
|
-
- popup 通过 `chrome.runtime.sendMessage` 发送 `download:submit`、`task:cancel`、`tasks:refresh`、`settings:test`、`cli:build`、`tab:clear`、`dom:rescan
|
|
143
|
-
- content script 通过 `dom:candidates` 上报 URL
|
|
157
|
+
- popup 通过 `chrome.runtime.sendMessage` 发送 `download:submit`、`task:cancel`、`tasks:refresh`、`settings:test`、`server:version`、`cli:build`、`tab:clear`、`dom:rescan`。`server:version` 请求后端的 `GET /api/version`;设置页底部显示「插件 vX · 后端 vY」,并附 PyPI 链接;无法连接/需要 Token/旧版 serve(无该接口)分别显示对应状态。
|
|
158
|
+
- content script 通过 `dom:candidates` 上报 URL 与 `title`(附 `frameTop` 标记是否顶层 frame),用 `sender.tab.id` 归属标签页。
|
|
144
159
|
|
|
145
160
|
## 已知限制
|
|
146
161
|
|
|
147
162
|
- popup 关闭后任务轮询依赖 `chrome.alarms`,最短周期为 30 秒(旧版 Chrome 为 1 分钟);popup 打开时每 2 秒刷新一次。
|
|
148
163
|
- 只做网络请求与 DOM 扫描,不注入 MAIN world 去 hook `fetch`/`XMLHttpRequest`;缓存命中或没有实际请求的地址可能只由 DOM 扫描发现。
|
|
149
164
|
- 不支持 Shadow DOM 内的元素;iframe 支持依赖 `all_frames` 和网络层。
|
|
165
|
+
- 标题依赖页面元数据:SPA 或动态改写的标题可能滞后,拿不到时回退到 URL 派生名;标题超过 200 字符会被截断,标签页标题可能带站点后缀(如「- 示例站」),需要时可手动改名。
|
|
166
|
+
- serve 还会按输出目录长度进一步截短文件名(Windows `MAX_PATH` 或 POSIX 255 字节限制),任务响应里的 `output` 是实际使用的名字;「换名重试」会使用较短的名字,避免时间戳被截掉。
|
|
150
167
|
- SPA 路由切换不会自动清空候选,使用“清空”或刷新页面。
|
|
151
168
|
- 每个标签页最多保留 30 条候选,任务历史最多 20 条。
|
|
152
169
|
- 下载能力受 `m3u8-cli` 本身限制(AES-128 identity、无 DRM、不支持 BYTERANGE 等),详见根 README。
|