first commit
This commit is contained in:
262
scripts/batch-upload-videos.cjs
Executable file
262
scripts/batch-upload-videos.cjs
Executable file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* 批量上传视频脚本
|
||||
* 从指定目录批量上传视频文件到服务器
|
||||
*/
|
||||
|
||||
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 };
|
||||
Reference in New Issue
Block a user