☰
m3u8下载全攻略:从ffmpeg命令到加密解密避坑指南
2026/10/7 3:31:30 网站建设 项目流程

简介:面向Python开发者的m3u8视频流下载脚本,针对需要将HLS协议下的分段TS文件转为本地MP4播放的场景,提供从解析播放列表、批量下载分片到调用FFmpeg合并的完整实现。压缩包仅含1个py文件,大小约2KB,以单个可运行脚本的形式封装核心逻辑,适合用来理解m3u8链接获取、TS片断抓取及加密流处理等流程。资源目前已有1343人学习浏览。借助这份脚本,读者可以快速搭建自己的m3u8下载工具,并结合代码结构掌握requests、正则解析、subprocess调用FFmpeg等关键用法;若遇到网络中断或分片缺失,还可在此基础上扩充断点续传与多线程下载能力。整体轻量且实用,适合有基础Python经验、希望处理在线视频离线缓存问题的开发者参考。

1. m3u8 下载为什么总是“下回一个几 KB 的文本”:索引和分片的关系要先立住

把 m3u8 链接粘进浏览器地址栏,回车之后往往只得到一个几 KB 的文本文件,里面一行行全是.ts后缀的路径——这是 m3u8 下载最经典的第一课。m3u8 本身不是视频,而是一份索引,它告诉播放器去哪里找真正承载画面的分片、按什么顺序播放、每个分片播多久。视频网站把一段完整视频切成几十个甚至几百个几秒的 TS 分片,再配上这份索引,于是直播可以边推边播,回放可以按需补片,码率切换也只影响后续分片。

你的下载目标也跟着变成两步:先抓到这份 m3u8 索引,再把索引里指向的全部分片按顺序取回并合并,必要时还要还原加密。这篇文章就按这两步往下拆,给出可以直接复制的命令、输出参数选择,以及我在实际下载中踩过的一些坑。适合谁:拿直播回放想离线看的用户、做内容备份的编辑、需要二次剪辑素材的编导,以及刚接触 HLS 的新手。

2. m3u8 下载前先看懂三样东西:索引结构、分片序列与转码链路

2.1 用文本编辑器打开 m3u8:从 EXTM3U 到 EXTINF 的语法

m3u8 是 UTF-8 文本文件,用记事本或 VSCode 就能打开。它的标签行以#开头,非#行是分片地址。下面是一个典型的点播播放列表:

#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.0, https://cdn.example.com/hls/seg_0000.ts #EXTINF:10.0, https://cdn.example.com/hls/seg_0001.ts #EXTINF:9.56, https://cdn.example.com/hls/seg_0002.ts #EXT-X-ENDLIST

重点看三处。#EXTINF:后面第一个数字是分片实际时长,单位秒,它决定合并时的时间轴;#EXT-X-MEDIA-SEQUENCE是起始分片序号,很多加密规则要拿它当 IV 基准;#EXT-X-ENDLIST表示这是一个完整的点播列表,没有这行说明是直播源,列表会持续滑动。分片地址还可能是相对路径,比如../segment/0000.ts,下载时要按 m3u8 所在的目录去拼完整地址,否则工具只会报 404。

2.2 分片为什么是几秒一段:直播回放、码率切换与失败重试

视频网站把流切成 4 到 10 秒的分片,不是闲得没事。切分之后,服务端可以边生成边发布:直播时只要不断追加分片文件、刷新索引,播放器就会按新列表继续拉流,这就是“m3u8 直播源能看”的基本原理。对播放器来说,分片粒度小还能做码率切换:带宽下降时播放器只请求更低位率的下一组分片,画面在几秒内完成降级,不会整个视频重新加载。

这个粒度对下载者同样是好处。理论上你可以逐片下载、检查、合并,而不是一次性拉一个可能中断的大文件;某个分片坏了,也只重试那一片,不用重来整个视频。很多 m3u8 downloader 工具会把分片先落到本地再合并,正是利用了这个特性。反过来,你也该意识到:一个损坏的分片如果没被发现,会直接影响最终 MP4 的可用性,所以后面第 5 章里我会专门讲“转码失败”这类问题。

