fix: 生成时报错「没有可渲染的就绪片段」— mark_clips_ready 仅标记有素材的片段 (#1270) #1276
@@ -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 "请先选择一个克隆音色"
|
||||
|
||||
@@ -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 是 ready,clip2 仍是 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)
|
||||
|
||||
Reference in New Issue
Block a user