/** * 诊断视频转码问题 * 检查指定素材的转码状态和可能的问题 */ import { PrismaClient } from '@prisma/client' import { existsSync } from 'fs' import { join } from 'path' import { access, constants } from 'fs/promises' const prisma = new PrismaClient() async function diagnoseMaterial(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 || 'null'}`) console.log(` 素材类型: ${material?.materialType || 'null'}`) console.log(` 创建时间: ${material?.createdAt || 'null'}`) console.log(` 转码状态: ${material?.videoProcessingStatus || 'null'}`) console.log(` 转码尝试次数: ${material?.transcodeAttempts || 0}`) console.log(` 最后错误: ${material?.transcodeLastError || '无'}`) console.log(` videoSourceUrl: ${material?.videoSourceUrl || 'null'}`) console.log(` zipFileName: ${material?.zipFileName || 'null'}`) console.log(` zipFilePath: ${material?.zipFilePath || '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 为空') console.log(' 可能原因:上传时未正确设置 videoSourceUrl') return } console.log(`\n📁 检查视频文件:`) const videoSourceUrl = String(material.videoSourceUrl || '') // 检查文件是否存在 let fileExists = false let filePath = '' if (videoSourceUrl.startsWith('http://') || videoSourceUrl.startsWith('https://')) { console.log(` ✅ 视频文件是 HTTP URL: ${videoSourceUrl}`) console.log(` ⚠️ 注意:转码需要下载远程文件,可能较慢`) fileExists = true } else { // 本地路径 if (videoSourceUrl.startsWith('/uploads/')) { const uploadDir = process.env.UPLOAD_DIR || './uploads' const absoluteUploadDir = uploadDir.startsWith('/') ? uploadDir : join(process.cwd(), uploadDir) filePath = join(absoluteUploadDir, videoSourceUrl.replace('/uploads/', '')) } else { filePath = videoSourceUrl.startsWith('/') ? videoSourceUrl : join(process.cwd(), videoSourceUrl) } console.log(` 检查路径: ${filePath}`) try { await access(filePath, constants.F_OK) fileExists = true console.log(` ✅ 视频文件存在`) } catch (error) { console.log(` ❌ 视频文件不存在`) console.log(` 错误: ${error}`) } } // 检查转码状态 console.log(`\n🔄 转码状态分析:`) const status = String(material?.videoProcessingStatus || '') if (!status || status === 'null') { console.log(` ❌ 问题:videoProcessingStatus 为 null`) console.log(` 可能原因:上传时未正确设置转码状态`) console.log(` 建议:手动触发转码`) } else if (status === 'pending') { console.log(` ⚠️ 状态:pending(等待转码)`) if (!fileExists) { console.log(` ❌ 问题:视频文件不存在,无法转码`) } else { console.log(` ✅ 文件存在,可以触发转码`) console.log(` 建议:调用 /api/materials/transcode POST 接口触发转码`) } } else if (status === 'processing') { console.log(` ⚠️ 状态:processing(转码中)`) console.log(` 可能原因:`) console.log(` 1. 转码任务正在执行`) console.log(` 2. 上次转码中断,状态未重置`) console.log(` 建议:检查服务器日志,或重置状态为 pending 后重试`) } else if (status === 'done') { console.log(` ✅ 状态:done(转码完成)`) console.log(` 预览视频: ${material?.videoPreview540Url || '无'}`) console.log(` 预览片段: ${material?.videoPreviewClipUrl || '无'}`) console.log(` 封面图: ${material?.videoCoverUrl || '无'}`) } else if (status === 'failed') { console.log(` ❌ 状态:failed(转码失败)`) console.log(` 错误信息: ${material?.transcodeLastError || '无'}`) console.log(` 建议:检查错误信息,修复问题后重试`) } // 检查转码产物 console.log(`\n📦 转码产物:`) console.log(` 540P预览视频: ${material?.videoPreview540Url || '无'}`) console.log(` 10秒预览片段: ${material?.videoPreviewClipUrl || '无'}`) console.log(` 封面图: ${material?.videoCoverUrl || '无'}`) // 总结 console.log(`\n📊 诊断总结:`) if (!fileExists && !videoSourceUrl.startsWith('http')) { console.log(` ❌ 主要问题:视频文件不存在`) console.log(` 解决方案:检查文件路径或重新上传`) } else if (!status || status === 'null') { console.log(` ❌ 主要问题:转码状态未设置`) console.log(` 解决方案:手动触发转码`) } else if (status === 'pending' && fileExists) { console.log(` ⚠️ 问题:转码任务未触发`) console.log(` 解决方案:调用转码 API 触发转码`) } else if (status === 'processing') { console.log(` ⚠️ 问题:转码状态卡在 processing`) console.log(` 解决方案:检查服务器日志,或重置状态后重试`) } else if (status === 'failed') { console.log(` ❌ 问题:转码失败`) console.log(` 解决方案:查看错误信息,修复后重试`) } else { console.log(` ✅ 状态正常`) } } catch (error) { console.error('诊断过程出错:', error) } finally { await prisma.$disconnect() } } // 主函数 async function main() { const materialId = process.argv[2] if (!materialId) { console.error('用法: tsx scripts/diagnose-video-transcode.ts ') console.error('示例: tsx scripts/diagnose-video-transcode.ts 0895f84d-8837-465e-be50-4db57b5eab9e') process.exit(1) } await diagnoseMaterial(materialId) } main().catch(console.error)