模特库标签分页与全能创作收口:藏长视频、选择器分页

角色库导入打标签并支持筛选;模特库与全能创作角色/商品选择改为每页 20 条分页。临时限制成片 ≤60 秒,过滤对话里的超长时长选项,并收拢本地全能创作与后台用户相关修复。
This commit is contained in:
Azmat@qq.com
2026-09-21 16:24:37 +08:00
parent 690eb3d843
commit 0bd1db6bf9
48 changed files with 3386 additions and 896 deletions
+43 -9
View File
@@ -44,6 +44,23 @@ FREE_VIDEO_MODELS = {
"doubao-seedance-2-0-mini-260615",
}
HIGH_RES_MODEL = "doubao-seedance-2-0-260128" # 1080p/4k 仅标准档(火山限制)
# 火山对 seed 的硬上限是 int32 正数上限;超一点整条请求直接 InvalidParameter 被拒。
VIDEO_SEED_MAX = 2147483647
def normalize_video_seed(value) -> int:
"""把任何来源的 seed 收进火山认的区间。-1 = 不指定(随机)。
越界不报错而是折回区间内:长视频各段共用同一个确定性 seed 保一致性,
折算规则固定,同一个会话每次算出来仍是同一个值。
"""
try:
seed = int(value)
except (TypeError, ValueError):
return -1
if seed < 0:
return -1
return seed & VIDEO_SEED_MAX
# 视频复刻固定 Seedance 2.5(单次最长 30 秒)。原片只用来提炼分镜,不传给火山。
REPLACE_MODEL = "doubao-seedance-2-5-260628"
# 出片时长的兜底上限。真实上限按 ModelConfig.metadata.durations 取(见 model_duration_range),
@@ -129,6 +146,29 @@ def _refresh_processing_free_asset(free_asset: FreeAsset) -> bool:
return False
def _reference_asset(team, asset_id) -> Asset | None:
"""出片参考图允许两种来源:团队自有资产,以及官方模特库的人像/三视图。
官方模特挂在平台团队名下,按 team 过滤会直接查不到 —— 这是模特库里明明存在、
生成时却报「不存在或已被删除」的根因。放行范围与视频复刻(video_replace)保持一致。
"""
from django.db.models import Q
from apps.assets.models import Model as AssetModel
asset = Asset.objects.filter(id=asset_id, is_deleted=False).first()
if asset is None:
return None
if asset.team_id == team.id:
return asset
is_official_model_asset = (
AssetModel.objects.filter(is_official=True, is_deleted=False, purged_at__isnull=True)
.filter(Q(portrait_asset=asset) | Q(triview_asset=asset))
.exists()
)
return asset if is_official_model_asset else None
def _guard_asset_reference(asset: Asset, label: str, ref_type: str = "") -> None:
"""三库引用前的审核闸(模块4 · 4.2)。
@@ -317,7 +357,7 @@ def build_content_items(
# 全能创作 resolve_refs / 前端常带 type=character|product 且 source 缺省为 upload,
# 只要有 asset_id 就按 Asset 解析(含审核态 → asset://),避免直链分支把语义 type skipped。
if ref.get("asset_id") and source in {"asset", "upload", ""}:
asset = Asset.objects.filter(id=ref["asset_id"], team=team, is_deleted=False).first()
asset = _reference_asset(team, ref["asset_id"])
if asset is None:
raise ValueError(f"素材「{label or '未命名'}」不存在或已被删除")
_guard_asset_reference(asset, label, ref_type)
@@ -480,10 +520,7 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
duration = int(params.get("duration") or 5)
except (TypeError, ValueError):
raise ValueError("时长参数无效")
try:
seed = int(params.get("seed") if params.get("seed") is not None else -1)
except (TypeError, ValueError):
seed = -1
seed = normalize_video_seed(params.get("seed"))
if not prompt:
raise ValueError("提示词不能为空")
@@ -637,10 +674,7 @@ def start_pending_free_video(task: AITask) -> AITask:
duration = int(payload.get("duration") or 5)
except (TypeError, ValueError):
duration = 5
try:
seed = int(payload.get("seed") if payload.get("seed") is not None else -1)
except (TypeError, ValueError):
seed = -1
seed = normalize_video_seed(payload.get("seed"))
references = list(payload.get("references") or [])
if feature == "video_replace" and payload.get("replace_mode") == "product":
# 商品复刻:原片只用来提炼,绝不能进 Seedance,否则真人素材直接被火山拒。