目录

  1. 不想装软件的情况
  2. 安装 FFmpeg
  3. 核心命令:concat 解复用器 + 流复制
  4. 列表生成:顺序不出错才是关键
  5. Windows bat 脚本:双击合并
  6. Python 脚本:批量处理多个文件夹
  7. 常见报错与解决
  8. 什么时候只能重新编码

一、不想装软件的情况

三个 TS 测试分片已加载,显示文件统计、按编号排序与开始合并
展开清晰大图(左右滑动查看)
打开原图

实拍(2026-10-30):已选择 segment_000.ts、segment_001.ts、segment_002.ts,并点击「按编号排序」;确认列表后再点「开始合并」。来源:EZ在线工具网;使用合成测试素材演示,点击图片可放大。

如果只是偶尔合一次、文件也不大,先不必折腾安装配置。浏览器里就能完成,比如 ezwebtools.net/ts-merge,把 TS 拖进去、确认顺序、点合并。

挑这类工具时,先看清一件事:它是在你本机处理,还是要把文件上传到服务器。

判断方法:打开页面 → F12 → Network 面板 → 断开网络 → 拖文件进去点合并。还能跑完,就说明处理确实发生在本地;网络不通还能合,总不至于把文件传出去了。

浏览器方案也有边界。它跑的是编译到 WebAssembly 的 FFmpeg,比原生慢一截,而且受浏览器可用内存约束。几个 GB 的量级,还是装个本地 FFmpeg 踏实。


二、安装 FFmpeg

TS 合并这件事基本绕不开 FFmpeg,多数带界面的工具底层也是调它。

Windows

  1. 打开 ffmpeg.org/download.html
  2. Windows 区域选 gyan.dev 或 BtbN 的构建
  3. 下载 release-full 版本(.7z 用 7-Zip 解压),解压到固定位置,比如 C:\ffmpeg
  4. 把 C:\ffmpeg\bin 加进系统环境变量 Path
  5. Win+R → powershell → 执行 ffmpeg -version,输出版本信息就装好了

macOS:brew install ffmpeg

Linux(Debian/Ubuntu 系):sudo apt install ffmpeg


三、核心命令:concat 解复用器 + 流复制

FFmpeg 合并:列表 → 流复制 → 验证(示意图)

图:适用于编码参数兼容的 TS 分片;命令和脚本见正文。

1
ffmpeg -y -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4
参数 含义
-y 输出文件已存在时直接覆盖,不交互等待
-f concat 使用 concat 解复用器,按列表顺序读取多个输入
-safe 0 允许列表里出现绝对路径、.. 等特殊写法
-i list.txt 输入文件列表
-c copy 流复制,不解码不重新编码,画质零损失
-bsf:a aac_adtstoasc 将 AAC 的 ADTS 封装转换为 AudioSpecificConfig 并移除 ADTS 头,适配 MP4;不是时间戳修复工具

list.txt 长这样:

1
2
3
file 'segment_000.ts'
file 'segment_001.ts'
file 'segment_002.ts'
浏览器中打开本次实际使用的 list.txt,包含三个 TS 分片路径
展开清晰大图(左右滑动查看)
打开原图

实拍(2026-10-30):浏览器直接打开本次合并使用的原始 list.txt,每行是一个分片,顺序为 000、001、002。这是文件内容截图,不是终端截图。

核对时检查三件事:第一行是不是首个分片、有没有漏片、文件名是否与实际文件一致。可查看本次原始列表,或下载合成测试包自己复现;解压后在该目录执行文中的合并命令。

-c copy 是整个操作的关键。 没有它,ffmpeg 会把每个分片解码再重编一遍,画质必定下降,耗时也会高一个数量级。

合并后的实际检查结果

本次实际合并成片的 ffprobe 检测输出,显示约 6.19 秒、H.264 视频和 AAC 音频
展开清晰大图(左右滑动查看)
打开原图

实拍(2026-10-30):三段合成 TS 合并后执行 ffprobe -hide_banner output.mp4,将实际输出保存为文本并在浏览器中打开。截图不是模拟的终端界面;日志仅将临时目录路径省略为文件名。

