项目定位

m3u8下载器 是一个桌面端视频下载工作台。它不是单纯命令行脚本,而是把 M3U8 直链下载、批量任务、请求头配置、片源搜索和 FFmpeg 合并整合到一个 PySide6 GUI 里。

项目主入口是 main.py,核心下载逻辑在 m3u8_downloader.py。搜索源按文件拆开,比如 search.pysearch_555.pysearch_mqtv.pysearch_ncat.pysearch_mofa.py

整体链路

用户输入 m3u8 URL / 通过片源搜索拿到 m3u8
      |
      v
M3U8Parser.parse_m3u8
      |
      +--> 识别 master playlist
      +--> 递归进入子播放列表
      +--> 解析 EXTINF 片段
      +--> 解析 EXT-X-KEY AES-128 信息
      |
      v
M3U8Downloader._download_segments
      |
      +--> ThreadPoolExecutor 并发下载 TS
      +--> 失败指数退避重试
      +--> AESDecryptor 解密片段
      |
      v
FFmpegMerger.merge_segments
      |
      +--> ffmpeg concat stream copy
      +--> 找不到 ffmpeg 时使用备用 TS 合并

这个链路做得比较完整:能处理 master playlist,能处理相对路径 TS,能处理 #EXT-X-KEY,还能把进度通过 callback 回传给 GUI。

GUI 工作台

桌面层用 PySide6 写,MainWindow 里维护:

  • download_tasks:当前任务组件列表。
  • custom_headers:用户自定义请求头。
  • active_downloads:当前活跃下载数。
  • download_queue:等待队列。

界面支持单链接和批量模式。批量模式下一行一个 M3U8 链接,创建任务后根据并发设置自动启动。每个任务是独立的 DownloadTaskWidget,下载完成后发信号给主窗口,主窗口再处理队列里的下一个任务。

这个设计比“点一下就阻塞主界面”的脚本舒服很多:GUI 只负责组织任务和展示状态,真正下载在线程/任务里跑。

M3U8 解析

M3U8Parser 做了几件关键事。

第一是请求头可配置。默认 UA、Accept、Accept-Language 足够处理很多普通 CDN,但用户也可以给特殊站点补 Referer、Origin 或其它 header。

第二是 base URL 处理。M3U8 里经常出现相对路径:

#EXTINF:6.0,
segment00001.ts

解析器会根据原始 M3U8 地址推导基础路径,再用 urljoin 得到完整 TS 地址。

第三是 master playlist 处理。如果发现 #EXT-X-STREAM-INF,说明当前文件不是最终分片列表,而是清晰度索引。项目会取子播放列表继续递归解析。

第四是加密信息。遇到:

#EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x...

解析器会记录 methoduriiv,后面下载每个分片时交给 AESDecryptor

AES-128 解密

AESDecryptorcryptography 做 CBC 解密。它会缓存 key,避免每个分片都重新请求 key 地址。

解密流程大概是:

  1. 请求 EXT-X-KEY 里的 key URI。
  2. 如果 M3U8 没有显式 IV,就用分片 index 生成 16 字节 IV。
  3. AES-CBC 解密 TS bytes。
  4. 去掉 PKCS7 padding。
  5. 写入临时 TS 文件。

这块的关键点不是算法本身,而是请求头复用。很多 key 地址和 TS 地址一样需要 Referer/UA,所以解密器会使用下载器当前 session 的 headers。

多线程下载

下载器使用 ThreadPoolExecutor 并发下载分片,默认线程数在配置里是 16,GUI 里可以调。

单个分片下载失败会按指数退避重试:

attempt 1 -> 立即重试
attempt 2 -> 等 2 秒
attempt 3 -> 等 4 秒
...

所有分片结果按 index 放回列表,最后只拿成功的文件合并。如果成功数太少,下载器会给出警告,而不是假装任务正常完成。

进度由 ProgressCallback 管理,它维护 completed_segmentsfailed_segmentstotal_segments 和起始时间。GUI 收到的数据里能拿到百分比、速度和 ETA。

FFmpeg 合并

项目专门抽了 FFmpegMerger,启动时按顺序找:

  1. 项目内置 ffmpeg/ffmpeg.exeffmpeg/ffmpeg
  2. 系统 PATH。
  3. Windows、macOS、Linux 常见安装路径。

找得到就用 concat demuxer:

ffmpeg -f concat -safe 0 -i concat.txt -c copy -y output.mp4

这个方案能正确处理 TS 时间戳和 PAT/PMT 信息,音画同步会比手动二进制拼接稳很多。

找不到 ffmpeg 时,项目会走备用方案:第一个 TS 完整写入,后续 TS 跳过前 10 个 188 字节 TS 包,再写入输出文件。它不如 ffmpeg 完美,但比直接 copy /b 或简单拼接更稳。

站点搜索源

项目不只接受用户手动粘贴 M3U8,还内置了多个搜索源:

  • AiGua:搜索后解析详情页和播放接口,收集剧集 M3U8。
  • VideoSearcher555:解析播放页里的 player 数据,处理加密播放地址。
  • MQTVSearcher:搜索接口有 token 和加密响应,项目里做了 encode/decode。
  • NCatSearcher:从搜索结果和播放页里提取 M3U8/MP4。
  • MofaSearcher:搜索结果本身就能直接给出播放路由。

这些搜索源都有一个共同模式:

搜索关键词
  -> 搜索页 / 搜索 API
  -> 详情页
  -> 剧集列表
  -> 播放页
  -> M3U8 URL

不同站点的差异主要在请求头、参数、token、加密响应和播放页字段上。GUI 层不用理解这些细节,只要拿到最终 M3U8 列表即可。

配置和工具函数

config.py 里放默认线程数、重试次数、超时、窗口大小、默认 UA、临时目录后缀和状态文案。

utils.py 里是比较实用的工具:

  • URL 和 M3U8 URL 校验。
  • 文件名清理。
  • 自动补 .mp4
  • 文件大小和时间格式化。
  • 临时目录创建/清理。
  • 避免文件名冲突。
  • 输出路径权限检查。
  • 从 URL 推断任务标题。

这些小函数让 GUI 和下载器都能保持干净,不用到处散落路径处理逻辑。

我觉得这个项目有意思的点

这个项目的价值不只在“能下载 M3U8”,而是在把下载相关的边角问题都收进一个桌面工具里:

  • 用户可以手动补请求头,适配 Referer/Origin 检查。
  • 搜索源可以直接批量生成 M3U8 任务。
  • 下载器知道 master playlist、AES-128、分片重试和进度统计。
  • 合并优先用 ffmpeg,保证输出 MP4 的稳定性。
  • GUI 有队列和并发控制,不会一下把所有任务同时打满。

后续如果继续打磨,我会优先把下载核心、搜索源和 GUI 进一步解耦:搜索源统一成接口,下载任务做持久化,日志从 print 换成结构化 logging,再给每个站点搜索源补无网络 fixture 测试。这样它就会从“能用的桌面工具”变成“更容易持续维护的下载工作台”。