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:
zyc
2026-06-24 14:33:23 +08:00
co-authored by Claude Opus 4.8
parent 191cd1c3e5
commit d2aa26248d
2 changed files with 96 additions and 0 deletions
+28
View File
@@ -140,6 +140,22 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
# 列表用轻量序列化(只给列表/仪表盘/侧栏要的字段);详情/写操作用全量 ProjectSerializer
return ProjectListSerializer if self.action == "list" else ProjectSerializer
def retrieve(self, request, *args, **kwargs):
# 详情加载(=进入流水线页)时自愈视频片段数:历史项目在「采用前增删分镜」未同步,
# 或项目创建固定铺的 4 段从未被收口,会让视频步骤的片段数与故事板/采用版分镜对不上。
# 按采用版分镜数收口一次(幂等;只裁从未生成过的尾段,生成中/已出片的段绝不动)。
# 用轻量查询判定 + 同步,再交给 super 做重 prefetch 序列化(避免重查询跑两遍)。
proj = (
Project.objects.filter(pk=kwargs.get("pk"), **{self.team_field: self.get_team()})
.only("id", "current_stage")
.first()
)
if proj is not None and proj.current_stage == ProjectStage.Stage.VIDEO:
adopted_script = proj.script_versions.filter(is_adopted=True).order_by("-created_at").first()
if adopted_script is not None:
self._sync_video_segments_to_script(proj, adopted_script)
return super().retrieve(request, *args, **kwargs)
def get_queryset(self):
# 列表:轻查询(只 select_related product + 计数注解),不做详情那串 12 个重 prefetch
# ——原列表把每个项目的 阶段/片段/故事板/时间线/资产文件全拉出,20 个项目实测 ~2s。
@@ -285,6 +301,10 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
ScriptVersion.objects.filter(project=project).update(is_adopted=False)
script.is_adopted = True
script.save(update_fields=["is_adopted", "updated_at"])
# 采用脚本时把视频片段数对齐到这版分镜数:用户常在「采用前」就增删分镜,
# 那些编辑因 _sync 的 is_adopted 闸而未同步到 VideoSegment(项目创建时固定铺了 4 段),
# 不在此收口的话视频步骤会一直停在 4 段,与故事板/分镜数对不上。已生成的段绝不动。
self._sync_video_segments_to_script(project, script)
stage, _ = ProjectStage.objects.get_or_create(project=project, stage=ProjectStage.Stage.SCRIPT)
stage.status = ProjectStage.Status.SUCCEEDED
stage.save(update_fields=["status", "updated_at"])
@@ -631,6 +651,10 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
stage, _ = ProjectStage.objects.get_or_create(project=project, stage=ProjectStage.Stage.STORYBOARD)
stage.status = ProjectStage.Status.SUCCEEDED
stage.save(update_fields=["status", "updated_at"])
# 进入视频阶段前再收口一次视频片段数,确保与采用版分镜数一致(已生成的段不动)
adopted_script = project.script_versions.filter(is_adopted=True).order_by("-created_at").first()
if adopted_script is not None:
self._sync_video_segments_to_script(project, adopted_script)
project.current_stage = ProjectStage.Stage.VIDEO
project.status = Project.Status.VIDEOING
project.save(update_fields=["current_stage", "status", "updated_at"])
@@ -644,6 +668,10 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
stage, _ = ProjectStage.objects.get_or_create(project=project, stage=ProjectStage.Stage.STORYBOARD)
stage.status = ProjectStage.Status.SKIPPED
stage.save(update_fields=["status", "updated_at"])
# 跳过故事板直接进视频时,同样把视频片段数对齐到采用版分镜数(已生成的段不动)
adopted_script = project.script_versions.filter(is_adopted=True).order_by("-created_at").first()
if adopted_script is not None:
self._sync_video_segments_to_script(project, adopted_script)
project.current_stage = ProjectStage.Stage.VIDEO
project.status = Project.Status.VIDEOING
project.save(update_fields=["current_stage", "status", "updated_at"])