看 Duration 是否符合预期,再看是否同时出现 Video: h264 与 Audio: aac。本次样例为 640×360、时长约 6.19 秒,输出文件为 643438 字节;这些数值只是本次合成素材的结果,不是所有视频的固定参数。检测到音轨也不代表听感一定正常,仍应播放并抽查分片衔接。可查看原始检测输出和下载测试包。

本次 FFmpeg 合并后的合成 MP4 在浏览器播放器中显示测试画面与总时长

实拍(2026-10-30):浏览器已加载本次实际生成的 MP4,显示 640×360 的合成测试画面及约 6 秒总时长。这里展示的是检测结果对应的同一个文件,不是网上找到的示例成片。

可以打开这段测试成片,检查开头、中间、结尾能否正常播放;自己的视频还需要确认内容连续、声音和画面同步。

参数含义参见 FFmpeg 官方位流过滤器文档。aac_adtstoasc 不负责修复音画不同步。

容易混淆的两个东西

很多人说的”concat 协议”,严格讲不是一回事:

  • concat 解复用器(-f concat):读一个列表文件,适合文件多、要走磁盘的情况,容错和处理能力更好
  • concat 协议:直接把文件用管道符串起来,例如:
1
ffmpeg -i "concat:s1.ts|s2.ts|s3.ts" -c copy output.mp4

文件少的时候方便,但文件名里不能出现特殊字符,也不能处理分辨率/编码不一致的输入。文件一多就不如列表方式可靠,所以一般用列表。


四、列表生成:顺序不出错才是关键

TS 合并前:排序比点击合并更重要(示意图)

图:以下用 1、2、10、100 四个分片说明两种排序的区别。

TS 合并最常出的问题不是命令写错,而是顺序。文件系统默认给的是字典序:

1
2
字典序:1.ts → 10.ts → 100.ts → 2.ts
期望: 1.ts → 2.ts → ... → 10.ts → 100.ts

如果文件名是补零的(segment_0001.ts),字典序和数字序一致,直接用默认排序没问题。没补零就必须做自然排序。

macOS / Linux

1
2
3
4
5
# 命名补零时
printf "file '%s'\n" *.ts > list.txt

# 命名没补零时用版本排序
ls *.ts | sort -V | sed "s/^/file '/; s/$/'/" > list.txt

Windows PowerShell

1
2
3
4
Get-ChildItem -Filter *.ts |
Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(12, '0') }) } |
ForEach-Object { "file '$($_.Name)'" } |
Out-File -Encoding utf8 list.txt

这个写法把文件名里的数字左侧补零到 12 位再排序,video(2).ts 就能正确排在 video(10).ts 前面。

IDM 那种 video(1).ts 命名,数字在括号里,单独提取括号内的数字更准:

1
2
3
4
Get-ChildItem "video(*).ts" |
Sort-Object { [int]($_.Name -replace '.*\((\d+)\).*', '$1') } |
ForEach-Object { "file '$($_.Name)'" } |
Out-File -Encoding utf8 list.txt

生成完一定用记事本打开 list.txt 扫一眼,确认第一行是第一个分片。这一步省不得——顺序错了,合出来的视频会在中间跳画面、断声音,而且排查起来很费时间。

如果你有 index.m3u8,它的片段顺序就是权威答案,比猜文件名靠谱。

文件名里有单引号怎么办

concat 列表用单引号包裹路径,文件名自带 ' 时会把格式搞坏。规范写法是转义成 '\'':

1
2
file 'it's_a_test.ts'     ← 错误
file 'it'\''s_a_test.ts' ← 正确

实际场景少见,但脚本里最好处理掉,下面 Python 脚本已经处理了。


五、Windows bat 脚本:双击合并

每次都敲命令太麻烦。把下面内容存成 merge_ts.bat(保存编码选 ANSI/GBK,脚本首行已切 UTF-8 代码页),丢到 TS 文件所在目录,双击运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@echo off
chcp 65001 >nul
where ffmpeg >nul 2>&1 || (echo [错误] 未检测到 FFmpeg,请检查环境变量 & pause & exit /b 1)

if not exist *.ts (echo [提示] 当前目录没有 .ts 文件 & pause & exit /b 1)

