proc_open 比 exec 更适合视频处理,因其可控制进程、捕获实时输出、设置超时、主动终止卡死进程,并绕过 disable_functions 限制;而 exec 会阻塞请求、无法获取 PID 和错误流、不支持超时与强制终止。

用 proc_open 替代 exec 处理视频,核心是获得进程控制权、捕获实时输出、防止超时阻塞,并规避 disable_functions 限制。它更适合截图、转码、提取元信息等需稳定性和可观测性的场景。
为什么 proc_open 比 exec 更适合视频处理
exec 会阻塞 PHP 请求直到命令结束,大视频可能卡住 Web 响应;错误流默认不返回,失败时静默;无法获取 PID、无法主动终止、无法设超时。proc_open 则能:
- 通过管道分别读取 stdout 和 stderr,便于区分正常输出与报错
- 配合
stream_select或循环读取实现超时控制(如限制 15 秒) - 调用
proc_get_status实时检查进程是否存活 - 用
proc_terminate强制杀掉卡死的 FFmpeg 进程 - 绕过 php.ini 中对 exec 的禁用(只要 proc_open 未被禁用)
基础调用结构:以提取视频时长为例
用 ffprobe 获取时长,命令需带 2>&1 合并错误流,并使用绝对路径:
$cmd = '/usr/bin/ffprobe -v quiet -show_entries format=duration -of default=nw=1 "' . escapeshellarg($videoPath) . '" 2>&1';
$descriptors = [
0 => ['pipe', 'r'], // stdin(一般不用)
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'] // stderr
];
$process = proc_open($cmd, $descriptors, $pipes);
if (!is_resource($process)) {
throw new RuntimeException('无法启动 ffprobe 进程');
}
// 关闭 stdin(避免子进程等待输入)
fclose($pipes[0]);
// 读取标准输出和错误输出
$output = stream_get_contents($pipes[1]);
$error = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$returnCode = proc_close($process);
if ($returnCode !== 0) {
throw new RuntimeException("ffprobe 执行失败: {$error}");
}
// 解析 duration=123.45 → 提取浮点数
if (preg_match('/duration=(\d+\.\d+)/', $output, $m)) {
$duration = (float)$m[1];
}
关键安全与健壮性要点
实际部署中容易忽略但直接影响稳定性:
立即学习“PHP免费学习笔记(深入)”;
- 所有用户传入的路径(如
$videoPath)必须用escapeshellarg()包裹,防止命令注入 - FFmpeg/ffprobe 必须用绝对路径,不能依赖 PATH,否则在 Web 用户(如 www-data)下常找不到命令
- PHP 进程用户需对视频文件有读权限,对输出目录有写权限(如生成封面图)
- 大视频建议加超时逻辑:用
stream_select轮询管道,超过设定时间就proc_terminate - 执行后务必
fclose所有管道、proc_close进程,否则句柄泄漏会导致服务器 eventually run out of file descriptors
延伸:截图 + 封面有效性验证
调用 ffmpeg 截图后,不能只看返回码为 0 就认为成功——可能生成了空文件或非 JPEG:
- 用
file_exists($coverPath)确认文件落地 - 用
getimagesize($coverPath)验证是否为有效图像(返回数组才可信) - 可选:用
exif_imagetype()进一步校验格式是否为 JPG/PNG - 若失败,清理残缺文件并抛出明确异常,便于重试或告警



















