gongxiangp2/scripts/fix-video-zip-ext.js
2026-03-06 14:14:50 +08:00

126 lines
3.3 KiB
JavaScript

const path = require('path')
const fs = require('fs/promises')
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
const VIDEO_TYPES = ['主图视频', '安装视频', '种草视频']
function detectExtFromHeader(buffer) {
if (buffer.slice(4, 8).toString('ascii').startsWith('ftyp')) {
return '.mp4'
}
if (buffer.slice(0, 4).toString('ascii') === 'RIFF') {
return '.avi'
}
if (buffer.slice(0, 4).toString('ascii') === 'fLaC') {
return '.flac'
}
if (buffer.slice(0, 3).toString('ascii') === 'ID3') {
return '.mp3'
}
return null
}
function resolvePath(filePath) {
if (!filePath) return ''
return path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath)
}
function buildStoredPath(originalStoredPath, newName) {
if (!originalStoredPath) {
return `uploads/${newName}`
}
const dir = path.dirname(originalStoredPath)
return path.join(dir, newName).replace(/\\/g, '/').replace(/^\.\//, '')
}
function buildVideoSourceUrl(videoSourceUrl, newName) {
if (!videoSourceUrl) {
return `/uploads/${newName}`
}
if (videoSourceUrl.startsWith('/uploads/')) {
const segments = videoSourceUrl.split('/')
segments[segments.length - 1] = newName
return segments.join('/')
}
if (videoSourceUrl.startsWith('uploads/')) {
return `uploads/${newName}`
}
return videoSourceUrl
}
async function main() {
const records = await prisma.material.findMany({
where: {
materialType: { in: VIDEO_TYPES },
zipFileName: { endsWith: '.zip' },
deletedAt: null,
},
select: {
id: true,
zipFileName: true,
zipFilePath: true,
videoSourceUrl: true,
},
})
if (records.length === 0) {
console.log('没有需要修复的视频文件')
return
}
let fixedCount = 0
let skippedMissing = 0
for (const record of records) {
try {
const absolutePath = resolvePath(record.zipFilePath)
const fileHandle = await fs.open(absolutePath, 'r')
const buffer = Buffer.alloc(16)
await fileHandle.read(buffer, 0, 16, 0)
await fileHandle.close()
const detectedExt = detectExtFromHeader(buffer) || '.mp4'
const newName = record.zipFileName.replace(/\.zip$/i, detectedExt)
const newAbsolutePath = path.join(path.dirname(absolutePath), newName)
try {
await fs.access(newAbsolutePath)
console.warn(`目标文件已存在,跳过重命名: ${newAbsolutePath}`)
} catch {
await fs.rename(absolutePath, newAbsolutePath)
}
const storedPath = buildStoredPath(record.zipFilePath, newName)
const sourceUrl = buildVideoSourceUrl(record.videoSourceUrl, newName)
await prisma.material.update({
where: { id: record.id },
data: {
zipFileName: newName,
zipFilePath: storedPath,
videoSourceUrl: sourceUrl,
},
})
fixedCount += 1
console.log(`已修复: ${record.id} -> ${newName}`)
} catch (error) {
skippedMissing += 1
console.error(`修复失败 ${record.id}:`, error.message)
}
}
console.log(`修复完成:${fixedCount} 条成功,${skippedMissing} 条失败`)
}
main()
.catch((error) => {
console.error('修复过程出错:', error)
process.exitCode = 1
})
.finally(async () => {
await prisma.$disconnect()
})