101 lines
3.1 KiB
PHP
101 lines
3.1 KiB
PHP
|
|
<?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;
|
|||
|
|
}
|
|||
|
|
}
|