新闻详情

新闻详情

首页 / 资讯中心 / 详情

PHP7.4怎么用php-ffmpeg库批量处理视频

发布时间:2026/10/2 9:53:59来源:尧图网络
PHP7.4怎么用php-ffmpeg库批量处理视频
前言批量处理视频的翻车现场通常长这样本地拿两个 MP4 试跑一切正常一旦把脚本指向存放几百个文件的目录就集中爆发——脚本跑了几十分钟被 PHP-FPM 的request_terminate_timeout掐死日志里连一行错误都没有或者十几二十个 ffmpeg 进程同时开跑机器 load 飙到几十转出来的文件没有音频轨甚至源文件被写成半截。这些问题的根源不在 php-ffmpeg 这个库上。PHP-FFMpeg 只是 ffmpeg 命令行的一层面向对象封装它本身不提供任何并发、队列或断点续跑能力。而「批量」意味着要把几百次同步的外部进程调用串起来同时不把 CPU、内存和磁盘 IO 压垮——这是调度问题不是转码问题。本文分三步讲先把依赖装对并确认版本约束再选批量执行模式最后给出一个能直接保存成.php跑的完整脚本含断点续跑、超时控制、失败隔离。文中代码限制在PHP 7.4语法范围内类型化属性、箭头函数可以用命名参数、构造函数属性提升、match、?-这些 PHP 8.0 才有的语法不要写。需要留个心眼的是PHP 7.4 已经结束生命周期官方安全支持到 2022 年 11 月结束新项目不该再选它。本文按题目要求仍以 7.4 为基线但文末会给出升级评估的提示。一、装依赖先确认二进制再让 Composer 定版本php-ffmpeg 的包名是php-ffmpeg/php-ffmpeg命名空间是FFMpeg\。它自己不实现解码而是通过proc_open去调外部的ffmpeg和ffprobe。所以第一步永远是确认这两个二进制存在且可执行ffmpeg -version ffprobe -version两行都要能打印出版本信息。如果提示 command not found先装 ffmpeg或把二进制目录写进PATH否则后面所有代码都会以「无法启动进程」的形式失败。第二步是安装。这里有个关键点不要凭记忆写版本号。该库的高版本线已经把最低 PHP 版本抬到了 8.0PHP 7.4 只能落在更早的 0.x 系列上。正确做法是让 Composer 自己解析再用composer why-not复核原因composer require php-ffmpeg/php-ffmpeg --no-interaction composer show php-ffmpeg/php-ffmpeg composer why-not php-ffmpeg/php-ffmpeg 1.0.0composer why-not会直接回答「为什么 1.0.0 装不上」输出里通常会有一行平台约束类似「requires php ^8.0」。看到这行就说明 PHP 7.4 确实只能待在 0.x 上别硬改composer.json里的platform去骗过检查。以本地 Composer 的解析结果为准而不是以任何文章里的版本号为准。判断项命令结论怎么看二进制是否就绪ffmpeg -version有版本输出即就绪实际装到了哪个版本composer show php-ffmpeg/php-ffmpeg看 versions 那一行的实际安装版本为什么高版本装不上composer why-not php-ffmpeg/php-ffmpeg 1.0.0输出里的requires php就是原因扩展依赖是否齐全composer check-platform-reqs缺哪个扩展会直接列出来二、批量处理的三种模式别一上来就 for 循环批量任务本质上是「一个生产者 N 个执行者」的调度问题。三种常见模式的对比如下模式实现方式优点代价适用场景纯串行单进程foreachsave()代码最简单内存最省吞吐等于单机单核几百个文件要跑很久文件数少几十个以内、质量优先受控并发xargs -P N或多个 worker 进程吞吐接近核数上限需要处理共享状态日志、状态文件的竞争几百到几千个最常用队列化任务表 常驻 worker 重试可中断、可重试、可分布式组件多初期成本高上万文件、或需要跨机器扩展「受控并发」是性价比最高的一档。要点有两个第一并发数要乘上 ffmpeg 自己的线程数一起算。ffmpeg 默认按 CPU 核数开线程如果 PHP 侧再开 8 个 worker等于 8 × 核数 个线程在抢 CPU转码速度反而下降还会因为内存放大触发 OOM。所以要在配置里显式限制ffmpeg.threads让「worker 数 × 单进程线程数」不超过物理核数。第二状态文件必须用原子追加。多个 worker 同时写「已完成列表」用普通file_put_contents会互相覆盖结果就是断点续跑失效、文件被重复转码。加FILE_APPEND | LOCK_EX才是安全的写法。三、完整可运行脚本下面这份代码可以直接保存为batch_transcode.php运行PHP 7.4 及以上都能跑?php declare(strict_types1); /** * 批量转码PHP 7.4 * 用法: php batch_transcode.php /data/in /data/out * 断点续跑: 重复执行时自动跳过已完成文件 */ use FFMpeg\FFMpeg; use FFMpeg\FFProbe; use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\TimeCode; use FFMpeg\Format\Video\X264; final class BatchTranscoder { /** var FFMpeg */ private $ffmpeg; /** var FFProbe */ private $probe; /** var string */ private $stateFile; public function __construct(string $ffmpegBin, string $ffprobeBin, string $stateFile) { $this-stateFile $stateFile; // 关键整个批次只 create 一次复用同一个实例 $this-ffmpeg FFMpeg::create([ ffmpeg.binaries $ffmpegBin, ffprobe.binaries $ffprobeBin, timeout 1800, // 单个任务 30 分钟超时抛异常而不是挂死 ffmpeg.threads 2, // 限制 ffmpeg 线程配合 worker 数一起算 ]); $this-probe $this-ffmpeg-getFFProbe(); } /** 读取已完成列表断点续跑 */ private function loadDone(): array { if (!is_file($this-stateFile)) { return []; } $done []; foreach (explode(\n, (string) file_get_contents($this-stateFile)) as $line) { $line trim($line); if ($line ! ) { $done[$line] true; } } return $done; } /** 多进程安全追加写 文件锁 */ private function markDone(string $src): void { file_put_contents($this-stateFile, $src . \n, FILE_APPEND | LOCK_EX); } private function dstPath(string $srcDir, string $dstDir, string $src): string { $rel str_replace(\\, /, substr($src, strlen($srcDir))); $rel ltrim($rel, /); $rel (string) preg_replace(/\.[^.]$/, .mp4, $rel); $target rtrim(str_replace(\\, /, $dstDir), /) . / . $rel; $dir dirname($target); if (!is_dir($dir) !mkdir($dir, 0755, true) !is_dir($dir)) { throw new RuntimeException(无法创建输出目录: . $dir); } return $target; } private function transcodeOne(string $src, string $dst): void { // 1) 先探测拿到分辨率与音频轨信息 $streams $this-probe-streams($src); $video $streams-videos()-first(); if ($video null) { throw new RuntimeException(没有视频轨); } $width (int) $video-get(width); $hasAudio $streams-audios()-count() 0; // 2) 打开并挂滤镜 $media $this-ffmpeg-open($src); if ($width 1280) { $media-filters()-resize(new Dimension(1280, 720))-synchronize(); } // 3) 拼装输出格式 $format new X264(aac, libx264); $format-setKiloBitrate(1200); // 4) 导出失败时抛 FFMpeg\Exception\RuntimeException $media-save($format, $dst); // 5) 产物校验不能只看文件是否存在 if (!is_file($dst) || filesize($dst) 1024) { throw new RuntimeException(产物异常过小或缺失: . $dst); } if (!$hasAudio) { echo [提示] 源文件无音频轨输出也不会有声音\n; } } /** 抽帧做封面PHP 7.4 的箭头函数在这里正好顺手 */ public function thumbnail(string $src, string $dstJpg, int $atSecond 3): void { $this-ffmpeg-open($src) -frame(TimeCode::fromSeconds($atSecond)) -save($dstJpg, true, 90); } public function run(string $srcDir, string $dstDir): void { if (!is_dir($dstDir) !mkdir($dstDir, 0755, true) !is_dir($dstDir)) { throw new RuntimeException(无法创建输出根目录: . $dstDir); } $done $this-loadDone(); $ok 0; $skip 0; $fail 0; $allow [mp4 true, mov true, mkv true, avi true]; $it new RecursiveIteratorIterator( new RecursiveDirectoryIterator($srcDir, FilesystemIterator::SKIP_DOTS) ); /** var SplFileInfo $file */ foreach ($it as $file) { if (!$file-isFile()) { continue; } $ext strtolower($file-getExtension()); if (!isset($allow[$ext])) { continue; } $src $file-getPathname(); if (isset($done[$src])) { $skip; continue; } try { $dst $this-dstPath($srcDir, $dstDir, $src); $this-transcodeOne($src, $dst); $this-markDone($src); $ok; echo [OK] {$src}\n; } catch (Throwable $e) { // 单个文件失败不影响整批 $fail; echo [FAIL] {$src} . $e-getMessage() . \n; file_put_contents( $dstDir . /failed.log, $src . \t . $e-getMessage() . \n, FILE_APPEND | LOCK_EX ); } } echo 完成: 成功 {$ok} / 跳过 {$skip} / 失败 {$fail}\n; } } $in $argv[1] ?? __DIR__ . /in; $out $argv[2] ?? __DIR__ . /out; $job new BatchTranscoder( getenv(FFMPEG_BIN) ?: ffmpeg, getenv(FFPROBE_BIN) ?: ffprobe, $out . /.done.state ); $job-run($in, $out);要让多个 worker 并行跑同一批文件用 shell 侧的受控并发即可状态文件里的LOCK_EX保证不会互相覆盖# 4 个并发每个文件一个 PHP 进程worker 数 × ffmpeg.threads 不要超过物理核数 find /data/in -type f \( -name *.mp4 -o -name *.mov \) -print0 \ | xargs -0 -n1 -P4 -I{} php batch_transcode.php /data/in /data/out需要注意的是上面这条命令给每个文件启动了一个新的 PHP 进程也就意味着每次都重新FFMpeg::create()一次create()内部要做一次二进制探测这是有小额外开销的。文件数极大时更好的做法是让一个 PHP 进程常驻、自己从任务队列里取任务模式三。选择哪种取决于任务规模不必一开始就上队列。常见坑点1. 在循环体里创建 FFMpeg 实例❌foreach ($files as $f) { $ffmpeg FFMpeg::create([...]); $ffmpeg-open($f)-save(...); }✅ 在构造函数里create()一次整个批次复用同一个实例。create()不只是 new 一个对象它会去探测 ffmpeg/ffprobe 的版本信息。放在循环里几百个文件就是几百次多余的进程启动。2. 用exec()拼接文件路径❌exec(ffmpeg -i $src -c:v libx264 $dst);✅ 交给库的进程封装处理或自己拼命令时逐个escapeshellarg()。文件名里带空格、引号、$()、中文括号都会让这条命令行为诡异——最坏的情况是命令被截断输出一个空文件却返回成功状态码。3. 只看文件是否存在就判定成功❌if (file_exists($dst)) { $done[] $src; }✅ 再加一道filesize()或 ffprobe 时长校验。进程被 kill、磁盘写满、参数写错都可能留下一个 0 字节或几 KB 的残缺文件。这种文件一旦被记进状态文件断点续跑就永远不会重试它。4. 不限制并发和线程数❌ 直接启动 16 个 workerffmpeg 再用默认线程数。 ✅worker 数 × ffmpeg.threads ≈ 物理核数。超配带来的不是加速而是上下文切换开销和内存放大每个 ffmpeg 进程都要为多帧分配缓冲区核数越多、分辨率越高常驻内存涨得越快。5. 在 PHP-FPM 里跑批量脚本❌ 用浏览器触发一个接口等它把 300 个视频转完。 ✅ 用 CLI 执行交给 systemd、supervisor 或计划任务托管。FPM 有max_execution_time、request_terminate_timeout、pm.max_children三重限制。批量任务会长时间占满一个 worker把线上接口的可用进程数直接挤没。6. 输出路径覆盖源文件❌$dst $src;原地替换 ✅ 输出到独立的目录树文件名只从源路径推导。转码过程中断会把源文件写坏而这个源文件通常是你唯一的一份。7. 把失败文件默默跳过❌catch (Throwable $e) {}后面什么都不做。 ✅ 把失败路径和原因写进failed.log批次结束后人工处理。批量任务里最贵的成本是「不知道哪几个没成功」。空 catch 会让你在几天后才发现有一批文件根本没产出。8. 认为把库升到 1.x 就能继续跑在 PHP 7.4❌ 看到新版本就改composer.json里的约束然后删掉composer.lock重装。 ✅ 先跑composer why-not看平台约束确认 PHP 版本满足再升。平台约束不满足时强行安装通常以运行时错误收场依赖用了 8.0 的语法而你的解释器不认报错位置离真正原因很远很难排查。总结环节关键做法常见错误依赖先验二进制再让 Composer 解析版本凭记忆写版本号实例每进程创建一次长期复用循环里反复create()并发受控并发线程数按核数反推无限制起进程状态追加写 LOCK_EX普通写入被覆盖校验文件大小 时长双重检查只看文件存在失败独立日志单文件失败不中断批次空 catch 吞掉运行环境CLI 进程管理器在 FPM 里跑长任务php-ffmpeg 本身很简单批量处理视频真正的难点是把「外部进程调度」这件事做好单实例复用、受控并发、原子状态、产物校验、失败隔离这五件事做齐脚本就能在几百上千个文件上稳定跑完。最后提醒一句PHP 7.4 和 PHP 8.0 都已结束官方支持长期项目应规划向仍在支持周期内的版本迁移本文中的代码在 8.x 上同样可以运行。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

邻接矩阵与邻接表:图的存储结构选型、C语言实现与避坑指南 2026/10/2 10:36:30

邻接矩阵与邻接表:图的存储结构选型、C语言实现与避坑指南

简介:这份资源面向正在学习数据结构中图结构的学生与开发者,聚焦图的两种核心存储方式——邻接矩阵与邻接表,帮助解决二者之间相互转换的实现难题。资源以C语言代码为主线,完整呈现了从邻接矩阵转换为邻接表、再由邻接表转回邻接矩…

阅读更多 →
OpenAI推理集群配置解析:基于AMD 9V74与vLLM的NUMA调优实操 2026/10/2 10:36:24

OpenAI推理集群配置解析:基于AMD 9V74与vLLM的NUMA调优实操

近期关于大型语言模型底层基础设施的讨论在技术社区持续升温。一份被标记为 OpenAI Dot 的虚拟机配置清单在开发者论坛中曝光,其中明确指出了 AMD 霄龙 9V74 处理器以及 9.7 这一关键版本参数。这一配置不仅揭示了大型语言模型在推理阶段的硬件选择倾向,…

阅读更多 →
Linux下npm start后台运行原理与生产部署方案 2026/10/2 10:36:24

Linux下npm start后台运行原理与生产部署方案

1. 项目概述:为什么“npm start”在Linux里一关终端就停?这根本不是bug,是Unix进程模型的天然设计 你刚在服务器上跑起一个Vue或React项目,执行 npm start ,浏览器能正常访问,一切OK。可一旦你关闭SSH终端…

阅读更多 →
高情商沟通的底层逻辑与实战方法:从连接到表达 2026/10/2 10:36:11

高情商沟通的底层逻辑与实战方法:从连接到表达

1. 沟通的底层逻辑:先搞清楚“高情商”到底在解决什么问题 先说个真实感受。我在团队里带过不少人,发现一个特别普遍的误解:很多人觉得高情商沟通就是嘴甜、圆滑、会来事儿,说白了就是“哄人开心”。可真到了工作中你会发现&#…

阅读更多 →
找次品动画演示:HarmonyOS ArkTS状态管理与ArkUI动画实战 2026/10/2 10:36:11

找次品动画演示:HarmonyOS ArkTS状态管理与ArkUI动画实战

前阵子在 DevEco Studio 里刷华为官方示例集,按顺序整理到自己练习库里的时候,正好做到“HarmonyOS 应用实例 97:找次品动画演示”。这个题目一看就很戳我。名字里的“找次品”是小学数学里特别经典的逻辑题:一堆外观完全一样的球…

阅读更多 →
微信商城小程序毕业设计源码解析与前后端MySQL联调实战指南 2026/10/2 10:36:10

微信商城小程序毕业设计源码解析与前后端MySQL联调实战指南

简介:面向高校学生与初学者的微信商城小程序毕业设计源码包,整合了完整前后端、MySQL数据库、说明文档与LW论文,适合毕业设计、课程设计或小程序电商入门实践。项目覆盖商品展示、购物车、下单处理、支付对接与订单管理等核心功能&#xff0c…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