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 日志,方便管理员排查,不再要求用户把英文错误码发给开发者翻译。
+
+