import path from 'path'; import fs from 'fs'; import { execSync } from 'child_process'; import { runInit, loadJSON, PIPELINE_SCRIPT, PROJECT_ROOT } from './shared'; import type { ToolDefinition } from './types'; export const generateVideos: ToolDefinition = { name: 'generate_videos', description: '图生视频:根据已有图片和提示词生成 AI 视频。内部创建临时 manifest,先上传图片再执行 videos 阶段。返回视频文件路径。', input_schema: { type: 'object', properties: { accountId: { type: 'string', description: '账号ID,用于继承视频模型等配置' }, imagePath: { type: 'string', description: '图片文件路径(本地绝对路径或相对 PROJECTROOT 的路径)' }, prompt: { type: 'string', description: '视频提示词(videoPrompt),描述图片如何动起来' }, videoModel: { type: 'string', description: '视频模型(可选,默认继承账号配置): veo3-fast, veo3-fast-frames, kling, grok' }, imagePrompt: { type: 'string', description: '图片提示词(可选),用于 manifest 记录' }, }, required: ['accountId', 'imagePath', 'prompt'], }, execute: async (params) => { const { accountId, imagePath, prompt, videoModel, imagePrompt } = params as Record; const resolvedImagePath = path.isAbsolute(imagePath) ? imagePath : path.resolve(PROJECT_ROOT, imagePath); if (!fs.existsSync(resolvedImagePath)) { return `错误: 图片文件不存在: ${resolvedImagePath}`; } const baseName = path.basename(resolvedImagePath); const item = { id: 1, shotDesc: imagePrompt || prompt, script: '', imagePrompt: imagePrompt || prompt, videoPrompt: prompt, keyword: 'generated', file: `images/${baseName}`, }; const manifestPath = runInit({ account: accountId, mode: 'single', items: [item], videoModel: videoModel, }); // Copy image into manifest's images dir const manifestDir = path.dirname(manifestPath); const imagesDir = path.join(manifestDir, 'images'); if (!fs.existsSync(imagesDir)) fs.mkdirSync(imagesDir, { recursive: true }); const targetPath = path.join(imagesDir, baseName); if (resolvedImagePath !== targetPath) { fs.copyFileSync(resolvedImagePath, targetPath); } // Run upload + videos phases execSync(`node "${PIPELINE_SCRIPT}" run --manifest "${manifestPath}" --phase upload,videos`, { cwd: PROJECT_ROOT, encoding: 'utf-8', }); const manifest = loadJSON(manifestPath) as { items?: Array<{ id: number; video?: string; videoUrl?: string; videoDuration?: number; status?: string }>; }; const results = (manifest.items || []).map((it) => ({ id: it.id, video: it.video || null, videoUrl: it.videoUrl || null, videoDuration: it.videoDuration || null, status: it.status, })); return JSON.stringify({ manifestPath, videos: results }, null, 2); }, };