From 788c2288530a52016cebfe5cd1d7a6c18e94dd8f Mon Sep 17 00:00:00 2001 From: leether Date: Sun, 7 Jun 2026 16:10:30 +0800 Subject: [PATCH] fix(concat): two critical ffmpeg filter_complex bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: Empty filter name when fade_in=fade_out=0 Generated: [0:v]setpts=PTS-STARTPTS,[v0] ffmpeg parsed ',' as filter separator → [v0] treated as filter name Fix: [0:v]setpts=PTS-STARTPTS[v0] (no comma between filter and output pad) Bug 2: Audio stream lost in _normalize_video -an deleted original audio, but need_audio logic skipped recovery when source had audio (has_audio=True → need_audio=False). Result: normalized videos had no audio → acrossfade 'matches no streams'. Fix: replace -an with -c:a copy to preserve original audio stream. Both bugs caused filter_complex path to fail with exit code 8. Fast path was unaffected. Closes: concat-engine-ffmpeg-bugs --- core/concat_engine.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/core/concat_engine.py b/core/concat_engine.py index fcb46fb..5eff436 100644 --- a/core/concat_engine.py +++ b/core/concat_engine.py @@ -155,7 +155,7 @@ def _normalize_video( "-c:v", self.config.video_codec, "-pix_fmt", self.config.pixel_format, "-crf", str(self.config.crf), - "-an", # 先不处理音频 + "-an", # 图片无音频 output_path, ] else: @@ -183,9 +183,9 @@ def _normalize_video( "-vf", filter_str, "-t", str(target_duration), "-c:v", self.config.video_codec, + "-c:a", "copy", # 保留原始音频流(filter_complex 路径需要) "-pix_fmt", self.config.pixel_format, "-crf", str(self.config.crf), - "-an", # 先不处理音频 output_path, ] @@ -346,22 +346,20 @@ def _build_filter_complex( duration = clip["duration"] # 视频 fade - video_filters = [f"[{i}:v]setpts=PTS-STARTPTS"] + video_filters = ["setpts=PTS-STARTPTS"] if fade_in > 0: video_filters.append(f"fade=t=in:st=0:d={fade_in}") if fade_out > 0: video_filters.append(f"fade=t=out:st={duration - fade_out}:d={fade_out}") - video_filters.append(f"[v{i}]") - filter_parts.append(",".join(video_filters)) + filter_parts.append(f"[{i}:v]{','.join(video_filters)}[v{i}]") # 音频 fade - audio_filters = [f"[{i}:a]asetpts=PTS-STARTPTS"] + audio_filters = ["asetpts=PTS-STARTPTS"] if fade_in > 0: audio_filters.append(f"afade=t=in:st=0:d={fade_in}") if fade_out > 0: audio_filters.append(f"afade=t=out:st={duration - fade_out}:d={fade_out}") - audio_filters.append(f"[a{i}]") - filter_parts.append(",".join(audio_filters)) + filter_parts.append(f"[{i}:a]{','.join(audio_filters)}[a{i}]") # 链式应用 transition video_chain = "v0" @@ -453,7 +451,13 @@ def _concat_effect_path( output_video, ] - subprocess.run(cmd, capture_output=True, check=True) + result = subprocess.run(cmd, capture_output=True, check=False) + if result.returncode != 0: + print(f"[ConcatEngine] ffmpeg failed with code {result.returncode}") + print(f"[ConcatEngine] stderr: {result.stderr.decode('utf-8', errors='replace')[:2000]}") + raise subprocess.CalledProcessError( + result.returncode, cmd, output=result.stdout, stderr=result.stderr + ) return Path(output_video) # ═══════════════════════════════════════════════════════