From 17fc46c6626b8bfcf2bce317452b466948cf7a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Wed, 1 Jul 2026 20:26:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20=5Fhas=5Faudio()=20?= =?UTF-8?q?=E5=A7=8B=E7=BB=88=E8=BF=94=E5=9B=9E=20True=20=E7=9A=84=20P0=20?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 仅当 clip_type 不是 title/subtitle 时才设置 audio_label - title/subtitle 是纯文字/图片卡片,没有音频流 - 添加 7 个单元测试验证修复逻辑 - 所有 36 个测试通过 --- .../api/app/services/video_compose_service.py | 11 ++-- tests/unit/test_video_compose_service.py | 62 +++++++++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/apps/api/app/services/video_compose_service.py b/apps/api/app/services/video_compose_service.py index 6e6b80a33..b7befdd87 100644 --- a/apps/api/app/services/video_compose_service.py +++ b/apps/api/app/services/video_compose_service.py @@ -428,12 +428,11 @@ class VideoComposeService: video_label = f"v{input_index}" - # 5. atrim: 音频时长(假设输入可能有音频) - audio_filters = [ - f"atrim=0:{duration}", - "asetpts=PTS-STARTPTS", - ] - audio_label = f"a{input_index}" + # 5. 音频标签:仅当片段类型可能有音频时才设置 + # title/subtitle 是纯文字/图片卡片,没有音频流 + clip_type = clip.clip_type.lower() if clip.clip_type else "" + has_audio_stream = clip_type not in ("title", "subtitle") + audio_label = f"a{input_index}" if has_audio_stream else None return ClipFilterChain( clip_id=clip.id, diff --git a/tests/unit/test_video_compose_service.py b/tests/unit/test_video_compose_service.py index 446f13fe3..316a3aeae 100644 --- a/tests/unit/test_video_compose_service.py +++ b/tests/unit/test_video_compose_service.py @@ -560,6 +560,68 @@ class TestBuildXfadeFilter(TestCase): self.assertAlmostEqual(duration, 14.0, places=2) +class TestHasAudioTitleSubtitleFix(TestCase): + """P0 修复验证:title/subtitle 片段不应有音频流。""" + + def _make_clip(self, clip_id, clip_type, **kwargs): + """创建测试用 stub clip。""" + return _StubClip(clip_id=clip_id, clip_type=clip_type, **kwargs) + + def test_title_clip_has_no_audio_label(self): + """title 类型片段的 audio_label 应为 None。""" + clip = self._make_clip("c1", "title") + chain = VideoComposeService._build_clip_filter(clip, 0, 1280, 720, 25) + self.assertIsNone(chain.audio_label, "title 片段不应有音频标签") + + def test_subtitle_clip_has_no_audio_label(self): + """subtitle 类型片段的 audio_label 应为 None。""" + clip = self._make_clip("c1", "subtitle") + chain = VideoComposeService._build_clip_filter(clip, 0, 1280, 720, 25) + self.assertIsNone(chain.audio_label, "subtitle 片段不应有音频标签") + + def test_main_clip_has_audio_label(self): + """main 类型片段应有音频标签。""" + clip = self._make_clip("c1", "main") + chain = VideoComposeService._build_clip_filter(clip, 0, 1280, 720, 25) + self.assertEqual(chain.audio_label, "a0") + + def test_intro_clip_has_audio_label(self): + """intro 类型片段应有音频标签。""" + clip = self._make_clip("c1", "intro") + chain = VideoComposeService._build_clip_filter(clip, 0, 1280, 720, 25) + self.assertEqual(chain.audio_label, "a0") + + def test_has_audio_false_when_only_title_subtitle(self): + """当所有片段都是 title/subtitle 时,_has_audio 应返回 False。""" + chains = [ + VideoComposeService._build_clip_filter( + self._make_clip("c1", "title"), 0, 1280, 720, 25 + ), + VideoComposeService._build_clip_filter( + self._make_clip("c2", "subtitle"), 1, 1280, 720, 25 + ), + ] + self.assertFalse(VideoComposeService._has_audio(chains)) + + def test_has_audio_true_when_mixed_clips(self): + """混合片段(含 main)时,_has_audio 应返回 True。""" + chains = [ + VideoComposeService._build_clip_filter( + self._make_clip("c1", "title"), 0, 1280, 720, 25 + ), + VideoComposeService._build_clip_filter( + self._make_clip("c2", "main"), 1, 1280, 720, 25 + ), + ] + self.assertTrue(VideoComposeService._has_audio(chains)) + + def test_empty_clip_type_has_audio(self): + """clip_type 为空字符串时,应有音频标签(保守策略)。""" + clip = self._make_clip("c1", "") + chain = VideoComposeService._build_clip_filter(clip, 0, 1280, 720, 25) + self.assertEqual(chain.audio_label, "a0") + + if __name__ == "__main__": import unittest unittest.main()