PHP-FFMpeg的watermark()方法水印偏移主因是未处理旋转元数据和SAR不一致:手机竖屏视频rotate=90时坐标错位,SAR≠1导致播放器缩放使水印视觉偏移;须用absolute坐标+custom filter归一化setsar/transpose并显式指定format=rgba与alpha=straight。

直接用 php-ffmpeg 封装的 watermark() 方法加水印,90% 的偏移问题都出在没处理旋转元数据和 SAR 不一致——不是库不行,是它默认按原始像素坐标贴图,而你看到的画面已经转过 90° 或被播放器拉伸了。
PHP-FFMpeg 的 watermark() 为什么右下角水印总跑偏
这个方法表面简单,但底层仍走 FFmpeg 的 overlay 滤镜,所以逃不开坐标系和媒体元数据那几条坑:
-
position => 'relative'时,right/bottom值是相对于「显示尺寸」算的,但 FFmpeg 实际叠加在「存储尺寸」上;手机竖拍视频带rotate=90元数据,right=50就会贴到左边去 - 如果水印 PNG 是 200×80,但主视频 SAR ≠ 1(比如某些老 DV 视频),播放器为保持宽高比会缩放整个画面,水印看着就“偏”了
-
watermark()不自动做setsar=1或transpose预处理,得自己补 filter 链
实操建议:别依赖 position,改用 absolute + 显式计算坐标,并在 filters() 里先 normalize 视频:
$video->filters()->resize(new Dimension(1920, 1080))
->synchronize()
->custom('setsar=1')
->custom('transpose=0'); // 强制归一化方向
$video->filters()->watermark('logo.png', [
'position' => 'absolute',
'x' => 'W-w-20',
'y' => 'H-h-20'
]);
用 exec() 调 overlay 时 W/w/H/h 大小写写错的典型表现
命令行里变量大小写敏感,写错一个字母,水印就飞了。常见错误现象:
立即学习“PHP免费学习笔记(深入)”;
-
x=w-w-10→ 恒为 -10,水印永远贴左边界外 -
y=H-H-10→ 恒为 -10,水印永远贴顶边外 -
x=W/2-w/2写成x=w/2-W/2→ 水印跑到左边负区域
判断依据很简单:把实际数值代进去心算。例如主视频 1280×720,水印 300×120,那么 x=W-w-20 = 1280−300−20 = 960,而 W-w = 980,960 ∈ [0,980],合理;x=w-w-20 = −20,明显越界。
注意:W/H 是主输入流的宽高,w/h 是水印图的宽高——它们不是常量,是 FFmpeg filter 表达式里的动态变量,只在 overlay 上下文中有效。
drawtext 文字水印中文乱码和位置飘移的根源
两个独立问题,但常被当成一个来调:
- 乱码:Windows 下 PHP
exec()启动 cmd 默认是 GBK 编码,而你的 PHP 文件是 UTF-8,text="测试"传过去就变乱码。解决不是换字体,而是统一编码:iconv('UTF-8', 'GBK', $text)再拼进命令 - 位置飘移:
drawtext的x/y是基于当前帧分辨率算的,但如果视频有旋转元数据,w/h返回的是旋转后的逻辑尺寸,而 drawtext 还是往原始像素上画 —— 结果就是你设y=h/2,水印却出现在 1/4 高度处
稳妥做法:先用 ffprobe 查 rotation,再根据值调整表达式。例如检测到 rotate=90,就把 y=h/2 改成 x=h/2,因为此时宽高已互换:
$rotation = json_decode(shell_exec("ffprobe -v quiet -show_entries stream_tags=rotate -of json $input"), true);
$angle = $rotation['streams'][0]['tags']['rotate'] ?? 0;
if ($angle == 90 || $angle == 270) {
$x_expr = '(h-text_w)/2'; // 原来的 y 位置现在是 x
$y_expr = '(w-text_h)/2';
} else {
$x_expr = '(w-text_w)/2';
$y_expr = '(h-text_h)/2';
}
$cmd = "ffmpeg -i $input -vf \"drawtext=fontfile=simhei.ttf:text='中文':x=$x_expr:y=$y_expr:fontsize=32\" $output";
透明 PNG 水印发灰、边缘糊、alpha 通道失效
这不是 PHP 或 FFmpeg bug,是 pixel format 和 alpha 处理策略不匹配:
- 默认 overlay 输出是
yuv420p,但 PNG 带 alpha 通道,YUV 格式不存 alpha,直接叠会导致半透明区域变灰或黑边 - 必须显式指定
format=rgba或format=yuva420p(后者兼容性更好) - 还要加
alpha=straight,否则 FFmpeg 会误以为是 premultiplied alpha,把透明度乘进 RGB 里
正确 filter 链示例(用 php-ffmpeg custom filter):
$video->filters()->custom(
"movie=logo.png[wm];[wm]format=rgba,alpha=straight[wm2];[0:v][wm2]overlay=W-w-20:H-h-20"
);
关键点:format=rgba 确保 alpha 通道保留,alpha=straight 告诉 FFmpeg 别预乘,overlay 才能正确混合。
最易被忽略的是:哪怕你用了透明 PNG,只要没在 filter 里声明 format 和 alpha,FFmpeg 就按默认 yuv420p 处理——这时候水印不是“偏”,是根本没叠对。



















