diff --git a/.claude/skills/video-from-script/scripts/batch-pipeline.js b/.claude/skills/video-from-script/scripts/batch-pipeline.js index a41658f..0ae607d 100644 --- a/.claude/skills/video-from-script/scripts/batch-pipeline.js +++ b/.claude/skills/video-from-script/scripts/batch-pipeline.js @@ -304,6 +304,11 @@ function cmdNext(args) { return } + // 原子标记为 processing,防止同一行被重复取出 + item.status = 'processing' + batch.stats = calcStats(batch.items) + writeJson(manifestPath, batch) + const result = { done: false, row: item.row, diff --git a/.claude/skills/video-from-script/scripts/lib/phase-videos.js b/.claude/skills/video-from-script/scripts/lib/phase-videos.js index 8222520..e0f5f6c 100644 --- a/.claude/skills/video-from-script/scripts/lib/phase-videos.js +++ b/.claude/skills/video-from-script/scripts/lib/phase-videos.js @@ -43,6 +43,8 @@ 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 + // 已有视频(本地文件或远程 URL)且状态为 done → 跳过,避免重复生成 + if (it.status === 'done' && (it.video || it.videoUrl)) return false return ['done', 'pending', 'failed'].includes(it.status) }) @@ -54,7 +56,9 @@ async function phaseVideos(manifest, manifestPath, options) { if (it.confirmed === false) reasons.push("confirmed=false") if (!it.url) reasons.push("缺少 url(图片未上传)") if (!it.videoPrompt) reasons.push("缺少 videoPrompt") - if (it.confirmed !== false && it.url && it.videoPrompt && !["done","pending","failed"].includes(it.status)) { + if (it.status === 'done' && (it.video || it.videoUrl)) { + reasons.push("视频已生成,已跳过") + } else if (!["done","pending","failed"].includes(it.status)) { reasons.push("status=" + (it.status || "undefined") + "(不在 done/pending/failed 中)") } console.log(" - item", it.id || manifest.items.indexOf(it), ":", reasons.length > 0 ? reasons.join(", ") : "已满足全部条件(不应在此)") @@ -66,6 +70,20 @@ async function phaseVideos(manifest, manifestPath, options) { const items = [] for (const it of videoCandidates) { + // 磁盘兜底:本地视频文件已存在则恢复引用并跳过 + if (!it.video && it.id) { + const fs = require('fs') + const existingVideos = fs.readdirSync(videosDir).filter(f => + f.includes('_item' + it.id + '_') || f.includes('_item' + it.id + '.') + ) + if (existingVideos.length > 0) { + it.video = 'videos/' + existingVideos[existingVideos.length - 1] + it.status = 'done' + delete it.videoTaskId + log('videos', ` item ${it.id} 发现已有视频文件 ${it.video},跳过生成`) + continue + } + } if (it.video || it.videoUrl) { if (it.status === 'done') continue delete it.video