From 25099fc4f9bd6a8d306b6f70ffb930d49da9a960 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 18 Jul 2026 16:06:39 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(P0):=20unified=E6=A8=AA=E5=B1=8Fpad?= =?UTF-8?q?=E5=8F=96=E6=95=B4+=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA+legacy=E5=AF=BC=E5=85=A5+=E7=B4=A0=E6=9D=90c?= =?UTF-8?q?reated=5Fat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4项修复: 1. unified横屏normalize: pad坐标(ow-iw)/2可能为小数导致ffmpeg报错,加trunc取整 2. unified路径ffmpeg错误日志: stderr取最后2000字符而非前3000,真正的报错在末尾 3. legacy导入路径: apps/api/app/缺少__init__.py,导致ModuleNotFoundError 4. 素材列表接口: AssetResponse加created_at字段返回给前端排序用 --- apps/api/app/__init__.py | 0 apps/api/app/api/routes/assets.py | 1 + apps/api/app/schemas/asset.py | 2 ++ apps/worker/video_processing/render_adapter.py | 4 ++-- .../video_processing/unified_render_service.py | 16 +++++++++++----- tests/unit/test_unified_render_service.py | 2 +- 6 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 apps/api/app/__init__.py mode change 100755 => 100644 apps/api/app/api/routes/assets.py mode change 100755 => 100644 apps/api/app/schemas/asset.py diff --git a/apps/api/app/__init__.py b/apps/api/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py old mode 100755 new mode 100644 index 7e2f9b1a0..7d90cf9c7 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -71,6 +71,7 @@ def _to_asset_response(item, storage_service=None) -> AssetResponse: status=item.status.value, classification_status=item.classification_status.value, quality_score=item.quality_score, + created_at=item.created_at.isoformat() if item.created_at else None, uploaded_by_user_id=item.uploaded_by_user_id, tag_ids=getattr(item, "tag_ids", []), ) diff --git a/apps/api/app/schemas/asset.py b/apps/api/app/schemas/asset.py old mode 100755 new mode 100644 index afd5f74be..e3f6744b7 --- a/apps/api/app/schemas/asset.py +++ b/apps/api/app/schemas/asset.py @@ -18,6 +18,7 @@ class CreateAssetRequest(BaseModel): status: str = Field(default="uploading") classification_status: str = Field(default="pending") quality_score: float | None = Field(default=None, ge=0, le=100) + created_at: str uploaded_by_user_id: str = Field(default="", max_length=100) @@ -50,6 +51,7 @@ class AssetResponse(BaseModel): status: str classification_status: str quality_score: float | None = None + created_at: str uploaded_by_user_id: str tag_ids: list[str] = Field(default_factory=list) diff --git a/apps/worker/video_processing/render_adapter.py b/apps/worker/video_processing/render_adapter.py index 7c6955a30..59938a596 100755 --- a/apps/worker/video_processing/render_adapter.py +++ b/apps/worker/video_processing/render_adapter.py @@ -258,12 +258,12 @@ class RenderAdapter: plan_id, job_id, exc.returncode, - stderr_text[:3000], + stderr_text[-2000:] if len(stderr_text) > 2000 else stderr_text, ) return RenderAdapterResult( success=False, error_message=f"FFmpeg渲染失败(exit={exc.returncode}): {stderr_text[:200]}", - error_detail=stderr_text[:3000], + error_detail=stderr_text[-2000:] if len(stderr_text) > 2000 else stderr_text, ) except Exception as exc: logger.exception( diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py index 3e97eeaa3..f97b1aef4 100755 --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -1008,7 +1008,7 @@ class UnifiedRenderService: else: # main / broll: 等比缩放 + 居中留黑边(保持原始比例,不裁剪内容) filters.append(f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=decrease") - filters.append(f"pad={self.output_width}:{self.output_height}:(ow-iw)/2:(oh-ih)/2:black") + filters.append(f"pad={self.output_width}:{self.output_height}:trunc((ow-iw)/2):trunc((oh-ih)/2):black") # 调色滤镜 color_grade = ColorGradeConfig.from_dict(clip.config.get("color_grade")) @@ -1112,12 +1112,15 @@ class UnifiedRenderService: try: run_ffmpeg(command) except subprocess.CalledProcessError as e: + stderr_text = (e.stderr or "").strip() + stderr_tail = stderr_text[-1500:] if len(stderr_text) > 1500 else stderr_text logger.error( - "直通渲染失败: plan_id=%s clip=%s exit_code=%d\nvf=%s", + "直通渲染失败: plan_id=%s clip=%s exit_code=%d\nvf=%s\nstderr(last 1500):\n%s", self.plan.id, clip.clip_id, e.returncode, vf_str[:2000], + stderr_tail, ) raise @@ -1342,7 +1345,7 @@ class UnifiedRenderService: filters.append( f"scale={self.output_width}:{self.output_height}" ":force_original_aspect_ratio=decrease" ) - filters.append(f"pad={self.output_width}:{self.output_height}:(ow-iw)/2:(oh-ih)/2:black") + filters.append(f"pad={self.output_width}:{self.output_height}:trunc((ow-iw)/2):trunc((oh-ih)/2):black") # 调色滤镜(每个 clip 独立的 color grade 配置) color_grade = ColorGradeConfig.from_dict(clip.config.get("color_grade")) @@ -1576,12 +1579,15 @@ class UnifiedRenderService: try: run_ffmpeg(command) except subprocess.CalledProcessError as e: - # 额外记录 filter_complex,方便排查滤镜链构建问题 + # 额外记录 filter_complex + stderr,方便排查滤镜链构建问题 + stderr_text = (e.stderr or "").strip() + stderr_tail = stderr_text[-1500:] if len(stderr_text) > 1500 else stderr_text logger.error( - "渲染失败: plan_id=%s exit_code=%d\nfilter_complex:\n%s", + "渲染失败: plan_id=%s exit_code=%d\nfilter_complex:\n%s\nstderr(last 1500):\n%s", self.plan.id, e.returncode, filter_complex[:5000], + stderr_tail, ) raise diff --git a/tests/unit/test_unified_render_service.py b/tests/unit/test_unified_render_service.py index a9f025f11..837ff2d0f 100755 --- a/tests/unit/test_unified_render_service.py +++ b/tests/unit/test_unified_render_service.py @@ -1801,7 +1801,7 @@ class TestConcatNormalizeVideoResolution: # 必须用 pad(留黑边),不能用 crop(裁剪) assert "force_original_aspect_ratio=decrease" in fc, "main 层应使用 decrease 模式(等比缩放到画布内)" assert "pad=" in fc, "main 层应有 pad 滤镜(留黑边到目标分辨率)" - assert "(ow-iw)/2:(oh-ih)/2:black" in fc, "pad 应该居中+黑边" + assert "trunc((ow-iw)/2):trunc((oh-ih)/2):black" in fc, "pad 应该居中取整+黑边" # 注意:background 层也用 crop,但 main/broll 应该用 pad # 检查第一个 [0:v] 处理链(第一个 clip 是 main) first_v_chain = fc.split("[v0]")[0] -- 2.54.0 From d3f39622d45048da3e494ecdeb42fc376178a659 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 18 Jul 2026 16:49:56 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20CreateAssetRequest=E8=AF=AF=E5=8A=A0?= =?UTF-8?q?created=5Fat=E5=BF=85=E5=A1=AB=E5=AD=97=E6=AE=B5=E5=AF=BC?= =?UTF-8?q?=E8=87=B4422?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/app/schemas/asset.py | 1 - 1 file changed, 1 deletion(-) mode change 100644 => 100755 apps/api/app/schemas/asset.py diff --git a/apps/api/app/schemas/asset.py b/apps/api/app/schemas/asset.py old mode 100644 new mode 100755 index e3f6744b7..dd6d15c86 --- a/apps/api/app/schemas/asset.py +++ b/apps/api/app/schemas/asset.py @@ -18,7 +18,6 @@ class CreateAssetRequest(BaseModel): status: str = Field(default="uploading") classification_status: str = Field(default="pending") quality_score: float | None = Field(default=None, ge=0, le=100) - created_at: str uploaded_by_user_id: str = Field(default="", max_length=100) -- 2.54.0