ffmpeg 裁剪视频踩坑:一个 -t 参数位置导致文件膨胀 50 倍

2026-06-24

ffmpeg 裁剪视频踩坑:一个 -t 参数位置导致文件膨胀 50 倍

最近在做视频处理流水线时遇到一个隐蔽的 bug:用 ffmpeg 的 stream copy 模式裁剪一个 18 分钟的视频,本来应该输出几秒到几十秒的小片段,结果最后一个片段膨胀到了 76MB。更诡异的是,片段大小呈严格递增模式——1.7MB、6MB、20MB……一直到 76MB。

排查了很久才定位到根因:-t 参数放在了 -i 前面,而在 Docker 容器里的老版本 ffmpeg(4.4.x)会完全忽略这个参数。

这个 bug 长什么样

原始裁剪命令:

ffmpeg -y -ss 120 -t 5 -i input.mp4 \
  -c:v copy -an -avoid_negative_ts make_zero output.mp4

期望行为:从第 120 秒开始,裁剪 5 秒视频。

实际行为(ffmpeg 4.4.x):从第 120 秒开始,一直复制到文件末尾。-t 5 被完全忽略。

用 ffprobe 验证:

# 正常的片段(1.7MB)
ffprobe -v quiet -show_entries stream=codec_type,duration -of csv output_small.mp4
# stream,video,15.0
# stream,audio,12.0

# 异常的片段(76.7MB)
ffprobe -v quiet -show_entries stream=codec_type,duration -of csv output_big.mp4
# stream,video,712.7   ← 视频 12 分钟!
# stream,audio,12.0     ← 音频正常 12 秒

视频流被复制了整个源文件的剩余部分,音频流却正常裁剪了 12 秒。两个流的时长差了 700 秒,全塞在一个文件里。

为什么会有这个问题

ffmpeg 的 -ss 参数在 -i 前后的行为不同,这个很多人知道:

  • -ss-i 前面:快速 seek,跳到最近的关键帧,时间戳从 0 开始
  • -ss-i 后面:逐帧解码到精确位置,速度慢但精准

-t 参数的位置语义,几乎没人提过。实际上:

  • -t-i 后面(output-side):正确限制输出时长,所有版本都支持
  • -t-i 前面(input-side):4.x 版本直接忽略,8.x 版本部分生效

这在 FFmpeg 官方文档里没有明确说明。Seeking 那篇 Wiki 只说”To extract a segment, combine -ss with -t”,示例里 -t 总是放在 -i 后面,但没有解释放前面会怎样。

Docker 里的版本陷阱

这才是问题变得隐蔽的原因。同样的命令,不同环境表现完全不同:

环境 ffmpeg 版本 -t 在 -i 前面的表现
Ubuntu 24.04 本地 8.0.1 部分生效,多出 2-12 秒
Docker Ubuntu 22.04 4.4.x 完全忽略,复制到文件末尾
Docker Ubuntu 24.04 8.x 部分生效

本地开发用的是 Ubuntu 24.04,ffmpeg 8.0.1,-t 虽然放在 -i 前面,但”大部分时候工作”——输出只比预期多几秒。测了几次没发现问题就上线了。

生产环境跑在 Docker 容器里,基础镜像是 Ubuntu 22.04,ffmpeg 4.4.x。这个版本对 input-side -t 的处理是:直接当它不存在。

认知锚点:ffmpeg 参数位置与版本差异

问题不是每天都能触发,因为前面的片段”看起来”是正常的——一个几秒的片段 1.7MB、6MB 都很合理。直到仔细看 ffprobe 才发现,即使是”正常”的小片段,视频流也比音频流长了几秒。只是源文件小时膨胀不明显,不会引起注意。

排查过程

发现异常是通过归档日志:同一源视频(18 分钟)裁出的 13 个片段,合计 614MB。这比源文件还大。

第一步,diff 对比代码——本地和仓库的裁剪脚本完全一致,排除代码差异。

第二步,ffprobe 分析。大小文件各取一个,发现大文件的视频流 712 秒、音频流 12 秒。说明 -c:v copy 模式下视频流没有被 -t 截断。

第三步,在本地复现。本地 ffmpeg 8.0.1 下,-t-i 前面时输出确实比预期长,但只长 2-12 秒,不会出现 700 秒的差距。本地”几乎复现”但不够明显。

第四步,进 Docker 容器跑。ffmpeg 4.4.x 下直接复现:-t 完全失效。

排查路径:从日志异常到根因定位

修复

-t-i 前面移到 -i 后面:

# 修复前(buggy)
ffmpeg -y -ss 120 -t 5 -i input.mp4 \
  -c:v copy -an -avoid_negative_ts make_zero output.mp4

# 修复后
ffmpeg -y -ss 120 -i input.mp4 -t 5 \
  -c:v copy -an -avoid_negative_ts make_zero output.mp4

-ss 保留在 -i 前面不动——这是刻意选择,input-side seeking 在 stream copy 模式下音视频对齐更好。只需要移动 -t

修复后,所有 ffmpeg 版本(4.4.x、5.x、6.x、8.x)输出时长都正确。

如果你在 Docker 里跑 ffmpeg

几个实用建议:

1. 检查容器里的 ffmpeg 版本

docker run --rm your-image ffmpeg -version | head -1

Docker 镜像的 ffmpeg 版本取决于基础镜像:
- Ubuntu 22.04 → 大概率 4.4.x
- Ubuntu 24.04 → 6.1+ 或 8.x
- Debian bookworm → 5.1.x
- Alpine → 取决于 apk 源

2. 需要新版 ffmpeg 时,两种方式

# 方式1:从 PPA 安装
RUN add-apt-repository ppa:ubuntuhandbook1/ffmpeg6 && \
    apt-get update && apt-get install -y ffmpeg

# 方式2:用静态编译版
ADD https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz /tmp/
RUN tar xf /tmp/ffmpeg-release-*.tar.xz -C /usr/local/bin/ --strip-components=1

3. 永远把 output options 放在 -i 后面

这是一个安全的习惯:-ss-i 前面做快速 seek,-t-to 这些输出控制参数放 -i 后面。这样不管 ffmpeg 版本怎么变都不会出问题。

小结

这个 bug 的核心是一个参数位置问题。ffmpeg 的 -t 放在 -i 前面时,在 4.x 版本里被静默忽略,在 8.x 里部分生效。因为本地开发环境和 Docker 生产环境的 ffmpeg 版本不同,bug 在本地难以复现、在生产环境才完全暴露。

如果你在用 ffmpeg stream copy 做视频裁剪,尤其是脚本需要跨版本运行的场景,检查一下你的 -t 参数放在哪里。

标签: ffmpeg Docker 踩坑