如何用FFmpeg实现过场单屏、其余分屏的混合视频输出?
FFmpeg分屏与过场动画切换的优化实现
需求说明
现有视频素材需处理为:
- 00:00-01:15、10:00-10:30、29:00至结尾:显示单源单屏的过场动画(各时段内容一致)
- 其余时段:显示四屏分屏画面(各屏内容不同,带玩家名称字幕)
现有脚本(全程生成分屏)
ffmpeg -i "$player_one.$player_one_extension" \ -i "$player_two.$player_two_extension" \ -i "$player_three.$player_three_extension" \ -i "$player_four.$player_four_extension" \ -filter_complex \ "nullsrc=size=${video_width}x${video_height} [base]; \ [0:v] trim=start=$player_one_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_one_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [upperleft]; \ [1:v] trim=start=$player_two_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_two_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [upperright]; \ [2:v] trim=start=$player_three_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_three_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [lowerleft]; \ [3:v] trim=start=$player_four_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_four_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [lowerright]; \ [3:a] atrim=start=$audio_offset,asetpts=PTS-STARTPTS[outa]; [base][upperleft] overlay=shortest=1 [tmp1]; [tmp1][upperright] overlay=shortest=1:x=${half_width} [tmp2]; [tmp2][lowerleft] overlay=shortest=1:y=${half_height} [tmp3]; [tmp3][lowerright] overlay=shortest=1:x=${half_width}:y=${half_height}[v]" -map "[v]" -map "[outa]" -c:v libx264 -shortest -t $duration "100_100_hardcore_ghost_recon_future_soldier_coop_pc_${mission}4k.mp4"
问题
当前脚本全程生成分屏画面,我想到的方案是分段制作各时段视频后用concat合并,但这样会生成多个中间文件。请问有没有更高效的单命令实现方式?
最优实现方案:单命令搞定,无中间文件
当然有更高效的方式——不用分段生成视频再合并,直接在单个FFmpeg命令的滤镜链里完成所有时段的裁剪、画面处理和拼接,全程无中间文件,步骤更简洁:
核心思路
- 复用原有逻辑生成完整的分屏画面流
- 从分屏流里裁剪出需要显示分屏的时段(01:15-10:00、10:30-29:00)
- 处理过场动画流,裁剪出需要显示单屏的时段(00:00-01:15、10:00-10:30、29:00至结尾)
- 把所有处理好的视频段按时间顺序用
concat滤镜拼接 - 同步处理音频的分段裁剪与拼接,保证音画同步
完整脚本示例
假设过场动画单独存为opening.mp4,如果过场是某个玩家视频的片段,直接替换成对应输入流即可:
ffmpeg -i "$player_one.$player_one_extension" \ -i "$player_two.$player_two_extension" \ -i "$player_three.$player_three_extension" \ -i "$player_four.$player_four_extension" \ -i "opening.mp4" \ -filter_complex \ # 1. 生成完整分屏画面(复用原有分屏逻辑) "nullsrc=size=${video_width}x${video_height} [base]; \ [0:v] trim=start=$player_one_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_one_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [ul]; \ [1:v] trim=start=$player_two_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_two_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [ur]; \ [2:v] trim=start=$player_three_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_three_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [ll]; \ [3:v] trim=start=$player_four_offset,setpts=PTS-STARTPTS, scale=${half_width}x${half_height}, drawtext=fontfile=control-freak-font/CONTF___.ttf:text='${player_four_name}':fontsize=$font_size:fontcolor=0xFFFFFF:alpha='$alpha':x=w-text_w-${font_padding}:y=h-text_h-${font_padding} [lr]; \ [base][ul] overlay=shortest=1 [t1]; [t1][ur] overlay=shortest=1:x=${half_width} [t2]; [t2][ll] overlay=shortest=1:y=${half_height} [t3]; [t3][lr] overlay=shortest=1:x=${half_width}:y=${half_height} [full_split]; \ # 2. 裁剪分屏需要显示的时段(转成秒计算) [full_split] trim=start=75:end=600,setpts=PTS-STARTPTS [split_seg1]; \ [full_split] trim=start=630:end=1740,setpts=PTS-STARTPTS [split_seg2]; \ # 3. 处理过场动画,裁剪对应时段 [4:v] scale=${video_width}x${video_height},setpts=PTS-STARTPTS [opening_full]; \ [opening_full] trim=end=75,setpts=PTS-STARTPTS [opening_seg1]; \ [opening_full] trim=start=0:end=30,setpts=PTS-STARTPTS [opening_seg2]; \ [opening_full] trim=start=0,setpts=PTS-STARTPTS [opening_seg3]; \ # 4. 按顺序拼接所有视频段 [opening_seg1][split_seg1][opening_seg2][split_seg2][opening_seg3] concat=n=5:v=1:a=0 [v_out]; \ # 5. 同步处理音频(以player4的音频为例,按对应时段裁剪拼接) [3:a] atrim=start=$audio_offset:end=$((audio_offset+75)),asetpts=PTS-STARTPTS [a_seg1]; \ [3:a] atrim=start=$((audio_offset+75)):end=$((audio_offset+600)),asetpts=PTS-STARTPTS [a_seg2]; \ [3:a] atrim=start=$((audio_offset+600)):end=$((audio_offset+630)),asetpts=PTS-STARTPTS [a_seg3]; \ [3:a] atrim=start=$((audio_offset+630)):end=$((audio_offset+1740)),asetpts=PTS-STARTPTS [a_seg4]; \ [3:a] atrim=start=$((audio_offset+1740)),asetpts=PTS-STARTPTS [a_seg5]; \ [a_seg1][a_seg2][a_seg3][a_seg4][a_seg5] concat=n=5:v=0:a=1 [a_out]" \ -map "[v_out]" -map "[a_out]" -c:v libx264 -c:a aac "100_100_hardcore_ghost_recon_future_soldier_coop_pc_${mission}4k.mp4"
注意事项
- 所有时间统一用秒计算,避免时:分:秒格式的解析问题(比如01:15=75秒,10:00=600秒)
- 如果过场动画是从某个玩家的视频里截取的(比如player1的开头片段),把
[4:v]换成[0:v] trim=start=xxx:end=xxx即可,不用额外导入过场文件 - 音频要严格对应视频的时段长度,否则会出现音画不同步的问题
- 若分屏流的时长不够,可去掉
shortest=1参数,或者确保所有输入视频的时长满足需求
内容的提问来源于stack exchange,提问作者PirateApp
相关产品推荐
相关产品推荐

