前置條件
- 服務器安裝 FFmpeg:
ffmpeg -version 可見版本信息
- PHP 配置允許
passthru/exec(部分虛擬主機禁用)
- 知曉 FFmpeg 可執行文件路徑(如
/usr/bin/ffmpeg)
核心解析函數
function getVideoInfo($filePath) {
if (!file_exists($filePath)) {
return ['error' => '文件不存在'];
}
// 定義 ffmpeg 探測命令
$cmd = sprintf('ffmpeg -i "%s" 2>&1', $filePath);
// 執行並捕獲輸出(stderr 包含元信息)
ob_start();
passthru($cmd);
$output = ob_get_clean();
$info = [
'file' => $filePath,
'size' => filesize($filePath),
];
// 1. 解析時長、碼率
if (preg_match('/Duration: ([\d:]+), start: ([\d.]+), bitrate: (\d+) kb\/s/', $output, $m)) {
$info['duration'] = $m[1]; // HH:MM:SS.xx
$info['start'] = (float)$m[2]; // 秒
$info['bitrate'] = (int)$m[3]; // kbps
// 轉換為總秒數
$parts = explode(':', $m[1]);
$info['duration_seconds'] = $parts[0]*3600 + $parts[1]*60 + $parts[2];
$info['play_time'] = $info['duration_seconds'] + $info['start'];
}
// 2. 解析視頻流信息
if (preg_match('/Video: (\w+), ([^,]+), ([^,]+)/', $output, $m)) {
$info['vcodec'] = $m[1]; // h264, hevc, vp9...
$info['vformat'] = $m[2]; // yuv420p 等
$info['resolution'] = $m[3]; // 1920x1080
if (preg_match('/(\d+)x(\d+)/', $m[3], $rm)) {
$info['width'] = (int)$rm[1];
$info['height'] = (int)$rm[2];
}
}
// 3. 解析音頻流信息
if (preg_match('/Audio: (\w+), (\d+) Hz/', $output, $m)) {
$info['acodec'] = $m[1]; // aac, mp3, opus...
$info['asamplerate'] = (int)$m[2]; // 44100, 48000...
}
return $info;
}
// 使用示例
$videoInfo = getVideoInfo('/var/www/videos/demo.mp4');
print_r($videoInfo);
輸出示例
Array
(
[file] => /var/www/videos/demo.mp4
[size] => 15728640
[duration] => 00:01:30.45
[start] => 0.000000
[bitrate] => 1250
[duration_seconds] => 90
[play_time] => 90
[vcodec] => h264
[vformat] => yuv420p
[resolution] => 1920x1080
[width] => 1920
[height] => 1080
[acodec] => aac
[asamplerate] => 44100
)
進階:使用 ffprobe 獲取結構化 JSON(推薦)
function getVideoInfoJson($filePath) {
$cmd = sprintf(
'ffprobe -v quiet -print_format json -show_format -show_streams "%s" 2>&1',
$filePath
);
ob_start();
passthru($cmd);
$json = ob_get_clean();
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['error' => 'ffprobe 輸出解析失敗'];
}
$result = ['file' => $filePath, 'size' => filesize($filePath)];
// Format 信息
if (isset($data['format'])) {
$f = $data['format'];
$result['duration'] = (float)$f['duration'];
$result['bitrate'] = (int)$f['bit_rate'];
$result['format_name'] = $f['format_name'];
}
// Streams 信息
foreach ($data['streams'] ?? [] as $stream) {
if ($stream['codec_type'] === 'video') {
$result['video'] = [
'codec' => $stream['codec_name'],
'width' => $stream['width'],
'height' => $stream['height'],
'pix_fmt' => $stream['pix_fmt'],
'r_frame_rate' => $stream['r_frame_rate'],
'avg_frame_rate' => $stream['avg_frame_rate'],
];
} elseif ($stream['codec_type'] === 'audio') {
$result['audio'] = [
'codec' => $stream['codec_name'],
'sample_rate' => $stream['sample_rate'],
'channels' => $stream['channels'],
'channel_layout' => $stream['channel_layout'],
];
}
}
return $result;
}
ffprobe JSON 輸出優勢
- 結構化、可擴展、無需正則解析
- 包含幀率、編碼配置文件、色彩空間等豐富信息
- 官方推薦的程序化調用方式
常見問題與解決
| 問題 | 原因 | 解決 |
| passthru 被禁用 | 虛擬主機安全策略 | 遷移至 VPS/雲服務器,或使用 PHP-FFMpeg 库(純 PHP 解析容器格式) |
| 執行超時 | 大文件探測耗時 | 設置 set_time_limit(0),或異步隊列處理 |
| 路徑含空格/特殊字符 | 命令注入風險 | 使用 escapeshellarg() 包裹路徑 |
| 權限不足 | PHP 進程無法讀取文件 | 確保文件可讀,或將視頻存儲在 PHP 可訪問目錄 |
生產環境建議
- 使用 PHP-FFMpeg (composer require php-ffmpeg/php-ffmpeg) 面向對象封裝,避免直接調用命令
- 視頻上傳後異步提取元信息,存入數據庫,前端直接讀取
- 建立視頻處理管道:上傳 → 轉碼/截圖/元信息提取 → CDN 分發
- 監控 FFmpeg 進程資源佔用,防止佔滿 CPU/內存