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
@@ -851,7 +851,7 @@ def run_triview_task(*, task_id: str) -> None:
|
||||
charge_reserved_credit(reservation=reservation, actual_amount=task.actual_cost)
|
||||
asset = _store_generated_media(
|
||||
team=project.team, user=user, project=project, task=task, media=media,
|
||||
name=f"{project.name}-三视图", category=Asset.Category.PERSON, asset_type=Asset.Type.IMAGE,
|
||||
name=f"{project.name}-三视图", category=Asset.Category.TRI_VIEW, asset_type=Asset.Type.IMAGE,
|
||||
)
|
||||
# 复用该立绘的三视图组(triview_of==立绘asset id):追加候选 + 采用最新
|
||||
group = next((g for g in project.base_asset_groups.filter(kind=BaseAssetGroup.Kind.PERSON).order_by("created_at")
|
||||
@@ -864,6 +864,23 @@ def run_triview_task(*, task_id: str) -> None:
|
||||
group.candidate_assets.add(asset)
|
||||
group.adopted_asset = asset
|
||||
group.save(update_fields=["adopted_asset", "updated_at"])
|
||||
# B 路(期3):视频流程新生成的「角色」立绘 + 三视图成套 → 自动加入模特库(团队级可复用)。
|
||||
# 幂等:同一立绘只建一次 Model;best-effort,不影响主链路。
|
||||
from apps.assets.models import Model as ModelEntity
|
||||
|
||||
portrait = Asset.objects.filter(id=asset_key).first()
|
||||
if portrait is not None and not ModelEntity.objects.filter(portrait_asset=portrait).exists():
|
||||
ModelEntity.objects.create(
|
||||
team=project.team, created_by=user,
|
||||
name=(portrait.name or f"{project.name}-角色").split("·")[0].strip()[:255] or "角色",
|
||||
source=ModelEntity.Source.AI,
|
||||
portrait_asset=portrait, triview_asset=asset,
|
||||
metadata={"from_project": str(project.id), "auto_enrolled": True},
|
||||
)
|
||||
# 合规(期3):三视图含人脸,视频生成前必须过火山审核 → 事务提交后静默送审(best-effort)
|
||||
from apps.assets.review import submit_asset_for_review
|
||||
|
||||
transaction.on_commit(lambda a=asset: submit_asset_for_review(a))
|
||||
except Exception as exc: # noqa: BLE001 — 失败退费 + 错误记进 AITask 供前端轮询读取;不向上抛(避免 celery 重试二次扣费)
|
||||
task.status = AITask.Status.FAILED
|
||||
task.error_message = str(exc)
|
||||
@@ -1144,7 +1161,7 @@ def _storyboard_frame_worker(task_id, version_id, segment_id, user_id) -> None:
|
||||
task=task,
|
||||
media=media,
|
||||
name=f"{project.name}-storyboard-{segment.sort_order + 1}",
|
||||
category=Asset.Category.SCENE,
|
||||
category=Asset.Category.STORYBOARD,
|
||||
asset_type=Asset.Type.IMAGE,
|
||||
)
|
||||
with transaction.atomic():
|
||||
@@ -1163,6 +1180,10 @@ def _storyboard_frame_worker(task_id, version_id, segment_id, user_id) -> None:
|
||||
sort_order=segment.sort_order,
|
||||
prompt=segment.visual_prompt,
|
||||
)
|
||||
# 合规(期3):分镜图含人脸,视频生成前必须过火山审核 → 事务提交后静默送审(best-effort)
|
||||
from apps.assets.review import submit_asset_for_review
|
||||
|
||||
transaction.on_commit(lambda a=asset: submit_asset_for_review(a))
|
||||
except Exception as exc: # noqa: BLE001 — 失败回滚额度,标记任务失败供 poll 上报
|
||||
task.status = AITask.Status.FAILED
|
||||
task.error_message = str(exc)
|
||||
|
||||
Reference in New Issue
Block a user