
作为一名 PHP 开发者,你是否曾想过用最熟悉的语言构建一个智能视频处理助手 Agent?
今天,我将分享如何使用 Webman + Neuron AI + FFmpeg 构建一个智能视频处理助手,让你用自然语言就能完成各种视频编辑操作。
一个完整可落地的实现方案,适合视频处理、短视频平台、内容管理系统等场景。
<?php
/**
* @desc FFmpeg AI Agent - 通过自然语言处理音视频文件
* @author Tinywan(ShaoBo Wan)
*
* 功能:
* - 视频转码 (支持多种分辨率)
* - 视频合并
* - 视频截图
* - 音频提取
* - 添加水印 (图片/文字)
* - 视频信息查询
*/
declare(strict_types=1);
namespace app\neuron;
use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\ZAI\ZAI;
use app\neuron\tool\VideoTranscodeTool;
use app\neuron\tool\VideoConcatTool;
use app\neuron\tool\VideoScreenshotTool;
use app\neuron\tool\AudioExtractTool;
use app\neuron\tool\VideoWatermarkTool;
use app\neuron\tool\VideoInfoTool;
class FFmpegAgent extends Agent
{
/**
* @desc provider
* @author Tinywan(ShaoBo Wan)
* @return AIProviderInterface
*/
protectedfunction provider(): AIProviderInterface
{
returnnew ZAI(
key: '7312460bbad94517a18981c93a12c2ea.xxxxxxxxxxxxx',
model: 'glm-5.1',
parameters: [],
);
}
/**
* @desc instructions
* @author Tinywan(ShaoBo Wan)
* @return string
*/
publicfunction instructions(): string
{
return (string)new SystemPrompt(
background: [
'你是 FFmpeg Agent,专业的音视频处理助手。',
],
steps: [
'理解用户需求',
'调用工具处理',
'直接返回结果',
],
toolsUsage: [
'使用绝对路径',
'输出文件路径要完整',
'错误要明确',
'只输出最终结果,不要输出思考过程',
],
);
}
/**
* @desc 注册可用的音视频处理工具
* @return array
* @author Tinywan(ShaoBo Wan)
*/
protectedfunction tools(): array
{
return [
new VideoInfoTool(), // 视频信息查询
new VideoScreenshotTool(), // 视频截图
new VideoWatermarkTool(), // 添加水印
new VideoTranscodeTool(), // 视频转码
new VideoConcatTool(), // 视频合并
new AudioExtractTool(), // 音频提取
];
}
}
<?php
/**
* @desc 视频信息查询工具
* @author Tinywan(ShaoBo Wan)
*/
declare(strict_types=1);
namespace app\neuron\tool;
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ToolProperty;
use NeuronAI\Tools\PropertyType;
class VideoInfoTool extends Tool
{
publicfunction __construct()
{
parent::__construct(
'video_info',
'获取视频文件的详细信息,包括分辨率、时长、帧率、编码格式等'
);
}
protectedfunction properties(): array
{
return [
new ToolProperty(
name: 'input_file',
type: PropertyType::STRING,
description: '输入视频/音频文件的完整路径',
required: true
),
];
}
publicfunction __invoke(string $input_file): string
{
if (!file_exists($input_file)) {
return"错误: 文件不存在 - {$input_file}";
}
$cmd = sprintf('ffprobe -v quiet -print_format json -show_format -show_streams %s', escapeshellarg($input_file));
$output = shell_exec($cmd);
if (!$output) {
return"错误: 无法获取文件信息,请确保 ffprobe 已正确安装";
}
$info = json_decode($output, true);
if (!$info) {
return"错误: 无法解析文件信息";
}
return$this->formatVideoInfo($input_file, $info);
}
privatefunction formatVideoInfo(string $filename, array $info): string
{
$lines = [];
$lines[] = "文件信息: " . basename($filename);
$lines[] = str_repeat('-', 50);
if (isset($info['format'])) {
$format = $info['format'];
$lines[] = "格式名称: " . ($format['format_name'] ?? '未知');
$lines[] = "文件大小: " . $this->formatFileSize($format['size'] ?? 0);
$lines[] = "时长: " . $this->formatDuration($format['duration'] ?? 0);
$lines[] = "比特率: " . $this->formatBitrate($format['bit_rate'] ?? 0);
}
$lines[] = "";
$lines[] = "流信息:";
$videoCount = 0;
$audioCount = 0;
foreach ($info['streams'] ?? [] as $stream) {
if ($stream['codec_type'] === 'video') {
$videoCount++;
$lines[] = "";
$lines[] = " 视频流 #{$videoCount}:";
$lines[] = " 编码: " . ($stream['codec_name'] ?? '未知');
$lines[] = " 分辨率: " . (($stream['width'] ?? 0) . 'x' . ($stream['height'] ?? 0));
$lines[] = " 帧率: " . $this->formatFrameRate($stream['r_frame_rate'] ?? '0/0');
$lines[] = " 像素格式: " . ($stream['pix_fmt'] ?? '未知');
} elseif ($stream['codec_type'] === 'audio') {
$audioCount++;
$lines[] = "";
$lines[] = " 音频流 #{$audioCount}:";
$lines[] = " 编码: " . ($stream['codec_name'] ?? '未知');
$lines[] = " 采样率: " . (($stream['sample_rate'] ?? 0) . ' Hz');
$lines[] = " 声道: " . ($stream['channels'] ?? 0);
}
}
$lines[] = "";
$lines[] = str_repeat('-', 50);
return implode("\n", $lines);
}
privatefunction formatFileSize(int|string $bytes): string
{
$bytes = (int)$bytes;
if ($bytes >= 1073741824) {
return round($bytes / 1073741824, 2) . ' GB';
}
if ($bytes >= 1048576) {
return round($bytes / 1048576, 2) . ' MB';
}
if ($bytes >= 1024) {
return round($bytes / 1024, 2) . ' KB';
}
return $bytes . ' bytes';
}
privatefunction formatDuration(float|string $seconds): string
{
$seconds = (float)$seconds;
$totalSeconds = (int)$seconds;
$hours = intdiv($totalSeconds, 3600);
$remaining = $totalSeconds % 3600;
$minutes = intdiv($remaining, 60);
$secs = $remaining % 60;
if ($hours > 0) {
return sprintf('%d:%02d:%02d', $hours, $minutes, $secs);
}
return sprintf('%d:%02d', $minutes, $secs);
}
privatefunction formatBitrate(int|string $bitrate): string
{
$bitrate = (int)$bitrate;
if ($bitrate >= 1000000) {
return round($bitrate / 1000000, 2) . ' Mbps';
}
return round($bitrate / 1000, 2) . ' Kbps';
}
privatefunction formatFrameRate(string $frameRate): string
{
$parts = explode('/', $frameRate);
if (count($parts) === 2 && (int)$parts[1] > 0) {
$fps = (int)$parts[0] / (int)$parts[1];
return number_format($fps, 2) . ' fps';
}
return $frameRate;
}
}
其余工具大差不差
<?php
/**
* @desc FFmpeg Agent 控制器
* @author Tinywan(ShaoBo Wan)
*/
declare(strict_types=1);
namespace app\controller;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Chat\Messages\ContentBlocks\TextContent;
use NeuronAI\Chat\Messages\ContentBlocks\ReasoningContent;
use support\Request;
use support\Response;
class FfmpegController
{
/**
* FFmpeg Agent 聊天页面
*/
publicfunction index(): Response
{
return view('ffmpeg/index');
}
/**
* 聊天 API
*/
publicfunction chat(Request $request): Response
{
$message = $request->post('message', '');
if (empty($message)) {
return json(['error' => '消息不能为空'], 400);
}
$agent = \app\neuron\FFmpegAgent::make();
// 普通响应
try {
$response = $agent->chat(new UserMessage($message));
$message = $response->getMessage();
// 只获取文本内容,排除思考过程
$content = '';
foreach ($message?->getContentBlocks() ?? [] as $block) {
if ($block instanceof TextContent && !($block instanceof ReasoningContent)) {
$content .= $block->content;
}
}
if (empty($content)) {
$content = '处理完成,但没有返回内容';
}
return json([
'success' => true,
'message' => $content,
]);
} catch (\Exception $e) {
return json([
'success' => false,
'error' => $e->getMessage(),
], 500);
}
}
}

获取视频信息

视频截图

视频转码
