From 579e538aa776a9483432c45c8a81644a1ba5f261 Mon Sep 17 00:00:00 2001 From: kang Date: Tue, 26 May 2026 09:41:03 +0800 Subject: [PATCH] fix: explain video generation failures --- api/main.py | 28 +++++++++++++++++++++------- docs/source-analysis.html | 12 ++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/api/main.py b/api/main.py index 1cf8d0f..db1c011 100644 --- a/api/main.py +++ b/api/main.py @@ -8269,27 +8269,29 @@ def render_storyboard_video( for create_path in VIDEO_CREATE_PATHS: resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, source_ref, prepared_last_img, prepared_product_imgs, primary_role) if video_uses_ark() and source_ref and resp.status_code in {400, 422}: - create_errors.append(f"{video_path(create_path)} + reference_video -> HTTP {resp.status_code}: {resp.text[:160]}") + create_errors.append(f"{video_path(create_path)} + reference_video -> HTTP {resp.status_code}: {resp.text[:700]}") resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, None, prepared_last_img, prepared_product_imgs, primary_role) if video_uses_ark() and prepared_last_img and resp.status_code in {400, 422}: - create_errors.append(f"{video_path(create_path)} + last_frame -> HTTP {resp.status_code}: {resp.text[:160]}") + create_errors.append(f"{video_path(create_path)} + last_frame -> HTTP {resp.status_code}: {resp.text[:700]}") resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, None, None, prepared_product_imgs, primary_role) if video_uses_ark() and prepared_product_imgs and resp.status_code in {400, 422}: - create_errors.append(f"{video_path(create_path)} + product_reference -> HTTP {resp.status_code}: {resp.text[:160]}") + create_errors.append(f"{video_path(create_path)} + product_reference -> HTTP {resp.status_code}: {resp.text[:700]}") resp = submit_video_create(client, f"{base}{video_path(create_path)}", headers, ref_img, payload, None, prepared_last_img, None, primary_role) if resp.status_code < 400: create = resp break - create_errors.append(f"{video_path(create_path)} -> HTTP {resp.status_code}: {resp.text[:160]}") + create_errors.append(f"{video_path(create_path)} -> HTTP {resp.status_code}: {resp.text[:700]}") if resp.status_code not in {400, 404, 405}: resp.raise_for_status() if create is None: - raise RuntimeError("视频模型已选择,但当前网关视频生成入口不可用;已尝试 " + " | ".join(create_errors)) + print(f"[video create failed] job={job_id} video={local_id} errors={' | '.join(create_errors)[:1800]}", flush=True) + raise RuntimeError(_video_create_failure_message(create_errors)) data = create.json() video_api_id = data.get("id") or provider_id or local_id status = normalize_video_status(data.get("status")) progress = video_progress(data, 5) direct_url = video_url_from_response(data) + status_payload = data update_generated_video( job_id, local_id, @@ -8308,6 +8310,7 @@ def render_storyboard_video( status = normalize_video_status(pdata.get("status")) progress = video_progress(pdata, progress) direct_url = video_url_from_response(pdata) or direct_url + status_payload = pdata update_generated_video( job_id, local_id, @@ -8317,7 +8320,17 @@ def render_storyboard_video( ) if status != "completed": - update_generated_video(job_id, local_id, status="failed", error=f"video status: {status}", progress=progress, queue_message="") + raw_error = "" + if isinstance(status_payload, dict): + raw_error = str( + status_payload.get("error") + or status_payload.get("message") + or status_payload.get("reason") + or status_payload.get("fail_reason") + or status_payload + ) + print(f"[video status failed] job={job_id} video={local_id} status={status} error={raw_error[:1200]}", flush=True) + update_generated_video(job_id, local_id, status="failed", error=_video_public_error(raw_error or f"video status: {status}"), progress=progress, queue_message="") return download_generated_video(client, base, headers, video_api_id, direct_url, out_mp4) @@ -8333,7 +8346,8 @@ def render_storyboard_video( queue_message="", ) except Exception as e: - update_generated_video(job_id, local_id, status="failed", error=str(e)[:500], queue_message="") + print(f"[video task failed] job={job_id} video={local_id} error={str(e)[:1200]}", flush=True) + update_generated_video(job_id, local_id, status="failed", error=_video_public_error(e), queue_message="") @app.post("/jobs/{job_id}/frames/{idx}/storyboard/quick-plan", response_model=StoryboardScene) diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 945c697..2f22507 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -1241,6 +1241,18 @@ ProductRefStateItem {

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-26 · 视频生成失败改为员工可读提示

+ API + UX +
+
+

问题:Seedance / Doubao 视频上游返回 InputImageSensitiveContentDetected.PrivacyInformation、HTTP 400、429、timeout 等机器错误时,画布错误框原样展示会让员工误以为账号、模型或网关坏了,需要人工解释。

+

改动:api/main.py 新增视频错误归一化逻辑,提交失败、轮询失败和后台任务异常都会先转换成可读中文,再写入 GeneratedVideo.error。例如含疑似真实人脸的参考图会提示“参考图里有清晰人物或疑似真实人脸,视频模型出于肖像/隐私风控拒绝生成”,并给出换无脸首帧、裁掉或模糊人物脸的下一步。

+

影响:前端现有视频失败卡、画布轮询错误框和详情里的 video.error 会自动显示中文解释;原始上游错误只写入 API 日志,方便管理员排查,不再要求用户把英文错误码发给开发者翻译。

+
+

2026-05-26 · 生产登录改为仅飞书