Files
cunkebao_v3/Server/application/common/Video.php

101 lines
3.1 KiB
PHP
Raw Normal View History

2025-03-12 12:18:06 +08:00
<?php
namespace app\common;
class Video {
/**
* 图片合成视频
*
* @param array $images
* @return string
* @throws \Exception
*/
static public function compositing(array $images) {
if (empty($images)) {
throw new \Exception('图片列表不能为空');
}
// 设置视频参数
$width = 800;
$height = 600; // 4:3 比例
$frameRate = 30; // 调整帧率为30fps使动画更流畅
// 创建临时文件夹
$tempDir = ROOT_PATH . DS . 'runtime' . DS . 'video_temp';
if (!is_dir($tempDir)) {
mkdir($tempDir, 0777, true);
}
// 生成唯一的输出文件名
$outputFile = $tempDir . DS . uniqid() . '.mp4';
// 准备图片列表文件
$listFile = $tempDir . DS . uniqid() . '.txt';
$content = '';
// 生成临时图片序列目录
$tempImagesDir = $tempDir . DS . uniqid();
if (!is_dir($tempImagesDir)) {
mkdir($tempImagesDir, 0777, true);
}
// 处理每张图片,生成临时文件
$tempImages = [];
foreach ($images as $index => $image) {
if (!file_exists($image)) {
throw new \Exception('图片不存在:' . $image);
}
$tempImage = $tempImagesDir . DS . sprintf('%04d.jpg', $index);
copy($image, $tempImage);
$tempImages[] = $tempImage;
}
// 构建 FFmpeg 命令
$filterComplex = [];
// 处理所有输入流的格式和缩放
foreach ($tempImages as $i => $image) {
$filterComplex[] = sprintf('[%d:v]format=yuv420p,scale=%d:%d:force_original_aspect_ratio=decrease,pad=%d:%d:(ow-iw)/2:(oh-ih)/2[v%d]',
$i, $width, $height, $width, $height, $i);
}
// 构建转场效果链
$lastOutput = 'v0';
for ($i = 1; $i < count($tempImages); $i++) {
$filterComplex[] = sprintf('[%s][v%d]xfade=transition=fade:duration=1:offset=%d[fade%d]',
$lastOutput, $i, ($i * 3) - 1, $i);
$lastOutput = sprintf('fade%d', $i);
}
$inputFiles = '';
foreach ($tempImages as $image) {
$inputFiles .= sprintf('-loop 1 -t %d -i "%s" ', count($tempImages) * 3, $image);
}
$duration = count($tempImages) * 3 - (count($tempImages) - 1); // 总时长计算
$cmd = sprintf(
'ffmpeg5 %s -filter_complex "%s" -map "[%s]" -t %d -c:v libx264 -pix_fmt yuv420p -r %d "%s" 2>&1',
$inputFiles,
implode(';', $filterComplex),
$lastOutput,
$duration,
$frameRate,
$outputFile
);
// 执行命令
exec($cmd, $output, $returnCode);
// 清理临时文件
array_map('unlink', glob($tempImagesDir . DS . '*'));
rmdir($tempImagesDir);
if ($returnCode !== 0) {
throw new \Exception('视频生成失败:' . implode("\n", $output));
}
return $outputFile;
}
}