This commit is contained in:
Azmat@qq.com
2026-09-16 16:52:01 +08:00
parent 7f5307d97c
commit b487b65575
5 changed files with 158 additions and 26 deletions
+39 -8
View File
@@ -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")