/** * 视频生成路由 * POST /api/video/generate — 提交生成任务 * GET /api/video/status/:id — 查询进度 * GET /api/video/preview/:id — 获取预览URL * GET /api/video/download/:id — 下载视频 */ const express = require('express'); const router = express.Router(); const { v4: uuidv4 } = require('uuid'); const videoDispatch = require('../services/video-dispatch'); // 任务存储 (P0: 内存Map,P1迁移到PostgreSQL) const tasks = new Map(); // ── 提交生成任务 ──────────────────────────────────── router.post('/generate', async (req, res) => { try { const { prompt, model = 'jimeng', duration = '5', resolution = '1080p', style } = req.body; if (!prompt || !prompt.trim()) { return res.status(400).json({ error: '提示词不能为空' }); } if (prompt.length > 3000) { return res.status(400).json({ error: '提示词超过1000字限制' }); } const taskId = uuidv4(); const task = { id: taskId, prompt: prompt.trim(), model, params: { duration, resolution, style }, status: 'pending', progress: 0, createdAt: new Date().toISOString(), completedAt: null, videoUrl: null, previewUrl: null, apiTaskId: null, error: null, }; tasks.set(taskId, task); // 异步启动生成 processVideoTask(taskId, req.app.get('io')); res.json({ taskId, status: 'pending', message: '视频生成任务已提交' }); } catch (err) { console.error('[POST /api/video/generate]', err); res.status(500).json({ error: '服务器内部错误' }); } }); // ── 查询进度 ──────────────────────────────────────── router.get('/status/:taskId', (req, res) => { const task = tasks.get(req.params.taskId); if (!task) return res.status(404).json({ error: '任务不存在' }); res.json({ taskId: task.id, status: task.status, progress: task.progress, videoUrl: task.previewUrl, error: task.error, createdAt: task.createdAt, completedAt: task.completedAt, }); }); // ── 预览URL ───────────────────────────────────────── router.get('/preview/:taskId', (req, res) => { const task = tasks.get(req.params.taskId); if (!task) return res.status(404).json({ error: '任务不存在' }); if (task.status !== 'completed') { return res.status(400).json({ error: '视频尚未生成完成', status: task.status }); } res.json({ previewUrl: task.previewUrl }); }); // ── 下载 ──────────────────────────────────────────── router.get('/download/:taskId', (req, res) => { const task = tasks.get(req.params.taskId); if (!task) return res.status(404).json({ error: '任务不存在' }); if (task.status !== 'completed') { return res.status(400).json({ error: '视频尚未生成完成' }); } res.json({ downloadUrl: task.videoUrl || task.previewUrl }); }); // ── 异步处理 ──────────────────────────────────────── async function processVideoTask(taskId, io) { const task = tasks.get(taskId); if (!task) return; const emit = (data) => io.emit('video:progress', { taskId, ...data }); try { // 1) 提交到即梦 task.status = 'generating'; task.progress = 10; emit({ status: 'generating', progress: 10, message: '正在提交到即梦...' }); const submitResult = await videoDispatch.submitTask({ prompt: task.prompt, duration: task.params.duration, resolution: task.params.resolution, style: task.params.style, }); task.apiTaskId = submitResult.taskId; task.progress = 20; emit({ status: 'generating', progress: 20, message: '任务已提交,等待生成...' }); // 2) 轮询 (每5秒,最多10分钟) const MAX_ATTEMPTS = 120; for (let i = 0; i < MAX_ATTEMPTS; i++) { await sleep(5000); const result = await videoDispatch.queryTask(submitResult.taskId); if (result.status === 'completed') { task.status = 'completed'; task.progress = 100; task.videoUrl = result.videoUrl; task.previewUrl = result.videoUrl; task.completedAt = new Date().toISOString(); emit({ status: 'completed', progress: 100, message: '✅ 视频生成完成!', videoUrl: result.videoUrl }); console.log(`[video] ✅ 完成: ${taskId}`); return; } if (result.status === 'failed') { throw new Error(result.error || '即梦返回生成失败'); } // 进度: 20→90 线性 const progress = Math.min(90, 20 + Math.floor((i / MAX_ATTEMPTS) * 70)); task.progress = progress; emit({ status: 'generating', progress, message: `生成中... ${progress}%` }); } throw new Error('生成超时(超过10分钟)'); } catch (err) { task.status = 'failed'; task.error = err.message; emit({ status: 'failed', progress: 0, message: `❌ ${err.message}` }); console.error(`[video] ❌ 失败: ${taskId}`, err.message); } } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } module.exports = router;