diff --git a/apps/api/app/schemas/generation_task.py b/apps/api/app/schemas/generation_task.py index 9b95a57ea..8c0148293 100755 --- a/apps/api/app/schemas/generation_task.py +++ b/apps/api/app/schemas/generation_task.py @@ -7,8 +7,8 @@ from pydantic import BaseModel, Field, field_validator, model_validator class ConfirmGenerationRequest(BaseModel): """确认生成请求体 — 基于预览任务创建正式生成任务""" - output_width: int = Field(default=1080, description="输出视频宽度") - output_height: int = Field(default=1920, description="输出视频高度") + output_width: int = Field(default=1080, ge=100, description="输出视频宽度") + output_height: int = Field(default=1920, ge=100, description="输出视频高度") cover_url: str = Field(default="", description="自定义封面图片 URL") custom_title: str = Field(default="", description="自定义视频标题") diff --git a/apps/web/src/pages/generate/hooks/useGenerateVideo.ts b/apps/web/src/pages/generate/hooks/useGenerateVideo.ts index b3a15fd26..9363f81a9 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateVideo.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateVideo.ts @@ -56,10 +56,41 @@ export function useGenerateVideo(props: UseGenerateVideoProps) { try { // 使用确认生成 API(基于预览任务) - // 解析分辨率 - const [widthStr, heightStr] = (props.videoRatio || "1080x1920").split("x") - const outputWidth = parseInt(widthStr, 10) || 1080 - const outputHeight = parseInt(heightStr, 10) || 1920 + // 解析分辨率:videoRatio 可能是 "9:16"(宽高比)或 "1080x1920"(分辨率) + const ratio = props.videoRatio || "9:16" + let outputWidth: number + let outputHeight: number + + if (ratio.includes(":")) { + // 宽高比格式,如 "9:16" → 基于基准高度 1920 计算 + const [rw, rh] = ratio.split(":").map(Number) + if (rw > 0 && rh > 0) { + // 基准:长边 1920,短边按比例计算 + const [longSide, shortSide] = rw < rh ? [rh, rw] : [rw, rh] + const baseLong = 1920 + const baseShort = Math.round((baseLong * shortSide) / longSide) + // 确保偶数(FFmpeg 要求) + const evenShort = baseShort - (baseShort % 2) + if (rw < rh) { + outputWidth = evenShort + outputHeight = baseLong + } else { + outputWidth = baseLong + outputHeight = evenShort + } + } else { + outputWidth = 1080 + outputHeight = 1920 + } + } else if (ratio.includes("x")) { + // 分辨率格式,如 "1080x1920" + const [wStr, hStr] = ratio.split("x") + outputWidth = parseInt(wStr, 10) || 1080 + outputHeight = parseInt(hStr, 10) || 1920 + } else { + outputWidth = 1080 + outputHeight = 1920 + } await confirmGeneration(props.previewTaskId, { output_width: outputWidth, diff --git a/apps/worker/video_processing/render_adapter.py b/apps/worker/video_processing/render_adapter.py index 8bf624ae8..49930092e 100755 --- a/apps/worker/video_processing/render_adapter.py +++ b/apps/worker/video_processing/render_adapter.py @@ -47,7 +47,14 @@ def _parse_resolution(resolution_str: str | None) -> tuple[int, int]: w, h = resolution_str.lower().split("x", 1) width = int(w.strip()) height = int(h.strip()) - if width <= 0 or height <= 0: + # 最小 100px 防护:避免前端传入宽高比(如 "9:16")被 parseInt 截断为极小值 + if width < 100 or height < 100: + logger.warning( + "分辨率异常小 (%dx%d),使用默认值。原始值: %s", + width, + height, + resolution_str, + ) return DEFAULT_OUTPUT_WIDTH, DEFAULT_OUTPUT_HEIGHT return width, height except (ValueError, TypeError): diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index f641e66c6..fb0079eb2 100644 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -1501,6 +1501,16 @@ def generate_video(self, task_id: str) -> dict: # 动态分辨率:优先使用 output_width/output_height,其次 resolution 字符串 _ow = task_info.get("output_width", OUTPUT_WIDTH) or OUTPUT_WIDTH _oh = task_info.get("output_height", OUTPUT_HEIGHT) or OUTPUT_HEIGHT + # 防护:前端可能误传宽高比(如 parseInt("9:16") = 9),宽度 < 100 时忽略 + if _ow < 100 or _oh < 100: + logger.warning( + "[task_id=%s] output_width/output_height 异常 (%dx%d),回退到默认", + task_id, + _ow, + _oh, + ) + _ow = OUTPUT_WIDTH + _oh = OUTPUT_HEIGHT if _ow != OUTPUT_WIDTH or _oh != OUTPUT_HEIGHT: _resolved_resolution = f"{_ow}x{_oh}" else: