添加seedance2.5

This commit is contained in:
Azmat@qq.com
2026-08-28 16:03:23 +08:00
parent c0a0b9603b
commit a618f02315
16 changed files with 416 additions and 80 deletions
+50 -9
View File
@@ -38,11 +38,36 @@ from .video_pricing import get_resolution
logger = logging.getLogger(__name__)
FREE_VIDEO_MODELS = {
"doubao-seedance-2-5-260628",
"doubao-seedance-2-0-260128",
"doubao-seedance-2-0-fast-260128",
"doubao-seedance-2-0-mini-260615",
}
HIGH_RES_MODEL = "doubao-seedance-2-0-260128" # 1080p/4k 仅标准档(火山限制)
# 视频复刻固定用这一档:只有它支持 30 秒单次出片
REPLACE_MODEL = "doubao-seedance-2-5-260628"
# 出片时长的兜底上限。真实上限按 ModelConfig.metadata.durations 取(见 model_duration_range),
# 元数据缺失时回落这里 —— 别写死 15,否则新模型上来就被老常量卡住。
DEFAULT_MAX_DURATION = 15
MIN_DURATION = 4
def model_duration_range(model_config) -> tuple[int, int]:
"""模型支持的出片时长区间。取 metadata.durations(catalog / 后台可配),缺失回落 4–15。"""
meta = (model_config.metadata or {}) if model_config is not None else {}
durations = (meta.get("capabilities") or {}).get("durations") or meta.get("durations") or []
values = [int(v) for v in durations if str(v).isdigit()]
if not values:
return MIN_DURATION, DEFAULT_MAX_DURATION
return min(values), max(values)
def model_resolutions(model_config) -> set[str]:
"""模型支持的分辨率档。缺失回落全集,交给火山自己拒 —— 总比把能用的档误拒好。"""
meta = (model_config.metadata or {}) if model_config is not None else {}
listed = (meta.get("capabilities") or {}).get("resolutions") or meta.get("resolutions") or []
values = {str(v) for v in listed if str(v) in RESOLUTIONS}
return values or set(RESOLUTIONS)
RATIOS = {"21:9", "16:9", "4:3", "1:1", "3:4", "9:16"}
RESOLUTIONS = {"480p", "720p", "1080p", "4k"}
MODES = {"universal", "keyframe"}
@@ -126,7 +151,9 @@ def _guard_asset_reference(asset: Asset, label: str) -> None:
raise ValueError(f"素材「{name}」是上传素材,已提交审核,通过后即可引用")
def build_content_items(*, team, prompt: str, mode: str, references: list) -> dict:
def build_content_items(
*, team, prompt: str, mode: str, references: list, max_ref_seconds: float = REF_DURATION_MAX
) -> dict:
"""references → 火山 content_items + api_prompt(@label 已替换)。
移植 jimeng views.py:369-567:URL 去重、blob: 拦截、素材库引用(FreeAsset → asset://)、
@@ -322,8 +349,9 @@ def build_content_items(*, team, prompt: str, mode: str, references: list) -> di
raise ValueError(f"参考音频最多 3 条,当前 {audio_n} 条,请减少后重试")
if audio_n > 0 and image_n + video_n == 0:
raise ValueError("音频不能单独作为参考素材,请同时提供参考图片或视频")
if video_duration_total > REF_DURATION_MAX:
raise ValueError("参考视频总时长不能超过 15 秒,请缩短后重试")
if video_duration_total > max_ref_seconds:
# 上限跟着模型走:自由创作是 Seedance 2.0(15 秒),视频复刻走 2.5(30 秒)。
raise ValueError(f"参考视频总时长不能超过 {int(max_ref_seconds)} 秒,请缩短后重试")
# @label 替换:按 label 长度降序,防子串吞噬
ordered = sorted(label_to_placeholder.items(), key=lambda kv: len(kv[0]), reverse=True)
@@ -444,10 +472,6 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
raise ValueError("画面比例无效")
if resolution not in RESOLUTIONS:
raise ValueError("分辨率无效")
if not 4 <= duration <= 15:
raise ValueError("视频时长需在 4-15 秒之间")
if resolution in ("1080p", "4k") and model_name != HIGH_RES_MODEL:
raise ValueError(f"{resolution} 仅标准档模型支持,请切换模型或降低分辨率")
get_resolution(aspect_ratio, resolution) # 组合合法性 fail loud
orphan = find_orphan_material_mention(prompt, references)
@@ -471,6 +495,17 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
if model_config is None:
raise ValueError("视频模型未配置,请联系管理员")
# 分辨率与时长都按所选模型自己的能力判,不再写死「1080p/4k 仅 2.0 标准档」——
# Seedance 2.5 同样支持 1080p/4k,写死会把它误拒。能力表见 ModelConfig.metadata。
allowed_resolutions = model_resolutions(model_config)
if resolution not in allowed_resolutions:
raise ValueError(
f"{model_config.display_name or model_name} 不支持 {resolution},请切换模型或降低分辨率"
)
min_seconds, max_seconds = model_duration_range(model_config)
if not min_seconds <= duration <= max_seconds:
raise ValueError(f"视频时长需在 {min_seconds}-{max_seconds} 秒之间")
_reap_stale_free_video_tasks(team=team)
# 团队并发闸(移植 jimeng Layer2.6):视频是长时高价任务,必须限并发
@@ -481,7 +516,10 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
if in_flight >= max_concurrent:
raise ValueError(f"当前有 {in_flight} 个视频任务进行中(上限 {max_concurrent}),请等待完成后再提交")
built = build_content_items(team=team, prompt=prompt, mode=mode, references=references)
built = build_content_items(
team=team, prompt=prompt, mode=mode, references=references,
max_ref_seconds=max_seconds,
)
# 统一计价引擎:¥成本 × 毛利系数 → 积分;预留 = 积分 × buffer(均为 BillingConfig 可配)
tokens, quote = quote_video_estimate(
@@ -583,7 +621,10 @@ def start_pending_free_video(task: AITask) -> AITask:
seed = -1
references = payload.get("references") or []
try:
built = build_content_items(team=task.team, prompt=prompt, mode=mode, references=references)
built = build_content_items(
team=task.team, prompt=prompt, mode=mode, references=references,
max_ref_seconds=model_duration_range(task.model_config)[1],
)
except ValueError as exc:
message = str(exc)
if "正在审核" in message or "已提交审核" in message or "尚未完成合规审核" in message: