优化脚本

This commit is contained in:
Azmat@qq.com
2026-08-28 14:45:38 +08:00
parent 849b14a0f6
commit c0a0b9603b
37 changed files with 541 additions and 1893 deletions
+27 -80
View File
@@ -14,10 +14,6 @@ from apps.projects.models import (
ProjectStage,
ScriptSegment,
ScriptVersion,
StoryboardFrame,
StoryboardShot,
StoryboardShotVersion,
StoryboardVersion,
SubtitleTrack,
Timeline,
TimelineClip,
@@ -67,14 +63,13 @@ class ProjectApiTests(TestCase):
project = Project.objects.get(id=response.data["id"])
self.assertEqual(project.team, self.team)
self.assertEqual(project.created_by, self.user)
self.assertEqual(project.stages.count(), 5)
self.assertEqual(project.stages.count(), 4)
self.assertEqual(project.video_segments.count(), 4)
self.assertEqual(
list(project.stages.values_list("stage", flat=True)),
[
ProjectStage.Stage.SCRIPT,
ProjectStage.Stage.BASE_ASSETS,
ProjectStage.Stage.STORYBOARD,
ProjectStage.Stage.VIDEO,
ProjectStage.Stage.EXPORT,
],
@@ -574,76 +569,29 @@ class ProjectApiTests(TestCase):
capability=ModelConfig.Capability.IMAGE, endpoint="images/generations", unit_price="1.0000",
)
def _mk_shot(self, project, script, order, *, review_status="active"):
"""建一个已出片的 StoryboardShot(+ 已采用版本,资产审核态可控),供过审/采用测试。"""
def _mk_person(self, project, script, order, *, review_status="active"):
"""给某镜挂一个「已采用的角色立绘」(审核态可控),供过审测试。
故事板下线后,含真人脸的待审资产只剩人物立绘。"""
from apps.projects.models import BaseAssetGroup
seg = script.segments.filter(sort_order=order).first() or ScriptSegment.objects.create(
script_version=script, sort_order=order, narration=f"n{order}", visual_prompt=f"v{order}")
a = Asset.objects.create(team=self.team, created_by=self.user, name=f"sb{order}", asset_type="image",
source="ai_generated", category=Asset.Category.STORYBOARD, review_status=review_status)
shot = StoryboardShot.objects.create(project=project, script_segment=seg, sort_order=order,
status=StoryboardShot.Status.SUCCEEDED)
ver = StoryboardShotVersion.objects.create(shot=shot, asset=a, is_adopted=True)
shot.adopted_version = ver
shot.save(update_fields=["adopted_version"])
return shot, ver, a
seg.entity_refs = ["c1"]
seg.save(update_fields=["entity_refs"])
meta = dict(project.metadata or {})
meta["script_entities"] = [{"id": "c1", "type": "character", "name": "女主"}]
project.metadata = meta
project.save(update_fields=["metadata"])
asset = Asset.objects.create(team=self.team, created_by=self.user, name=f"person{order}", asset_type="image",
source="ai_generated", category=Asset.Category.PERSON, review_status=review_status)
AssetFile.objects.create(asset=asset, object_key=f"p{order}.png", bucket="b",
content_type="image/png", preview_url=f"http://x/p{order}.png", is_primary=True)
BaseAssetGroup.objects.create(project=project, kind=BaseAssetGroup.Kind.PERSON,
adopted_asset=asset, metadata={"label": "女主", "adopt": "adopted"})
return asset
def test_submit_storyboard_creates_one_shot_per_segment_all_queued(self):
"""开始生成故事板:确保每镜一个 shot、全部置 QUEUED(真正出图交给 poll)。"""
from apps.ai.services import submit_storyboard
self._mk_image_model()
project = Project.objects.create(team=self.team, created_by=self.user, product=self.product, name="P")
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}")
targets = submit_storyboard(project=project, user=self.user, prompt="风格")
self.assertEqual(project.storyboard_shots.count(), 3)
self.assertEqual(len(targets), 3)
self.assertTrue(all(s.status == StoryboardShot.Status.QUEUED for s in project.storyboard_shots.all()))
project.refresh_from_db()
self.assertEqual((project.metadata or {}).get("storyboard_prompt"), "风格") # 整张风格存项目级
def test_submit_storyboard_single_shot_only_targets_that_shot(self):
"""单场重跑:只把指定 shot 置 QUEUED,其余已出片的场不动。"""
from apps.ai.services import submit_storyboard
self._mk_image_model()
project = Project.objects.create(team=self.team, created_by=self.user, product=self.product, name="P")
script = ScriptVersion.objects.create(project=project, is_adopted=True)
for i in range(2):
ScriptSegment.objects.create(script_version=script, sort_order=i, narration=f"n{i}", visual_prompt=f"v{i}")
shot0, _, _ = self._mk_shot(project, script, 0)
shot1, _, _ = self._mk_shot(project, script, 1)
submit_storyboard(project=project, user=self.user, shot_ids=[str(shot1.id)])
shot0.refresh_from_db(); shot1.refresh_from_db()
self.assertEqual(shot0.status, StoryboardShot.Status.SUCCEEDED) # 没动
self.assertEqual(shot1.status, StoryboardShot.Status.QUEUED) # 只重跑这场
def test_adopt_storyboard_shot_version_switches_adopted(self):
"""采用某场的历史版本:切换该场采用的分镜图。"""
self._mk_image_model()
project = Project.objects.create(team=self.team, created_by=self.user, product=self.product, name="P")
script = ScriptVersion.objects.create(project=project, is_adopted=True)
ScriptSegment.objects.create(script_version=script, sort_order=0, narration="n", visual_prompt="v")
shot, v1, _ = self._mk_shot(project, script, 0)
a2 = Asset.objects.create(team=self.team, created_by=self.user, name="sb-v2", asset_type="image",
source="ai_generated", category=Asset.Category.STORYBOARD)
v2 = StoryboardShotVersion.objects.create(shot=shot, asset=a2, is_adopted=False)
res = self.client.post(
f"/api/projects/{project.id}/adopt-storyboard-shot-version/",
{"shot_id": str(shot.id), "version_id": str(v2.id)}, format="json",
)
self.assertEqual(res.status_code, 200)
shot.refresh_from_db(); v1.refresh_from_db(); v2.refresh_from_db()
self.assertEqual(shot.adopted_version_id, v2.id)
self.assertTrue(v2.is_adopted)
self.assertFalse(v1.is_adopted)
def test_video_review_precheck_flags_unreviewed_storyboard_then_clears(self):
"""生成视频前的过审闸:某镜故事板分镜未过审(非 active)→ precheck 列为 blocker;
def test_video_review_precheck_flags_unreviewed_person_then_clears(self):
"""生成视频前的过审闸:某镜的人物立绘未过审(非 active)→ precheck 列为 blocker;
过审(active)后 → blockers 清空,可放行生成。"""
project = Project.objects.create(
team=self.team, created_by=self.user, product=self.product, name="P",
@@ -652,21 +600,21 @@ class ProjectApiTests(TestCase):
VideoSegment.objects.create(project=project, sort_order=0, target_duration_seconds=15)
script = ScriptVersion.objects.create(project=project, is_adopted=True)
ScriptSegment.objects.create(script_version=script, sort_order=0, narration="n", visual_prompt="v")
_, _, frame_asset = self._mk_shot(project, script, 0, review_status="")
person_asset = self._mk_person(project, script, 0, review_status="")
res = self.client.post(f"/api/projects/{project.id}/video-review-precheck/", format="json")
self.assertEqual(res.status_code, 200)
blockers = res.json()["blockers"]
self.assertTrue(any(b["kind"] == "storyboard" and b["scene_no"] == 1 for b in blockers))
self.assertTrue(any(b["kind"] == "person" and b["scene_no"] == 1 for b in blockers))
# 过审后再查 → 不再拦
frame_asset.review_status = "active"
frame_asset.save(update_fields=["review_status"])
person_asset.review_status = "active"
person_asset.save(update_fields=["review_status"])
res2 = self.client.post(f"/api/projects/{project.id}/video-review-precheck/", format="json")
self.assertEqual(res2.json()["blockers"], [])
def test_video_review_precheck_skips_succeeded_segments_in_batch(self):
"""整批校验(不传 segment_id)只看未出片的段:已 succeeded 的段即便分镜未过审也不拦。"""
"""整批校验(不传 segment_id)只看未出片的段:已 succeeded 的段即便人物未过审也不拦。"""
project = Project.objects.create(
team=self.team, created_by=self.user, product=self.product, name="P",
current_stage=ProjectStage.Stage.VIDEO,
@@ -674,7 +622,7 @@ class ProjectApiTests(TestCase):
seg = VideoSegment.objects.create(project=project, sort_order=0, status=VideoSegment.Status.SUCCEEDED)
script = ScriptVersion.objects.create(project=project, is_adopted=True)
ScriptSegment.objects.create(script_version=script, sort_order=0, narration="n", visual_prompt="v")
self._mk_shot(project, script, 0, review_status="")
self._mk_person(project, script, 0, review_status="")
# 整批:succeeded 段跳过 → 不拦
res = self.client.post(f"/api/projects/{project.id}/video-review-precheck/", format="json")
@@ -936,7 +884,6 @@ class WorkerGateTests(TestCase):
cases = [
(f"/api/projects/{self.project.id}/generate-base-asset/", {"kind": "person"}),
(f"/api/projects/{self.project.id}/generate-triview/", {"portrait_asset_id": "x"}),
(f"/api/projects/{self.project.id}/generate-storyboard/", {}),
(f"/api/projects/{self.project.id}/submit-video-segment/", {"video_segment_id": "x"}),
("/api/ai/generate-image/", {"prompt": "测试"}),
]