fix: 生成时报错「没有可渲染的就绪片段」— mark_clips_ready 仅标记有素材的片段 (#1270) #1276

Merged
auto-approve-bot merged 4 commits from fix/issue-1270-no-renderable-clips into develop 2026-08-07 20:46:00 +08:00
3 changed files with 55 additions and 23 deletions
+19 -5
View File
@@ -585,10 +585,18 @@ class EditPlanService:
if not clips:
return False, "请先添加片段后再生成视频"
# 检查是否至少有一个片段分配了素材
if not any(c.asset_id for c in clips):
return False, "没有可渲染的就绪片段,请确保已选择素材"
return True, ""
def mark_clips_ready(self, plan_id: str) -> int:
"""所有 pending 状态的片段标记为 ready
"""已分配素材的 pending 片段标记为 ready
只标记同时满足以下条件的片段:
- status == PENDING
- asset_id 非空(已分配素材)
Returns:
int: 标记的片段数量
@@ -599,10 +607,16 @@ class EditPlanService:
)
count = 0
for clip in clips:
clip.mark_ready()
self._clip_repo.update(clip)
count += 1
logger.info("标记片段就绪: plan_id=%s count=%d", plan_id, count)
if clip.asset_id:
clip.mark_ready()
self._clip_repo.update(clip)
count += 1
logger.info(
"标记片段就绪: plan_id=%s marked=%d total_pending=%d",
plan_id,
count,
len(clips),
)
return count
def update_plan_config(self, plan_id: str, config_updates: Dict[str, Any]) -> EditPlan:
@@ -61,14 +61,23 @@ export const buildEditPlanPayload = (props: UseGenerateVideoProps) => {
* 返回错误信息,通过则返回 null
*/
export const validateGenerateInputs = (props: UseGenerateVideoProps): string | null => {
const { titleSettings, materialMode, selectedMaterials, voiceMode, selectedClonedVoice } = props
const {
titleSettings,
materialMode,
selectedMaterials,
smartSelectedIds,
voiceMode,
selectedClonedVoice,
} = props
// AI 自动选择模式下,标题可以为空(后端会自行生成)
if (!titleSettings.aiAutoSelect && !titleSettings.title?.trim()) {
return "请先选择或输入标题"
}
if (materialMode === "manual" && selectedMaterials.length === 0) {
return "请至少选择一个素材"
// 无论手动还是自动模式,都必须有素材
const materialIds = materialMode === "auto" ? smartSelectedIds || [] : selectedMaterials || []
if (materialIds.length === 0) {
return materialMode === "auto" ? "AI 未匹配到素材,请手动选择素材后重试" : "请至少选择一个素材"
}
if (voiceMode === "clone" && !selectedClonedVoice) {
return "请先选择一个克隆音色"
+24 -15
View File
@@ -501,11 +501,22 @@ class TestGenerationWorkflow:
svc = _make_service()
p = svc.create_plan("tpl-001", "测试")
svc.transition_status(p.id, EditPlanStatus.EDITING)
svc.create_clip(p.id, "intro", 0)
clip = svc.create_clip(p.id, "intro", 0)
svc.assign_asset(clip.id, "asset-001")
can, reason = svc.can_generate(p.id)
assert can is True
assert reason == ""
def test_can_generate_no_assets_fails(self):
"""片段存在但没有分配素材时,can_generate 应返回 False"""
svc = _make_service()
p = svc.create_plan("tpl-001", "测试")
svc.transition_status(p.id, EditPlanStatus.EDITING)
svc.create_clip(p.id, "intro", 0)
can, reason = svc.can_generate(p.id)
assert can is False
assert "没有可渲染" in reason or "素材" in reason
def test_can_generate_draft_fails(self):
svc = _make_service()
p = svc.create_plan("tpl-001", "测试")
@@ -523,23 +534,20 @@ class TestGenerationWorkflow:
assert "请先添加片段后再生成视频" in reason
def test_mark_clips_ready(self):
"""只有分配了素材的 pending 片段才会被标记为 ready"""
svc = _make_service()
p = svc.create_plan("tpl-001", "测试")
svc.create_clip(p.id, "intro", 0)
svc.create_clip(p.id, "main", 1)
clip1 = svc.create_clip(p.id, "intro", 0)
clip2 = svc.create_clip(p.id, "main", 1)
# 只给 clip1 分配素材
svc.assign_asset(clip1.id, "asset-001")
count = svc.mark_clips_ready(p.id)
assert count == 2
# 验证所有片段都是 ready 状态
assert count == 1 # 只有 clip1 被标记
# 验证 clip1 是 readyclip2 仍是 pending
clips = svc.list_clips(p.id)
for c in clips:
assert c.status == EditPlanClipStatus.READY
svc = _make_service()
p = svc.create_plan("tpl-001", "测试")
svc.create_clip(p.id, "intro", 0)
result = svc.get_generation_status(p.id)
assert result["plan"].id == p.id
assert len(result["clips"]) == 1
assert result["generation_task_id"] is None
clips_by_order = {c.order: c for c in clips}
assert clips_by_order[0].status == EditPlanClipStatus.READY
assert clips_by_order[1].status == EditPlanClipStatus.PENDING
def test_update_plan_config(self):
svc = _make_service()
@@ -613,7 +621,8 @@ class TestResumeEditingAndRegenerate:
"""完成后编辑 → can_generate 返回 True,可再生成"""
svc = _make_service()
p = svc.create_plan("tpl-001", "测试")
svc.create_clip(p.id, "main", 0)
clip = svc.create_clip(p.id, "main", 0)
svc.assign_asset(clip.id, "asset-001")
svc.transition_status(p.id, EditPlanStatus.EDITING)
svc.transition_status(p.id, EditPlanStatus.RENDERING)
svc.transition_status(p.id, EditPlanStatus.COMPLETED)