From 30804f956b2175d35526c9e2b4efb6b32f6c0ccc Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 18 Aug 2026 00:47:45 +0800 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=E6=B8=85=E7=90=86=20generation.py?= =?UTF-8?q?=20=E4=B8=AD=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=87=BD=E6=95=B0=E5=92=8C=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除已废弃的 FFmpeg 渲染辅助函数(已被 RenderAdapter 统一路径替代): - _create_fallback_clip: 无素材时创建 fallback 视频 - _mux_audio_track: 后处理混音(视频+音频合并) - _prepare_bgm_track: BGM 下载与准备 同时清理相关的未使用常量: - OUTPUT_DURATION_SECONDS - GENERATED_FILES_DIR - FFMPEG_BIN, run_ffmpeg (from video_processing.ffmpeg_utils) - import os --- apps/worker/worker_app/tasks/generation.py | 158 --------------------- 1 file changed, 158 deletions(-) diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index 38a32f6ae..dfef315d2 100644 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -15,7 +15,6 @@ from __future__ import annotations import json import logging -import os import tempfile import time from pathlib import Path @@ -37,8 +36,6 @@ from packages.domain.bgm_utils import merge_bgm_config OUTPUT_WIDTH = 1280 OUTPUT_HEIGHT = 720 OUTPUT_FPS = 25.0 -OUTPUT_DURATION_SECONDS = 5.0 -GENERATED_FILES_DIR = Path(os.getenv("GENERATED_FILES_DIR", "/app/generated")) logger = logging.getLogger(__name__) @@ -155,7 +152,6 @@ def _flush_logs(task_id: str, gen_task) -> None: # ── 共享工具模块导入 ────────────────────────────────────────────────────────── from video_processing.dedup_helpers import create_video_record_and_dedup -from video_processing.ffmpeg_utils import FFMPEG_BIN, run_ffmpeg from video_processing.oss_helpers import ( download_asset, get_signed_download_url, @@ -330,61 +326,6 @@ def _build_plan_and_clips_from_task( return plan, clips, asset_path_map -def _create_fallback_clip(output_path: Path, title: str) -> None: - """创建 fallback 视频(无素材时)""" - safe_title = title.replace(":", "\\:").replace("'", "\\'")[:80] - run_ffmpeg( - [ - FFMPEG_BIN, - "-y", - "-f", - "lavfi", - "-i", - f"color=c=#111827:s={OUTPUT_WIDTH}x{OUTPUT_HEIGHT}:d={OUTPUT_DURATION_SECONDS}:r={int(OUTPUT_FPS)}", - "-vf", - f"drawtext=text='{safe_title}':fontcolor=white:fontsize=48:x=(w-text_w)/2:y=(h-text_h)/2", - "-c:v", - "libx264", - "-pix_fmt", - "yuv420p", - "-movflags", - "+faststart", - str(output_path), - ] - ) - - -def _mux_audio_track(video_path: Path, audio_path: str, output_path: Path) -> None: - """将音频轨混入已渲染的视频(后处理步骤)。 - - 使用 FFmpeg 将视频和音频合并,视频时长为准,音频不足则循环, - 音频过长则截断。 - """ - command = [ - FFMPEG_BIN, - "-y", - "-i", - str(video_path), - "-i", - audio_path, - "-c:v", - "copy", - "-c:a", - "aac", - "-b:a", - "192k", - "-shortest", - "-map", - "0:v:0", - "-map", - "1:a:0", - "-movflags", - "+faststart", - str(output_path), - ] - run_ffmpeg(command) - - def _download_voice_asset(voice_library_id: str, local_path: Path) -> bool: """下载配音文件。 @@ -428,105 +369,6 @@ def _download_voice_asset(voice_library_id: str, local_path: Path) -> bool: return download_asset(storage_key, local_path) -def _prepare_bgm_track( - *, - bgm_config: dict, - temp_path: Path, - task_id: str = "", -) -> str | None: - """准备 BGM 音频文件(下载到本地). - - 支持 3 种来源(按优先级): - 1. audio_url — 外部直链 URL(最高优先级) - 2. asset_id — 素材库中的音频素材 - 3. preset_id — 预设 BGM 库 - - Returns: - BGM 本地文件路径,准备失败返回 None - """ - from urllib.parse import urlparse - - audio_url = bgm_config.get("audio_url", "") or "" - asset_id = bgm_config.get("asset_id", "") or "" - preset_id = bgm_config.get("preset_id", "") or "" - - bgm_file = temp_path / f"bgm_{task_id or 'track'}.mp3" - - # 优先级1:外部直链 URL - if audio_url: - try: - parsed = urlparse(audio_url) - if parsed.scheme in ("http", "https"): - from video_processing.url_security import ( - ALLOWED_AUDIO_MIME_TYPES, - safe_download_file, - ) - - logger.info("[task_id=%s] [BGM] 从URL下载: %s", task_id, audio_url[:80]) - safe_download_file( - audio_url, - str(bgm_file), - purpose="bgm_download", - allowed_mime_types=ALLOWED_AUDIO_MIME_TYPES, - timeout=60.0, - ) - if bgm_file.exists() and bgm_file.stat().st_size > 0: - return str(bgm_file) - except Exception as e: - logger.warning("[task_id=%s] [BGM] URL下载失败: %s", task_id, e) - - # 优先级2:素材库素材 - if asset_id: - try: - from app.core.db import SessionLocal - - from packages.adapters.sqlalchemy_impl.models import AssetModel - - session = SessionLocal() - try: - model = session.query(AssetModel).filter(AssetModel.id == asset_id).first() - if model and (model.storage_key or model.file_url): - # 兼容存量数据:storage_key 为空时 fallback 到 file_url - storage_key = model.storage_key or model.file_url - logger.info("[task_id=%s] [BGM] 从素材库下载: asset_id=%s", task_id, asset_id) - ok = download_asset(storage_key, bgm_file) - if ok and bgm_file.exists() and bgm_file.stat().st_size > 0: - return str(bgm_file) - finally: - session.close() - except Exception as e: - logger.warning("[task_id=%s] [BGM] 素材库下载失败: %s", task_id, e) - - # 优先级3:预设 BGM 库 - if preset_id: - try: - from packages.domain.preset_bgm import get_preset_bgm - - preset = get_preset_bgm(preset_id) - if preset and preset.audio_url: - from video_processing.url_security import ( - ALLOWED_AUDIO_MIME_TYPES, - safe_download_file, - ) - - logger.info("[task_id=%s] [BGM] 从预设库下载: preset_id=%s", task_id, preset_id) - safe_download_file( - preset.audio_url, - str(bgm_file), - purpose="bgm_preset_download", - allowed_mime_types=ALLOWED_AUDIO_MIME_TYPES, - timeout=60.0, - ) - if bgm_file.exists() and bgm_file.stat().st_size > 0: - return str(bgm_file) - except Exception as e: - logger.warning("[task_id=%s] [BGM] 预设库下载失败: %s", task_id, e) - - # 所有来源都失败 - logger.warning("[task_id=%s] [BGM] 所有来源都无法获取BGM,跳过", task_id) - return None - - def _verify_url_accessible( url: str, timeout: float = 10.0, -- 2.54.0 From ef6a1bda799c9296b89ed796a8c1456751497714 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 18 Aug 2026 11:19:14 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E6=B8=85=E7=90=86=20test=5Ffull=5Fp?= =?UTF-8?q?ipeline.py=20=E4=B8=AD=E5=AF=B9=E5=B7=B2=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=9A=84=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 _create_fallback_clip、_mux_audio_track 的 import - 删除 TestFallbackClip、TestMuxAudioTrack 测试类 - TestFullPipeline 中的混音改用直接 ffmpeg 命令 --- tests/integration/test_full_pipeline.py | 80 ++++++------------------- 1 file changed, 17 insertions(+), 63 deletions(-) diff --git a/tests/integration/test_full_pipeline.py b/tests/integration/test_full_pipeline.py index 92224ef53..97104e537 100644 --- a/tests/integration/test_full_pipeline.py +++ b/tests/integration/test_full_pipeline.py @@ -14,11 +14,7 @@ import pytest from video_processing.unified_render_service import ( UnifiedRenderService, ) -from worker_app.tasks.generation import ( - _build_plan_and_clips_from_task, - _create_fallback_clip, - _mux_audio_track, -) +from worker_app.tasks.generation import _build_plan_and_clips_from_task pytestmark = pytest.mark.skipif( not shutil.which("ffmpeg"), @@ -61,62 +57,6 @@ def _generate_test_audio(path: Path, duration: float = 5.0) -> None: ] subprocess.run(cmd, check=True, capture_output=True, timeout=30) - -# ── 测试 _create_fallback_clip ──────────────────────────────────────────────── - - -class TestFallbackClip: - """测试 fallback 视频生成。""" - - def test_fallback_clip_creates_video(self): - with tempfile.TemporaryDirectory() as tmpdir: - output = Path(tmpdir) / "fallback.mp4" - _create_fallback_clip(output, "Test Fallback") - - assert output.exists() - assert output.stat().st_size > 0 - - -# ── 测试 _mux_audio_track ──────────────────────────────────────────────────── - - -class TestMuxAudioTrack: - """测试视频+音频混合。""" - - def test_mux_audio_into_video(self): - with tempfile.TemporaryDirectory() as tmpdir: - video_path = Path(tmpdir) / "video.mp4" - audio_path = Path(tmpdir) / "audio.aac" - output_path = Path(tmpdir) / "output.mp4" - - _generate_test_video(video_path, duration=3.0) - _generate_test_audio(audio_path, duration=5.0) - - _mux_audio_track(video_path, str(audio_path), output_path) - - assert output_path.exists() - assert output_path.stat().st_size > 0 - - # 验证输出文件包含音频轨 - probe_cmd = [ - "ffprobe", - "-v", - "quiet", - "-show_streams", - "-select_streams", - "a", - "-of", - "csv=p=0", - str(output_path), - ] - result = subprocess.run(probe_cmd, capture_output=True, text=True, timeout=10) - # 如果有音频流,输出非空 - assert result.stdout.strip() != "" or result.returncode == 0 - - -# ── 测试 PlanGenerator → UnifiedRenderService 全链路 ───────────────────────── - - class TestFullPipeline: """验证从虚拟 plan 构建到渲染输出的完整流程。""" @@ -175,12 +115,26 @@ class TestFullPipeline: ) render_result = service.render() - # 混音 + # 混音 - 直接用 ffmpeg(_mux_audio_track 已被清理) audio_path = work_dir / "voice.aac" _generate_test_audio(audio_path, duration=5.0) final_path = work_dir / "final.mp4" - _mux_audio_track(render_result.output_path, str(audio_path), final_path) + mux_cmd = [ + "ffmpeg", + "-y", + "-i", + str(render_result.output_path), + "-i", + str(audio_path), + "-c:v", + "copy", + "-c:a", + "aac", + "-shortest", + str(final_path), + ] + subprocess.run(mux_cmd, check=True, capture_output=True, timeout=30) assert final_path.exists() assert final_path.stat().st_size > 0 -- 2.54.0 From e5e46bb02bf11faf71f2f697a662873386a8ebd4 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 18 Aug 2026 03:23:08 +0000 Subject: [PATCH 3/3] style: auto-format with black + isort + prettier [skip ci-format-check] --- tests/integration/test_full_pipeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_full_pipeline.py b/tests/integration/test_full_pipeline.py index 97104e537..70307d4a1 100644 --- a/tests/integration/test_full_pipeline.py +++ b/tests/integration/test_full_pipeline.py @@ -57,6 +57,7 @@ def _generate_test_audio(path: Path, duration: float = 5.0) -> None: ] subprocess.run(cmd, check=True, capture_output=True, timeout=30) + class TestFullPipeline: """验证从虚拟 plan 构建到渲染输出的完整流程。""" -- 2.54.0