Files
ggl/scripts/batch-upload-videos.cjs
2025-09-23 07:35:11 +00:00

262 lines
7.4 KiB
JavaScript
Executable File
Raw Permalink 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.

/**
* 批量上传视频脚本
* 从指定目录批量上传视频文件到服务器
*/
const fs = require('fs');
const path = require('path');
const FormData = require('form-data');
const axios = require('axios');
const sqlite3 = require('sqlite3').verbose();
// 配置
const VIDEO_SOURCE_DIR = '/www/wwwroot/ggl/video';
const SERVER_URL = 'http://localhost:3001';
const UPLOAD_ENDPOINT = '/api/videos/upload';
const LOGIN_ENDPOINT = '/api/auth/login';
const DB_PATH = path.join(process.cwd(), 'database', 'goguryeo_video.db');
// 测试用户
const TEST_USER = {
username: 'admin',
password: 'admin123'
};
// 全局token
let authToken = null;
// 支持的视频格式
const SUPPORTED_FORMATS = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv'];
// 默认专题映射(根据文件名关键词)
const TOPIC_MAPPING = {
'可爱': ['文化艺术', '民俗传统'],
'小': ['文化艺术'],
'动物': ['民俗传统'],
'萌': ['文化艺术'],
'治愈': ['文化艺术'],
'成长': ['文化艺术', '民俗传统']
};
// 登录获取token
async function login() {
try {
console.log('正在登录获取认证token...');
const response = await axios.post(`${SERVER_URL}${LOGIN_ENDPOINT}`, TEST_USER);
if (response.data.success && response.data.data && response.data.data.token) {
authToken = response.data.data.token;
console.log('✅ 登录成功获取到token');
return true;
} else {
console.error('❌ 登录失败:', response.data.message || '未知错误');
return false;
}
} catch (error) {
console.error('❌ 登录请求异常:', error.message);
return false;
}
}
// 获取专题ID
async function getTopicIds() {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database(DB_PATH, (err) => {
if (err) {
reject(err);
return;
}
});
db.all('SELECT id, name FROM topics WHERE status = "active"', (err, rows) => {
if (err) {
reject(err);
} else {
const topicMap = {};
rows.forEach(row => {
topicMap[row.name] = row.id;
});
resolve(topicMap);
}
db.close();
});
});
}
// 根据文件名推断专题
function inferTopics(filename, topicMap) {
const topicIds = [];
const lowerFilename = filename.toLowerCase();
for (const [keyword, topics] of Object.entries(TOPIC_MAPPING)) {
if (lowerFilename.includes(keyword)) {
topics.forEach(topicName => {
if (topicMap[topicName] && !topicIds.includes(topicMap[topicName])) {
topicIds.push(topicMap[topicName]);
}
});
}
}
// 如果没有匹配到专题,默认分配到"文化艺术"
if (topicIds.length === 0 && topicMap['文化艺术']) {
topicIds.push(topicMap['文化艺术']);
}
return topicIds;
}
// 生成视频标题和描述
function generateVideoInfo(filename) {
// 移除文件扩展名和特殊字符
let title = path.parse(filename).name;
title = title.replace(/[\-快手]/g, '').trim();
// 如果标题太长截取前50个字符
if (title.length > 50) {
title = title.substring(0, 50) + '...';
}
const description = `精彩视频内容:${title}`;
return { title, description };
}
// 上传单个视频文件
async function uploadVideo(filePath, topicMap) {
const filename = path.basename(filePath);
const { title, description } = generateVideoInfo(filename);
const topicIds = inferTopics(filename, topicMap);
console.log(`正在上传: ${filename}`);
console.log(`标题: ${title}`);
console.log(`专题: ${topicIds.map(id => Object.keys(topicMap).find(key => topicMap[key] === id)).join(', ')}`);
try {
const formData = new FormData();
formData.append('video', fs.createReadStream(filePath));
formData.append('title', title);
formData.append('description', description);
formData.append('category', '文化艺术');
formData.append('topicIds', JSON.stringify(topicIds));
if (!authToken) {
throw new Error('未获取到认证token请先登录');
}
const response = await axios.post(`${SERVER_URL}${UPLOAD_ENDPOINT}`, formData, {
headers: {
...formData.getHeaders(),
'Authorization': `Bearer ${authToken}`
},
maxContentLength: Infinity,
maxBodyLength: Infinity,
timeout: 300000 // 5分钟超时
});
if (response.data.code === 201 || response.data.code === 200) {
console.log(`✅ 上传成功: ${filename}`);
return { success: true, filename, videoId: response.data.data.id };
} else {
console.error(`❌ 上传失败: ${filename} - ${response.data.message}`);
return { success: false, filename, error: response.data.message };
}
} catch (error) {
console.error(`❌ 上传异常: ${filename} - ${error.message}`);
return { success: false, filename, error: error.message };
}
}
// 获取视频文件列表
function getVideoFiles(directory) {
if (!fs.existsSync(directory)) {
throw new Error(`目录不存在: ${directory}`);
}
const files = fs.readdirSync(directory);
const videoFiles = files.filter(file => {
const ext = path.extname(file).toLowerCase();
return SUPPORTED_FORMATS.includes(ext);
});
return videoFiles.map(file => path.join(directory, file));
}
// 主函数
async function main() {
try {
console.log('开始批量上传视频...');
console.log(`源目录: ${VIDEO_SOURCE_DIR}`);
// 先登录获取token
console.log('\n=== 用户认证 ===');
const loginSuccess = await login();
if (!loginSuccess) {
console.log('❌ 登录失败,无法继续上传');
return;
}
// 获取专题映射
console.log('\n=== 获取专题信息 ===');
const topicMap = await getTopicIds();
console.log('可用专题:', Object.keys(topicMap));
// 获取视频文件列表
console.log('\n=== 扫描视频文件 ===');
const videoFiles = getVideoFiles(VIDEO_SOURCE_DIR);
console.log(`发现 ${videoFiles.length} 个视频文件`);
if (videoFiles.length === 0) {
console.log('没有找到视频文件,退出程序');
return;
}
// 批量上传
console.log('\n=== 开始上传 ===');
const results = [];
for (let i = 0; i < videoFiles.length; i++) {
const filePath = videoFiles[i];
console.log(`\n[${i + 1}/${videoFiles.length}]`);
const result = await uploadVideo(filePath, topicMap);
results.push(result);
// 添加延迟避免服务器压力
if (i < videoFiles.length - 1) {
console.log('等待 2 秒...');
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
// 统计结果
console.log('\n=== 上传结果统计 ===');
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`总计: ${results.length} 个文件`);
console.log(`成功: ${successful.length}`);
console.log(`失败: ${failed.length}`);
if (failed.length > 0) {
console.log('\n失败的文件:');
failed.forEach(f => {
console.log(`- ${f.filename}: ${f.error}`);
});
}
console.log('\n✅ 批量上传完成!');
} catch (error) {
console.error('❌ 批量上传过程中出现错误:', error);
process.exit(1);
}
}
// 如果直接运行此脚本
if (require.main === module) {
main();
}
module.exports = { uploadVideo, getVideoFiles, getTopicIds };