2.3 主播放列表与子播放列表:同一地址给了多个清晰度

很多 m3u8 地址并不是分片列表,而是“主播放列表”。它里面不直接写.ts,而是通过#EXT-X-STREAM-INF指向多个不同码率的子列表:

#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720 https://cdn.example.com/hls/720p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=600000,RESOLUTION=640x360 https://cdn.example.com/hls/360p/index.m3u8

BANDWIDTH 是码率字段,RESOLUTION 是分辨率。下载器如果只拿到第一行子列表,下载的就是 720p;如果你想要 720p,却把整个主列表丢给 ffmpeg,ffmpeg 默认会自己选一路,通常是最高的,但也不排除选错。更稳的做法是先用文本编辑器打开这个 m3u8,确认里面有没有#EXT-X-STREAM-INF,有的话就把对应子列表完整提取出来,用它作为下载输入。还有少数站点会用#EXT-X-MEDIA提供独立音轨或字幕轨,那种情况要留意下载器是否会一并处理。

2.4 直播源与点播回放的差别:有没有 ENDLIST 是分水岭

#EXT-X-ENDLIST这行是最快判断标准。有它,说明索引固定,是一个点了就能从头下到尾;没有它,索引是动态窗口,服务端只保留最近一段时间的分片,下载器跑得再快也补不回你已经错过的部分。很多人把“群晖电视 m3u8 源”这样的直播源当成下载地址,结果发现工具永远在“下载中”,其实不是工具坏了,而是协议类型不允许你拿到历史画面。

回放在大多数站点会生成独立的 VOD 播放列表,URL 里常常带有playback、record、vod之类的关键字。要做离线保存,优先去找回放地址,而不是在直播流上硬抓。这个区别想清楚,能省掉大量无效等待。

3. 用 ffmpeg 下载 m3u8:最小命令、防盗链参数与三种输出容器

3.1 先跑通最小命令:-i 加列表地址,-c copy 加输出文件名

最常见的做法是直接使用 ffmpeg,不需要额外安装客户端。它自己会去请求索引、依次拉取分片、解密、合并,一条命令完成。最小命令是这样:

ffmpeg -i "https://cdn.example.com/hls/index.m3u8" -c copy -f mp4 "output.mp4"

逻辑不复杂:-i指定输入的 m3u8 地址;-c copy表示不重新编码,把源编码原样搬运,CPU 占用几乎为零;-f mp4强制输出容器为 MP4。如果源视频是 H.264/AAC,这条命令足够;如果源编码是 MPEG-2 或 AC-3,封装进 MP4 经常报错,这时把后缀改成.mkv更稳。换句话说,下载器最终给你.ts或.mkv,不一定是你选错了,可能是源编码不适合 MP4 封装。

3.2 防盗链场景下补三个参数:Referer、User-Agent 与超时

第一次下载大概率会遇到 403。原因是 ffmpeg 默认请求带上的是User-Agent: FFmpeg/短横线版本这类字符串,不少 CDN 见到就拒绝。浏览器能放、ffmpeg 不能下,基本都是这个原因。解决方法是把播放页面里的请求头补进去:

ffmpeg -headers $'Referer: https://www.example.com/video/123\r\nOrigin: https://www.example.com\r\n' \ -user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \ -i "https://cdn.example.com/hls/index.m3u8" \ -c copy -f mp4 "output.mp4"

这里有两个坑。第一个是-headers里的多个请求头必须用\r\n分隔,写成一行或只用空格分隔都会失效;第二个是 Referer 的地址要精确到播放页,而不是站点根域名,CDN 校验的是完整路径。遇到还要登录的站点,把 Cookie 也以Cookie: xxx的形式加进-headers。另外建议补一个-rw_timeout 10000000,意思是单个分片请求 10 秒超时,避免网络抖动时 ffmpeg 直接放弃。

