完成极速成片
This commit is contained in:
@@ -2217,10 +2217,20 @@ def generate_person_triview(*, project, user, portrait_asset) -> AITask:
|
||||
ref_url = _asset_preview_url(portrait_asset)
|
||||
# 人物三视图提示词:正文可在 admin「提示词」页改(无占位符)
|
||||
tri_prompt = render_prompt("person_triview", THREE_VIEW_PROMPT)
|
||||
portrait_label = ""
|
||||
for group in project.base_asset_groups.filter(kind=BaseAssetGroup.Kind.PERSON):
|
||||
meta = group.metadata or {}
|
||||
if meta.get("triview_of"):
|
||||
continue
|
||||
candidates = [str(value) for value in (group.candidate_assets or [])]
|
||||
if str(group.adopted_asset_id or "") == asset_key or asset_key in candidates:
|
||||
portrait_label = str(meta.get("label") or "")
|
||||
break
|
||||
payload = {
|
||||
"model": model_config.name,
|
||||
"prompt": tri_prompt,
|
||||
"kind": "person",
|
||||
"label": portrait_label or str(portrait_asset.name or ""),
|
||||
"triview_of": asset_key,
|
||||
"reference_image": ref_url,
|
||||
"model_routing_v1": True,
|
||||
@@ -3234,6 +3244,26 @@ def collect_video_review_blockers(project, only_segment: "VideoSegment | None" =
|
||||
return blockers
|
||||
|
||||
|
||||
_VIDEO_INFLIGHT_STATUSES = {
|
||||
AITask.Status.CREATED,
|
||||
AITask.Status.RESERVED,
|
||||
AITask.Status.SUBMITTED,
|
||||
AITask.Status.POLLING,
|
||||
AITask.Status.POSTPROCESSING,
|
||||
}
|
||||
|
||||
|
||||
def video_segment_has_inflight_task(video_segment: VideoSegment) -> bool:
|
||||
"""同一镜头是否已有在途视频任务。防止极速成片并发推进把同一段提交两次。"""
|
||||
segment_id = str(video_segment.id)
|
||||
tasks = AITask.objects.filter(
|
||||
project_id=video_segment.project_id,
|
||||
task_type=AITask.Type.VIDEO_SEGMENT,
|
||||
status__in=_VIDEO_INFLIGHT_STATUSES,
|
||||
).only("request_payload")
|
||||
return any(str((task.request_payload or {}).get("video_segment_id") or "") == segment_id for task in tasks)
|
||||
|
||||
|
||||
def submit_video_segment(
|
||||
*,
|
||||
video_segment: VideoSegment,
|
||||
@@ -3261,7 +3291,17 @@ def submit_video_segment(
|
||||
model_config = get_default_model(ModelConfig.Capability.VIDEO)
|
||||
if model_config is None:
|
||||
raise ValueError("no active video model configured")
|
||||
project = video_segment.project
|
||||
|
||||
with transaction.atomic():
|
||||
video_segment = VideoSegment.objects.select_for_update().select_related("project").get(pk=video_segment.pk)
|
||||
project = video_segment.project
|
||||
if video_segment.status in {VideoSegment.Status.RUNNING, VideoSegment.Status.SUCCEEDED}:
|
||||
return None
|
||||
if video_segment_has_inflight_task(video_segment):
|
||||
return None
|
||||
if video_segment.status != VideoSegment.Status.QUEUED:
|
||||
video_segment.status = VideoSegment.Status.QUEUED
|
||||
video_segment.save(update_fields=["status", "updated_at"])
|
||||
|
||||
# 衔接:按 sort_order 把视频段绑到对应脚本镜,并织出跟住该镜的提示词。
|
||||
scene = None
|
||||
@@ -3289,31 +3329,38 @@ def submit_video_segment(
|
||||
references=[],
|
||||
team=project.team,
|
||||
)
|
||||
task = create_ai_task(
|
||||
project=project,
|
||||
user=user,
|
||||
task_type=AITask.Type.VIDEO_SEGMENT,
|
||||
model_config=model_config,
|
||||
quote=quote,
|
||||
reserve_amount=video_reserve_amount(quote.points),
|
||||
request_payload={
|
||||
"model": model_config.name,
|
||||
"endpoint": model_config.endpoint,
|
||||
"prompt": final_prompt,
|
||||
"duration": video_segment.target_duration_seconds,
|
||||
"ratio": aspect_ratio,
|
||||
"resolution": resolution,
|
||||
"estimated_tokens": est_tokens,
|
||||
# 团队价格系数快照:按实结算用它,中途改价不影响在途任务(jimeng 同款纪律)
|
||||
"price_multiplier": quote.meta.get("price_multiplier", "1"),
|
||||
"video_segment_id": str(video_segment.id),
|
||||
"reference_images": reference_images,
|
||||
"model_routing_v1": True,
|
||||
},
|
||||
)
|
||||
# 提交尝试的实际平台成本由 AIModelAttempt 累加;成片后再用实际模型的 usage true-up 覆盖。
|
||||
task.base_cost = Decimal("0")
|
||||
task.save(update_fields=["base_cost", "updated_at"])
|
||||
with transaction.atomic():
|
||||
video_segment = VideoSegment.objects.select_for_update().select_related("project").get(pk=video_segment.pk)
|
||||
project = video_segment.project
|
||||
if video_segment.status in {VideoSegment.Status.RUNNING, VideoSegment.Status.SUCCEEDED}:
|
||||
return None
|
||||
if video_segment_has_inflight_task(video_segment):
|
||||
return None
|
||||
task = create_ai_task(
|
||||
project=project,
|
||||
user=user,
|
||||
task_type=AITask.Type.VIDEO_SEGMENT,
|
||||
model_config=model_config,
|
||||
quote=quote,
|
||||
reserve_amount=video_reserve_amount(quote.points),
|
||||
request_payload={
|
||||
"model": model_config.name,
|
||||
"endpoint": model_config.endpoint,
|
||||
"prompt": final_prompt,
|
||||
"duration": video_segment.target_duration_seconds,
|
||||
"ratio": aspect_ratio,
|
||||
"resolution": resolution,
|
||||
"estimated_tokens": est_tokens,
|
||||
# 团队价格系数快照:按实结算用它,中途改价不影响在途任务(jimeng 同款纪律)
|
||||
"price_multiplier": quote.meta.get("price_multiplier", "1"),
|
||||
"video_segment_id": str(video_segment.id),
|
||||
"reference_images": reference_images,
|
||||
"model_routing_v1": True,
|
||||
},
|
||||
)
|
||||
# 提交尝试的实际平台成本由 AIModelAttempt 累加;成片后再用实际模型的 usage true-up 覆盖。
|
||||
task.base_cost = Decimal("0")
|
||||
task.save(update_fields=["base_cost", "updated_at"])
|
||||
try:
|
||||
routed = execute_routed_video_submit(
|
||||
task=task,
|
||||
|
||||
Reference in New Issue
Block a user