简介:这是一款面向普通用户与音视频爱好者的TS视频合并工具,专门解决浏览器下载视频后碎片化、无法直接播放的问题。当视频以0.ts、1.ts、2.ts等连续序号形式散落在文件夹中时,只需打开工具页面输入末尾序号并生成合并脚本,即可将碎片快速拼接为完整MP4文件,无需掌握命令行或专业剪辑软件,属于零门槛的实用小工具。资源包共5个文件,包含html工具页面、bat批处理脚本、txt使用手册以及两段wmv操作演示视频,压缩包约1.54MB,体积轻巧、解压即用。目前已有2633人学习下载,说明该方案在同类需求中具备一定认可度。需要留意的是,工具仅支持非加密且序号连续的ts文件;若文件名带有随机前缀,则需另行联系作者处理。整体而言,配套的图文手册与视频教程能帮助读者快速理解合并原理与排错思路,适合需要批量整理本地缓存视频的用户参考使用。
1. 从一堆 0.ts、1.ts 说起:这类分片视频到底怎么无损合并
手里拿到一个文件夹,里面躺着0.ts、1.ts、2.ts……一直到几百号,每个文件几兆到几十兆不等,播放器单独打开能放,但拖进度条就断,想发给别人又没法一个个传。这就是典型的 HLS 分片落地形态:视频被切成等长的 MPEG-TS 片段,靠一个播放列表串起来顺序播放。你要做的不是转码,而是把这些碎片按正确顺序拼回一个完整文件,最好还不重新编码、不掉画质、几分钟搞定。
这件事的门槛比想象中低,但坑比想象中多。顺序错一个,整段音画就乱;时间戳没对齐,合并后进度条会跳;封装格式选错,某些播放器直接黑屏。下面按「先搞懂 TS 是什么 → 选工具 → 写命令 → 批量脚本 → 排错 → 进阶校验」的路径走一遍,新手能照着复现,熟手能直接拿去改。
2. 先弄明白 TS 分片和合并的底层逻辑:为什么不能直接 cat
2.1 MPEG-TS 的结构决定了「拼接」这件事的可行性
MPEG-TS 全称 MPEG Transport Stream,设计初衷就是为传输而生。它把音视频数据打成固定 188 字节的包,每个包带 PID 标识属于哪路流,再靠 PTS/DTS 时间戳决定播放顺序。关键点在于:TS 本身就是为「流式拼接」设计的,每个分片都是自包含的,理论上按顺序首尾相接就能还原成一条连续流。
这也是为什么很多老工程师第一反应是cat 0.ts 1.ts 2.ts > out.ts。这个做法在纯 TS 流上确实经常能work,因为 TS 没有全局文件头,不需要像 MP4 那样在开头写 moov box。但它有两个致命前提:分片必须严格按序、且时间戳连续。一旦中间缺片或者时间戳有跳变,cat 出来的文件播放器就会卡死或音画不同步。
2.2 直接 cat 会在什么情况下翻车
我踩过的典型场景有三个。第一,分片不是从 0 开始编号,比如从 100.ts 开始,说明前面还有内容,直接 cat 会丢头。第二,分片之间存在重叠或间隙,某些下载工具会重复写入边界帧,cat 后出现花屏。第三,音视频 PID 在不同分片里不一致,这种情况少见但一旦出现,cat 出来的文件只有画面没声音。
所以结论是:cat 可以作为快速验证手段,但不能作为生产方案。真正可靠的合并要么用 ffmpeg 的 concat 协议,要么先转成 MPEG-TS 再走 concat demuxer,让工具去处理时间戳和边界。
2.3 合并前必须先做的三件事
动手之前花两分钟做检查,能省掉后面半小时的排错。
第一,确认分片完整性。看编号是否连续,有没有0.ts到N.ts中间缺号。缺号意味着数据不完整,合并出来必然有断点。
第二,确认分片格式。用ffprobe抽查一两个文件,看容器是不是 mpegts、编码是不是 h264/aac。有些站点会把分片伪装成 .ts 但实际是 fMP4 片段,这种就不能按 TS 处理。
第三,确认播放列表。如果目录里有.m3u8文件,直接读它,里面#EXTINF的顺序就是权威顺序,比按文件名排序靠谱得多。文件名排序在遇到10.ts和2.ts时会按字典序排成10在2前面,这是新手最常翻的车。
# 抽查分片格式,确认是 mpegts 容器 ffprobe -v error -show_entries format=format_name -of default=noprint_wrappers=1 0.ts # 查看分片时长和编码,确认音视频流都存在 ffprobe -v error -show_entries stream=index,codec_name,codec_type -of csv 0.ts # 如果有 m3u8,直接看它的分片顺序 grep -E '\.ts' playlist.m3u8 | head -20ffprobe的-v error只输出错误,避免刷屏;format_name返回mpegts就说明是标准 TS。stream那行会列出每个流的 codec,正常应该看到一个 h264 视频流和一个 aac 音频流。如果只有视频没有音频,说明分片本身有问题,合并也救不回来。
3. 用 ffmpeg 合并 TS:concat 协议和 concat demuxer 怎么选
3.1 两种合并方式的本质区别
ffmpeg 提供两条路。第一条是concat 协议,写法是concat:0.ts|1.ts|2.ts,它把多个文件当成一个虚拟输入流,底层直接做字节级拼接。优点是快,几乎不消耗 CPU,因为不解码。缺点是要求所有分片的编码参数完全一致,且对时间戳跳变容忍度低。
第二条是concat demuxer,需要先写一个列表文件,然后ffmpeg -f concat -i list.txt -c copy out.mp4。它会解析每个分片的时间戳并重新对齐,容错性更好,还能顺便换封装格式。缺点是比协议方式稍慢,但依然是 copy 模式,不重新编码。
我的选择习惯是:分片来自同一个源、编号连续、格式统一,用 concat 协议一行命令搞定;分片来源混杂、时间戳有跳变、或者要输出 MP4,用 concat demuxer。
3.2 concat 协议的最小可用命令
# 最简形式,按顺序列出所有分片,-c copy 表示不重新编码 ffmpeg -i "concat:0.ts|1.ts|2.ts|3.ts" -c copy -bsf:a aac_adtstoasc output.mp4这里有几个参数必须说清楚。-c copy是核心,它告诉 ffmpeg 直接复制音视频流,不做解码再编码,所以速度快、画质无损。-bsf:a aac_adtstoasc是音频比特流过滤器,作用是把 AAC 的 ADTS 头转成 ASC 格式,因为 TS 里的 AAC 带 ADTS 头,而 MP4 容器要求 ASC 格式,不加这个参数输出 MP4 时音频会报错或直接丢失。如果输出还是 .ts 格式,这个过滤器可以不加。
分片数量少的时候手写管道符没问题,但几百个分片就不现实了。这时候用 shell 拼字符串:
# 用 seq 生成 0 到 299 的分片列表,拼成 concat 协议字符串 files=$(seq -f "%g.ts" 0 299 | paste -sd '|') ffmpeg -i "concat:$files" -c copy -bsf:a aac_adtstoasc output.mp4seq -f "%g.ts" 0 299生成0.ts到299.ts,paste -sd '|'用竖线把它们连成一行。这个写法比手敲可靠,但前提是编号连续且从 0 开始。如果编号有跳号,得先手动核对。
3.3 concat demuxer 的列表文件写法
# 生成 concat 列表,file 后面跟路径,单引号防止特殊字符 for f in $(ls -v *.ts); do echo "file '$PWD/$f'" done > list.txt # 用 concat demuxer 合并,输出 MP4 ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4ls -v是关键,它做的是自然排序(version sort),会把2.ts排在10.ts前面,避免字典序的坑。-f concat指定使用 concat 解复用器,-safe 0允许列表里出现绝对路径(不加这个,ffmpeg 会拒绝非相对路径)。列表文件里每行file '路径',路径用单引号包住,防止空格和特殊字符出问题。
提示:如果分片文件名里有单引号,需要转义成
'\'',这种情况少见但遇到会报语法错误。
3.4 输出格式怎么选:TS 还是 MP4
如果只是自己存档、用支持 TS 的播放器看,直接输出.ts最省事,不需要aac_adtstoasc,也不会有封装兼容问题。但如果要发给别人、上传到常见平台、或者用手机默认播放器打开,输出 MP4 兼容性最好。
输出 MP4 时注意一点:-c copy模式下,如果原视频是 h264、音频是 aac,MP4 完全支持,直接封装没问题。但如果视频是 h265,老设备可能放不了,这不是合并的问题,是编码兼容性问题,需要重新编码才能解决,而重新编码就违背了「无损快速合并」的初衷。
4. 批量合并脚本:从单目录到多目录的自动化处理
4.1 单目录批量合并的 shell 脚本
实际场景往往不是合并一个视频,而是一批。比如下载了十几个课程,每个课程一个文件夹,里面都是 TS 分片。手敲命令不现实,写个脚本一次处理。
#!/bin/bash # 遍历当前目录下所有子目录,每个子目录当作一个视频的分片集合 for dir in */; do # 去掉目录名末尾的斜杠,作为输出文件名 name="${dir%/}" # 进入目录,按自然序生成列表 cd "$dir" || continue if ls *.ts >/dev/null 2>&1; then for f in $(ls -v *.ts); do echo "file '$PWD/$f'" done > list.txt # 输出到上级目录,文件名用目录名 ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc "../${name}.mp4" -y echo "完成: ${name}.mp4" else echo "跳过: ${dir} 没有 ts 文件" fi cd .. done这个脚本的逻辑是:外层循环遍历所有子目录,内层用ls -v生成自然序列表,然后调用 ffmpeg 合并,输出到上级目录并以目录名命名。-y表示覆盖已存在文件,避免交互确认卡住脚本。ls *.ts >/dev/null 2>&1用来判断目录里有没有 TS 文件,没有就跳过,防止空目录报错。
4.2 处理编号不连续的分片
编号不连续是常见情况,比如0.ts、1.ts、3.ts,缺了2.ts。这时候直接合并会丢一段内容,但有时候缺的那片本来就没下载成功,你只能接受。脚本层面要做的是:检测缺号并提示,而不是默默合并。
# 检测编号连续性,输出缺失的编号 prev=-1 for f in $(ls -v *.ts); do num="${f%.ts}" # 只处理纯数字文件名 if [[ "$num" =~ ^[0-9]+$ ]]; then if [[ $prev -ge 0 && $((num - prev)) -ne 1 ]]; then echo "警告: $((prev+1)) 到 $((num-1)) 之间缺片" fi prev=$num fi done这段逻辑遍历所有 TS 文件,提取文件名里的数字,检查相邻编号差值是否为 1。不是 1 就说明中间有缺号,打印警告。[[ "$num" =~ ^[0-9]+$ ]]用来过滤掉非纯数字命名的文件,避免误判。
4.3 用 Python 做更可控的批量处理
shell 脚本适合简单场景,但如果要处理几百个目录、需要记录日志、需要跳过已完成的,Python 更合适。
import os import subprocess from pathlib import Path def merge_ts_dir(src_dir, out_file): """合并单个目录下的 TS 分片,返回是否成功""" ts_files = sorted( [f for f in os.listdir(src_dir) if f.endswith('.ts')], key=lambda x: int(x[:-3]) if x[:-3].isdigit() else 0 ) if not ts_files: return False list_path = os.path.join(src_dir, 'list.txt') with open(list_path, 'w', encoding='utf-8') as fp: for f in ts_files: fp.write(f"file '{os.path.join(src_dir, f)}'\n") cmd = [ 'ffmpeg', '-f', 'concat', '-safe', '0', '-i', list_path, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', out_file, '-y' ] result = subprocess.run(cmd, capture_output=True) return result.returncode == 0 # 遍历当前目录下所有子目录 for d in sorted(os.listdir('.')): if os.path.isdir(d): out = f"{d}.mp4" if os.path.exists(out): print(f"已存在,跳过: {out}") continue ok = merge_ts_dir(d, out) print(f"{'成功' if ok else '失败'}: {out}")关键点在排序函数:key=lambda x: int(x[:-3])把文件名去掉.ts后转成整数排序,这样10.ts会正确排在2.ts后面。subprocess.run用capture_output=True捕获输出,避免 ffmpeg 的日志刷屏,同时通过returncode判断成功与否。跳过已存在文件的逻辑,让脚本可以中断后重跑,不用从头再来。
5. 合并 TS 分片最容易踩的坑:排查清单
5.1 合并后音画不同步,进度条乱跳
现象:合并出来的文件能播放,但声音比画面快或慢几秒,拖进度条时时间显示异常。
原因:分片之间的 PTS 时间戳不连续,concat 协议直接拼接时没有重新对齐时间戳,播放器按错误的时间轴解码。
解决:改用 concat demuxer,它会重新计算时间戳。如果还不行,加-fflags +genpts让 ffmpeg 重新生成 PTS:
ffmpeg -f concat -safe 0 -i list.txt -c copy -fflags +genpts -bsf:a aac_adtstoasc output.mp45.2 输出 MP4 后没有声音
现象:合并过程没报错,但输出文件只有画面,音频轨缺失。
原因:TS 里的 AAC 音频带 ADTS 头,MP4 容器不认,ffmpeg 在 copy 模式下不会自动转换,导致音频流被丢弃。
解决:加上-bsf:a aac_adtstoasc。如果加了还是没声音,用ffprobe检查原分片有没有音频流,可能源头就没有。
5.3 分片顺序错乱,画面跳来跳去
现象:合并后内容顺序不对,比如第 10 分钟的内容跑到了第 2 分钟。
原因:用了字典序排序,10.ts排在了2.ts前面。
解决:用ls -v或 Python 的整数排序。如果目录里有 m3u8,直接按 m3u8 里的顺序生成列表,那是最权威的。
5.4 ffmpeg 报 "Non-monotonous DTS" 警告
现象:合并时刷大量Non-monotonous DTS in output stream警告,输出文件可能仍有问题。
原因:分片的时间戳有回退或重复,常见于从不同来源下载的分片混在一起。
解决:这种警告在 copy 模式下无法完全消除,因为 ffmpeg 不能改时间戳。可行的办法是先转成 MPEG-TS 中间格式再合并,或者接受警告但检查输出是否可正常播放。如果严重影响播放,只能重新编码,用-c:v libx264 -c:a aac牺牲速度换正确性。
5.5 合并后文件巨大或异常小
现象:输出文件比所有分片加起来还大,或者小得离谱。
原因:文件巨大通常是误用了重新编码且码率设太高;文件异常小通常是 concat 列表只包含了一部分分片,或者路径写错导致 ffmpeg 跳过了文件。
解决:检查 list.txt 的行数是否等于分片数,检查每条路径是否真实存在。用-c copy确认没有触发编码。正常情况输出大小应该约等于所有分片大小之和。
6. 合并之后的校验与进阶技巧:别让几小时下载毁在最后一步
合并完成不等于万事大吉。我一般会做三件事:校验时长、抽查关键帧、确认音视频轨完整。
时长校验最直接,用ffprobe读输出文件的 duration,和所有分片时长之和对比,误差在几秒内算正常。如果差太多,说明有分片没被合并进去。
# 查看合并后文件的总时长和流信息 ffprobe -v error -show_entries format=duration -show_entries stream=codec_type,codec_name -of default=noprint_wrappers=1 output.mp4抽查关键帧用ffmpeg截几张图,看画面是否正常、有没有花屏。尤其是分片边界处,最容易出问题。
# 在 10%、50%、90% 位置各截一帧,检查画面 ffmpeg -ss 00:10:00 -i output.mp4 -frames:v 1 check1.png -y ffmpeg -ss 00:30:00 -i output.mp4 -frames:v 1 check2.png -y进阶一点的做法是,如果分片数量特别多(上千个),concat 列表会很长,ffmpeg 解析列表本身会变慢。这时候可以分批合并:先每 100 个分片合并成一个中间 TS,再把中间 TS 合并成最终文件。这样每步的列表都短,整体更快,而且中间产物还能作为断点续做的依据。
另一个技巧是保留原始分片直到确认输出无误。我吃过亏,合并完觉得没问题就把分片删了,结果发现输出文件在某个播放器上不兼容,想重来已经没有原料了。分片占的空间虽然不小,但在确认最终文件能正常播放、时长正确、音画同步之前,别急着清理。
最后一个习惯:合并命令和 list.txt 一起存到输出目录旁边。过几个月回头看,能立刻知道这个文件是怎么来的、用的什么参数。这比事后回忆靠谱得多。
希望帮到你。
本文还有配套的精品资源,点击获取