3.3 三种输出形态:直接 MP4、中间 MKV、按需转码

结合我自己的使用场景,下载输出一般分三种,不要一上来就贪“一条命令出 MP4”。

# 中间先封装成 MKV,保留原始时间轴,适合下载拷贝 ffmpeg -i "https://cdn.example.com/hls/index.m3u8" -c copy "videos/output.mkv" # 确认音视频流都是 H.264/AAC 后,从 MKV 无损转封装成 MP4 ffmpeg -i "videos/output.mkv" -c copy "videos/output.mp4" # 源编码不适合 MP4 时,转成通用 H.264/AAC,代价是耗时 ffmpeg -i "videos/output.mkv" -c:v libx264 -preset medium -crf 20 -c:a aac "videos/output.mp4"

为什么要中间先做成 MKV?因为 HLS 分片流里的时间戳常常不是规整的,MP4 对时间戳要求更严;MKV 能保留可变帧率、异常字幕轨,几乎不会因为“容器不接受”而中断。确认能安全封装后,再用-c copy转成 MP4,这一步不重编码,速度很快。第三条命令开始真正转码,-crf 20在体积和画面质量之间比较均衡,非常在意的可以降到18,但文件会更大。

3.4 没有 ffmpeg 的服务器:一份用 curl 逐片下载的最小脚本

有时候你在远程服务器上想下载,但环境里没装 ffmpeg,或者想保留原始 TS 分片直接抽帧。这种场景我一般用脚本解析 m3u8,再用 curl 逐片拉取:

# 从 m3u8 逐行下载分片,要求 m3u8 内是完整 URL while IFS= read -r line; do case "$line" in \#*|"") continue ;; *) curl -sS -O -- "$line" ;; esac done < "playlist.m3u8"

这个脚本把#开头的标签行跳过,只处理分片地址,-sS表示静默但保留错误信息。很多 m3u8 里写的是相对路径,需要先拼上基础目录:

base_url="https://cdn.example.com/hls" while IFS= read -r line; do case "$line" in \#*|"") continue ;; /*) line="${base_url}${line}" ;; *) line="${base_url}/${line}" ;; esac curl -sS -O -- "$line" done < "playlist.m3u8"

这里的拼接逻辑比较粗糙:以/开头就当作站内绝对路径,否则直接拼在基础目录后面。遇到../这种上层路径还是不行,需要单独处理。逐片下载的另一个好处是能对比每个 TS 文件大小,快速定位坏分片,这一点在后面的避坑章会用到。

4. 加密 m3u8 的下载:看懂 EXT-X-KEY,再让 ffmpeg 自动解密

4.1 判断分片是否加密:看 EXT-X-KEY 和 METHOD 字段

要判断 m3u8 是不是加密的,打开索引文件搜索EXT-X-KEY。出现下面的标签,说明分片用 AES-128 做了加密:

#EXTM3U #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:METHOD=AES-128,URI="https://cdn.example.com/hls/key.key" #EXTINF:10.0, https://cdn.example.com/hls/enc_0000.ts #EXTINF:10.0, https://cdn.example.com/hls/enc_0001.ts #EXT-X-ENDLIST

其中METHOD是加密算法,标准 HLS 最常见的写法是AES-128;URI是密钥下载地址;如果后面还有IV=0x…,说明服务端显式指定了初始向量。没写 IV 时,标准做法是取#EXT-X-MEDIA-SEQUENCE的序号转成 16 字节十六进制当作 IV。这个规则像一个黑匣子,但排错时很好用:很多花屏问题的根源,就是下载器用的 IV 和加密端不一致。

需要注意的是,如果标签写的是METHOD=SAMPLE-AES,不再是一整片分片全部加密,而是只加密部分字节,faig... 不展开,这类内容通常和 FairPlay/Widevine 等 DRM 绑定,ffmpeg 支持很有限。

