直接跟你说结论:这堆.ts文件完全不用怕,它们不是损坏的视频碎片,只是一段完整视频被按时间切成了若干个小块。你只要按顺序把它们接起来,再换个封装格式,就能得到流畅播放的 MP4。我前前后后用这个方法处理过几十个从浏览器缓存里扒下来的视频,折腾多了发现核心就一个工具、一条命令的事。这篇就给你把原理、命令、坑一次性讲明白。
1. 先从根上搞清楚:TS 文件是什么,为什么会有一大堆
1.1 TS 文件不是"残缺文件",而是视频流的原始分片
很多朋友第一次看到几百个.ts文件时的第一反应是"这视频被切碎了,是不是没救"。其实TS的全称是 Transport Stream,也就是传输流。它本身就是一种音视频封装格式,最早是用在数字电视广播里的,讲究的是"哪怕网络抖一抖、丢几个包,也能尽量把画面继续播下去"。所以每个.ts文件实际上都是一个可以独立解码的视频小片段,自带音频和视频数据,只看单个文件也能播放出那几秒钟的内容。
你在浏览器里下载到的这一堆 TS 文件,本质上就是播放器从服务器拉取下来的一段 CDN 加速后的流媒体缓存。视频网站为了让你拖动进度条不卡顿、边下边播,会把整部影片切成 5 到 10 秒一小段,然后用一个播放列表去索引这些片段。你看到的这些.ts文件,就是这些已经被下载到本地缓存的"原材料"。
1.2 浏览器下载视频时到底发生了什么
正常情况下,你在网页里看视频,地址栏里是看不到.mp4的。因为页面里嵌入的播放器通常不是直接请求一个完整的大文件,而是先请求一个m3u8或mpd格式的索引文件。以m3u8为例,它里面写的不是视频内容本身,而是一张"地图":从哪个地址下载第 1 个文件,从哪个地址下载第 2 个文件,每个文件播放多久,视频的分辨率、码率是多少。
当页面加载完成,播放器会根据这个地图,把 TS 分片一个个下载到浏览器缓存目录里。你如果在文件资源管理器里看浏览器的缓存文件夹,自然就会看到xxx_00001.ts、xxx_00002.ts这种按序号排好的文件。这根本不是"视频下载到了某个文件夹",而是"浏览器把已经播放过的分片留在本地了"。
1.3 播放列表文件 M3U8 的作用
我建议你先找到那个m3u8文件。如果它还在,后面的合并工作会简单很多。.m3u8本质上是一个 UTF-8 编码的文本文件,里面每一行都指向一个 TS 分片的路径,有的还标注了EXTINF(Duration,时长信息)。举个例子:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.000000, index_00001.ts #EXTINF:10.000000, index_00002.ts ... #EXT-X-ENDLIST这个东西就是分片之间的"锁链"。只要有了它,FFmpeg 就能自动按列表顺序读取所有分片,无缝拼成一个完整的视频流。后面我会教你怎么从浏览器缓存里把 m3u8 一起捞出来,或者干脆手动生成一个等价清单。
1.4 合并前,先把工具备齐
不需要安装一堆花里胡哨的软件。我实测下来,最省心的组合是:
| 工具 | 用途 | 获取方式 |
|---|---|---|
| FFmpeg | 合并、转换、修复音画同步 | 官网下载对应系统的可执行文件 |
| 一个能看十六进制或文本的编辑器 | 检查 m3u8 内容、修改文件清单 | Notepad++ / VS Code / 系统自带记事本 |
| 文件管理器 | 排序、重命名、清点分片数量 | Windows/macOS/Linux 自带 |
如果你不想下载额外软件,Windows 系统下用 PowerShell、macOS 或 Linux 下用终端加 FFmpeg 就能完成 80% 的工作。接下来我会把核心操作拆开讲,保证你在十分钟内能把第一段视频合并出来。
2. 合并转换的核心思路:一条 FFmpeg 命令为什么能搞定
2.1 先看 FFmpeg 的合并命令长什么样
假设你已经把某个视频所有的 TS 分片放在同一个文件夹里,并且找到了对应的 m3u8 文件,那么合并命令非常简单:
ffmpeg -allowed_extensions ALL -i input.m3u8 -c copy -bsf:a aac_adtstoasc output.mp4我知道新手看到这一串字母会慌,我来拆给你看:
-allowed_extensions ALL:允许 FFmpeg 读取本地文件时跨目录访问相关分片。如果m3u8里写的是相对路径,不加这个参数可能报错。-i input.m3u8:指定输入文件,注意这里把m3u8当输入,FFmpeg 会自动读取列表并逐个加载 TS 分片。-c copy:这是最关键的参数,意思是"流复制"。它告诉 FFmpeg:不要重新编码视频和音频,直接把原始数据拷进新的封装容器里。相当于把一盒积木按原样搬到另一个盒子里,不改变每一块的形状。-bsf:a aac_adtstoasc:把音频从 ADTS 格式转换成 ASC 格式。TS 流里的 AAC 音频通常带一个 ADTS 头,MP4 需要的则是 ASC 配置信息。不写这个参数,输出文件播放时经常只有画面没有声音,或者音画不同步。output.mp4:输出的文件名。你可以自己指定路径。
就这么一条命令,它做了三件事:读取播放列表、按顺序拼接所有分片、把最终结果封装成 MP4。
2.2 为什么优先推荐"流复制"而不是"重新编码"
很多教程会让你写ffmpeg -i input.m3u8 -c:v libx264 -c:a aac output.mp4,我强烈不建议你直接用这个。因为-c:v libx264意味着 FFmpeg 会把视频重新解码,再用 x264 编码器重新压一遍。这个过程有两个明显毛病:
第一,极其耗时。一个 1 小时的 1080p 视频,重新编码可能需要 20 分钟甚至更久,而-c copy通常几十秒就完成了。
第二,画质损失。哪怕你把码率参数调得很高,二次编码总会引入额外的压缩误差。特别是原本码率比较低的在线视频,重压之后会出现明显的色块和模糊。
你可以把 TS 分片理解成一张张已经裁剪好的照片。复制拼接是直接把照片按序号订成册,画面原封不动;重新编码则相当于把每张照片拿扫描仪扫一遍、修图、再打印出来,怎么能不糊?所以除非遇到下面两个场景,否则永远优先用-c copy。
- 原视频编码格式过于古老,比如 MPEG-2 编码的 TS,MP4 容器虽然能装它,但兼容性很差,手机可能播不了。这种情况下可以只转视频编码。
- 你确实需要压缩体积,比如原视频码率非常高,你想转一个小文件发出去。这时候可以接受画质损失。
如果你不确定原视频是什么编码,先用这个命令看一眼:
ffprobe -i input.m3u8输出结果里会写明Video: h264还是Video: hevc或Video: mpeg2video。如果是 h264 或 hevc,放心用-c copy。
2.3 m3u8 文件丢了,还能不能合并
这是最常见的情况。浏览器缓存里只有一堆xxx_0.ts、xxx_1.ts,翻遍了都找不到 m3u8。别慌,我们可以自己生成一个。
在 Linux 或 macOS 的终端里,先进入分片所在目录,然后执行:
for f in *.ts; do echo "file '$f'"; done > list.txtWindows 下的 PowerShell 用:
Get-ChildItem -Name *.ts | ForEach-Object { "file '$_'" } | Out-File -Encoding ascii list.txt然后使用 FFmpeg 的 concat 格式直接合并:
ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4这里的-f concat告诉 FFmpeg 按文本清单拼接文件。注意每一行必须写成file '文件名'的格式,如果是中文文件名,最好先把文件重命名成简单的数字序列,防止编码问题。
2.4 没装 FFmpeg 的备选方案:用播放器或网站在线转换
如果电脑上暂时没有 FFmpeg,又急着合并,可以考虑这类办法:用支持"本地 M3U8 播放"的播放器打开对应的 m3u8 文件,确认能完整播放后,再用它的录制/导出功能。不过我不太推荐这条路线,因为播放器导出要么是录屏(画质受损),要么依赖特殊插件,成功率低。另一个方案是把 m3u8 文件上传到某个在线转换网站,但视频隐私和文件大小都是问题。所以我的建议很明确:花几分钟装个 FFmpeg 才是最省事、最可靠的路。
3. 完整实操:从一堆 TS 文件到流畅播放的 MP4
3.1 第一步:把分片文件"归拢"到同一个文件夹
先把浏览器缓存目录里的相关文件复制到一个新文件夹。不要直接在原目录操作,一是避免文件被浏览器自动清理,二是保持操作环境干净,防止混入别的视频碎片。具体操作方式:
- Windows 的 Edge/Chrome 等浏览器,在地址栏输入
edge://version或chrome://version可以看到"个人资料路径",缓存目录通常在这个路径下的Cache或Code Cache里。实际文件往往没有扩展名,你需要按文件大小排序来找体积较大的那一批。 - Safari、Firefox 也有类似的缓存目录。实在找不到就搜索
m3u8,一般能定位到最近播放的视频的索引文件。
如果你是从某些第三方工具或网页下载按钮直接保存的视频,文件夹里可能已经是一个个.ts文件,那更好办。把所有这些文件放进同一个目录,并且确保播放列表里的路径与真实文件名一致。
3.2 第二步:确认分片的顺序和完整性
合并最怕的是顺序错乱和缺片。先按名称排序检查一下。如果是类似index_0.ts、index_1.ts、index_2.ts这种序号开头的,通常没问题。但如果文件名是带时间戳的哈希值(比如a1b2c3.ts、d4e5f6.ts这种毫无规律的名字),顺序就完全依赖 m3u8 列表了,这时一定要找到 m3u8 作为索引。
完整性怎么查?最快速的方法是用 FFmpeg 直接尝试合并,过程中会提示403、404或Invalid data等字眼。不过更稳妥的是写个小脚本统计每个文件的大小。以我经验,同一个视频分片,除了开头和结尾的文件可能明显小一些,中间的分片大小应该都在一个相近的范围内。如果发现某个文件特别小,比如只有几百字节,那很可能下载不完整。
3.3 第三步:用 M3U8 清单合并,并保持原画质
现在假设你已经拷贝好文件、找到了 m3u8,执行:
ffmpeg -allowed_extensions ALL -i playlist.m3u8 -c copy -bsf:a aac_adtstoasc merged.mp4如果成功,会出现一长串进度记录,最后显示muxing overhead之类的信息。此时用播放器打开merged.mp4,拖动进度条确认无异常即可。
这里有个小细节:如果 m3u8 里的路径是完整 URL(http://.../xxx.ts),FFmpeg 会直接去网络拉流。你本地已经有缓存的情况下,应该把 m3u8 里的 URL 改成相对路径或本地路径,否则要么下载不了,要么速度很慢。推荐用文本编辑器打开 m3u8,把每一行的http....../前缀去掉,只保留文件名。如果分片和 m3u8 在同一个目录,这一步是必须的。
3.4 第四步:没有 M3U8 时的手动清单合并
没有 m3u8 文件,就按 2.3 节的方法生成list.txt。这里有一个提升成功率的技巧:生成前先把文件名批量重命名成四位数的序号,比如0001.ts、0002.ts,避免因为序号位数不齐导致排序错误。Windows 下可以使用 PowerShell:
$i=1; Get-ChildItem *.ts | Sort-Object Name | ForEach-Object { Rename-Item -Path $_.FullName -NewName ('{0:D4}.ts' -f $i) ; $i++ }这样之后生成的list.txt内容就非常干净了。然后执行 2.3 的合并命令。
3.5 第五步:检查输出文件,清理中间文件
合并完成后,先用ffprobe看一眼输出文件的基本信息:
ffprobe merged.mp4主要确认三样:时长是否和原视频一致、音频流是否存在、视频编码是否为 h264/hevc。如果时长和你记忆中差不多,那就基本成功了。
我习惯把最终 MP4 挪到安全目录,然后删除原文件夹里的分片和临时 list.txt,避免它们占用大量空间。这也是经验之谈:一个 2 小时的视频,分片加起来的体积可能比你想象的还大,合并不代表分片消失,两者会同时占磁盘。确认无误后及时清理。
4. 常见问题与排查技巧实录
4.1 合并出来的视频有画面没声音
十次有八次是这个原因:没有用-bsf:a aac_adtstoasc。前面我已经说过,TS 里的 AAC 音频和 MP4 里的组织方式不同,需要转换 bitstream filter。补上参数重新跑一次就正常了。
但还有另一种情况:原视频本身是 AC3 或 EAC3 音轨,MP4 容器对这两种音频格式支持得不太好。这时你可以只把音频转成 AAC:
ffmpeg -allowed_extensions ALL -i input.m3u8 -c:v copy -c:a aac -bsf:a aac_adtstoasc output.mp4注意这里-c:v copy保留视频,-c:a aac单独转音频,速度也很快。
4.2 播放器提示"文件损坏"或中途跳帧
排查分几步:
- 用 FFmpeg 的日志看有没有报错。如果某一行提示
Packet corrupt,说明对应的 TS 分片可能下载不完整。 - 找到那个序号的分片,看它的大小是否明显偏小,尝试重新从浏览器缓存里捞一次,或者重新拉取。
- 如果只是个别文件问题,可以编辑 m3u8 或 list.txt 跳过那个文件,但这样可能造成画面跳动几十秒,属于下策。
我遇到这种情况,通常直接临时换一条合并策略:不按清单,而是把所有文件按顺序用 concat 协议硬拼:
ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4concat 协议在遇到个别损坏分片时的容忍度比 m3u8 读取模式高一点。如果还是失败,那就别硬撑,重新下载原视频更省时间。
4.3 合并成功了,但画质明显比在线播放差
这个问题九成不是合并造成的,而是你下载的分片本身码率就不高。很多网站在播放时会根据你的带宽自动选择清晰度,如果当时网络一般,浏览器缓存的可能是 480p 甚至 360p 的分片。合并只是"搬运",不会把低清变高清。
解决办法是:主动把浏览器播放器的清晰度调到最高,然后重新完整播放一遍(哪怕倍速播放),让所有高码率分片下载到本地,再重复合并操作。另外有些网站的 m3u8 里包含多组不同清晰度的流,开头会有RESOLUTION=1920x1080这样的标签,你可以手动指定最高分辨率的那一组索引。不要被伪 4K 骗了,很多电视端的 4K 在线流,码率比普通 1080p 高不了多少。
4.4 分片带有加密,合并后内容花屏或直接黑屏
现在很多正规视频平台都对 HLS 分片做了 AES-128 加密。m3u8 文件里会多一行#EXT-X-KEY:METHOD=AES-128,URI="key.key",分片的内容不是纯视频流,而是加密后的密文。这时你如果硬合并,得到的 MP4 是花的或黑的。
处理思路有两条:
- 找到解密的 key 文件,并在 m3u8 中保留正确的 URI,FFmpeg 会自动解密。前提是 key 文件路径可访问。你需要把 key 同步到本地,并将 m3u8 里的 URI 改成本地路径。
- 如果 key 是通过网络请求动态获取的,且已失效,那你基本无法离线解密,除非在播放时实时获取。这个属于版权边界问题,我不建议折腾,也没必要。
一句话总结:带加密的 HLS 流,能不能离线合并取决于你是否有合法的解密密钥。这不是工具能硬解的。
4.5 分片数量太多,Windows 路径长度报错
当分片文件名很长,或者文件目录层级很深,Windows 会报"文件名太长"的错误。解决办法很简单:把临时目录放在盘符根目录下,比如D:\merge\,并且把分片重命名为0001.ts这样的短名字。这个问题我在处理长视频时经常遇到,提前做好重命名可以省掉很多麻烦。
4.6 合并过程中内存或磁盘爆满
用-c copy合并时,FFmpeg 并不会把全部内容载入内存,内存占用一般很低。但输出文件也是完整大小,所以磁盘空间至少要剩视频体积的两倍(分片 + 输出)。如果空间不足,可以分两次:先合并前面一半,再合并后面一半,最后用 concat 把两个半段 MP4 接起来。当然这属于应急方案,正常情况下不建议分多次。
我个人现在处理这种多 TS 文件转 MP4 的需求,第一反应永远是先找 m3u8,第二反应才是手动生成 list.txt。前一种方法能自动确保顺序、处理加密和变体流,后一种需要自己确认的信息更多。你要是只偶尔处理一次,用上面的命令足够;要是频繁处理这类视频,建议把 2.3 的生成清单脚本和合并命令写成一个批处理文件,丢到右键菜单里,能省下不少重复劳动。最后再提醒一个很多人忽略的点:合并后的 MP4 一定要自己完整拉一遍进度条,别只看开头几十秒没问题就以为大功告成,中途某个分片缺失往往要等播放到那里才露馅。