feat(storyboard): 故事板改分镜制(单场独立重跑/历史)+ 视频过审闸 + 审核前端如实化

三块改动在 services/views/pipeline/App/api 等文件按行交织、且过审闸后端依赖故事板新
模型,无法拆成各自独立可编译的提交,故合并为一次:

· 故事板分镜制:新增 StoryboardShot/StoryboardShotVersion(对标 VideoSegment/Version)
  + 数据迁移(采用版每帧转一 shot+已采用版本)。每镜一个 shot,各自历史版本,可单场
  重跑、逐场切换/采用,新增「开始生成故事板/单场重跑/采用某版」端点。重跑非阻塞(后台
  静默轮询出图,不锁其它场按钮);历史「查看」与「采用」分离(切版本不丢在制重跑)。
  视频取分镜图/删分镜清理/过审闸全部改读 storyboard_shots。
· 视频生成前过审闸:点生成校验含真人脸的人物立绘/故事板分镜是否过审,未过审弹窗列出
  哪一镜/哪个,可一键送审,挡住不生成(collect_video_review_blockers + precheck 端点)。
· 审核前端如实化:不再 `|| "processing"` 伪造审核中;送审失败经 onNotify 如实提示;
  审核轮询扩到视频阶段,送审后能看着盾变绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-24 17:11:55 +08:00
co-authored by Claude Opus 4.8
parent d6cb280018
commit 658aaa222f
12 changed files with 936 additions and 243 deletions
+48
View File
@@ -151,6 +151,54 @@ class StoryboardFrame(TimeStampedModel):
ordering = ["sort_order", "created_at"]
# ── 故事板「分镜制」(对标视频 VideoSegment/VideoSegmentVersion)──
# 一镜 = 一个 StoryboardShot(有自己的采用版 + 状态);每次出图 = 一条 StoryboardShotVersion(历史版本)。
# 取代旧的「整版制」StoryboardVersion/StoryboardFrame:单场可独立重跑、单场各有历史、可逐场切换采用。
class StoryboardShot(TimeStampedModel):
class Status(models.TextChoices):
NOT_STARTED = "not_started", "Not Started"
QUEUED = "queued", "Queued"
RUNNING = "running", "Running"
SUCCEEDED = "succeeded", "Succeeded"
FAILED = "failed", "Failed"
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="storyboard_shots")
script_segment = models.ForeignKey(
ScriptSegment,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="storyboard_shots",
)
sort_order = models.PositiveIntegerField(default=0)
status = models.CharField(max_length=32, choices=Status.choices, default=Status.NOT_STARTED)
prompt = models.TextField(blank=True)
adopted_version = models.ForeignKey(
"projects.StoryboardShotVersion",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="adopted_by_shots",
)
error_message = models.TextField(blank=True)
class Meta:
unique_together = [("project", "sort_order")]
ordering = ["sort_order", "created_at"]
class StoryboardShotVersion(TimeStampedModel):
shot = models.ForeignKey(StoryboardShot, on_delete=models.CASCADE, related_name="versions")
task = models.ForeignKey("ai.AITask", on_delete=models.SET_NULL, null=True, blank=True, related_name="storyboard_shot_versions")
asset = models.ForeignKey("assets.Asset", on_delete=models.PROTECT, related_name="storyboard_shot_versions")
prompt = models.TextField(blank=True)
is_adopted = models.BooleanField(default=False)
metadata = models.JSONField(default=dict, blank=True)
class Meta:
ordering = ["-created_at"]
class VideoSegment(TimeStampedModel):
class Status(models.TextChoices):
NOT_STARTED = "not_started", "Not Started"