4.2 让 ffmpeg 自动解密:密钥地址可达,命令不变

只要密钥地址能被 ffmpeg 正常请求到,下载命令和普通 m3u8 几乎一样:

ffmpeg -headers $'Referer: https://www.example.com/video/123\r\n' \ -user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \ -i "https://cdn.example.com/hls/encrypted.m3u8" \ -c copy -f mp4 "decrypted.mp4"

ffmpeg 会先请求 m3u8,读到EXT-X-KEY后再去请求密钥文件,然后边拉分片边解密,最终输出的是明文内容。关键条件是密钥端点要能访问:有些站点给密钥地址做了鉴权,需要带 Referer 或临时 token,那就用-headers把请求头补上。密钥地址也常见写成相对路径,ffmpeg 会按 m3u8 所在目录自动拼接,一般不用你手动处理。

如果密钥请求失败,ffmpeg 会直接中断,输出文件停留在几 KB 或 0 字节,不会给你一个“半解密”的文件。遇到这种情况,先用ffmpeg -loglevel debug重跑一遍,看日志里到底请求了哪个 URL、返回了什么状态码,多半能直接定位到是 403 还是证书问题。

4.3 解密失败的三个经典症状与对应排查

第一种,输出文件为 0 字节。原因是密钥或第一个分片没有成功获取,优先检查网络和请求头,尤其是 key 端点的独立鉴权。第二种,输出文件能播放但画面花屏、有声音或没声音。这多半是 IV 不匹配:m3u8 里写IV=0x…时 ffmpeg 会照用;没写时它会拿MEDIA-SEQUENCE算,如果服务端加密用的是另一套序号,就会花。第三种,时长正常但整段黑屏。这可能不是解密失败,而是视频轨本来就只有黑场,或源流里带了额外黑场分片,和加密无关。

遇到加密 m3u8,我的原则是先确认自己有没有权利下载,再动手。能解决标准 AES-128 的工具已经很多,但 DRM 保护的商业内容本来就不该通过浏览器抓包绕过,这部分不在“m3u8 下载”的合理范围内。

5. m3u8 下载避坑实录:403、转码失败与花屏的排查样本

5.1 中途中断在某个分片:Invalid data found when processing input

现象:ffmpeg 下载到 80%,最后几行报Invalid data found when processing input,输出文件停在一个没法播放的中间状态。

原因:某个 TS 分片不完整,或者分片 URL 返回的是 HTML 错误页而不是视频数据。ffmpeg 按顺序拉分片,拿到非法数据后会终止,没有“跳过这一个片继续下一个”的默认策略。

解决:先给命令加-rw_timeout 10000000提高单分片等待时间;如果还是失败,就按 3.4 的脚本逐片下载,比对每个 TS 文件大小,找到那个明显偏小的分片单独重拉。重拉成功后,可以用cat把它并回原序列再手动让 ffmpeg 合并。这一步虽然麻烦,但比盲目换下载器管用。

5.2 浏览器能播放,ffmpeg 却 403 Forbidden

现象:在浏览器里打开视频正常,把同一个 m3u8 地址粘到 ffmpeg,立刻 403。

原因:CDN 校验请求头,ffmpeg 默认的 User-Agent 和缺失的 Referer 被识破。浏览器里能放是因为浏览器自动带了完整上下文,ffmpeg 什么也没带。

解决:用浏览器的开发者工具,在 Network 面板找到播放列表请求,把 Request Headers 里的User-Agent、Referer、Origin、Cookie原样复制到 ffmpeg 的-headers和-user_agent参数里。注意 header 列表要以\r\n分隔,Referer里的地址不要和网页地址不一致,CDN 对这类校验非常敏感。

5.3 合并后的视频音画不同步,时长还少了十几秒

