分片数 ≥1800 触发 partial merge 会损坏时间轴,合并后时长严重偏短(即使已启用 --use-ffmpeg-concat-demuxer)
环境
- N_m3u8DL-RE 版本:Beta 20260628(0.6.0+df70f0b3)
- ffmpeg:7.1.2
- 系统:Linux(Docker 容器,Debian)
现象
下载一个分片数 ≥1800 的 HLS 长视频(电影 / 电视录播),合并完成后输出的 mp4 时长严重偏短。
实测案例:某电影 HLS 流,播放列表共 2140 个分片 / 142.62 分钟。
- 合并后文件体积 955MB(数据基本齐全)
- 但 ffprobe 显示 duration ≈ 1983s(约 33 分钟),且 DTS 出现 ~13 小时的异常值
- 日志中出现
Segments more than 1800, start partial merge...,以及大量Non-monotonic DTS; previous: 26784000, current: -7200
即:数据都在,但时间轴被写坏,并非分片缺失。
复现条件
- 分片数 ≥ 1800
- 内容存在分片内时间戳重置 / 回绕(电视录播、部分点播源常见,日志表现为
Non-monotonic DTS) - 即使已加
--use-ffmpeg-concat-demuxer也会触发(见下方根因)
根因分析
src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs:590:
// 大于1800分片,需要分步骤合并
if (files.Length >= 1800)
{
Logger.WarnMarkUp(ResString.partMerge);
files = MergeUtil.PartialCombineMultipleFiles(files);
...
}src/N_m3u8DL-RE/Util/MergeUtil.cs:88 的 PartialCombineMultipleFiles 会把每 100 个分片按原始字节直接拼接成一个大 TS 中间文件(T0000.ts…),删除原分片,再对这些中间文件做 ffmpeg 合并。
问题在于两点:
- 这个 partial merge 无论是否启用
--use-ffmpeg-concat-demuxer都会触发。而 concat demuxer 本身是逐个流式读取分片文件的,不会一次性打开大量文件、不会 "Too many open files"——所以在这个模式下 partial merge 是多余的。 - 对分片内时间戳会重置的内容,把 100 个分片硬拼进单个 TS 文件后,ffmpeg 会把它当作一条连续流来读,无法处理文件内部的时间戳重置 → 时间轴错乱、时长严重偏短。
建议修复
当 UseFFmpegConcatDemuxer == true 时,跳过 PartialCombineMultipleFiles,直接对所有独立分片文件做单遍 concat demuxer 合并。concat demuxer 对文件数量没有句柄限制(逐个打开),2140 个分片单遍合并完全可行。
(非 concat demuxer 的 concat: 协议模式仍可能需要 partial merge / 提高句柄上限,那部分逻辑可保留,见 #338、#89。)
验证
在下游用 ffmpeg 对独立分片文件做单遍 concat demuxer 合并:
ffmpeg -f concat -safe 0 -i list -map 0:v? -map 0:a? -map 0:s? -c copy out.mp4- 400 分片子集 → 27.1 分钟(正确)
- 全量 2140 分片 → 145 分钟(正确完整时长)
即单遍合并独立分片文件能正确处理分片间 / 分片内的时间戳重置,得到正确时长。下游项目目前通过 --skip-merge 只下载分片、再自行用上述方式合并来绕开此问题。
影响范围
所有分片数 ≥1800 且时间戳会重置的长视频(约 2 小时以上的电影、电视录播等)。这类内容目前无论默认合并还是加 --use-ffmpeg-concat-demuxer,都会得到时长错误的文件。
Source: nilaoda/N_m3u8DL-RE