/** * 手动触发视频转码 * 用于修复未触发转码的素材 */ import { PrismaClient } from '@prisma/client' import { enqueueVideoTranscodeTask } from '../lib/video-transcode-task' const prisma = new PrismaClient() async function triggerTranscode(materialId: string) { console.log(`\n==== 手动触发转码: ${materialId} ====\n`) try { // 查询素材信息 const material = await prisma.material.findUnique({ where: { id: materialId }, }) as any if (!material) { console.error('❌ 素材不存在') return } console.log('📋 素材信息:') console.log(` 素材ID: ${material.id}`) console.log(` 素材类型: ${material.materialType}`) console.log(` 转码状态: ${material.videoProcessingStatus || 'null'}`) console.log(` videoSourceUrl: ${material.videoSourceUrl || 'null'}`) // 检查是否为视频类型 const videoTypes = ['主图视频', '安装视频', '种草视频', '视频', 'VIDEO'] const materialType = String(material?.materialType || '') if (!videoTypes.includes(materialType)) { console.log('\n⚠️ 此素材不是视频类型,不需要转码') return } // 检查 videoSourceUrl if (!material?.videoSourceUrl) { console.log('\n❌ 错误:videoSourceUrl 为空,无法转码') return } // 判断是否需要生成封面图 const needsCoverImage = !material?.videoCoverUrl || String(material.videoCoverUrl || '').trim() === '' const hasPreviewUrl = material?.previewUrl && String(material.previewUrl || '').trim() !== '' console.log(`\n🔄 转码配置:`) console.log(` 需要生成封面: ${needsCoverImage}`) console.log(` 已有预览图: ${hasPreviewUrl}`) // 如果状态是 processing,先重置为 pending const currentStatus = String(material?.videoProcessingStatus || '') if (currentStatus === 'processing') { console.log('\n⚠️ 当前状态是 processing,重置为 pending...') await prisma.material.update({ where: { id: materialId }, data: { videoProcessingStatus: 'pending', transcodeLastError: null, } as any, }) console.log('✅ 状态已重置') } // 触发转码 console.log(`\n🚀 触发转码任务...`) try { await enqueueVideoTranscodeTask( material.id, String(material.videoSourceUrl || ''), needsCoverImage ) console.log('✅ 转码任务已入队') // 更新状态为 pending(如果之前是 null) if (!currentStatus || currentStatus === 'null') { await prisma.material.update({ where: { id: materialId }, data: { videoProcessingStatus: 'pending', } as any, }) console.log('✅ 状态已更新为 pending') } } catch (error: any) { console.error('❌ 入队失败:', error) console.error('错误详情:', error?.message || String(error)) console.error('堆栈:', error?.stack) // 记录错误到数据库 await prisma.material.update({ where: { id: materialId }, data: { videoProcessingStatus: 'pending', transcodeLastError: `手动触发失败: ${error?.message || String(error)}`, } as any, }) throw error } console.log(`\n✅ 转码任务已成功触发`) console.log(` 素材ID: ${materialId}`) console.log(` 视频源: ${material.videoSourceUrl}`) console.log(` 状态: pending -> 将转为 processing`) } catch (error) { console.error('\n❌ 操作失败:', error) throw error } finally { await prisma.$disconnect() } } // 主函数 async function main() { const materialId = process.argv[2] if (!materialId) { console.error('用法: tsx scripts/manual-trigger-transcode.ts ') console.error('示例: tsx scripts/manual-trigger-transcode.ts 0895f84d-8837-465e-be50-4db57b5eab9e') process.exit(1) } await triggerTranscode(materialId) } main().catch(console.error)