/** * Phase: videos — 视频生成(VEO / Grok / Kling) * * 图生视频,批量提交,生成后自动上传 OSS */ const path = require('path') const { saveManifest, ensureDir, log, getManifestDir } = require('./pipeline-utils') async function phaseVideos(manifest, manifestPath, options) { const dir = getManifestDir(manifestPath) const videosDir = path.join(dir, 'videos') ensureDir(videosDir) const accountConfig = options.accountConfig || {} const videoModel = manifest.videoModel || accountConfig.videoModel || 'veo3-fast-frames' const items = manifest.items.filter(it => it.status === 'done' && it.confirmed !== false && it.url && it.videoPrompt && !it.video ) if (items.length === 0) { log('videos', '无待处理 item,跳过'); return } // 选择生成器 let generator const modelLower = videoModel.toLowerCase() if (modelLower.includes('grok')) { generator = require('../grok-video-generator') } else if (modelLower.includes('kling')) { generator = require('../kling-video-generator') } else { generator = require('../veo-video-generator') } log('videos', `共 ${items.length} 个, 模型: ${videoModel}`) const tasks = items.map((item, i) => { const task = { id: item.id || i + 1, prompt: item.videoPrompt, image: item.url, outputDir: videosDir, } if (item.lastFrameUrl) { task.images = [item.url, item.lastFrameUrl] task.lastFrameUrl = item.lastFrameUrl } else { task.images = [item.url] } return task }) try { const results = await generator.batchGenerate(tasks, { videoModel, aspectRatio: manifest.format || '9:16', outputDir: videosDir, skipManifestWrite: true, }) for (let i = 0; i < results.length; i++) { const result = results[i] const item = items[i] if (!item) continue if (result.success && result.file) { item.video = path.relative(dir, result.file).replace(/\\/g, '/') item.videoDuration = result.duration } else { item.status = 'failed' item.error = result.error || '视频生成失败' log('videos', ` item ${(item.id || '?')} 失败: ${item.error}`) } } } catch (err) { log('videos', `批量生成失败: ${err.message}`) for (const item of items) { if (!item.video) { item.status = 'failed' item.error = `批量生成异常: ${err.message}` } } } // 上传视频到 OSS const { uploadFile } = require('../oss-upload') const videoItems = manifest.items.filter(it => it.video && !it.videoUrl) if (videoItems.length > 0) { log('videos', `上传 ${videoItems.length} 个视频到 OSS...`) for (const item of videoItems) { const videoPath = path.resolve(dir, item.video) try { const { url } = await uploadFile(videoPath) item.videoUrl = url log('videos', ` ${item.video} → OK`) } catch (err) { log('videos', ` ${item.video} 上传失败: ${err.message}`) } saveManifest(manifestPath, manifest) } } saveManifest(manifestPath, manifest) } module.exports = { phaseVideos }