gongxiangp2/scripts/check-video-source-files.ts

180 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 检查 video-source-* 格式的文件
* 可能这些文件还在,但数据库中的 videoSourceUrl 已经更新为 video-* 格式了
*/
import { PrismaClient } from '@prisma/client'
import { readdir, stat } from 'fs/promises'
import { existsSync } from 'fs'
import path from 'path'
const prisma = new PrismaClient()
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads'
const VIDEO_TYPES = ['主图视频', '安装视频', '种草视频']
async function main() {
console.log('==== 检查 video-source-* 格式的文件 ====\n')
// 1. 列出本地 uploads 目录中所有 video-source-*.mp4 文件
console.log('1. 检查本地 uploads 目录中的 video-source-*.mp4 文件:\n')
let videoSourceFiles: string[] = []
try {
const files = await readdir(UPLOAD_DIR)
videoSourceFiles = files.filter((f) => f.startsWith('video-source-') && f.endsWith('.mp4'))
console.log(`找到 ${videoSourceFiles.length} 个 video-source-*.mp4 文件\n`)
if (videoSourceFiles.length > 0) {
console.log('前 20 个文件:')
for (const file of videoSourceFiles.slice(0, 20)) {
const filePath = path.join(UPLOAD_DIR, file)
const stats = await stat(filePath)
console.log(` - ${file} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`)
}
if (videoSourceFiles.length > 20) {
console.log(` ... 还有 ${videoSourceFiles.length - 20} 个文件\n`)
}
}
} catch (error) {
console.log(`无法读取 uploads 目录: ${error}\n`)
}
// 2. 查询所有 videoSourceUrl 是本地路径但文件不存在的视频素材
console.log('\n2. 查询数据库中 videoSourceUrl 为本地路径但文件缺失的记录:\n')
const allVideos = await prisma.material.findMany({
where: {
deletedAt: null,
materialType: { in: VIDEO_TYPES } as any,
videoSourceUrl: {
not: null,
startsWith: '/uploads/video-',
} as any,
},
select: {
id: true,
materialType: true,
videoSourceUrl: true,
zipFileName: true,
createdAt: true,
},
})
// 找出缺失的文件
const missingFiles: typeof allVideos = []
for (const m of allVideos) {
const fileName = (m.videoSourceUrl as string).replace('/uploads/', '')
const localPath = path.join(UPLOAD_DIR, fileName)
if (!existsSync(localPath)) {
missingFiles.push(m)
}
}
console.log(`共找到 ${missingFiles.length} 条 videoSourceUrl 为本地路径但文件缺失的记录\n`)
// 3. 尝试匹配:检查这些缺失的文件是否对应 video-source-* 格式的文件
console.log('3. 尝试匹配:检查缺失的文件是否对应 video-source-* 格式的文件:\n')
let matchedCount = 0
const matchedFiles: Array<{
materialId: string
materialType: string
videoSourceUrl: string
videoSourceFile: string
}> = []
for (const material of missingFiles) {
const videoUrl = material.videoSourceUrl as string
const expectedFileName = videoUrl.replace('/uploads/video-', '')
// 尝试匹配 video-source-{materialId}-{timestamp}.mp4 格式
// 或者 video-source-{其他格式}.mp4
const possibleMatches = videoSourceFiles.filter((f) => {
// 如果文件名包含 materialId,可能是匹配的
if (f.includes(material.id)) {
return true
}
// 或者检查时间戳是否接近
const urlTimestamp = expectedFileName.match(/^([^-]+)/)?.[1]
const fileTimestamp = f.match(/video-source-[^-]+-(\d+)/)?.[1]
if (urlTimestamp && fileTimestamp) {
// 时间戳接近(相差不超过 1 小时)
const urlTime = parseInt(urlTimestamp)
const fileTime = parseInt(fileTimestamp)
const diff = Math.abs(urlTime - fileTime)
return diff < 3600000 // 1小时
}
return false
})
if (possibleMatches.length > 0) {
matchedCount++
matchedFiles.push({
materialId: material.id,
materialType: material.materialType,
videoSourceUrl: videoUrl,
videoSourceFile: possibleMatches[0],
})
if (matchedCount <= 10) {
console.log(`✅ 匹配到: [${material.id}] ${material.materialType}`)
console.log(` 数据库: ${videoUrl}`)
console.log(` 实际文件: ${possibleMatches[0]}\n`)
}
}
}
if (matchedCount > 10) {
console.log(`... 还有 ${matchedCount - 10} 个匹配\n`)
}
console.log(`\n匹配结果:`)
console.log(` 找到 ${matchedCount} 个可能的匹配`)
console.log(` 还有 ${missingFiles.length - matchedCount} 个未匹配\n`)
// 4. 检查是否有 video-source-* 文件但数据库中没有对应记录
console.log('4. 检查是否有 video-source-* 文件但数据库中没有对应记录:\n')
const orphanedFiles: string[] = []
for (const file of videoSourceFiles) {
// 尝试从文件名提取 materialId
const materialIdMatch = file.match(/video-source-([a-f0-9-]{36})/)
if (materialIdMatch) {
const materialId = materialIdMatch[1]
const exists = allVideos.some((m) => m.id === materialId)
if (!exists) {
orphanedFiles.push(file)
}
}
}
if (orphanedFiles.length > 0) {
console.log(`找到 ${orphanedFiles.length} 个孤立文件(数据库中没有对应记录):`)
orphanedFiles.slice(0, 10).forEach((f) => {
console.log(` - ${f}`)
})
if (orphanedFiles.length > 10) {
console.log(` ... 还有 ${orphanedFiles.length - 10} 个`)
}
} else {
console.log('没有找到孤立文件')
}
console.log('\n==== 检查完成 ====')
console.log(`\n💡 建议:`)
if (matchedCount > 0) {
console.log(`1. 发现 ${matchedCount} 个文件可能是 video-source-* 格式,需要重命名为 video-* 格式`)
console.log(`2. 或者更新数据库中的 videoSourceUrl 指向正确的文件名`)
}
console.log(`3. 检查 MinIO 中是否还有这些源文件(我们之前说过确认后再删除)`)
await prisma.$disconnect()
}
main().catch((error) => {
console.error('脚本执行出错:', error)
process.exit(1)
})