# 视频转码问题修复说明 ## 问题描述 视频类素材上传后,后台转码任务未触发。素材 ID: `0895f84d-8837-465e-be50-4db57b5eab9e` ## 根本原因分析 通过诊断脚本发现以下问题: 1. **类型不匹配问题** - `lib/video-transcode-queue.ts` 的 `process` 方法中,`taskFn` 的类型定义缺少 `retryCount` 参数 - 导致 TypeScript 类型检查可能失败,或运行时参数传递错误 2. **错误被静默吞掉** - `app/api/materials/upload/route.ts` 中,转码任务入队失败时,错误被 catch 捕获但只打印日志 - 没有更新数据库状态,导致无法追踪问题 3. **动态导入可能失败** - 使用 `await import('@/lib/video-transcode-task')` 可能因模块路径、循环依赖等问题失败 - 错误被捕获后,用户无法感知 4. **缺少文件验证** - 入队前未验证视频文件是否存在 - 如果文件不存在,转码会失败,但错误信息不够明确 5. **队列处理逻辑不完善** - 队列处理时缺少详细的日志和错误处理 - 如果 `process` 方法本身出错,可能导致队列卡死 6. **恢复逻辑只在 POST 请求时触发** - 恢复逻辑只在转码 API 的 POST 方法中触发 - GET 请求(前端轮询)时不会触发恢复,导致 pending 状态的视频无法自动恢复 ## 修复内容 ### 1. 修复类型不匹配 (`lib/video-transcode-queue.ts`) **问题**:`process` 方法的 `taskFn` 类型定义与 `add` 方法不一致 **修复**: ```typescript // 修复前 private async process( taskFn: (materialId: string, videoSourceUrl: string, needsCoverImage: boolean) => Promise ): Promise // 修复后 private async process( taskFn: (materialId: string, videoSourceUrl: string, needsCoverImage: boolean, retryCount?: number) => Promise ): Promise ``` ### 2. 改进错误处理和日志 (`app/api/materials/upload/route.ts`) **问题**:入队失败时错误被静默吞掉 **修复**: - 改用静态导入,避免动态导入失败 - 入队失败时,记录详细错误信息到数据库 - 保持 `pending` 状态,允许手动重试 - 添加详细的日志输出 ```typescript // 修复后 import { enqueueVideoTranscodeTask } from '@/lib/video-transcode-task'; if (videoSourceUrl) { const needsCoverImage = !previewUrl || previewUrl.trim() === ''; try { await enqueueVideoTranscodeTask(material.id, videoSourceUrl, needsCoverImage); console.log('[Upload] ✅ Video transcode task enqueued successfully:', material.id); } catch (e: any) { // 记录详细错误信息到数据库 const errorMsg = e?.message || String(e) || '未知错误'; const errorStack = e?.stack ? `\n堆栈: ${e.stack.substring(0, 1000)}` : ''; const fullErrorMsg = `入队失败: ${errorMsg}${errorStack}`; console.error('[Upload] ❌ Failed to enqueue video transcode task:', { materialId: material.id, videoSourceUrl, error: errorMsg, stack: e?.stack, }); // 更新数据库状态,记录错误信息 await prisma.material.update({ where: { id: material.id }, data: { videoProcessingStatus: 'pending', transcodeLastError: fullErrorMsg.substring(0, 1000), } as any, }); } } ``` ### 3. 添加文件验证 (`lib/video-transcode-task.ts`) **问题**:入队前未验证文件是否存在 **修复**: - 添加 `validateVideoFile` 函数,验证视频文件是否存在 - 入队前先验证文件,如果不存在则抛出错误 ```typescript async function validateVideoFile(videoSourceUrl: string): Promise { // 验证逻辑... } export async function enqueueVideoTranscodeTask(...) { // 验证视频文件是否存在 const fileExists = await validateVideoFile(videoSourceUrl) if (!fileExists) { throw new Error(`视频文件不存在: ${videoSourceUrl}`) } // 入队... } ``` ### 4. 改进队列处理逻辑 (`lib/video-transcode-queue.ts`) **问题**:队列处理时缺少详细的日志和错误处理 **修复**: - 添加详细的日志输出 - 改进错误处理,确保队列不会卡死 - 添加任务参数验证 - 改进 `add` 方法,检查重复任务 ```typescript async add(...) { // 检查是否已在队列中等待 const alreadyInQueue = this.queue.some(t => t.materialId === materialId) if (alreadyInQueue) { console.log(`[TranscodeQueue] Material ${materialId} is already in queue, skipping`) return } console.log(`[TranscodeQueue] Adding task to queue: materialId=${materialId}...`) // ... } private async process(...) { try { // 验证任务参数 if (!task.materialId || !task.videoSourceUrl) { console.error(`[TranscodeQueue] Invalid task parameters:`, task) task.reject(new Error('Invalid task parameters')) return } // 详细的日志输出 console.log(`[TranscodeQueue] 🚀 Starting task for material ${task.materialId}...`) // 改进的错误处理 // ... } catch (error) { console.error('[TranscodeQueue] Error in process() method:', error) // 确保队列不会卡死 setTimeout(() => { this.process(taskFn).catch(err => { console.error('[TranscodeQueue] Error in retry process():', err) }) }, 1000) } } ``` ### 5. 改进恢复逻辑 (`app/api/materials/transcode/route.ts`) **问题**:恢复逻辑只在 POST 请求时触发 **修复**: - 在 GET 请求时也触发恢复逻辑 - 确保前端轮询时能自动恢复 pending 状态的视频 ```typescript // GET - 查询转码状态 export async function GET(request: NextRequest) { // 懒加载恢复:第一次调用时自动恢复中断的任务(包括 GET 请求) await ensureRecoveryCompleted(); // ... } ``` ## 新增工具 ### 1. 诊断脚本 (`scripts/diagnose-video-transcode.ts`) 用于诊断视频转码问题,检查: - 素材信息 - 视频文件是否存在 - 转码状态分析 - 转码产物检查 - 诊断总结和建议 **使用方法**: ```bash tsx scripts/diagnose-video-transcode.ts ``` ### 2. 手动触发转码脚本 (`scripts/manual-trigger-transcode.ts`) 用于手动触发视频转码,修复未触发转码的素材。 **使用方法**: ```bash tsx scripts/manual-trigger-transcode.ts ``` ## 验证修复 1. **检查修复后的代码** - 所有类型定义已统一 - 错误处理已完善 - 日志输出已详细 2. **测试新上传的视频** - 上传一个视频素材 - 检查是否自动触发转码 - 查看日志确认入队成功 3. **修复已有问题素材** - 使用诊断脚本检查问题素材 - 使用手动触发脚本修复 ## 后续建议 1. **监控和告警** - 添加转码任务入队失败的监控 - 设置告警,及时发现未触发的转码任务 2. **定期检查** - 定期检查 pending 状态的视频 - 自动触发未转码的视频 3. **改进上传逻辑** - 确保所有视频类型都正确设置 `videoSourceUrl` 为本地路径 - 对于旧版本的 S3 URL,转码时自动处理 ## 相关文件 - `app/api/materials/upload/route.ts` - 上传接口 - `lib/video-transcode-task.ts` - 转码任务封装 - `lib/video-transcode-queue.ts` - 转码队列 - `app/api/materials/transcode/route.ts` - 转码 API - `scripts/diagnose-video-transcode.ts` - 诊断脚本 - `scripts/manual-trigger-transcode.ts` - 手动触发脚本