echo 正在按自然排序生成列表...
powershell -NoProfile -Command "$f = Get-ChildItem -Filter *.ts | Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(12,'0') }) }; $f | ForEach-Object { 'file ' + [char]39 + $_.Name + [char]39 } | Out-File -Encoding utf8 _list.txt"

echo 开始合并,请勿关闭窗口...
ffmpeg -y -nostdin -f concat -safe 0 -i _list.txt -c copy -bsf:a aac_adtstoasc merged.mp4

del _list.txt 2>nul
echo 完成:merged.mp4
pause

几个细节:

  • 用 PowerShell 而不是 for %%i in (*.ts) 生成列表,是因为后者按目录枚举顺序走,遇到未补零的编号会出错
  • -nostdin 防止 ffmpeg 消耗标准输入,脚本里跑循环时容易因为这个卡住
  • [char]39 是单引号,这么写是为了避开 cmd 对引号的处理
  • 想改输出名就把 merged.mp4 换成你要的

六、Python 脚本:批量处理多个文件夹

下载下来的视频往往一个视频一个文件夹,逐个处理太慢。下面这个脚本会遍历所有子文件夹,逐个合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"""
TS 文件批量合并
依赖:Python 3.8+,FFmpeg 需在 PATH 中
用法:改掉脚本末尾的 TARGET_DIR,然后 python ts_batch_merge.py
"""

import re
import subprocess
from pathlib import Path


def natural_key(name: str):
"""自然排序:1.ts, 2.ts, ..., 10.ts 按数值而非字典序排列"""
return [int(s) if s.isdigit() else s.lower() for s in re.split(r"(\d+)", name)]


def ffmpeg_entry(name: str) -> str:
"""拼接 concat 列表的一行,并转义文件名里的单引号"""
return "file '" + name.replace("'", "'\\''") + "'"


def merge_folder(folder: Path, output_name: str = "merged.mp4") -> bool:
ts_files = sorted([f.name for f in folder.glob("*.ts")], key=natural_key)
if not ts_files:
return False

print(f"\n[{folder.name}] 发现 {len(ts_files)} 个 TS 文件")

list_file = folder / "_ffmpeg_list.txt"
list_file.write_text(
"\n".join(ffmpeg_entry(n) for n in ts_files) + "\n", encoding="utf-8"
)

output_path = folder / output_name
cmd = [
"ffmpeg", "-y", "-nostdin",
"-f", "concat", "-safe", "0",
"-i", str(list_file),
"-c", "copy", "-bsf:a", "aac_adtstoasc",
str(output_path),
]

result = subprocess.run(cmd, capture_output=True, text=True)
list_file.unlink(missing_ok=True)

if result.returncode != 0:
err = result.stderr.strip().splitlines()[-3:]
print(" [失败] " + " | ".join(err))
return False

size_mb = output_path.stat().st_size / (1024 * 1024)
print(f" [完成] {output_name} ({size_mb:.1f} MB)")
return True


def batch_merge(base_dir: str, recursive: bool = True):
base = Path(base_dir)
if not base.exists():
print(f"目录不存在:{base_dir}")
return

targets = [d for d in base.iterdir() if d.is_dir()] if recursive else [base]
ok = fail = skip = 0

for folder in targets:
if not any(folder.glob("*.ts")):
skip += 1
continue
if merge_folder(folder):
ok += 1
else:
fail += 1

print("\n" + "=" * 40)
print(f"处理完毕:成功 {ok} / 失败 {fail} / 跳过 {skip}")
print("=" * 40)


if __name__ == "__main__":
TARGET_DIR = r"D:\Downloads\videos"
batch_merge(TARGET_DIR, recursive=True)

失败信息只取最后三行是为了不让输出被 ffmpeg 的日志淹没。真要排查时,把 capture_output=True 去掉,让日志直接打到终端更方便。

如果 TS 是跟着 M3U8 下载的,更稳的做法是直接解析 M3U8 里的片段顺序,而不是靠文件名排序——M3U8 是权威顺序,文件名可能是混淆过的。


七、常见报错与解决

1. Unsafe file name

1
2
[concat @ ...] Unsafe file name 'C:\videos\segment_000.ts'
list.txt: Operation not permitted

原因:命令里漏了 -safe 0,或者列表里用了 ffmpeg 认为不安全的路径写法。

