因为FFmpeg默认不自动清除视频元数据中的rotate标记,导致-vf transpose=1等滤镜旋转与元数据旋转叠加或冲突;正确做法是先用-metadata:s:v:0 rotate=0清除标记,再执行视觉旋转。

为什么 ffmpeg 命令加了 -vf rotate=90 却没生效?
因为 FFmpeg 默认不自动复制旋转元数据,很多手机拍的 MP4 会把旋转信息存在 rotate metadata 里,而不是真转帧。你加 -vf rotate=90 是强行重绘一帧一帧转,但原始视频可能已经带了 rotate=90 的标记——此时 FFmpeg 会先按元数据“逻辑旋转”播放,再套你的滤镜,结果翻倍或抵消。
正确做法是先清除元数据再处理:
- 用
ffmpeg -i input.mp4 -c:v copy -c:a copy -metadata rotate=0 clean.mp4去掉旋转标记 - 再对
clean.mp4执行-vf transpose=1(顺时针90°)或transpose=2(逆时针90°),比rotate滤镜更轻量、支持硬件加速 - 如果必须用
rotate,记得加-noautorotate参数禁用自动解析元数据
PHP 调用 exec() 执行 ffmpeg 时视频裁剪总偏移不对
常见原因是没指定输入帧率或忽略了 SAR/DAR,导致 crop 滤镜按像素坐标裁切,但显示时因宽高比拉伸造成视觉错位。
稳妥做法是先获取真实帧尺寸和像素宽高比:
立即学习“PHP免费学习笔记(深入)”;
- 用
ffprobe -v quiet -show_entries stream=width,height,sar -of csv=p=0 input.mp4获取原始分辨率与sar - 若
sar不为1:1,需先用-vf setdar=16/9或setsar=1归一化,再做crop -
crop参数顺序是crop=w:h:x:y,x/y 从左上角算起,单位是像素,不是百分比 - PHP 中拼命令时务必用
escapeshellarg()包裹路径,否则含空格或中文的文件名会导致截断
用 ffmpeg 同时旋转 + 裁剪时,为什么输出视频卡顿或花屏?
因为多个视频滤镜串联时,FFmpeg 默认按滤镜链顺序逐帧处理,transpose 改变宽高后,后续 crop 的坐标若没同步调整,就容易越界或读到未初始化内存。
关键点在于滤镜顺序和尺寸联动:
- 推荐链式写法:
-vf "transpose=1,crop=720:1280:0:0"—— 先转再裁,此时原 1280×720 变成 720×1280,裁剪宽高也得对应调换 - 避免混用
scale和crop在同一滤镜链里却不设force_original_aspect_ratio=decrease,否则缩放后 crop 区域错乱 - 加
-vsync vfr可缓解因帧率不匹配导致的卡顿,尤其源视频有 B-frame 或变帧率时 - 如果目标平台是移动端,建议加
-movflags +faststart,否则浏览器加载时进度条不可拖拽
PHP 中如何安全等待 ffmpeg 进程结束并捕获错误?
exec() 默认不阻塞,直接返回第一行输出,根本不知道转码是否成功。更糟的是,ffmpeg 错误信息全在 stderr,而 PHP 默认只捕获 stdout。
必须显式重定向 stderr 并检查退出码:
- 命令末尾加
2>&1,例如:ffmpeg -i input.mp4 -vf transpose=1 output.mp4 2>&1 - 用
exec($cmd, $output, $return_code),$return_code 为 0 才算成功,非 0 就要查 $output 里的具体错误 - 常见失败原因:
Invalid argument(参数错)、No such file or directory(路径没权限或不存在)、Could not write header(磁盘满或目录不可写) - 别依赖
sleep()等转码完成,大视频可能耗时几十秒,应改用proc_open()监控子进程状态
真正麻烦的不是命令怎么写,而是每台服务器的 FFmpeg 版本、编译选项(比如是否含 libx264)、甚至 glibc 版本都可能让同一行命令行为不同——上线前务必在生产环境镜像里实测。



















