m3u8下载全解:从HLS分片解析到ffmpeg合并避坑指南

📅 发布时间:2026/10/9 21:27:47
m3u8下载全解:从HLS分片解析到ffmpeg合并避坑指南
简介这是一份基于Python的m3u8下载与转换脚本面向需要从HLS流媒体地址获取视频并合并为MP4的开发者、视频运营人员。脚本封装了m3u8文件解析、TS分段地址提取、逐段下载等常用流程并通过调用FFmpeg完成TS合并与MP4封装同时给出AES-128加密流的解密处理思路可应对常见加密流媒体场景。压缩包为zip格式仅包含1个m3u8.py文件体积约2KB代码精简、注释清晰便于直接阅读、修改和嵌入到自己的项目中。该资源当前已有1343人学习下载属于轻量但实用的参考脚本适合想快速理解HLS下载原理、搭建个人视频下载工具或研究流媒体协议的用户。通过这份代码读者可掌握从m3u8到MP4的完整处理链路理解TS分片、密钥获取、临时文件管理等关键细节并据此扩展多线程加速、进度显示等高级功能。1. m3u8到底是什么一个播放列表为什么下载它比想象中麻烦很多人第一次接触m3u8下载是在浏览器F12里看到一个以m3u8结尾的链接以为是视频本体复制到下载工具里却只存下一个几KB的文件。打开一看全是文本里面密密麻麻全是“.ts”结尾的行。这就触及了m3u8下载的核心概念m3u8不是视频文件而是一个播放列表文件它记录着视频被切成的若干个TS分片文件的地址。HLS协议把一段完整视频切成两三百个几秒的小片段播放器按列表顺序请求并播放而你要做的是把这些片段全部拉下来再按顺序无缝拼成一个MP4。这个过程看起来简单实际踩坑的地方不少请求头缺失导致403、分片加密导致黑屏、合并后音画不同步这些都不是下载工具本身能替你解决的。适合谁看呢做视频采集、课程离线缓存、数据备份、需要批量获取历史视频的从业者这篇就是按你的实际场景来拆的。2. 拆解HLS结构从#EXTM3U到TS分片先看懂再动手2.1 点播列表和直播列表的差别先判断你手里是哪一种拿到一个m3u8链接第一件事不是急着下载而是打开看它的头部。点播类的m3u8文件结构是固定的包含完整的分片列表下载完整个列表就意味着拿到了完整视频。直播类的m3u8则是一个不断滑动的窗口文件末尾会有一个#EXT-X-ENDLIST标记点播列表有直播列表没有。这个标记是判断能否完整下载的关键。用文本编辑器打开一个典型的点播m3u8你会看到类似下面的内容#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:6 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:5.760, segment_001.ts #EXTINF:5.760, segment_002.ts #EXT-X-ENDLIST#EXTINF后面的数字是当前分片的时长单位秒。冒号后的地址是分片的相对路径拼接上m3u8文件所在的基础URL才能得到分片的真实地址。#EXT-X-TARGETDURATION标识所有分片的最大时长#EXT-X-MEDIA-SEQUENCE是起始序号直播列表里这个序号会一直增加用来标记新的分片位置。判断完类型后还要检查有没有#EXT-X-KEY行。这一行的存在意味着分片被AES-128加密了直接下载ts文件合并出来是花屏的。要让下载器正常工作你得知道密钥从哪来这个问题留到第4章细说。2.2 基础URL拼接规则为什么分片地址总是404m3u8文件里的分片地址有两种写法区别直接决定你后面下载成功还是失败。一种写的是完整地址以http://开头这种不需要任何处理直接用。另一种是相对地址可能有以下几种形态segment_001.ts和m3u8文件同目录/videos/segment_001.ts从站点根目录开始算../segment_001.ts往上一级目录你在下载工具里填了m3u8地址工具内部会先解析出基础URL再把相对地址拼上去。拼错的典型结果是第一条分片能下后面的全404。排查方法很简单用浏览器直接访问拼接后的地址如果能播放或者能下载ts文件说明拼接规则是对的。我一般习惯手工做一遍这个拼接练习不是为了手动下载而是为了理解下载工具报错时在报什么。比如某个开源下载器报“m3u8地址解析失败”多半就是相对路径里出现了它不支持的写法。这时候你手写一个拼接规则绕过它自己处理问题就解了。2.3 分片时长的坑为什么合并的视频总比原时长短#EXTINF标记的是每个分片的播放时长但注意一个细节分片时长和实际文件里的媒体流时长不一定完全相等。很多切分工具会在分片末尾多做一点或者少做一点播放器靠时间戳来同步合并工具则靠顺序拼接。如果你手动拼TS文件不同分片的时间戳是独立递增的合并后播放器读取的是流里的真实时间戳不是#EXTINF标注的时长。具体现象是视频总时长比期望值短了十几秒或者播放进度条走到一半突然跳回开头。原因是某一批分片的时间戳重置了。检查办法是用ffprobe查看每个ts文件的start_time和duration如果start_time不是从0递增说明这批分片源就有问题。多数下载工具会自动修正但如果你是自己写脚本合并这个坑几乎每批数据都会遇到。3. 三种下载姿势ffmpeg直连、TS批量拉取与专业工具实操3.1 ffmpeg一条命令直下参数少但一遇加密和请求头就抓瞎ffmpeg是处理m3u8最直接的方案系统里装好ffmpeg后拿到链接直接执行ffmpeg -i https://example.com/path/playlist.m3u8 -c copy -bsf:a aac_adtstoasc output.mp4-c copy表示不重新编码直接把原始视频流和音频流复制进MP4容器。-bsf:a aac_adtstoasc是音频流的比特流过滤器把ADTS格式的裸AAC转成MP4需要的ASC格式不加这个选项很多视频会丢失声音。-i后面是m3u8地址必须用引号包住因为链接里经常带之类的参数不包引号shell会截断。这个方案适合分片没有加密、CDN不校验请求头、网络稳定的场景。它的问题也明显ffmpeg遇到加密分片会尝试读取密钥如果密钥链接需要带Token或Referer它会直接报错。遇到这种情况我一般先给ffmpeg补请求头ffmpeg -headers Referer: https://example.com/ -user_agent Mozilla/5.0 -i https://example.com/path/playlist.m3u8 -c copy output.mp4-headers里的Referer要填视频所在页面的地址-user_agent填浏览器UA。这里最麻烦的是Cookie如果分片地址需要登录态ffmpeg的-headers也支持带Cookie但Cookie里的特殊字符会破坏请求头格式我后面会说排查办法。3.2 开源下载器的几个关键参数并发数、自动拼接、文件命名ffmpeg毕竟不是专门为m3u8下载设计的缺了并发控制、失败重试和分片完整性校验。实战中我更多用的是专门的开源下载器这类工具的典型用法是downloader https://example.com/path/playlist.m3u8 --thread 8 --auto-disposition --save-pattern /data/videos/ --enable-log--thread是并发线程数。HLS下载是IO密集型的不是CPU密集型线程开到8到16通常能有明显提速开太高反而触发CDN限流。--auto-disposition是自动解析HTTP响应头里的文件名CDN有时候会返回一个很短的带Content-Disposition的文件名不开这个选项时文件名会是一长串数字。--save-pattern指定保存目录注意目录路径不能含中文否则部分工具会编码错乱。--enable-log会生成一份完整的下载日志出问题时看日志比看屏幕输出靠谱得多。还有两个参数得解释清楚。一个是失败重试次数一般工具默认3次遇到弱网环境我会调大另一个是分片合并方式有的工具是下载完所有分片后统一合并有的工具是边下边拼。对于大视频边下边拼的好处是意外中断时已经拼好的前半段还能用。3.3 手动方案curl批量拉取TS分片再合并当工具的救命稻草某些特殊场景下工具会全线失效m3u8里的分片地址做了时效签名下载器解析完再请求时签名已过期或者分片地址无法被下载器的规则匹配。这时候手工方案兜底反而最可靠。先手动把分片列表提取出来curl -s https://example.com/path/playlist.m3u8 -H Referer: https://example.com/ -o playlist.m3u8 grep -v ^# playlist.m3u8 | while read line; do echo https://example.com/path/$line; done ts_urls.txtgrep -v ^#过滤掉所有以#开头的元数据行剩下的就是分片路径。然后用while read循环把每个相对路径拼上基础URL输出到ts_urls.txt。最后用xargs做并发下载cat ts_urls.txt | xargs -P 8 -I {} curl -s -o /data/videos/$(basename {}) {}xargs -P 8表示同时起8个curl进程-I {}把每行内容替换到后面的curl命令里。这一步最容易出错的是$(basename {})的写法因为分片URL末尾可能带查询参数basename会拿到类似segment_001.ts?signxxx这样的字符串最后的文件名含问号在合并时会有问题。手动下载的分片合并要看操作系统。Linux和macOS用cat命令直接按顺序拼cat $(cat ts_urls.txt | xargs -n1 basename) video.tsWindows环境用copy /bcopy /b segment_*.ts output.ts要注意Linux的cat拼接要求文件路径不能有空格否则要改用循环逐行读取。拼接出来的video.ts是TS流格式不是MP4播放器能放但导入剪辑软件可能会不认用ffmpeg转封装一下就行ffmpeg -i video.ts -c copy -bsf:a aac_adtstoasc final.mp44. 参数与加密请求头、并发数、KEY解析卡住的大多在这4.1 加密分片的处理KEY链接怎么找Token过期怎么办前面提到#EXT-X-KEY标记完整一行的格式是这样的#EXT-X-KEY:METHODAES-128,URI/encryption/key.key,IV0x00000000000000000000000000000001METHOD指定加密方式AES-128是最常见的。URI是密钥文件的地址可能是绝对地址也可能是相对地址。IV是初始向量有些列表不写IV这时候默认使用#EXT-X-MEDIA-SEQUENCE的值作为IV。下载器拿到密钥后用AES-128-CBC模式对每个分片解密密钥和IV缺一不可。开源下载器一般会自动处理这步但有一个常见翻车点密钥文件的地址也是需要带Token的。我在实际处理中遇到过密钥链接的有效期只有几分钟而视频比较大导致下载时间超过有效期中间的密钥请求就失败了。这类问题的解决思路不是延长密钥有效期而是用浏览器开发者工具手动拿到密钥文件并保存到本地然后改写m3u8里的密钥地址为本地路径。有一类工具支持--key-file参数手动指定密钥文件路径绕过对线上密钥地址的依赖downloader https://example.com/path/playlist.m3u8 --key-file /data/keys/video.key --key-iv 0x00000000000000000000000000000001--key-iv参数在m3u8里没有IV字段时尤为重要不指定它解密出来的画面会是错乱的色块。4.2 请求头和Cookie为什么能播放却下载不了浏览器能正常播放下载工具却报403这是m3u8下载里最高频的问题。原因几乎都是CDN校验了请求头的Referer或User-Agent甚至校验了Session Cookie。下载工具发出的请求不带这些信息CDN直接拒了。处理方法分几个层次。第一个层次加Referer。几乎所有CDN都校验这一项填成视频所在页面的完整地址downloader https://example.com/path/playlist.m3u8 --headers Referer: https://player.example.com/watch?id123第二个层次加密分片的密钥请求也需要带同一套请求头。有的下载工具会把对m3u8的请求头应用到所有子请求有的不会你得在配置里确认。我遇到过一种情况m3u8能下载但每个ts分片都403这说明分片地址需要对每个独立请求带上User-Agent而这个UA校验比m3u8的更严格。第三个层次Cookie。视频需要登录才能播放那么m3u8、密钥、分片三步请求都需要Cookie。工具的--headers参数里直接写Cookie: xxx可能因为分号导致解析失败建议把Cookie存成文件再导入downloader https://example.com/path/playlist.m3u8 --load-cookies /data/cookies.txtcookies.txt的格式和curl的Cookie文件格式一致每行一个域名一条记录。从浏览器里导出的Cookie文件往往带#HttpOnly_前缀部分工具解析不了这种格式需要用脚本做一次清洗。血泪经验是这步往往浪费半小时实际就是去掉前缀和空行。4.3 并发数与网络环境开32线程反而更慢的底层逻辑并发数不是越大越好。CDN在检测到同一IP的并发请求超过某个阈值时会主动降速或者拒绝服务。这个阈值不同平台差别很大有的平台8线程就限速有的平台32线程还能跑满带宽。我在实际项目中的经验是先开16线程跑10秒钟观察每个ts的平均下载时间和总体速度如果单线程速度较高而总速度上不去说明并发已经达到瓶颈如果出现大量连接超时说明触发限流了降回一半继续跑。还有个容易忽略的点并发下载的ts分片大小差异。多数切片工具产生的ts文件大小相近但有些切片工具在视频静止画面部分会产生特别小的分片在动作画面部分产生特别大的分片。下载器默认是顺序分配请求大头分片会拖尾。支持--sort-by-size参数的工具能按分片大小排序先下大的把小分片留在最后填充整体耗时能缩短30%以上。5. 避坑专题403、黑屏、半截视频四类高频翻车实录5.1 分片403同一套请求头为什么m3u8能下分片不能现象m3u8链接能正常下载但执行下载命令后每个ts分片都返回403 Forbidden视频文件只生成一个几KB的壳。原因分片资源所在的服务路径由CDN单独接管CDN对该路径设置了更严格的防盗链规则要求请求头必须同时包含正确的Referer和User-Agent。你只在全局配了Referer漏了User-Agent或者UA字段的值不是主流浏览器的完整UA。解决把两个请求头都显式配置上。我一般把完整的浏览器UA和页面Referer固定写到工具的配置文件里参数是这样的downloader https://example.com/path/playlist.m3u8 --headers User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 --headers Referer: https://example.com/注意配置顺序先User-Agent再Referer。部分CDN会校验Accept头如果加了这两个仍然403补一个Accept: */*再试。另外检查一下m3u8目录和ts目录是否是同域。跨域时Referer的值应该填视频播放页的地址而不是m3u8所在目录的地址这个区别很细微。5.2 合并后黑屏只有声音密钥没解密成功还是合并步长错位现象ts分片全部下载完成合并出的MP4用播放器打开只有一段黑屏进度条显示时长正常也能听到声音。原因分片其实是被AES-128加密过的下载器没有正确解析#EXT-X-KEY里的密钥而是把加密的ts文件直接拼进了MP4。播放器尝试解码这些数据画面部分全是噪声但音频流恰好是明文或者音频格式没有被完全打乱所以就出现了有声无画。解决先用ffprobe检查合并文件的编码信息ffprobe -v error -show_entries streamcodec_name -of defaultnoprint_wrappers1 final.mp4正常文件会输出类似h264和aac两行。如果画面流是h264但播放仍黑屏再用文本工具检查合并前最后一个ts分片的二进制头部看有没有#EXT-X-KEY对应的解密痕迹。更直接的做法是用支持强制解密参数的工具重新下载downloader https://example.com/path/playlist.m3u8 --use-key-file /data/keys/video.key --force-decrypt --save-name decrypted.mp4--force-decrypt会忽略m3u8里的密钥地址而是用--use-key-file指定的本地密钥文件解密。这个参数在密钥链接已经失效时特别管用。5.3 下载到60%卡死不再动日志文件里永远找不到的规律现象视频下载到一半进度条卡住等了一个小时没有增长。重新执行下载命令还是会在同一个位置卡住。原因卡住的这个ts分片在CDN上损坏了或者源站已经移除了这个分片。下载工具默认的重试策略是无限重试或连续重试固定次数这两种策略对上这情况都没用因为问题是分片本身不存在重试多少次都一样。解决有两种处理方案。第一种是跳过坏分片用ffmpeg补一个黑帧代价是那一秒是黑屏downloader https://example.com/path/playlist.m3u8 --skip-errors --skip-error-count 5--skip-errors会跳过请求失败的分片继续下一个--skip-error-count 5限制最多跳过5个分片防止坏片太多导致成品不可用。第二种方案是换源很多平台有多个CDN节点m3u8链接里改一下域名前缀就能换到另一个节点https://cdn-a.example.com/path/playlist.m3u8 https://cdn-b.example.com/path/playlist.m3u8改域名后重新下载坏分片在新节点上往往是好的。我的经验是先换源换源还卡再跳过。直接跳过会导致视频有缺口换源则是无损的。5.4 视频时长对不上分片时长表和一个实际案例现象下载完的视频用播放器看正常但用剪辑软件导入后时长比网页播放器显示的少了一两分钟。原因m3u8列表里的#EXTINF时长与ts文件内实际媒体流时长存在误差。网页播放器按m3u8列表求和来显示总时长剪辑软件则按文件内时间戳读取。切片工具在边界处有时会生成实际时长比#EXTINF少零点几秒的分片几百个分片叠加起来就少了两分钟。解决合并前用ffprobe逐个分片查实际时长把偏差超过500ms的分片用ffmpeg补齐到标准时长再合并。写起来是这样的for f in segment_*.ts; do duration$(ffprobe -v error -show_entries formatduration -of defaultnoprint_wrappers1:nokey1 $f) if (( $(echo $duration 5.5 | bc -l) )); then ffmpeg -i $f -t 6 -c copy ${f%.ts}_fixed.ts mv ${f%.ts}_fixed.ts $f fi done这段脚本的含义是遍历每个分片读取实际时长如果小于5.5秒就用ffmpeg把该分片强制生成为一个6秒长的文件。-t 6是指定输出时长由于ts流是复制拷贝末尾不足的部分会自动填充空包。这样处理后合并出来的视频总时长就和网页播放器一致了。6. 下载完不着急用两步校验与一份批量下载清单视频下载成功不等于视频可用。我每次批量处理完一批m3u8都会强制走一遍校验流程这个习惯是从几次翻车经历中养成的。校验分两步第一步是校验合并文件的完整性看时长和流信息第二步是校验实际的播放质量光靠播放器拖动进度条根本看不出问题。完整性校验用ffprobe读总时长和流数量ffprobe -v error -show_entries formatduration,size -show_entries streamcodec_type,codec_name -of json final.mp4输出里应该有两条codec_type一条是video一条是audio。如果只有video没有audio说明音频流在合并时丢了多半是-bsf:a aac_adtstoasc这个参数没带或者带错了。JSON输出里的duration值和源m3u8列表的#EXTINF总和做对比误差超过2%就说明分片有缺失或时长偏差。播放质量校验我会用快速抽帧的方法提取视频中段和末段各一帧画面检查有没有花屏ffmpeg -i final.mp4 -ss 00:05:00 -frames:v 1 frame_mid.jpg ffmpeg -i final.mp4 -ss 00:59:00 -frames:v 1 frame_end.jpg如果画面是花屏或纯黑说明中后段有解密不完整的分片混进去了。这两个文件生成后用看图工具肉眼扫一眼整个过程不用一分钟比来回拖动播放器可靠得多。批量下载的场景我会把每个待下载链接的参数写进一个清单文件结构是这样# video_list.txt # 格式: 视频名称 | m3u8地址 | 是否需要登录 | 输出文件名 课程A-第1节 | https://cdn-a.example.com/path/playlist_01.m3u8 | no | 01.mp4 课程A-第2节 | https://cdn-a.example.com/path/playlist_02.m3u8 | yes | 02.mp4处理清单的脚本逻辑是按行读取判断是否需要登录需要登录的加载Cookie文件然后执行下载命令。这里要注意输出文件名不能和已有文件重名否则会直接覆盖。我的习惯是下载前先统计清单数量和目标目录已有文件数对不上就不动工。从那以后我每次批量下载都会强制走一遍这个校验流程把校验命令写成一个脚本放服务器上下载完自动执行、自动报告结果。虽然多了两分钟检查时间但再也没出现过交付给业务方才发现视频花屏的尴尬。希望帮到你。本文还有配套的精品资源点击获取