fix(video): 视频片段数对齐采用版分镜数,修「删分镜后视频步骤仍显示旧数量」
项目创建固定铺 4 段视频片段,而 _sync_video_segments_to_script 仅在 增删分镜且该脚本 is_adopted 时才收口。用户通常「采用前」就增删分镜, 那些编辑被 is_adopted 闸跳过;adopt_script 自身又不收口 → 视频步骤 一直停在 4 段,与故事板/采用版分镜对不上。 - adopt_script:采用时按这版分镜数收口视频片段 - poll/skip-storyboard:进入视频阶段前再收口一次(安全网) - retrieve:视频阶段详情加载时自愈历史脏项目(只裁从未生成过的尾段) - 已生成/生成中的段绝不动;新增 3 个回归测试 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -402,6 +402,74 @@ class ProjectApiTests(TestCase):
|
||||
{"cast": [], "scenes": [], "cast_prompts": {}, "scene_prompts": {}},
|
||||
)
|
||||
|
||||
def test_adopt_script_syncs_video_segments_to_shot_count(self):
|
||||
"""采用脚本时把视频片段数对齐到分镜数:用户在「采用前」删掉一镜(4→3),
|
||||
采用后视频步骤的片段数必须跟着变 3,而不是停在项目创建固定铺的 4 段。"""
|
||||
# 项目创建 → 固定 4 段视频片段
|
||||
project = Project.objects.create(team=self.team, created_by=self.user, product=self.product, name="P")
|
||||
for i in range(4):
|
||||
VideoSegment.objects.create(project=project, sort_order=i, target_duration_seconds=15)
|
||||
# 草稿脚本只剩 3 镜(模拟用户建了 4 镜又删 1,且尚未采用)
|
||||
script = ScriptVersion.objects.create(project=project, is_adopted=False)
|
||||
for i in range(3):
|
||||
ScriptSegment.objects.create(script_version=script, sort_order=i, narration=f"n{i}", visual_prompt=f"v{i}")
|
||||
self.assertEqual(project.video_segments.count(), 4)
|
||||
|
||||
response = self.client.post(
|
||||
f"/api/projects/{project.id}/adopt-script/",
|
||||
{"script_version_id": str(script.id)},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# 采用后:多出的「从未生成过」尾段被裁掉,片段数 = 分镜数
|
||||
self.assertEqual(project.video_segments.count(), 3)
|
||||
|
||||
def test_retrieve_self_heals_stale_video_segment_count(self):
|
||||
"""历史脏项目(已在视频阶段、片段数比采用版分镜多)在详情加载时自愈到分镜数。"""
|
||||
project = Project.objects.create(
|
||||
team=self.team, created_by=self.user, product=self.product, name="P",
|
||||
current_stage=ProjectStage.Stage.VIDEO,
|
||||
)
|
||||
for i in range(4):
|
||||
VideoSegment.objects.create(project=project, sort_order=i, target_duration_seconds=15)
|
||||
script = ScriptVersion.objects.create(project=project, is_adopted=True)
|
||||
for i in range(3):
|
||||
ScriptSegment.objects.create(script_version=script, sort_order=i, narration=f"n{i}", visual_prompt=f"v{i}")
|
||||
|
||||
response = self.client.get(f"/api/projects/{project.id}/")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(project.video_segments.count(), 3)
|
||||
self.assertEqual(len(response.data["video_segments"]), 3)
|
||||
|
||||
def test_adopt_script_keeps_generated_segment_even_if_excess(self):
|
||||
"""收口绝不动已生成的段:即便采用版分镜少于已出片的片段数,已采用版本的尾段保留。"""
|
||||
project = Project.objects.create(team=self.team, created_by=self.user, product=self.product, name="P")
|
||||
segs = [VideoSegment.objects.create(project=project, sort_order=i, target_duration_seconds=15) for i in range(2)]
|
||||
# 第 2 段(sort_order=1)已出片(有 adopted_version)→ 不可裁
|
||||
asset = Asset.objects.create(
|
||||
team=self.team, created_by=self.user, name="clip",
|
||||
asset_type=Asset.Type.VIDEO, source=Asset.Source.AI_GENERATED, category=Asset.Category.VIDEO_CLIP,
|
||||
)
|
||||
ver = VideoSegmentVersion.objects.create(video_segment=segs[1], asset=asset, is_adopted=True)
|
||||
segs[1].adopted_version = ver
|
||||
segs[1].status = VideoSegment.Status.SUCCEEDED
|
||||
segs[1].save(update_fields=["adopted_version", "status"])
|
||||
# 采用版只有 1 镜
|
||||
script = ScriptVersion.objects.create(project=project, is_adopted=False)
|
||||
ScriptSegment.objects.create(script_version=script, sort_order=0, narration="n", visual_prompt="v")
|
||||
|
||||
response = self.client.post(
|
||||
f"/api/projects/{project.id}/adopt-script/",
|
||||
{"script_version_id": str(script.id)},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# 已出片的尾段保留 → 不裁到 1,停在 2
|
||||
self.assertEqual(project.video_segments.count(), 2)
|
||||
|
||||
def test_adopt_video_version_remaps_timeline_draft(self):
|
||||
"""切换采用版本后,时间线草稿里引用本场旧版本资产的片段必须跟随换成新资产(剪辑台/导出都读草稿)。"""
|
||||
project = Project.objects.create(team=self.team, created_by=self.user, product=self.product, name="P")
|
||||
|
||||
Reference in New Issue
Block a user