必须确认三件事:FFmpeg是否已安装(用which ffmpeg验证)、PHP进程用户对临时目录是否有读写权限(建议用sys_get_temp_dir()路径)、OSS/COS的AccessKey是否由环境变量或配置文件安全提供而非硬编码。

PHP 调用 FFmpeg 命令加水印前必须确认的三件事
直接跑 exec() 加水印失败,八成卡在这三处:FFmpeg 没装、没权限写临时文件、PHP 进程用户(如 www-data)无法访问云存储密钥。OSS/COS SDK 只负责上传下载,水印逻辑必须本地完成——所以得先拉视频到 PHP 可写的临时目录,处理完再传回去。
常见错误现象:ffmpeg: command not found 或 Permission denied;file_put_contents(): failed to open stream 本质是路径或权限问题。
- 确认服务器已安装 FFmpeg:运行
which ffmpeg,返回路径才算就绪 - PHP 进程用户需对临时目录有读写权限,建议用
/tmp/vid_XXXXX这类sys_get_temp_dir()返回的路径 - OSS/COS 的 AccessKey 必须由服务端环境变量或配置文件提供,绝不能硬编码在脚本里
用 OSS SDK 下载 + FFmpeg 处理 + 再上传的最小闭环
核心不是“怎么调 SDK”,而是控制好文件流和临时生命周期。别试图用 fopen("oss://...") 直接读——OSS 不支持这种伪协议流式水印,COS 同理。
典型流程:生成唯一临时名 → $ossClient->getObject() 下载到本地 → exec() 调 FFmpeg 加水印 → $ossClient->putObject() 上传结果 → unlink() 清理两个临时文件(原片 + 水印后)
立即学习“PHP免费学习笔记(深入)”;
- FFmpeg 命令示例(文字水印):
ffmpeg -i /tmp/vid_in.mp4 -vf "drawtext=text='©MySite':x=10:y=10:fontsize=24:fontcolor=white" -codec:a copy /tmp/vid_out.mp4 -y - 务必加
-y参数避免交互阻塞;-codec:a copy跳过音频重编码,省时间 - 注意
exec()的第三个参数是返回码,0才算成功,非零要记录$output排查
批量处理时如何避免超时和内存崩掉
一次循环处理 100 个视频?PHP 默认 max_execution_time=30,FFmpeg 单个 2 分钟就超时。别用 set_time_limit(0) 硬扛——它不解决内存累积,更不防进程被 OOM killer 杀掉。
- 改用分批 + sleep:每处理 5 个,
sleep(1)让系统喘口气;用memory_get_usage(true)监控,超 128MB 就gc_collect_cycles() - 临时文件名必须带唯一标识(如
uniqid('wm_')),否则并发时多个进程会覆盖彼此的/tmp/vid_in.mp4 - 别把所有 OSS 文件列表一次性
listObjects()拉进内存——用marker分页拉,每次最多 1000 个
COS 和 OSS 的 SDK 差异点:签名与 Endpoint 别写反
看着都是 putObject(),但 COS 要求 Region 和 Endpoint 严格匹配,OSS 则依赖 Endpoint 自动推导 Region。填错一个,上传直接报 InvalidAccessKeyId 或 SignatureDoesNotMatch。
- OSS Endpoint 示例:
https://oss-cn-hangzhou.aliyuncs.com(注意是aliyuncs.com,不是alicloud.com) - COS Endpoint 示例:
https://mybucket-1250000000.cos.ap-guangzhou.myqcloud.com(mybucket和1250000000必须和控制台一致) - COS 初始化必须显式传
region(如ap-guangzhou),OSS 初始化不用传 region,靠 endpoint 解析
水印本身不区分云厂商,但下载和上传环节的 SDK 配置错一个字符,整个批量任务就卡在第一个文件上不动。



















