Files
video-gen/video-gen-api/app/services/private_portrait/reference_resolver.py
T

154 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from copy import deepcopy
from typing import Any
from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.enums.private_portrait import (
PRIVATE_PORTRAIT_ASSET_URI_PREFIX,
PrivatePortraitAssetStatus,
PrivatePortraitAssetType,
PrivatePortraitEventSource,
PrivatePortraitEventStatus,
PrivatePortraitEventType,
PrivatePortraitReferenceSource,
)
from app.models.private_portrait import PrivatePortraitAsset
from app.services.operation_log_service import log_operation_error, log_operation_event
DOMAIN = "private_portrait"
_ASSET_TYPE_TO_REFERENCE_TYPE = {
PrivatePortraitAssetType.IMAGE.value: "image",
PrivatePortraitAssetType.VIDEO.value: "video",
PrivatePortraitAssetType.AUDIO.value: "audio",
}
_SUPPORTED_GEN_TYPES = {"image", "video"}
def _ref_get(ref: Any, key: str) -> Any:
if isinstance(ref, dict):
return ref.get(key)
return getattr(ref, key, None)
def _ref_set(ref: Any, key: str, value: Any) -> None:
if isinstance(ref, dict):
ref[key] = value
else:
setattr(ref, key, value)
def _normalize_gen_type(gen_type: str | None) -> str | None:
value = (gen_type or "").strip().lower()
if not value:
return None
if value not in _SUPPORTED_GEN_TYPES:
raise HTTPException(status_code=400, detail="gen_type 仅支持 image 或 video")
return value
def _normalize_ref_type(value: Any) -> str:
return str(value or "").strip().lower()
async def resolve_private_portrait_references(
db: AsyncSession,
*,
user_id: str,
media_references: list[Any] | None,
gen_type: str | None = None,
) -> list[Any] | None:
"""Resolve private portrait references before dispatching a generation task.
generation_ai_service.py 和 generation_task_factory_service.py 都会传 gen_type。
这里保留该参数用于兼容调用方,并做基础校验,避免接口因函数签名不一致直接 500。
"""
normalized_gen_type = _normalize_gen_type(gen_type)
if not media_references:
return media_references
refs = deepcopy(media_references)
ids = [
str(_ref_get(ref, "private_asset_id"))
for ref in refs
if _ref_get(ref, "source") == PrivatePortraitReferenceSource.PRIVATE_PORTRAIT_ASSET.value
and _ref_get(ref, "private_asset_id")
]
ids = list(dict.fromkeys(ids))
if not ids:
return refs
log_operation_event(
domain=DOMAIN,
event_type=PrivatePortraitEventType.REFERENCE_RESOLVE_START.value,
event_status=PrivatePortraitEventStatus.PENDING.value,
source=PrivatePortraitEventSource.SERVICE.value,
user_id=user_id,
detail={"private_asset_ids": ids, "gen_type": normalized_gen_type},
)
try:
rows = await db.execute(select(PrivatePortraitAsset).where(PrivatePortraitAsset.id.in_(ids)))
asset_map = {asset.id: asset for asset in rows.scalars().all()}
for ref in refs:
if _ref_get(ref, "source") != PrivatePortraitReferenceSource.PRIVATE_PORTRAIT_ASSET.value:
continue
asset_id = str(_ref_get(ref, "private_asset_id") or "")
if not asset_id:
raise HTTPException(status_code=400, detail="真人素材引用缺少 private_asset_id")
asset = asset_map.get(asset_id)
if not asset:
raise HTTPException(status_code=400, detail="真人素材不存在")
if asset.user_id != user_id:
raise HTTPException(status_code=403, detail="真人素材不属于当前用户")
if asset.deleted_at is not None:
raise HTTPException(status_code=400, detail="真人素材已删除")
if asset.status != PrivatePortraitAssetStatus.ACTIVE.value:
raise HTTPException(status_code=400, detail=f"真人素材状态为 {asset.status}Active 后才可用于生成")
if not asset.remote_asset_id:
raise HTTPException(status_code=400, detail="真人素材缺少远程 AssetId")
expected_ref_type = _ASSET_TYPE_TO_REFERENCE_TYPE.get(asset.asset_type)
ref_type = _normalize_ref_type(_ref_get(ref, "type"))
if expected_ref_type and ref_type and ref_type != expected_ref_type:
raise HTTPException(status_code=400, detail=f"真人素材类型不匹配:引用为 {ref_type},素材为 {expected_ref_type}")
_ref_set(ref, "source", PrivatePortraitReferenceSource.PRIVATE_PORTRAIT_ASSET.value)
_ref_set(ref, "private_asset_id", asset.id)
_ref_set(ref, "remote_asset_id", asset.remote_asset_id)
_ref_set(ref, "url", f"{PRIVATE_PORTRAIT_ASSET_URI_PREFIX}{asset.remote_asset_id}")
if expected_ref_type:
_ref_set(ref, "type", expected_ref_type)
if not _ref_get(ref, "name") and asset.name:
_ref_set(ref, "name", asset.name)
log_operation_event(
domain=DOMAIN,
event_type=PrivatePortraitEventType.REFERENCE_RESOLVE_SUCCESS.value,
event_status=PrivatePortraitEventStatus.SUCCESS.value,
source=PrivatePortraitEventSource.SERVICE.value,
user_id=user_id,
detail={"count": len(ids), "gen_type": normalized_gen_type},
)
return refs
except Exception as exc:
log_operation_error(
domain=DOMAIN,
event_type=PrivatePortraitEventType.REFERENCE_RESOLVE_FAILED.value,
source=PrivatePortraitEventSource.SERVICE.value,
user_id=user_id,
detail={"private_asset_ids": ids, "gen_type": normalized_gen_type},
exc=exc,
)
raise