完成视频复刻和优化

This commit is contained in:
Azmat@qq.com
2026-08-27 11:54:47 +08:00
parent c25060c6c6
commit 51620bf25b
46 changed files with 2575 additions and 663 deletions
+25 -7
View File
@@ -31,6 +31,7 @@ from apps.billing.services.ledger import charge_reserved_credit, release_credit,
from .models import AITask, ModelConfig
from .providers.volcano import VolcanoArkProvider
from .generation_errors import classify_generation_error, public_error_for_task
from .media_probe import REF_DURATION_MAX
from .video_errors import parse_provider_error
from .video_pricing import get_resolution
@@ -319,7 +320,7 @@ 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 > 15:
if video_duration_total > REF_DURATION_MAX:
raise ValueError("参考视频总时长不能超过 15 秒,请缩短后重试")
# @label 替换:按 label 长度降序,防子串吞噬
@@ -409,6 +410,7 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
"""提交一条自由创作视频。校验/估价/预留在前(失败不留半套),火山调用在事务外。
创建失败不抛:返回 FAILED 任务(带友好中文错误),前端渲染失败卡。校验类错误抛 ValueError → 400。"""
prompt = str(params.get("prompt") or "").strip()
feature = str(params.get("feature") or "free_video").strip() or "free_video"
mode = str(params.get("mode") or "universal")
model_name = str(params.get("model") or HIGH_RES_MODEL)
aspect_ratio = str(params.get("aspect_ratio") or "16:9")
@@ -486,7 +488,7 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
reserve_amount = video_reserve_amount(quote.points)
request_payload = {
"feature": "free_video",
"feature": feature,
"mode": mode,
"model": model_name,
"endpoint": model_config.endpoint,
@@ -505,6 +507,13 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
"references": built["snapshots"],
"model_routing_v1": True,
}
extra = params.get("extra_payload")
if isinstance(extra, dict):
protected = set(request_payload)
for key, value in extra.items():
if key in protected or value in (None, ""):
continue
request_payload[key] = value
# 建任务 + 预留同一事务:余额不足/限额拦截时回滚任务行,不留半套
with transaction.atomic():
@@ -546,7 +555,7 @@ def submit_free_video(*, team, user, params: dict) -> AITask:
generate_audio=generate_audio,
seed=seed if seed != -1 else None,
search_mode=search_mode,
request_summary={"feature": "free_video", "mode": mode},
request_summary={"feature": feature, "mode": mode},
)
response, provider_task_id = routed.value
# execute_model_call 以原子 F 表达式累计实际尝试的平台成本;刷新内存对象,
@@ -625,7 +634,7 @@ def _notify_failure(task: AITask, *, raw: str, hint: str) -> None:
task=task,
project=None,
recipient=task.created_by,
stage_label="自由创作视频",
stage_label="视频复刻" if (task.request_payload or {}).get("feature") == "video_replace" else "自由创作视频",
raw=raw,
hint=hint,
)
@@ -643,19 +652,27 @@ def _store_free_video_media(*, task: AITask, media: str) -> Asset:
asset_id = uuid.uuid4()
object_key = f"teams/{task.team_id}/free-create/{asset_id}.mp4"
stored = TosStorage().upload_fileobj(fileobj=fileobj, object_key=object_key, content_type=content_type)
prompt = (task.request_payload or {}).get("prompt") or ""
payload = task.request_payload or {}
prompt = payload.get("prompt") or ""
feature = payload.get("feature") or "free_video"
subject = str(payload.get("subject_name") or "").strip()
if feature == "video_replace":
mode_label = "角色复刻" if payload.get("replace_mode") == "character" else "商品复刻"
asset_name = f"{subject}{mode_label}" if subject else mode_label
else:
asset_name = prompt[:255] or "自由创作视频"
asset = Asset.objects.create(
id=asset_id,
team=task.team,
created_by=task.created_by,
# Asset.name 是通用资产字段,受 255 字符上限约束;完整显示标题由资产接口从
# 关联任务的 request_payload.prompt 派生,避免再出现 50 字符业务截断。
name=(prompt[:255] or "自由创作视频"),
name=asset_name[:255],
asset_type=Asset.Type.VIDEO,
source=Asset.Source.AI_GENERATED,
category=Asset.Category.FREE_CREATE,
origin_task=task,
metadata={"feature": "free_video"},
metadata={"feature": feature},
)
AssetFile.objects.create(
asset=asset,
@@ -892,6 +909,7 @@ def serialize_free_video_task(task: AITask, *, include_deleted_assets: bool = Fa
"status": task.status,
"mode": payload.get("mode") or "universal",
"model": payload.get("model") or "",
"feature": payload.get("feature") or "free_video",
"prompt": payload.get("prompt") or "",
"aspect_ratio": payload.get("aspect_ratio") or "16:9",
"resolution": payload.get("resolution") or "720p",