ffmpeg 9.0批量加图片水印成败关键在于filter_complex是否显式处理分辨率对齐、sar和旋转元数据;大小写混淆(如w/w、h/h)、未预检视频参数、shortest=1误用、输入流索引错位及未适配rotate/sar/编码差异,均会导致偏移、拉伸或崩溃。

FFmpeg 9.0 批量加图片水印,核心不在版本号,而在 filter_complex 链路是否显式处理了分辨率对齐、SAR 和旋转元数据——不处理这三项,哪怕用 FFmpeg 9.0,水印照样偏移、拉伸或错位。
为什么 overlay 在批量时总偏移?关键在 W/H/w/h 大小写和坐标范围
批量脚本里最容易犯的错,是把 overlay=x=W-w-10:y=H-h-10 写成 overlay=x=w-w-10:y=h-h-10。大小写混淆直接导致 x/y 恒为负数,水印被“贴”到画布外。更隐蔽的是:不同视频的分辨率(W×H)和水印图尺寸(w×h)不一致,但脚本没做预检,一跑就炸。
- 必须用
ffprobe提前读取每条视频的width、height、tags:rotate和sar(采样宽高比) - 水印 PNG 必须是 RGBA 格式且带 alpha 通道;用
convert logo.png -alpha on -background none -resize "200x80>" logo_resized.png预处理,避免缩放失真 - 动态坐标表达式里,
W和H是主视频原始帧宽高(非显示宽高),w和h是水印图当前帧宽高——如果水印图本身含旋转元数据,w/h会错乱,必须先用-vf "transpose=2"或setsar=1归一化
批量执行必须绕开 shortest=1 和 eof_action 的坑
单条命令加水印时,-filter_complex "overlay=10:10:shortest=1" 看似稳妥,但在批量场景下极易引发时长截断或静帧残留——尤其当水印是单帧 PNG 时,shortest=1 会让输出长度强制对齐水印时长(即 0 秒),结果整段视频只剩第一帧。
- 正确做法是去掉
shortest=1,改用enable='between(t,0,99999)'显式控制水印生命周期(99999 是足够覆盖所有视频的秒数) - 若水印是短视频(如动图),必须加
-stream_loop -1参数循环读取,并在overlay中设eof_action=repeat - 批量脚本中,对每条视频单独生成完整命令字符串,不要复用同一组
-i输入顺序——FFmpeg 9.0 对输入流索引更敏感,[1:v]错位会导致 overlay 绑定到音频流
Shell 批量脚本要防三类崩溃点
不是所有视频都“配得上”同一条 overlay 命令。手机竖屏(rotate=90)、监控横条(SAR=64:45)、HEVC 封装(VFR 时间戳)都会让默认命令直接报错或输出异常。
- 先用
ffprobe -v quiet -show_entries stream=width,height,codec_name,tags=rotate -of csv=p=0 "$f"提取关键元数据,再分支构造命令 - 对 rotate≠0 的视频,必须前置
-vf "transpose=2"(顺时针转90°)或"transpose=1"(逆时针),并同步交换 W/H 和 w/h 的使用逻辑 - HEVC 源片加水印易触发内存泄漏(FFmpeg 9.0 已修复部分,但未关掉
-threads 1仍可能崩),建议加-x264opts keyint=250:min-keyint=25:no-scenecut控制 GOP,避免 VFR 导致 overlay 时间轴漂移
真正卡住批量流程的,往往不是命令写不对,而是没意识到每条视频都是独立个体——它的旋转、SAR、帧率、编码器都可能悄悄改写 overlay 的坐标语义。FFmpeg 9.0 的 filter_graph 调度更严格,反而让这些隐性差异暴露得更彻底。











