262 lines
7.4 KiB
JavaScript
Executable File
262 lines
7.4 KiB
JavaScript
Executable File
/**
|
||
* 批量上传视频脚本
|
||
* 从指定目录批量上传视频文件到服务器
|
||
*/
|
||
|
||
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 }; |