feat(worker): ffmpeg渲染失败时捕获stderr写入error_detail,增强排障能力
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Failing after 32s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 22s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 17m53s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 19m11s

- 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测试全绿
This commit is contained in:
CI Bot
2026-07-17 17:07:14 +08:00
parent e0bd51027d
commit 9b99c46d7d
3 changed files with 54 additions and 1 deletions
@@ -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",
@@ -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("")
+34
View File
@@ -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):
"""进度回调被正确触发。"""