解决:加上 -safe 0。仍报错就把列表里的路径改成相对路径(列表文件和 TS 放同一目录时,直接写文件名最短最省事)。


2. Could not find tag for codec ... in stream

伴随 Stream mapping 输出后退出的情况。

原因:TS 里有一路 MP4 写不了的流——常见于字幕流、数据流(比如 teletext)。TS 容器什么都能装,MP4 不行。

解决:只挑视频和音频流:

1
ffmpeg -y -f concat -safe 0 -i list.txt -c copy -map 0:v -map 0:a output.mp4

需要保留字幕的话,输出成 MKV,或者用 -map 0 输出 MKV(见下一条)。


3. 合并后有画面没声音

原因:MP4 容器不支持源里的音频编码。TS 里常见 AC3、DTS、PCM 等,MP4 对它们的支持不完整。

解决 A(推荐,保持无损):输出成 MKV,容器兼容性好得多:

1
ffmpeg -y -f concat -safe 0 -i list.txt -c copy output.mkv

解决 B:只转音频,视频保持原样:

1
ffmpeg -y -f concat -safe 0 -i list.txt -c:v copy -c:a aac -b:a 192k output.mp4

4. Invalid data found / Packet loss

原因:某个 TS 片段在下载或录制时损坏了。

解决:先定位是哪个文件。Windows cmd 里逐个试:

1
for %i in (*.ts) do @ffmpeg -v error -i "%i" -f null - 2>&1 | findstr /i error

Linux / macOS:

1
for f in *.ts; do ffmpeg -v error -i "$f" -f null - 2>&1 | grep -i error; done

找到之后看体积:几百字节的基本是空文件,删掉即可;体积正常但报错的,可以试试重封装一遍修复:

1
ffmpeg -i broken.ts -c copy fixed.ts

5. 合并后顺序是乱的

现象:播放到一半突然跳到另一个画面,然后又跳回来。

原因:生成列表时用了字典序(见第四节)。

解决:用自然排序重新生成列表。有 M3U8 的话,按 M3U8 里的顺序排。


6. 音画不同步,并且越到后面越明显

原因:源分片的时间戳(PTS/DTS)不连续或不规范,concat 直接沿用导致累积偏移。

解决:先让 ffmpeg 重建时间戳:

1
ffmpeg -y -fflags +genpts -f concat -safe 0 -i list.txt -c copy output.mp4

-fflags +genpts 是输入选项,要放在 -i 前面,放后面不生效。

还不行的情况下只能重新编码,这会带来画质损失,但能把时间轴彻底理顺:

1
ffmpeg -y -f concat -safe 0 -i list.txt -c:v libx264 -crf 18 -c:a aac -b:a 192k output.mp4

八、什么时候只能重新编码

-c copy 的前提是所有分片的编码参数完全一致。以下情况它会失败或者结果不对,只能走编码路线:

  • 分片来自不同设备/不同录制过程(分辨率、帧率、编码格式不一致)
  • 需要裁剪片段、加水印、改分辨率
  • 输出容器不支持源编码(比如源是 HEVC,目标播放器只认 H.264)
  • concat 各种调整都试过仍然报错
1
2
3
ffmpeg -y -f concat -safe 0 -i list.txt \
-c:v libx264 -crf 18 -preset medium \
-c:a aac -b:a 192k output.mp4
参数 说明
-crf 18 恒定质量控制,数值越小画质越好体积越大,18 约等于”视觉无损”
-preset medium 编码速度与压缩率的权衡,越慢压得越小

小结

场景 做法
偶尔合一次、文件不大 浏览器工具 ezwebtools.net/ts-merge
常规本地合并 ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc out.mp4
想双击就用 第五节的 bat 脚本
几十上百个文件夹 第六节的 Python 脚本
合并后没声音 / 不同步 / 报错 对照第七节的症状表
分片参数不一致 只能重新编码

三个最容易踩的坑:漏掉 -c copy、列表顺序用的是字典序、输出 MP4 却塞了 MP4 不支持的流。避开这三条基本一次过。

本文的命令基于 2026 年 9 月的 FFmpeg 版本,各平台的构建源(gyan.dev / BtbN)下载方式可能调整,安装步骤以 ffmpeg.org 当前页面为准。