现象:下载成功,能播放,但人物对白和口型明显错位,总时长比播放器里显示的也短。

原因:m3u8 列表里存在#EXT-X-DISCONTINUITY标签。这个标签表示前后分片不是连续录制,中间可能插了广告或切过流,编码参数、时间基都可能变了。直接-c copy会把旧时间轴延续下去,导致错位。

解决:先数一下列表里有多少处中断:

grep -c "EXT-X-DISCONTINUITY" playlist.m3u8

结果大于 0 时,改用 MKV 中间容器,若仍错位就做一次完整转码:

ffmpeg -i playlist.m3u8 -c:v libx264 -crf 20 -c:a aac -af "aresample=async=1" fixed.mp4

aresample=async=1的作用是让音频流重新对齐到视频时间轴,虽然会增加转码时间,但能消掉大部分不同步问题。

5.4 直播源没有 ENDLIST,下载器永远在“下载中”

现象:用某个 m3u8 downloader 下载直播源,进度条一直在涨,跑了一个小时也没有结束。

原因:这个 m3u8 是直播列表,没有#EXT-X-ENDLIST,服务端不断追加新分片同时丢弃旧分片。下载器每刷新一次列表就看到新的分片,自然不会停止。

解决:先判断你要的是不是回放。如果站点保留点播回放,去找带playback或record关键字的播放列表,那个才是完整的。如果必须抓直播,加上播放时长限制,比如-t 3600抓一小时,输出用 MKV 而不是 MP4,并且要有专人值守,直播中断时立即重跑。

5.5 下载完拖动进度条卡顿,剪辑软件不认

现象:播放器能放,但拖动进度条明显卡顿;Premiere、剪映等软件直接提示文件不支持。

原因:很多下载器生成的 MP4 把元数据moov块放在文件尾部,播放器必须读完整个文件才能建立索引,表现就是“能放但拖不动”。剪辑软件对这类 MP4 兼容性很差。

解决:用 ffmpeg 把这个 MP4 重新整理一遍,不重编码,只把moov移到文件开头:

ffmpeg -i downloaded.mp4 -c copy -movflags +faststart "output_optimized.mp4"

如果剪辑软件还要求 H.264 编码,再用第 3.3 节的做法转一次码。这条命令我几乎每次保存 MP4 都会跑一遍,已经成了固定习惯。

6. 验证下载产物:用 ffprobe 给 m3u8 下载结果做体检

下载完成之后,我不急着打开播放器,而是先跑一遍 ffprobe,确认下载结果真的“健康”。这里说的健康不是能不能播放,而是时长、编码、轨道是否和源保持一致。命令很简单:

ffprobe -v error \ -show_entries format=duration,format_name,size \ -show_entries stream=codec_name,codec_type,width,height,avg_frame_rate \ -of default=noprint_wrappers=1 output.mp4

这条命令会输出容器格式、总时长、文件体积、每个流的编码类型、分辨率、平均帧率。我通常把它和 m3u8 里的EXTINF总时长对一下,误差应该在半秒以内。

检查项预期结果异常时的可能原因
format_namemp4 或 matroska输出容器选错,或源流编码不适配
duration与播放器显示时长一致漏分片、EXT-X-DISCONTINUITY 导致时间轴断裂
stream 数量至少 1 个 video 和 1 个 audio只下了视频轨,音频轨单独在别的 m3u8 里
avg_frame_rate25、30、60 等整数值附近源流里混入异常分片,需要重新下载或转码

这一步大概花不到十秒,却能把很多隐患拦在交给剪辑软件之前。比如发现时长偏短,我就知道某个分片没拉全,立刻去重试对应范围,而不是最后渲染到一半才发现黑屏。

我现在的习惯是,每次下载完先看 ffprobe 输出,再看文件大小,最后才打开播放器。顺序反过来,往往就是反复试播、反复删除、再重新下载的循环。这个习惯帮我少走了很多弯路。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询