如何用PHP+FFmpeg为拼接的视频添加淡入淡出过渡效果?
在PHP中用FFmpeg实现带淡入淡出过渡的视频拼接
直接使用concat滤镜只能完成简单拼接,要添加视频间的淡入淡出过渡,需要通过**复杂滤镜(complex filter)**结合fade和overlay滤镜实现。以下是具体实现方案:
实现思路
- 预获取所有待拼接视频的时长,用于计算过渡时间点;
- 对相邻视频对,将前一个视频的末尾N秒添加淡出效果,后一个视频的开头N秒添加淡入效果;
- 通过
overlay滤镜将淡入淡出的片段叠加,实现平滑过渡; - 最后将所有正常片段和过渡片段拼接为完整视频。
代码实现
假设过渡时长设为1秒,可根据需求自行调整:
use FFMpeg\FFMpeg; $ffmpeg = FFMpeg::create(); $transitionDuration = 1; // 过渡时长(单位:秒) $files_aux = [/* 你的视频URL数组 */]; $newfilename = "/output.mp4"; // 输出文件路径 // 预加载所有视频,获取每个视频的时长 $videoDurations = []; foreach ($files_aux as $file) { $video = $ffmpeg->open($file); $videoDurations[] = $video->getDuration(); } // 构建复杂滤镜字符串 $filterSegments = []; $totalVideos = count($files_aux); foreach ($files_aux as $idx => $file) { $inputLabel = "v{$idx}"; $currentDuration = $videoDurations[$idx]; if ($totalVideos === 1) { // 只有单个视频,直接输出 $filterSegments[] = "{$inputLabel},setpts=PTS-STARTPTS[v_final]"; break; } if ($idx === 0) { // 第一个视频:保留主体片段 + 末尾淡出片段 $mainEnd = $currentDuration - $transitionDuration; $filterSegments[] = "{$inputLabel}:trim=start=0:end={$mainEnd},setpts=PTS-STARTPTS[v0_main]"; $filterSegments[] = "{$inputLabel}:trim=start={$mainEnd}:end={$currentDuration},setpts=PTS-STARTPTS,fade=out:st=0:d={$transitionDuration}[v0_fadeout]"; } elseif ($idx === $totalVideos - 1) { // 最后一个视频:开头淡入片段 + 主体片段 $filterSegments[] = "{$inputLabel}:trim=start=0:end={$transitionDuration},setpts=PTS-STARTPTS,fade=in:st=0:d={$transitionDuration}[v{$idx}_fadein]"; $filterSegments[] = "{$inputLabel}:trim=start={$transitionDuration}:end={$currentDuration},setpts=PTS-STARTPTS[v{$idx}_main]"; // 拼接前一个视频的淡出和当前视频的淡入,再组合所有片段 $filterSegments[] = "[v" . ($idx-1) . "_fadeout][v{$idx}_fadein]overlay[v_trans]"; $filterSegments[] = "[v" . ($idx-1) . "_main][v_trans][v{$idx}_main]concat=n=3:v=1:a=0[v_final]"; } else { // 中间视频:开头淡入 + 主体 + 末尾淡出 $filterSegments[] = "{$inputLabel}:trim=start=0:end={$transitionDuration},setpts=PTS-STARTPTS,fade=in:st=0:d={$transitionDuration}[v{$idx}_fadein]"; $mainStart = $transitionDuration; $mainEnd = $currentDuration - $transitionDuration; $filterSegments[] = "{$inputLabel}:trim=start={$mainStart}:end={$mainEnd},setpts=PTS-STARTPTS[v{$idx}_main]"; $filterSegments[] = "{$inputLabel}:trim=start={$mainEnd}:end={$currentDuration},setpts=PTS-STARTPTS,fade=out:st=0:d={$transitionDuration}[v{$idx}_fadeout]"; // 拼接前一个过渡和当前淡入,再组合片段 $filterSegments[] = "[v" . ($idx-1) . "_fadeout][v{$idx}_fadein]overlay[v{$idx}_trans]"; $filterSegments[] = "[v" . ($idx-1) . "_combined][v{$idx}_trans][v{$idx}_main]concat=n=3:v=1:a=0[v{$idx}_combined]"; // 第二个视频处理后,初始化组合片段基准 if ($idx === 1) { $filterSegments[] = "[v0_main][v1_trans][v1_main]concat=n=3:v=1:a=0[v1_combined]"; } } } // 合并所有滤镜段 $filterString = implode(';', $filterSegments); // 构建FFmpeg命令并执行 $commandArgs = []; // 添加所有输入文件 foreach ($files_aux as $file) { $commandArgs[] = "-i"; $commandArgs[] = $file; } // 添加滤镜和输出参数 $commandArgs = array_merge($commandArgs, [ '-filter_complex', $filterString, '-map', '[v_final]', '-c:v', 'libx264', '-crf', '23', '-preset', 'medium', __DIR__ . $newfilename ]); $ffmpeg->getFFmpegDriver()->execute($commandArgs);
注意事项
- 分辨率统一:所有待拼接视频的分辨率必须一致,否则
overlay滤镜会导致画面错位。若分辨率不同,需在滤镜中添加scale命令统一尺寸; - 音频处理:上述代码仅处理视频,若需要同步处理音频的淡入淡出,可参照视频逻辑,对音频流使用
afade滤镜; - 性能优化:复杂滤镜会增加FFmpeg计算量,可根据服务器性能调整
-preset参数(如fast或ultrafast)加快处理速度; - 时长校验:确保每个视频时长大于过渡时长,避免出现参数错误。
内容的提问来源于stack exchange,提问作者Corboss
相关产品推荐
相关产品推荐

