补
This commit is contained in:
@@ -150,7 +150,8 @@ def _sync_segmented_video_message(message: CreationMessage) -> bool:
|
||||
"""同步全能创作的多段出片。
|
||||
|
||||
每段仍是普通 FREE_VIDEO 任务,故可沿用既有计费、轮询和资产落库;这里仅把它们聚合成
|
||||
一张结果卡。所有分段完成后停在结果卡,绝不在此处触发 ffmpeg。
|
||||
一张结果卡。任一片段先完成就先写回 GENERATING 卡供用户预览;所有分段完成后才转成
|
||||
可合并的结果卡,绝不在此处触发 ffmpeg。
|
||||
"""
|
||||
from .free_video import finalize_free_video
|
||||
from .models import AITask
|
||||
@@ -189,16 +190,46 @@ def _sync_segmented_video_message(message: CreationMessage) -> bool:
|
||||
number = next((index + 1 for index, task in enumerate(refreshed) if task.id == failed.id), 1)
|
||||
fail_generating_message(message, f"第 {number} 段生成失败:{failed.error_message or '请重试'}")
|
||||
return True
|
||||
assets: list[dict] = []
|
||||
completed_segments = 0
|
||||
for index, task in enumerate(refreshed, start=1):
|
||||
if task.status != AITask.Status.SUCCEEDED:
|
||||
continue
|
||||
task_assets = _assets_from_task(task)
|
||||
# 上游状态先成功、资产稍后才落库时,保留 GENERATING,下一轮再展示这段。
|
||||
if not task_assets:
|
||||
continue
|
||||
completed_segments += 1
|
||||
for asset in task_assets:
|
||||
assets.append({**asset, "label": f"第 {index} 段", "segment_index": index})
|
||||
|
||||
# 先完成的片段必须立刻回到前端,不能等另一段慢任务一起完成才出现。
|
||||
# 保持同一条 GENERATING 消息,避免把一个 60 秒视频拆成多条对话消息。
|
||||
if not all(task.status == AITask.Status.SUCCEEDED for task in refreshed):
|
||||
progress = {
|
||||
**payload,
|
||||
"assets": assets,
|
||||
"completed_segment_count": completed_segments,
|
||||
}
|
||||
if progress != payload:
|
||||
message.payload = progress
|
||||
message.save(update_fields=["payload", "updated_at"])
|
||||
return True
|
||||
return False
|
||||
|
||||
# 任务都成功但有片段的资产还在落库,继续保持生成中,避免最终结果缺片。
|
||||
if completed_segments != len(refreshed):
|
||||
progress = {
|
||||
**payload,
|
||||
"assets": assets,
|
||||
"completed_segment_count": completed_segments,
|
||||
}
|
||||
if progress != payload:
|
||||
message.payload = progress
|
||||
message.save(update_fields=["payload", "updated_at"])
|
||||
return True
|
||||
return False
|
||||
|
||||
assets: list[dict] = []
|
||||
for index, task in enumerate(refreshed, start=1):
|
||||
task_assets = _assets_from_task(task)
|
||||
if not task_assets:
|
||||
return False
|
||||
for asset in task_assets:
|
||||
assets.append({**asset, "label": f"第 {index} 段"})
|
||||
first = refreshed[0]
|
||||
finish_generating_message(
|
||||
message,
|
||||
|
||||
@@ -161,6 +161,33 @@ class GenerationBackfillTests(TestCase):
|
||||
message.refresh_from_db()
|
||||
self.assertEqual(message.kind, CreationMessage.Kind.GENERATING)
|
||||
|
||||
def test_segmented_video_shows_completed_first_segment_before_second_finishes(self):
|
||||
first = self._task(AITask.Status.SUCCEEDED, key="k-segment-first")
|
||||
second = self._task(AITask.Status.POLLING, key="k-segment-second")
|
||||
self._asset(first, url="https://cdn.example/segment-1.mp4")
|
||||
message = append_message(
|
||||
self.conversation,
|
||||
role="assistant",
|
||||
kind=CreationMessage.Kind.GENERATING,
|
||||
task=first,
|
||||
payload={
|
||||
"kind": "video_segments",
|
||||
"task_id": str(first.id),
|
||||
"task_ids": [str(first.id), str(second.id)],
|
||||
"segments": [
|
||||
{"index": 1, "task_id": str(first.id)},
|
||||
{"index": 2, "task_id": str(second.id)},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(sync_generating_messages(self.conversation), 1)
|
||||
message.refresh_from_db()
|
||||
self.assertEqual(message.kind, CreationMessage.Kind.GENERATING)
|
||||
self.assertEqual(message.payload["completed_segment_count"], 1)
|
||||
self.assertEqual(message.payload["assets"][0]["label"], "第 1 段")
|
||||
self.assertEqual(message.payload["assets"][0]["url"], "https://cdn.example/segment-1.mp4")
|
||||
|
||||
def test_retrieve_backfills_before_returning_messages(self):
|
||||
task = self._task(AITask.Status.SUCCEEDED, key="k-api")
|
||||
self._asset(task, url="https://cdn.example/api.png")
|
||||
|
||||
Reference in New Issue
Block a user