From 9b99c46d7dd63f79afd42dffecfd93c28351a3d4 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Fri, 17 Jul 2026 17:07:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(worker):=20ffmpeg=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=97=B6=E6=8D=95=E8=8E=B7stderr=E5=86=99?= =?UTF-8?q?=E5=85=A5error=5Fdetail=EF=BC=8C=E5=A2=9E=E5=BC=BA=E6=8E=92?= =?UTF-8?q?=E9=9A=9C=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RenderAdapterResult新增error_detail字段存完整stderr - render_adapter单独捕获CalledProcessError,把stderr写入error_message和error_detail - edit_plan_generation失败时把error_detail拼进gen_task.error_message - 新增test_ffmpeg_error_captures_stderr测试 - 35个render_adapter测试全绿 --- .../worker/video_processing/render_adapter.py | 16 +++++++++ .../worker_app/tasks/edit_plan_generation.py | 5 ++- tests/unit/test_render_adapter.py | 34 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/apps/worker/video_processing/render_adapter.py b/apps/worker/video_processing/render_adapter.py index ff3939141..7323e94c8 100755 --- a/apps/worker/video_processing/render_adapter.py +++ b/apps/worker/video_processing/render_adapter.py @@ -16,6 +16,7 @@ from __future__ import annotations import logging +import subprocess import tempfile from dataclasses import dataclass from pathlib import Path @@ -70,6 +71,7 @@ class RenderAdapterResult: rendered_clip_ids: list[str] = None # 成功渲染的 clip id 列表 failed_clip_ids: list[str] = None # 失败的 clip id 列表 error_message: str = "" + error_detail: str = "" # 详细错误信息(如 ffmpeg stderr),用于排查 def __post_init__(self): if self.rendered_clip_ids is None: @@ -249,6 +251,20 @@ class RenderAdapter: failed_clip_ids=failed_clip_ids, ) + except subprocess.CalledProcessError as exc: + stderr_text = (exc.stderr or "").strip() + logger.error( + "[render-adapter] ffmpeg渲染失败: plan_id=%s job_id=%s exit_code=%d\nstderr:\n%s", + plan_id, + job_id, + exc.returncode, + stderr_text[:3000], + ) + return RenderAdapterResult( + success=False, + error_message=f"FFmpeg渲染失败(exit={exc.returncode}): {stderr_text[:200]}", + error_detail=stderr_text[:3000], + ) except Exception as exc: logger.exception( "[render-adapter] render failed: plan_id=%s job_id=%s engine=unified error=%s", diff --git a/apps/worker/worker_app/tasks/edit_plan_generation.py b/apps/worker/worker_app/tasks/edit_plan_generation.py index 3e5e974c1..bba9f5cdd 100755 --- a/apps/worker/worker_app/tasks/edit_plan_generation.py +++ b/apps/worker/worker_app/tasks/edit_plan_generation.py @@ -265,8 +265,11 @@ def _render_with_unified( return {"status": "error", "message": f"渲染失败: {render_err}"} if not result.success: + full_error = result.error_message or "渲染失败" + if result.error_detail: + full_error = f"{full_error}\n--- stderr ---\n{result.error_detail}" logger.error("渲染失败(unified): %s — %s", plan_id, result.error_message) - _mark_plan_failed(plan_repo, plan_id, gen_task_repo, generation_task_id, result.error_message or "渲染失败") + _mark_plan_failed(plan_repo, plan_id, gen_task_repo, generation_task_id, full_error) return {"status": "error", "message": result.error_message or "渲染失败"} output_path = result.output_path or Path("") diff --git a/tests/unit/test_render_adapter.py b/tests/unit/test_render_adapter.py index eaadc64bc..e5f2ef79d 100755 --- a/tests/unit/test_render_adapter.py +++ b/tests/unit/test_render_adapter.py @@ -393,6 +393,40 @@ class TestRenderPlan: assert call_kwargs.kwargs["output_width"] == 1080 assert call_kwargs.kwargs["output_height"] == 1920 + @patch("video_processing.render_adapter.UnifiedRenderService") + @patch("video_processing.render_adapter.download_asset") + def test_ffmpeg_error_captures_stderr(self, mock_download, mock_render_cls, tmp_path): + """ffmpeg CalledProcessError 时 stderr 写入 error_detail 和 error_message。""" + import subprocess + + def _fake_download(storage_key, local_path): + local_path.parent.mkdir(parents=True, exist_ok=True) + local_path.write_bytes(b"fake data") + return True + + mock_download.side_effect = _fake_download + + mock_render = MagicMock() + mock_render.render.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=["ffmpeg", "-i", "input.mp4", "output.mp4"], + stderr="Invalid data found when processing input\nLast message repeated 3 times", + ) + mock_render_cls.return_value = mock_render + + plan = FakePlan(id="plan_001", status="editing") + clips = [_make_clip("c1", order=0, duration=5.0)] + asset_url_map = {"asset_c1.mp4": "https://test-bucket.oss.com/assets/asset_c1.mp4"} + adapter, _, _ = _make_adapter(plan=plan, clips=clips, asset_url_map=asset_url_map) + + result = adapter.render_plan("plan_001", job_id="job_001", work_dir=tmp_path / "work") + + assert not result.success + assert "FFmpeg渲染失败" in result.error_message + assert "exit=1" in result.error_message + assert "Invalid data found" in result.error_detail + assert "Invalid data found" in result.error_message + @patch("video_processing.render_adapter.download_asset") def test_progress_callback(self, mock_download, tmp_path): """进度回调被正确触发。"""