Files
video-gen/video-gen-api/app/services/private_portrait/virtual/service.py
T
2026-07-07 13:20:50 +08:00

127 lines
6.0 KiB
Python

from __future__ import annotations
import json
from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.enums.private_portrait import (
PRIVATE_PORTRAIT_REMOTE_PROJECT_NAME,
PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE,
PrivatePortraitAssetGroupStatus,
PrivatePortraitEventSource,
PrivatePortraitEventStatus,
PrivatePortraitEventType,
PrivatePortraitLibraryType,
PrivatePortraitProjectStatus,
)
from app.models.private_portrait import PrivatePortraitAssetGroup
from app.schemas.private_portrait import PrivatePortraitAssetCreate, PrivatePortraitProjectUpdate, PrivatePortraitVirtualProjectCreate
from app.services.operation_log_service import log_operation_error, log_operation_event
from app.services.private_portrait.ark_client import ArkPrivateAssetClient
from app.services.private_portrait.asset_service import create_asset
from app.services.private_portrait.project_service import create_project, refresh_project_counters, update_project
from app.utils.id_gen import generate_id
DOMAIN = "private_portrait"
def _json(data) -> str | None:
if data is None:
return None
return json.dumps(data, ensure_ascii=False, default=str)
def _remote_group_name(user_id: str, project_name: str) -> str:
safe_name = "".join(ch if ch.isalnum() or ch in "-_" else "_" for ch in project_name.strip())[:80]
return f"virtual-{user_id}-{safe_name}"[:128]
async def create_virtual_project(db: AsyncSession, *, user_id: str, payload: PrivatePortraitVirtualProjectCreate):
project = await create_project(
db,
user_id=user_id,
payload=payload, # type: ignore[arg-type]
library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value,
status=PrivatePortraitProjectStatus.CREATING_REMOTE_GROUP.value,
remote_project_name=PRIVATE_PORTRAIT_REMOTE_PROJECT_NAME,
)
remote_group_name = _remote_group_name(user_id, project.name)
log_operation_event(
domain=DOMAIN,
event_type=PrivatePortraitEventType.VIRTUAL_ASSET_GROUP_CREATE_REMOTE_START.value,
event_status=PrivatePortraitEventStatus.PENDING.value,
source=PrivatePortraitEventSource.API.value,
user_id=user_id,
project_id=project.id,
detail={"remote_group_name": remote_group_name, "remote_project_name": project.remote_project_name, "group_type": PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE},
)
try:
resp = await ArkPrivateAssetClient().create_asset_group(
project_name=project.remote_project_name,
name=remote_group_name,
description=project.description,
group_type=PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE,
)
remote_group_id = resp.get("Id") or resp.get("GroupId") or resp.get("groupId")
if not remote_group_id:
raise RuntimeError("CreateAssetGroup 未返回素材组 ID")
group = PrivatePortraitAssetGroup(
id=generate_id(),
user_id=user_id,
project_id=project.id,
library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value,
remote_group_id=remote_group_id,
remote_group_name=remote_group_name,
remote_project_name=project.remote_project_name,
group_type=PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE,
status=PrivatePortraitAssetGroupStatus.ACTIVE.value,
raw_response_json=_json(resp),
)
db.add(group)
project.status = PrivatePortraitProjectStatus.ACTIVE.value
await refresh_project_counters(db, [project.id])
await db.flush()
await db.refresh(project)
log_operation_event(
domain=DOMAIN,
event_type=PrivatePortraitEventType.VIRTUAL_ASSET_GROUP_CREATE_REMOTE_SUCCESS.value,
event_status=PrivatePortraitEventStatus.SUCCESS.value,
source=PrivatePortraitEventSource.API.value,
user_id=user_id,
project_id=project.id,
group_id=group.id,
detail={"remote_group_id": remote_group_id, "remote_group_name": remote_group_name, "remote_project_name": project.remote_project_name},
)
return project
except Exception as exc:
project.status = PrivatePortraitProjectStatus.CREATE_GROUP_FAILED.value
await db.flush()
log_operation_error(
domain=DOMAIN,
event_type=PrivatePortraitEventType.VIRTUAL_ASSET_GROUP_CREATE_REMOTE_FAILED.value,
source=PrivatePortraitEventSource.API.value,
user_id=user_id,
project_id=project.id,
exc=exc,
)
raise HTTPException(status_code=502, detail=f"创建火山虚拟人像素材组失败:{exc}") from exc
async def update_virtual_project(db: AsyncSession, *, user_id: str, project_id: str, payload: PrivatePortraitProjectUpdate):
project = await update_project(db, user_id=user_id, project_id=project_id, payload=payload, library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value)
# 远程同步失败不影响本地更新,记录日志便于排查。
try:
# 只同步当前激活组。
from app.services.private_portrait.asset_service import get_project_active_group
group = await get_project_active_group(db, user_id=user_id, project_id=project_id, library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value)
await ArkPrivateAssetClient().update_asset_group(project_name=project.remote_project_name, group_id=group.remote_group_id, name=group.remote_group_name, title=project.name, description=project.description)
except Exception as exc:
log_operation_error(domain=DOMAIN, event_type=PrivatePortraitEventType.ASSET_GROUP_UPDATE_REMOTE_FAILED.value, source=PrivatePortraitEventSource.API.value, user_id=user_id, project_id=project_id, exc=exc)
return project
async def create_virtual_asset(db: AsyncSession, *, user_id: str, project_id: str, payload: PrivatePortraitAssetCreate):
return await create_asset(db, user_id=user_id, project_id=project_id, payload=payload, library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value)