first commit
This commit is contained in:
243
scripts/batch-upload-animal-videos.cjs
Normal file
243
scripts/batch-upload-animal-videos.cjs
Normal file
@@ -0,0 +1,243 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const execPromise = util.promisify(exec);
|
||||
|
||||
// 数据库连接
|
||||
const db = new sqlite3.Database('goguryeo_video.db');
|
||||
|
||||
// 视频文件目录
|
||||
const VIDEO_DIR = './video';
|
||||
const UPLOAD_VIDEO_DIR = './uploads/videos';
|
||||
const UPLOAD_COVER_DIR = './uploads/covers';
|
||||
|
||||
// 确保上传目录存在
|
||||
if (!fs.existsSync(UPLOAD_VIDEO_DIR)) {
|
||||
fs.mkdirSync(UPLOAD_VIDEO_DIR, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(UPLOAD_COVER_DIR)) {
|
||||
fs.mkdirSync(UPLOAD_COVER_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// 动物分类映射
|
||||
const animalCategoryMap = {
|
||||
'羊': { category: 5, tags: ['萌宠', '小羊', '可爱', '动物'] },
|
||||
'老虎': { category: 5, tags: ['萌宠', '老虎', '动物园', '可爱'] },
|
||||
'荷兰猪': { category: 5, tags: ['萌宠', '荷兰猪', '可爱'] },
|
||||
'兔子': { category: 5, tags: ['萌宠', '兔子', '可爱'] },
|
||||
'企鹅': { category: 5, tags: ['萌宠', '企鹅', '可爱'] },
|
||||
'牛': { category: 5, tags: ['动物', '小牛', '农村生活', '可爱'] },
|
||||
'猴子': { category: 5, tags: ['猴子', '可爱', '动物'] },
|
||||
'马': { category: 5, tags: ['萌宠', '矮马', '可爱'] },
|
||||
'水獭': { category: 5, tags: ['水獭', '萌宠', '治愈', '可爱'] },
|
||||
'鸟': { category: 5, tags: ['可爱', '治愈', '萌宠'] },
|
||||
'狗': { category: 5, tags: ['宠物', '狗子', '萌宠'] },
|
||||
'象': { category: 5, tags: ['小象', '萌宠', '可爱'] },
|
||||
'猪': { category: 5, tags: ['小香猪', '小猪', '可爱'] },
|
||||
'猫': { category: 5, tags: ['小猫咪', '萌宠', '可爱', '治愈'] }
|
||||
};
|
||||
|
||||
// 根据文件名识别动物类型
|
||||
function identifyAnimalType(filename) {
|
||||
for (const [animal, config] of Object.entries(animalCategoryMap)) {
|
||||
if (filename.includes(animal)) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
// 默认分类为民俗风情
|
||||
return { category: 5, tags: ['萌宠', '可爱', '动物'] };
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
function generateUniqueFilename(originalName) {
|
||||
const timestamp = Date.now();
|
||||
const randomStr = Math.random().toString(36).substring(2, 15);
|
||||
const ext = path.extname(originalName);
|
||||
return `${timestamp}_${randomStr}${ext}`;
|
||||
}
|
||||
|
||||
// 获取视频时长
|
||||
async function getVideoDuration(videoPath) {
|
||||
try {
|
||||
const { stdout } = await execPromise(`ffprobe -v quiet -show_entries format=duration -of csv=p=0 "${videoPath}"`);
|
||||
return Math.round(parseFloat(stdout.trim()));
|
||||
} catch (error) {
|
||||
console.log(`无法获取视频时长: ${videoPath}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成视频封面
|
||||
async function generateThumbnail(videoPath, outputPath) {
|
||||
try {
|
||||
await execPromise(`ffmpeg -i "${videoPath}" -ss 00:00:01 -vframes 1 -y "${outputPath}"`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(`生成封面失败: ${videoPath}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 插入视频到数据库
|
||||
function insertVideo(videoData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = `
|
||||
INSERT INTO videos (
|
||||
title, description, video_url, file_path, cover_url, cover_image,
|
||||
duration, file_size, category, tags, user_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
db.run(sql, [
|
||||
videoData.title,
|
||||
videoData.description,
|
||||
videoData.video_url,
|
||||
videoData.file_path,
|
||||
videoData.cover_url,
|
||||
videoData.cover_image,
|
||||
videoData.duration,
|
||||
videoData.file_size,
|
||||
videoData.category,
|
||||
videoData.tags,
|
||||
1 // admin user_id
|
||||
], function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(this.lastID);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 处理单个视频文件
|
||||
async function processVideo(filename) {
|
||||
try {
|
||||
const originalPath = path.join(VIDEO_DIR, filename);
|
||||
const stats = fs.statSync(originalPath);
|
||||
|
||||
// 生成新文件名
|
||||
const newVideoName = generateUniqueFilename(filename);
|
||||
const newCoverName = newVideoName.replace('.mp4', '_cover.jpg');
|
||||
|
||||
const newVideoPath = path.join(UPLOAD_VIDEO_DIR, newVideoName);
|
||||
const newCoverPath = path.join(UPLOAD_COVER_DIR, newCoverName);
|
||||
|
||||
// 复制视频文件
|
||||
fs.copyFileSync(originalPath, newVideoPath);
|
||||
console.log(`复制视频: ${filename} -> ${newVideoName}`);
|
||||
|
||||
// 生成封面
|
||||
const thumbnailGenerated = await generateThumbnail(newVideoPath, newCoverPath);
|
||||
|
||||
// 获取视频时长
|
||||
const duration = await getVideoDuration(newVideoPath);
|
||||
|
||||
// 识别动物类型和分类
|
||||
const animalConfig = identifyAnimalType(filename);
|
||||
|
||||
// 清理标题(移除文件扩展名和平台标识)
|
||||
const title = filename
|
||||
.replace('.mp4', '')
|
||||
.replace('-快手', '')
|
||||
.trim();
|
||||
|
||||
// 准备视频数据
|
||||
const videoData = {
|
||||
title: title,
|
||||
description: `可爱的动物视频:${title}`,
|
||||
video_url: `/uploads/videos/${newVideoName}`,
|
||||
file_path: `/uploads/videos/${newVideoName}`,
|
||||
cover_url: thumbnailGenerated ? `/uploads/covers/${newCoverName}` : null,
|
||||
cover_image: thumbnailGenerated ? `/uploads/covers/${newCoverName}` : null,
|
||||
duration: duration,
|
||||
file_size: stats.size,
|
||||
category: animalConfig.category,
|
||||
tags: animalConfig.tags.join(','),
|
||||
};
|
||||
|
||||
// 插入数据库
|
||||
const videoId = await insertVideo(videoData);
|
||||
console.log(`视频上传成功: ${title} (ID: ${videoId})`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
videoId: videoId,
|
||||
title: title,
|
||||
category: animalConfig.category
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error(`处理视频失败 ${filename}:`, error.message);
|
||||
return {
|
||||
success: false,
|
||||
filename: filename,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
try {
|
||||
console.log('开始批量上传动物视频...');
|
||||
|
||||
// 读取视频目录
|
||||
const files = fs.readdirSync(VIDEO_DIR)
|
||||
.filter(file => file.endsWith('.mp4'));
|
||||
|
||||
console.log(`找到 ${files.length} 个视频文件`);
|
||||
|
||||
const results = [];
|
||||
const categoryStats = {};
|
||||
|
||||
// 处理每个视频文件
|
||||
for (const file of files) {
|
||||
console.log(`\n处理: ${file}`);
|
||||
const result = await processVideo(file);
|
||||
results.push(result);
|
||||
|
||||
if (result.success) {
|
||||
const category = result.category;
|
||||
categoryStats[category] = (categoryStats[category] || 0) + 1;
|
||||
}
|
||||
|
||||
// 添加延迟避免系统过载
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
// 统计结果
|
||||
const successful = results.filter(r => r.success).length;
|
||||
const failed = results.filter(r => !r.success).length;
|
||||
|
||||
console.log('\n=== 上传完成 ===');
|
||||
console.log(`成功: ${successful} 个`);
|
||||
console.log(`失败: ${failed} 个`);
|
||||
|
||||
console.log('\n=== 分类统计 ===');
|
||||
for (const [categoryId, count] of Object.entries(categoryStats)) {
|
||||
console.log(`分类 ${categoryId}: ${count} 个视频`);
|
||||
}
|
||||
|
||||
if (failed > 0) {
|
||||
console.log('\n=== 失败的文件 ===');
|
||||
results.filter(r => !r.success).forEach(r => {
|
||||
console.log(`${r.filename}: ${r.error}`);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('批量上传失败:', error);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行脚本
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { main, processVideo };
|
||||
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 };
|
||||
133
scripts/clean-test-data.cjs
Executable file
133
scripts/clean-test-data.cjs
Executable file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 清理测试视频数据脚本
|
||||
* 删除所有视频记录、相关文件和数据库记录
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
|
||||
// 数据库路径
|
||||
const DB_PATH = path.join(process.cwd(), 'database', 'goguryeo_video.db');
|
||||
|
||||
// 视频文件存储目录
|
||||
const VIDEOS_DIR = path.join(process.cwd(), 'uploads', 'videos');
|
||||
const COVERS_DIR = path.join(process.cwd(), 'uploads', 'covers');
|
||||
const THUMBNAILS_DIR = path.join(process.cwd(), 'uploads', 'thumbnails');
|
||||
|
||||
async function cleanDatabase() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const db = new sqlite3.Database(DB_PATH, (err) => {
|
||||
if (err) {
|
||||
console.error('数据库连接失败:', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
console.log('已连接到数据库');
|
||||
});
|
||||
|
||||
// 开始事务
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION');
|
||||
|
||||
// 删除视频相关的所有数据
|
||||
const queries = [
|
||||
'DELETE FROM play_history',
|
||||
'DELETE FROM user_likes',
|
||||
'DELETE FROM video_stats',
|
||||
'DELETE FROM video_tags',
|
||||
'DELETE FROM videos',
|
||||
'DELETE FROM tags WHERE id > 10', // 保留默认标签
|
||||
// 重置自增ID
|
||||
'DELETE FROM sqlite_sequence WHERE name IN ("videos", "video_tags", "play_history", "user_likes", "video_stats")'
|
||||
];
|
||||
|
||||
let completed = 0;
|
||||
const total = queries.length;
|
||||
|
||||
queries.forEach((query, index) => {
|
||||
db.run(query, (err) => {
|
||||
if (err) {
|
||||
console.error(`执行查询失败 [${index}]:`, err);
|
||||
db.run('ROLLBACK');
|
||||
db.close();
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
completed++;
|
||||
console.log(`已完成查询 ${completed}/${total}: ${query.split(' ')[0]} ${query.split(' ')[2]}`);
|
||||
|
||||
if (completed === total) {
|
||||
db.run('COMMIT', (err) => {
|
||||
if (err) {
|
||||
console.error('提交事务失败:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log('数据库清理完成');
|
||||
resolve();
|
||||
}
|
||||
db.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function cleanFiles() {
|
||||
const directories = [VIDEOS_DIR, COVERS_DIR, THUMBNAILS_DIR];
|
||||
|
||||
for (const dir of directories) {
|
||||
if (fs.existsSync(dir)) {
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (const file of files) {
|
||||
// 跳过示例文件和隐藏文件
|
||||
if (file.startsWith('.') || file.startsWith('sample')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(dir, file);
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(`已删除文件: ${filePath}`);
|
||||
} catch (err) {
|
||||
console.error(`删除文件失败 ${filePath}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`已清理目录: ${dir}`);
|
||||
} else {
|
||||
console.log(`目录不存在: ${dir}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('开始清理测试数据...');
|
||||
|
||||
// 清理数据库
|
||||
console.log('\n=== 清理数据库 ===');
|
||||
await cleanDatabase();
|
||||
|
||||
// 清理文件
|
||||
console.log('\n=== 清理文件 ===');
|
||||
await cleanFiles();
|
||||
|
||||
console.log('\n✅ 测试数据清理完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 清理过程中出现错误:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { cleanDatabase, cleanFiles };
|
||||
124
scripts/redistribute-videos.cjs
Normal file
124
scripts/redistribute-videos.cjs
Normal file
@@ -0,0 +1,124 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
|
||||
// 数据库连接
|
||||
const db = new sqlite3.Database('goguryeo_video.db');
|
||||
|
||||
// 重新分配视频到不同分类
|
||||
const redistributionPlan = [
|
||||
// 将一些动物视频重新分类为传统艺术(表演类)
|
||||
{
|
||||
videoIds: [17], // 巨肺狗子(表演性质)
|
||||
newCategory: 2, // 传统艺术
|
||||
reason: '表演性质的内容'
|
||||
},
|
||||
// 将一些动物视频重新分类为语言学习(教育性质)
|
||||
{
|
||||
videoIds: [11, 15], // 小牛叫妈妈、水獭
|
||||
newCategory: 3, // 语言学习
|
||||
reason: '教育和学习性质的内容'
|
||||
},
|
||||
// 将一些动物视频重新分类为考古发现(自然探索)
|
||||
{
|
||||
videoIds: [9, 18], // 小老虎、小象
|
||||
newCategory: 4, // 考古发现
|
||||
reason: '自然探索和发现类内容'
|
||||
},
|
||||
// 将一些动物视频重新分类为其他
|
||||
{
|
||||
videoIds: [16, 20], // 小肥啾、小猫咪
|
||||
newCategory: 6, // 其他
|
||||
reason: '综合性内容'
|
||||
}
|
||||
];
|
||||
|
||||
// 更新视频分类
|
||||
function updateVideoCategory(videoId, newCategory) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = 'UPDATE videos SET category = ? WHERE id = ?';
|
||||
db.run(sql, [newCategory, videoId], function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(this.changes);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 获取视频信息
|
||||
function getVideoInfo(videoId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sql = 'SELECT id, title, category FROM videos WHERE id = ?';
|
||||
db.get(sql, [videoId], (err, row) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(row);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
try {
|
||||
console.log('开始重新分配视频分类...');
|
||||
|
||||
for (const plan of redistributionPlan) {
|
||||
console.log(`\n重新分配到分类 ${plan.newCategory}: ${plan.reason}`);
|
||||
|
||||
for (const videoId of plan.videoIds) {
|
||||
try {
|
||||
// 获取视频信息
|
||||
const videoInfo = await getVideoInfo(videoId);
|
||||
if (!videoInfo) {
|
||||
console.log(`视频 ID ${videoId} 不存在`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` ${videoInfo.title} (ID: ${videoId}) 从分类 ${videoInfo.category} -> ${plan.newCategory}`);
|
||||
|
||||
// 更新分类
|
||||
await updateVideoCategory(videoId, plan.newCategory);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`更新视频 ${videoId} 失败:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n重新分配完成!');
|
||||
|
||||
// 显示最终的分类统计
|
||||
console.log('\n=== 最终分类统计 ===');
|
||||
const sql = `
|
||||
SELECT c.name, COUNT(v.id) as video_count
|
||||
FROM categories c
|
||||
LEFT JOIN videos v ON c.id = v.category
|
||||
GROUP BY c.id, c.name
|
||||
ORDER BY c.id
|
||||
`;
|
||||
|
||||
db.all(sql, [], (err, rows) => {
|
||||
if (err) {
|
||||
console.error('查询统计失败:', err);
|
||||
} else {
|
||||
rows.forEach(row => {
|
||||
console.log(`${row.name}: ${row.video_count} 个视频`);
|
||||
});
|
||||
}
|
||||
db.close();
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('重新分配失败:', error);
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行脚本
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { main };
|
||||
Reference in New Issue
Block a user