真人/虚拟人像库app build

This commit is contained in:
2026-07-07 15:18:52 +08:00
parent 6026ec8670
commit 98e8fe145f
15 changed files with 648 additions and 615 deletions
+16 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import Any
from pydantic import BaseModel, Field, field_validator
from pydantic import BaseModel, Field, field_validator, model_validator
from app.enums.private_portrait import (
PRIVATE_PORTRAIT_ENABLED_ASSET_TYPES,
@@ -13,6 +13,9 @@ from app.enums.private_portrait import (
)
from app.schemas.common import NaiveDatetimeOptional
PRIVATE_PORTRAIT_VIDEO_MIN_DURATION_SECONDS = 2
PRIVATE_PORTRAIT_VIDEO_MAX_DURATION_SECONDS = 15
class PrivatePortraitEnumItem(BaseModel):
value: str
@@ -160,6 +163,18 @@ class PrivatePortraitAssetCreate(BaseModel):
raise ValueError("Audio 暂未开放,当前仅支持 Image/Video")
return value
@model_validator(mode="after")
def validate_video_duration(self) -> "PrivatePortraitAssetCreate":
if self.asset_type == PrivatePortraitAssetType.VIDEO.value:
duration = self.video_duration
if duration is None:
raise ValueError("Video 素材必须提供 video_duration")
if duration < PRIVATE_PORTRAIT_VIDEO_MIN_DURATION_SECONDS:
raise ValueError(f"视频素材最短不能少于 {PRIVATE_PORTRAIT_VIDEO_MIN_DURATION_SECONDS}")
if duration > PRIVATE_PORTRAIT_VIDEO_MAX_DURATION_SECONDS:
raise ValueError(f"视频素材最长不能超过 {PRIVATE_PORTRAIT_VIDEO_MAX_DURATION_SECONDS}")
return self
class PrivatePortraitAssetOut(BaseModel):
id: str
@@ -46,6 +46,9 @@ from app.utils.id_gen import generate_id
DOMAIN = "private_portrait"
PRIVATE_PORTRAIT_VIDEO_MIN_DURATION_SECONDS = 2
PRIVATE_PORTRAIT_VIDEO_MAX_DURATION_SECONDS = 15
def _json(data: Any) -> str | None:
if data is None:
@@ -118,6 +121,18 @@ def _assert_enabled_asset_type(asset_type: str) -> None:
raise HTTPException(status_code=400, detail="Audio 暂未开放,当前仅支持 Image/Video")
def _assert_private_asset_video_duration(payload: PrivatePortraitAssetCreate) -> None:
if payload.asset_type != PrivatePortraitAssetType.VIDEO.value:
return
duration = payload.video_duration
if duration is None:
raise HTTPException(status_code=400, detail="Video 素材必须提供 video_duration")
if duration < PRIVATE_PORTRAIT_VIDEO_MIN_DURATION_SECONDS:
raise HTTPException(status_code=400, detail=f"视频素材最短不能少于 {PRIVATE_PORTRAIT_VIDEO_MIN_DURATION_SECONDS}")
if duration > PRIVATE_PORTRAIT_VIDEO_MAX_DURATION_SECONDS:
raise HTTPException(status_code=400, detail=f"视频素材最长不能超过 {PRIVATE_PORTRAIT_VIDEO_MAX_DURATION_SECONDS}")
def validate_session_to_out(session: PrivatePortraitValidateSession, *, include_user: bool = False) -> PrivatePortraitValidateSessionOut:
return PrivatePortraitValidateSessionOut(
id=session.id,
@@ -402,6 +417,7 @@ async def create_asset(
library_type: str | None = None,
) -> PrivatePortraitAsset:
_assert_enabled_asset_type(payload.asset_type)
_assert_private_asset_video_duration(payload)
project = await get_user_project(db, user_id=user_id, project_id=project_id, library_type=library_type)
if project.status != PrivatePortraitProjectStatus.ACTIVE.value:
raise HTTPException(status_code=400, detail="项目未激活,不能上传素材")
@@ -90,6 +90,8 @@ def _fill_private_portrait_reference_display_fields(ref: Any, asset: PrivatePort
expected_ref_type = _ASSET_TYPE_TO_REFERENCE_TYPE.get(asset.asset_type)
if expected_ref_type:
_ref_set(ref, "type", expected_ref_type)
if asset.asset_type == PrivatePortraitAssetType.VIDEO.value and asset.video_duration is not None:
_ref_set(ref, "duration", asset.video_duration)
if not _ref_get(ref, "name") and asset.name:
_ref_set(ref, "name", asset.name)
@@ -293,6 +295,10 @@ async def resolve_private_portrait_references(
_ref_set(ref, "preview_url", display_url)
if expected_ref_type:
_ref_set(ref, "type", expected_ref_type)
if asset.asset_type == PrivatePortraitAssetType.VIDEO.value:
if asset.video_duration is None:
raise HTTPException(status_code=400, detail="私域视频素材缺少 video_duration,不能用于生成")
_ref_set(ref, "duration", asset.video_duration)
if not _ref_get(ref, "name") and asset.name:
_ref_set(ref, "name", asset.name)