feat(video): 期3 视频趴 — 人物→角色 + 引用模特库/自动入库 + 分镜合规归类 + 资产库素材
后端: - B 路:视频流程新生成角色立绘+三视图成套 → 自动入模特库(run_triview_task,幂等 additive) - 合规闭合:三视图 person→tri_view、分镜图 scene→storyboard,两者均加 on_commit 送审(此前只有角色立绘送审,三视图/分镜图漏审 = 合规缺口) - 资产库 _tab_q:成片 finals→素材 materials(所有视频排除最终成片;最终成片隐藏不列) - 单测 TriviewAutoEnrollTests:自动入库 + 幂等 + 三视图落审核范围 前端: - 视频项目资产趴「人物」→「角色」(KIND_LABEL,区块名/新增/空态/详情弹窗一处全改;向导人设 persona 不动) - A 路:演员库收编模特库 —— 顶部「模特库」tab 取自模特库实体(含官方跨团队模特),选一个即用其形象图作角色参考 - 资产库 library:人物→角色、成片 tab→素材、分类标签补全(模特上身图/平台套图/自由创作/视频素材/最终成片) - vite.config:端口与后端代理目标可用 VITE_PORT/VITE_API_TARGET 覆盖(默认不变),避免多实例撞车 验收:后端单测全绿(基线既有 7 失败零新增);tsc+build 全绿;无头 0 console error (资产库角色/素材改名、最终成片隐藏;pipeline 资产趴显角色;演员库模特库 tab 含 5 模特) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c8f3f91d38
commit
c6e9c7e77b
@@ -275,6 +275,64 @@ class StandaloneCategoryTests(TestCase):
|
||||
self.assertEqual(a.category, Asset.Category.PERSON)
|
||||
|
||||
|
||||
class TriviewAutoEnrollTests(TestCase):
|
||||
"""B 路(期3):视频流程新生成的角色立绘 + 三视图成套 → 自动入模特库;幂等不重复建。"""
|
||||
|
||||
def setUp(self):
|
||||
from apps.projects.models import Project
|
||||
|
||||
self.user = User.objects.create_user(username="trio", password="p")
|
||||
self.team = Team.objects.create(name="TR", owner=self.user)
|
||||
CreditAccount.objects.create(team=self.team, balance="100.0000")
|
||||
self.product = Product.objects.create(team=self.team, created_by=self.user, title="P")
|
||||
self.project = Project.objects.create(team=self.team, name="项目甲", product=self.product, created_by=self.user)
|
||||
self.portrait = Asset.objects.create(
|
||||
team=self.team, created_by=self.user, name="项目甲-person", asset_type=Asset.Type.IMAGE,
|
||||
source=Asset.Source.AI_GENERATED, category=Asset.Category.PERSON,
|
||||
)
|
||||
AssetFile.objects.create(asset=self.portrait, object_key="p.png", bucket="b", content_type="image/png", preview_url="http://x/p.png", is_primary=True)
|
||||
|
||||
def _patch_provider(self):
|
||||
provider = patch("apps.ai.services.get_image_provider").start()
|
||||
prov = provider.return_value
|
||||
prov.image_edit.return_value = {"data": [{"url": "http://x/tri.png"}]}
|
||||
prov.extract_first_media_url.return_value = "http://x/tri.png"
|
||||
media = patch("apps.ai.services.VolcanoArkProvider.media_to_bytes").start()
|
||||
media.return_value = (BytesIO(b"img"), "image/png")
|
||||
store = patch("apps.ai.services.TosStorage").start()
|
||||
stored = store.return_value.upload_fileobj.return_value
|
||||
stored.object_key, stored.bucket, stored.content_type, stored.size_bytes = "o.png", "b", "image/png", 3
|
||||
self.addCleanup(patch.stopall)
|
||||
|
||||
def _gen_triview(self):
|
||||
from apps.ai.services import generate_person_triview, run_triview_task
|
||||
|
||||
task = generate_person_triview(project=self.project, user=self.user, portrait_asset=self.portrait)
|
||||
run_triview_task(task_id=str(task.id))
|
||||
|
||||
def test_triview_auto_enrolls_model(self):
|
||||
from apps.assets.models import Model
|
||||
|
||||
self._patch_provider()
|
||||
self._gen_triview()
|
||||
m = Model.objects.filter(portrait_asset=self.portrait).first()
|
||||
self.assertIsNotNone(m)
|
||||
self.assertIsNotNone(m.triview_asset) # 成套
|
||||
self.assertTrue(m.metadata.get("auto_enrolled"))
|
||||
self.assertEqual(m.source, Model.Source.AI)
|
||||
# 合规(期3):三视图归 tri_view → 落在送审范围内
|
||||
self.assertEqual(m.triview_asset.category, Asset.Category.TRI_VIEW)
|
||||
self.assertIn(m.triview_asset.category, Asset.REVIEW_CATEGORIES)
|
||||
|
||||
def test_auto_enroll_is_idempotent(self):
|
||||
from apps.assets.models import Model
|
||||
|
||||
self._patch_provider()
|
||||
self._gen_triview()
|
||||
self._gen_triview() # 同一立绘再出一版三视图
|
||||
self.assertEqual(Model.objects.filter(portrait_asset=self.portrait).count(), 1)
|
||||
|
||||
|
||||
class _FakeStreamResp:
|
||||
"""模拟 requests 流式响应:支持 with、raise_for_status、可写 encoding、iter_lines。"""
|
||||
status_code = 200
|
||||
|
||||
Reference in New Issue
Block a user