Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb1fc1cff2 | |||
| bee52f04b7 |
@@ -141,6 +141,19 @@ class EditPlanService:
|
||||
logger.info("创建剪辑计划: id=%s name=%s", created.id, created.name)
|
||||
return created
|
||||
|
||||
def _auto_resume_editing(self, plan_id: str) -> None:
|
||||
"""如果计划处于 completed/failed 状态,自动切回 editing(编辑操作前置)"""
|
||||
plan = self._plan_repo.get(plan_id)
|
||||
if plan is None:
|
||||
return
|
||||
if plan.status in (EditPlanStatus.COMPLETED, EditPlanStatus.FAILED):
|
||||
try:
|
||||
plan.resume_editing()
|
||||
self._plan_repo.update(plan)
|
||||
logger.info("自动重新编辑: plan_id=%s", plan_id)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def update_plan(
|
||||
self,
|
||||
plan_id: str,
|
||||
@@ -156,6 +169,10 @@ class EditPlanService:
|
||||
"""
|
||||
existing = self.get_plan_or_raise(plan_id)
|
||||
|
||||
# 自动从 completed/failed 切回 editing
|
||||
self._auto_resume_editing(plan_id)
|
||||
existing = self.get_plan_or_raise(plan_id)
|
||||
|
||||
updated = EditPlan(
|
||||
id=existing.id,
|
||||
template_id=existing.template_id,
|
||||
@@ -212,8 +229,24 @@ class EditPlanService:
|
||||
return plan
|
||||
|
||||
# 根据目标状态调用对应的状态机方法
|
||||
# EDITING 支持从 draft / completed / failed 进入
|
||||
if target_status == EditPlanStatus.EDITING:
|
||||
if plan.status == EditPlanStatus.DRAFT:
|
||||
plan.start_editing()
|
||||
elif plan.status in (EditPlanStatus.COMPLETED, EditPlanStatus.FAILED):
|
||||
plan.resume_editing()
|
||||
else:
|
||||
raise ValueError(f"无法从 {plan.status} 切换到 {target_status}")
|
||||
result = self._plan_repo.update(plan)
|
||||
logger.info(
|
||||
"状态流转: plan_id=%s %s → %s",
|
||||
plan_id,
|
||||
plan.status,
|
||||
target_status,
|
||||
)
|
||||
return result
|
||||
|
||||
transition_map = {
|
||||
EditPlanStatus.EDITING: plan.start_editing,
|
||||
EditPlanStatus.RENDERING: plan.start_rendering,
|
||||
EditPlanStatus.COMPLETED: plan.mark_completed,
|
||||
EditPlanStatus.FAILED: plan.mark_failed,
|
||||
@@ -292,6 +325,8 @@ class EditPlanService:
|
||||
"""
|
||||
# 确保计划存在
|
||||
self.get_plan_or_raise(plan_id)
|
||||
# 自动从 completed/failed 切回 editing
|
||||
self._auto_resume_editing(plan_id)
|
||||
|
||||
clip = EditPlanClip.create(
|
||||
plan_id=plan_id,
|
||||
@@ -339,6 +374,9 @@ class EditPlanService:
|
||||
"""
|
||||
existing = self.get_clip_or_raise(clip_id)
|
||||
|
||||
# 自动从 completed/failed 切回 editing
|
||||
self._auto_resume_editing(existing.plan_id)
|
||||
|
||||
# 速度边界钳制
|
||||
if playback_speed is not None:
|
||||
if playback_speed <= 0:
|
||||
@@ -381,6 +419,8 @@ class EditPlanService:
|
||||
ValueError: 片段不存在或 asset_id 为空
|
||||
"""
|
||||
clip = self.get_clip_or_raise(clip_id)
|
||||
# 自动从 completed/failed 切回 editing
|
||||
self._auto_resume_editing(clip.plan_id)
|
||||
clip.assign_asset(asset_id)
|
||||
result = self._clip_repo.update(clip)
|
||||
logger.info("分配素材: clip_id=%s asset_id=%s", clip_id, asset_id)
|
||||
@@ -511,6 +551,9 @@ class EditPlanService:
|
||||
更新后的计划
|
||||
"""
|
||||
plan = self.get_plan_or_raise(plan_id)
|
||||
# 自动从 completed/failed 切回 editing
|
||||
self._auto_resume_editing(plan_id)
|
||||
plan = self.get_plan_or_raise(plan_id)
|
||||
new_config = {**plan.config, **config_updates}
|
||||
|
||||
updated = EditPlan(
|
||||
|
||||
@@ -110,6 +110,13 @@ class EditPlan:
|
||||
self.status = EditPlanStatus.FAILED
|
||||
self.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
def resume_editing(self) -> None:
|
||||
"""重新进入编辑状态(完成/失败后重新编辑)"""
|
||||
if self.status not in (EditPlanStatus.COMPLETED, EditPlanStatus.FAILED):
|
||||
raise ValueError(f"只有 completed/failed 状态的计划可以重新编辑,当前状态: {self.status}")
|
||||
self.status = EditPlanStatus.EDITING
|
||||
self.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
def reset_to_draft(self) -> None:
|
||||
"""重置为草稿状态(仅从 failed 状态可重置)"""
|
||||
if self.status != EditPlanStatus.FAILED:
|
||||
|
||||
@@ -636,3 +636,94 @@ class TestGenerationWorkflow:
|
||||
p = svc.create_plan("tpl-001", "测试", config={"key1": "val1"})
|
||||
updated = svc.update_plan_config(p.id, {"key1": "new_val"})
|
||||
assert updated.config["key1"] == "new_val"
|
||||
|
||||
|
||||
# ── 重新编辑 & 再生成 ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestResumeEditingAndRegenerate:
|
||||
"""完成/失败后重新编辑 → 再生成的状态流转测试"""
|
||||
|
||||
def test_update_plan_from_completed_returns_to_editing(self):
|
||||
"""更新计划配置:completed → 自动切回 editing"""
|
||||
svc = _make_service()
|
||||
p = svc.create_plan("tpl-001", "测试")
|
||||
svc.transition_status(p.id, EditPlanStatus.EDITING)
|
||||
svc.transition_status(p.id, EditPlanStatus.RENDERING)
|
||||
svc.transition_status(p.id, EditPlanStatus.COMPLETED)
|
||||
|
||||
updated = svc.update_plan(p.id, name="新名字")
|
||||
assert updated.status == EditPlanStatus.EDITING
|
||||
assert updated.name == "新名字"
|
||||
|
||||
def test_update_plan_config_from_completed_returns_to_editing(self):
|
||||
"""update_plan_config: completed → 自动切回 editing"""
|
||||
svc = _make_service()
|
||||
p = svc.create_plan("tpl-001", "测试")
|
||||
svc.transition_status(p.id, EditPlanStatus.EDITING)
|
||||
svc.transition_status(p.id, EditPlanStatus.RENDERING)
|
||||
svc.transition_status(p.id, EditPlanStatus.COMPLETED)
|
||||
|
||||
updated = svc.update_plan_config(p.id, {"foo": "bar"})
|
||||
assert updated.status == EditPlanStatus.EDITING
|
||||
|
||||
def test_create_clip_from_completed_returns_to_editing(self):
|
||||
"""创建片段:completed → 自动切回 editing"""
|
||||
svc = _make_service()
|
||||
p = svc.create_plan("tpl-001", "测试")
|
||||
svc.transition_status(p.id, EditPlanStatus.EDITING)
|
||||
svc.transition_status(p.id, EditPlanStatus.RENDERING)
|
||||
svc.transition_status(p.id, EditPlanStatus.COMPLETED)
|
||||
|
||||
svc.create_clip(p.id, "main", 0)
|
||||
plan_after = svc.get_plan(p.id)
|
||||
assert plan_after.status == EditPlanStatus.EDITING
|
||||
|
||||
def test_assign_asset_from_failed_returns_to_editing(self):
|
||||
"""分配素材:failed → 自动切回 editing"""
|
||||
svc = _make_service()
|
||||
p = svc.create_plan("tpl-001", "测试")
|
||||
clip = svc.create_clip(p.id, "main", 0)
|
||||
svc.transition_status(p.id, EditPlanStatus.EDITING)
|
||||
svc.transition_status(p.id, EditPlanStatus.RENDERING)
|
||||
svc.transition_status(p.id, EditPlanStatus.FAILED)
|
||||
|
||||
svc.assign_asset(clip.id, "asset-001")
|
||||
plan_after = svc.get_plan(p.id)
|
||||
assert plan_after.status == EditPlanStatus.EDITING
|
||||
|
||||
def test_completed_can_regenerate_after_edit(self):
|
||||
"""完成后编辑 → can_generate 返回 True,可再生成"""
|
||||
svc = _make_service()
|
||||
p = svc.create_plan("tpl-001", "测试")
|
||||
svc.create_clip(p.id, "main", 0)
|
||||
svc.transition_status(p.id, EditPlanStatus.EDITING)
|
||||
svc.transition_status(p.id, EditPlanStatus.RENDERING)
|
||||
svc.transition_status(p.id, EditPlanStatus.COMPLETED)
|
||||
|
||||
# 完成后不能直接生成
|
||||
can, reason = svc.can_generate(p.id)
|
||||
assert not can
|
||||
assert "编辑" in reason
|
||||
|
||||
# 编辑后自动切回 editing,可以生成
|
||||
svc.update_plan_config(p.id, {"edited": True})
|
||||
can, reason = svc.can_generate(p.id)
|
||||
assert can, f"期望可生成,实际: {reason}"
|
||||
|
||||
def test_transition_completed_to_editing_via_service(self):
|
||||
"""通过 transition_status 从 completed 切到 editing"""
|
||||
svc = _make_service()
|
||||
p = svc.create_plan("tpl-001", "测试")
|
||||
svc.transition_status(p.id, EditPlanStatus.EDITING)
|
||||
svc.transition_status(p.id, EditPlanStatus.RENDERING)
|
||||
svc.transition_status(p.id, EditPlanStatus.COMPLETED)
|
||||
|
||||
result = svc.transition_status(p.id, EditPlanStatus.EDITING)
|
||||
assert result.status == EditPlanStatus.EDITING
|
||||
|
||||
def test_resume_editing_from_draft_raises(self):
|
||||
"""从 draft 直接 resume_editing 应该报错"""
|
||||
p = EditPlan.create("tpl-001", "测试")
|
||||
with pytest.raises(ValueError):
|
||||
p.resume_editing()
|
||||
|
||||
Reference in New Issue
Block a user