refactor(video-from-script): 提取轮询重试逻辑为共享工具
提取三个视频生成器中重复的 `pollWithRetry` 函数到共享模块 `video-poll-utils`,消除代码重复。新增两层重试机制:轮询级(处理网络瞬断)和任务级(创建新任务 + 提示词优化)。同时优化 `phase-videos` 中视频状态管理和 manifest 保存逻辑。
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
* 支持 task ID 恢复:中断后重跑时优先恢复已有任务
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { saveManifest, ensureDir, log, getManifestDir } = require('./pipeline-utils')
|
||||
|
||||
@@ -24,15 +23,15 @@ async function phaseVideos(manifest, manifestPath, options) {
|
||||
const videoCandidates = manifest.items.filter(it => {
|
||||
if (it.confirmed === false) return false
|
||||
if (!it.url || !it.videoPrompt) return false
|
||||
if (['done', 'pending', 'failed'].includes(it.status)) return true
|
||||
return false
|
||||
return ['done', 'pending', 'failed'].includes(it.status)
|
||||
})
|
||||
// 对重试 item 自动清理旧视频引用,无需 agent 手动删除
|
||||
|
||||
// 已有视频(本地或 OSS)且状态为 done 的跳过,其余清理后重新生成
|
||||
const items = []
|
||||
for (const it of videoCandidates) {
|
||||
if (it.video) {
|
||||
if (it.status === 'done') continue // 已有视频且完成,跳过
|
||||
delete it.video // pending/failed 但有旧 video → 清理重来
|
||||
if (it.video || it.videoUrl) {
|
||||
if (it.status === 'done') continue
|
||||
delete it.video
|
||||
delete it.videoUrl
|
||||
delete it.videoDuration
|
||||
delete it.videoTaskId
|
||||
@@ -70,7 +69,6 @@ async function phaseVideos(manifest, manifestPath, options) {
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询恢复的任务
|
||||
if (recovered.length > 0) {
|
||||
log('videos', `尝试恢复 ${recovered.length} 个中断任务...`)
|
||||
await Promise.allSettled(
|
||||
@@ -86,6 +84,7 @@ async function phaseVideos(manifest, manifestPath, options) {
|
||||
if (result.file) {
|
||||
item.video = path.relative(dir, result.file).replace(/\\/g, '/')
|
||||
item.videoDuration = result.duration
|
||||
item.status = 'done'
|
||||
delete item.videoTaskId
|
||||
log('videos', ` item ${item.id} 恢复成功`)
|
||||
}
|
||||
@@ -94,9 +93,9 @@ async function phaseVideos(manifest, manifestPath, options) {
|
||||
delete item.videoTaskId
|
||||
needSubmit.push(item)
|
||||
}
|
||||
saveManifest(manifestPath, manifest)
|
||||
})
|
||||
)
|
||||
saveManifest(manifestPath, manifest)
|
||||
}
|
||||
|
||||
if (needSubmit.length === 0) { log('videos', '全部通过恢复完成'); return }
|
||||
@@ -110,13 +109,9 @@ async function phaseVideos(manifest, manifestPath, options) {
|
||||
const batch = needSubmit.slice(i, i + concurrency)
|
||||
const batchResults = await Promise.allSettled(
|
||||
batch.map(async (item) => {
|
||||
const images = item.lastFrameUrl
|
||||
? [item.url, item.lastFrameUrl]
|
||||
: [item.url]
|
||||
const extraOpts = item.lastFrameUrl
|
||||
? { aspectRatio: ratio, lastFrameUrl: item.lastFrameUrl, mode: 'pro' }
|
||||
: { aspectRatio: ratio }
|
||||
|
||||
try {
|
||||
const taskId = await Api.create(item.url, item.videoPrompt, extraOpts)
|
||||
return { item, taskId, error: null }
|
||||
@@ -169,14 +164,15 @@ async function phaseVideos(manifest, manifestPath, options) {
|
||||
if (val.ok && val.result.file) {
|
||||
val.item.video = path.relative(dir, val.result.file).replace(/\\/g, '/')
|
||||
val.item.videoDuration = val.result.duration
|
||||
val.item.status = 'done'
|
||||
delete val.item.videoTaskId
|
||||
} else if (val.item) {
|
||||
val.item.status = 'failed'
|
||||
val.item.error = val.error || '视频生成未返回文件'
|
||||
delete val.item.videoTaskId
|
||||
}
|
||||
saveManifest(manifestPath, manifest)
|
||||
}
|
||||
saveManifest(manifestPath, manifest)
|
||||
|
||||
// 上传视频到 OSS
|
||||
const { uploadFile } = require('../oss-upload')
|
||||
@@ -192,11 +188,9 @@ async function phaseVideos(manifest, manifestPath, options) {
|
||||
} catch (err) {
|
||||
log('videos', ` ${item.video} 上传失败: ${err.message}`)
|
||||
}
|
||||
saveManifest(manifestPath, manifest)
|
||||
}
|
||||
saveManifest(manifestPath, manifest)
|
||||
}
|
||||
|
||||
saveManifest(manifestPath, manifest)
|
||||
}
|
||||
|
||||
module.exports = { phaseVideos }
|
||||
|
||||
Reference in New Issue
Block a user