PHP 8.4 本身不直接处理视频,需通过 exec()、shell_exec() 或 proc_open() 调用系统级 FFmpeg 工具;关键前提是服务器已安装 FFmpeg、PHP 未禁用相关函数、Web 进程具备执行权限与文件读写权,并严格使用 escapeshellarg() 防注入、2>&1 捕获错误、异步处理避免超时。

PHP 8.4 本身不直接支持视频处理,得靠系统级调用
PHP 没有内置视频编码或水印功能,ffmpeg 是独立的命令行工具,PHP 只能通过 exec()、shell_exec() 或 proc_open() 调用它。PHP 8.4 的版本更新(比如更严格的错误报告、废弃 create_function)不影响这个调用逻辑,但要注意:如果旧脚本用了被移除的函数,会直接报错,得先清理掉。
关键点是确保服务器上已安装并可执行 ffmpeg,且 PHP 进程有权限运行它(尤其是 Web 环境下,Apache/Nginx 用户常被限制执行外部命令)。
- 运行
which ffmpeg确认路径,比如返回/usr/bin/ffmpeg - 在 PHP 中用
exec('/usr/bin/ffmpeg -version', $output, $return_code)测试是否能调通 - 若返回空数组且
$return_code !== 0,大概率是 SELinux、禁用函数(检查disable_functions是否含exec,shell_exec,proc_open)或权限问题
用 FFmpeg 8.1 的 drawtext 实现动态时间水印
FFmpeg 8.1 的 drawtext 滤镜支持 localtime 和 gmtime 表达式,能实时渲染当前时间——注意这不是“视频录制时的时间”,而是转码过程中每一帧生成时的系统时间。要显示视频开始播放后的时间(如 00:01:23),得用 pts 相关表达式;但“添加录制时间戳”场景下,localtime 更常用。
典型命令如下:
立即学习“PHP免费学习笔记(深入)”;
ffmpeg -i input.mp4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf:fontsize=24:fontcolor=white:x=10:y=10:text='%{localtime:%Y-%m-%d %H\\\:%M\\\:%S}'" -c:a copy output.mp4
说明:
-
fontfile必须指定真实存在的字体路径,否则报错Cannot load font file;Linux 常用DejaVuSans.ttf,Windows 用C:/Windows/Fonts/msyh.ttc(注意双反斜杠转义) -
text中的:需要三重转义(\\\:),因为 FFmpeg 解析器和 shell 各吃一层,PHP 字符串里最终要写成'%{localtime:%Y-%m-%d %H\\\:%M\\\:%S}' -
-c:a copy表示音频流不重编码,节省时间;若视频也要跳过重编码(如只加水印不改分辨率),可用-c:v copy,但drawtext必须解码帧,所以-c:v copy和drawtext不能共存
PHP 8.4 中安全拼接命令并捕获错误
直接拼接用户输入进 exec() 是严重风险。即使时间水印内容固定,路径、文件名仍可能来自用户。必须过滤和转义。
- 用
escapeshellarg()包裹所有外部输入,包括$input_path、$output_path、$font_path - 避免用
system()或passthru(),它们可能输出二进制流导致页面乱码;优先用exec()并检查$return_code - FFmpeg 错误通常输出到 stderr,PHP 默认不捕获,需重定向:
2>&1加在命令末尾
简短示例:
$cmd = sprintf(
'/usr/bin/ffmpeg -i %s -vf "drawtext=fontfile=%s:fontsize=24:fontcolor=white:x=10:y=10:text='%%{localtime:%Y-%%m-%%d %%H\\\:%%M\\\:%%S}'" -c:a copy %s 2>&1',
escapeshellarg($input_path),
escapeshellarg($font_path),
escapeshellarg($output_path)
);
exec($cmd, $output_lines, $return_code);
if ($return_code !== 0) {
error_log('FFmpeg failed: ' . implode("
", $output_lines));
throw new RuntimeException('Video watermarking failed');
}
时间水印位置偏移、性能与常见失败点
FFmpeg 8.1 对 drawtext 的语法更严格,比如旧版允许 x=(w-tw)/2,新版要求显式写 x=(main_w-text_w)/2。时间水印若贴边或遮挡关键内容,需要手动调 x/y,或用 main_w/main_h 动态计算。
性能方面:加时间水印必然触发视频帧解码+重编码,耗时取决于分辨率和码率。1080p 视频可能每秒处理 2–5 帧,别指望秒出结果。建议异步处理(如队列 + 后台 worker),Web 请求只返回任务 ID。
- 常见失败:
Invalid argument多因字体路径错或未安装字体;Expression is not valid是表达式转义不足;Could not write header常因输出目录不可写或磁盘满 - FFmpeg 8.1 默认启用硬件加速(如
vaapi),但加水印时可能冲突,必要时加-hwaccel auto -noautorotate降级兼容性 - PHP 的
max_execution_time默认 30 秒,处理长视频必超时,CLI 模式下调高,Web 模式下必须异步
最易被忽略的是字体文件权限:Web 服务器用户(如 www-data)必须对 .ttf 文件有读取权,chown 或 chmod 不到位,FFmpeg 就静默失败。



















