优化脚本
This commit is contained in:
@@ -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": "测试"}),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user