Files
WYF-koubo/scripts/prepare-package.cjs
T
2026-06-19 18:45:55 +08:00

347 lines
14 KiB
JavaScript
Raw 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-extra');
const path = require('path');
const { syncSystemSubtitleSources } = require('./sync-system-subtitle-sources.cjs');
async function preparePackage() {
console.log('📦 开始准备打包资源...\n');
console.log('='.repeat(60));
const extraDir = path.join(__dirname, '../electron/resources/extra');
const commonDir = path.join(extraDir, 'common');
// 0. 发布前同步系统字幕模板,避免安装包带入旧候选模板或旧音效配置
console.log('\n0锔忊儯 鍚屾绯荤粺瀛楀箷妯℃澘...');
const syncResult = syncSystemSubtitleSources();
console.log(` 鉁?宸插悓姝? ${syncResult.count} 涓郴缁熷瓧骞曟ā鏉垮埌鍙戝竷閰嶇疆\n`);
// 1. 确保目录存在
console.log('\n1️⃣ 确保目录结构存在...');
await fs.ensureDir(path.join(commonDir, 'python'));
await fs.ensureDir(path.join(commonDir, 'chromium'));
await fs.ensureDir(path.join(commonDir, 'python-scripts'));
await fs.ensureDir(path.join(commonDir, 'models'));
await fs.ensureDir(path.join(commonDir, 'fonts'));
await fs.ensureDir(path.join(commonDir, 'bgm'));
await fs.ensureDir(path.join(commonDir, 'demo'));
await fs.ensureDir(path.join(commonDir, 'config'));
await fs.ensureDir(path.join(commonDir, 'cover-templates'));
await fs.ensureDir(path.join(extraDir, 'win-x86'));
console.log(' ✅ 目录结构准备完成\n');
// 2. 复制 Python 脚本
console.log('2️⃣ 复制 Python 脚本...');
const pythonSrc = path.join(__dirname, '../python');
const pythonDest = path.join(commonDir, 'python-scripts');
const pythonFilter = (src) => {
const normalizedSrc = src.replace(/\\/g, '/');
const fileName = path.basename(normalizedSrc).toLowerCase();
if (normalizedSrc.includes('/__pycache__/') || fileName.endsWith('.pyc')) {
return false;
}
if (
fileName.includes('.backup') ||
fileName.endsWith('.bak') ||
fileName.includes('backup_') ||
fileName.startsWith('test_')
) {
return false;
}
return true;
};
if (fs.existsSync(pythonSrc)) {
await copyWithLockTolerance(pythonSrc, pythonDest, {
overwrite: true,
filter: pythonFilter
});
const fileCount = await countFiles(pythonDest);
console.log(` ✅ Python 脚本复制到 python-scripts 完成 (${fileCount} 个文件)`);
const pythonRuntimeDir = path.join(commonDir, 'python');
await copyWithLockTolerance(pythonSrc, pythonRuntimeDir, {
overwrite: true,
filter: pythonFilter
});
console.log(` ✅ Python 脚本复制到 python 运行时目录完成\n`);
} else {
console.log(' ⚠️ Python 脚本目录不存在,跳过\n');
}
// 3. 复制字体
console.log('3️⃣ 复制字体文件...');
const fontSrc = path.join(__dirname, '../ziti');
const fontDest = path.join(commonDir, 'fonts/ziti');
if (fs.existsSync(fontSrc)) {
if (process.env.OEM_BATCH_BUILD === '1' && await fs.pathExists(fontDest)) {
const existingFileCount = await countFiles(fontDest);
if (existingFileCount > 0) {
console.log(` [SKIP] OEM batch build reuses prepared fonts (${existingFileCount} files)\n`);
} else {
await copyWithLockTolerance(fontSrc, fontDest, { overwrite: true });
}
} else {
const sourceFileCount = await countFiles(fontSrc);
const sourceTotalSize = await getDirSize(fontSrc);
const destReady = await fs.pathExists(fontDest)
&& await countFiles(fontDest) === sourceFileCount
&& await getDirSize(fontDest) === sourceTotalSize;
if (destReady) {
console.log(` [SKIP] Fonts already prepared (${sourceFileCount} files, ${(sourceTotalSize / 1024 / 1024).toFixed(2)} MB)\n`);
} else {
await copyWithLockTolerance(fontSrc, fontDest, { overwrite: true });
}
}
const fileCount = await countFiles(fontDest);
const totalSize = await getDirSize(fontDest);
console.log(` ✅ 字体复制完成 (${fileCount} 个文件, ${(totalSize / 1024 / 1024).toFixed(2)} MB)\n`);
} else {
console.log(' ⚠️ 字体目录不存在,跳过\n');
}
// 4. 复制 BGM
console.log('4️⃣ 复制背景音乐...');
const bgmSrc = path.join(__dirname, '../bgm');
const bgmDest = path.join(commonDir, 'bgm');
if (fs.existsSync(bgmSrc)) {
if (process.env.OEM_BATCH_BUILD === '1' && await fs.pathExists(bgmDest)) {
const existingFileCount = await countFiles(bgmDest);
if (existingFileCount > 0) {
console.log(` [SKIP] OEM batch build reuses prepared BGM (${existingFileCount} files)\n`);
} else {
await copyWithLockTolerance(bgmSrc, bgmDest, { overwrite: true });
}
} else {
await copyWithLockTolerance(bgmSrc, bgmDest, { overwrite: true });
}
const fileCount = await countFiles(bgmDest);
const totalSize = await getDirSize(bgmDest);
console.log(` ✅ BGM 复制完成 (${fileCount} 个文件, ${(totalSize / 1024 / 1024).toFixed(2)} MB)\n`);
} else {
console.log(' ⚠️ BGM 目录不存在,跳过\n');
}
// 5. 复制 Demo
console.log('5️⃣ 复制演示文件...');
const demoSrc = path.join(__dirname, '../demo');
const demoDest = path.join(commonDir, 'demo');
if (fs.existsSync(demoSrc)) {
if (process.env.OEM_BATCH_BUILD === '1' && await fs.pathExists(demoDest)) {
const existingFileCount = await countFiles(demoDest);
if (existingFileCount > 0) {
console.log(` [SKIP] OEM batch build reuses prepared Demo (${existingFileCount} files)\n`);
} else {
await copyWithLockTolerance(demoSrc, demoDest, { overwrite: true });
}
} else {
await copyWithLockTolerance(demoSrc, demoDest, { overwrite: true });
}
const fileCount = await countFiles(demoDest);
const totalSize = await getDirSize(demoDest);
console.log(` ✅ Demo 复制完成 (${fileCount} 个文件, ${(totalSize / 1024 / 1024).toFixed(2)} MB)\n`);
} else {
console.log(' ⚠️ Demo 目录不存在,跳过\n');
}
// 6. 复制配置文件
console.log('6️⃣ 复制配置文件...');
const configDest = path.join(commonDir, 'config');
const configFiles = [
{
src: path.join(__dirname, '../electron/config/default-templates.json'),
dest: path.join(configDest, 'default-templates.json'),
name: 'default-templates.json'
},
{
src: path.join(__dirname, '../electron/config/default-subtitle-templates.json'),
dest: path.join(configDest, 'default-subtitle-templates.json'),
name: 'default-subtitle-templates.json'
},
{
src: path.join(__dirname, '../python/preset_templates.json'),
dest: path.join(configDest, 'preset_templates.json'),
name: 'preset_templates.json'
},
{
src: path.join(__dirname, '../electron/config/default-ipagent-config.json'),
dest: path.join(configDest, 'default-ipagent-config.json'),
name: 'default-ipagent-config.json'
},
{
src: path.join(__dirname, '../electron/resources/extra/common/config/system-templates.json'),
dest: path.join(configDest, 'system-templates.json'),
name: 'system-templates.json (系统模板配置 - 8套字幕+8套封面)'
}
];
let copiedCount = 0;
for (const file of configFiles) {
if (fs.existsSync(file.src)) {
const samePath = path.resolve(file.src) === path.resolve(file.dest);
if (samePath) {
const size = (await fs.stat(file.src)).size;
console.log(` ${file.name} 已在目标位置 (${(size / 1024).toFixed(2)} KB)`);
copiedCount++;
continue;
}
await copyWithLockTolerance(file.src, file.dest, { overwrite: true });
const size = (await fs.stat(file.dest)).size;
console.log(`${file.name} (${(size / 1024).toFixed(2)} KB)`);
copiedCount++;
} else {
console.log(` ⚠️ ${file.name} - 源文件不存在`);
}
}
console.log(` 📊 配置文件复制完成 (${copiedCount}/${configFiles.length})\n`);
// 7. 检查外部依赖
console.log('7️⃣ 检查外部依赖...');
// FFmpeg DLL 文件列表
const ffmpegDlls = [
'avcodec-61.dll',
'avdevice-61.dll',
'avfilter-10.dll',
'avformat-61.dll',
'avutil-59.dll',
'postproc-58.dll',
'swresample-5.dll',
'swscale-8.dll'
];
// 动态查找最新的 Playwright Chromium 版本
const playwrightDir = path.join(commonDir, 'playwright');
let playwrightChromiumCheck = null;
if (await fs.pathExists(playwrightDir)) {
const chromiumDirs = (await fs.readdir(playwrightDir))
.filter(f => f.startsWith('chromium-'))
.sort()
.reverse();
if (chromiumDirs.length > 0) {
const latestChromium = chromiumDirs[0];
const chromePath = path.join(playwrightDir, latestChromium, 'chrome-win64/chrome.exe');
const chromePathAlt = path.join(playwrightDir, latestChromium, 'chrome-win/chrome.exe');
playwrightChromiumCheck = {
name: `Playwright Chromium (${latestChromium})`,
path: await fs.pathExists(chromePath) ? chromePath : chromePathAlt
};
}
}
const checks = [
{ name: 'Python 环境', path: path.join(commonDir, 'python/python.exe') },
{ name: 'Python 依赖 (opencv)', path: path.join(commonDir, 'python/Lib/site-packages/cv2') },
{ name: 'FFmpeg (win-x86)', path: path.join(extraDir, 'win-x86/ffmpeg.exe') },
{ name: 'FFprobe (win-x86)', path: path.join(extraDir, 'win-x86/ffprobe.exe') },
...ffmpegDlls.map(dll => ({ name: `FFmpeg DLL (${dll})`, path: path.join(extraDir, `win-x86/${dll}`) })),
...(playwrightChromiumCheck ? [playwrightChromiumCheck] : []),
{ name: 'U2-Net 模型', path: path.join(commonDir, 'models/u2net.onnx') },
];
let allOk = true;
for (const check of checks) {
const exists = await fs.pathExists(check.path);
if (exists) {
console.log(`${check.name}`);
} else {
console.log(`${check.name} - 缺失`);
allOk = false;
}
}
console.log('\n' + '='.repeat(60));
if (!allOk) {
console.log('⚠️ 警告:某些外部依赖缺失');
console.log('\n请先运行: node scripts/download-dependencies.js');
console.log('参考文档: C:\\aigcpanel-main\\打包方案.md 的 "阶段 1: 准备外部依赖"\n');
console.log('='.repeat(60));
process.exit(1);
} else {
console.log('✅ 所有资源准备完成!可以开始打包了');
console.log('\n下一步:');
console.log(' npm run build:win # 开始打包 Windows 版本');
console.log(' npm run build:mac # 开始打包 macOS 版本');
console.log('\n📝 字幕模板说明:');
console.log(' 系统默认字幕模板配置:');
console.log(' • 位置: electron/config/default-subtitle-templates.json');
console.log(' • 功能: 包含所有参数(普通字幕、关键词特效、标题字幕、音效)');
console.log('\n导出字幕模板功能:');
console.log(' • 用户在应用中导出字幕模板为 JSON 文件');
console.log(' • 其他用户可通过导入功能导入这些 JSON 文件');
console.log(' • 支持备份、分享、版本控制');
console.log('\n='.repeat(60));
}
}
async function copyWithLockTolerance(src, dest, options = {}) {
try {
await fs.copy(src, dest, options);
return true;
} catch (error) {
if (error && error.code === 'EPERM') {
console.warn(` ⚠️ 跳过被占用的复制目标: ${dest}`);
console.warn(` 原因: ${error.message}`);
return false;
}
throw error;
}
}
// 辅助函数:统计文件数量
async function countFiles(dir) {
let count = 0;
async function walk(directory) {
const files = await fs.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stat = await fs.stat(filePath);
if (stat.isDirectory()) {
await walk(filePath);
} else {
count++;
}
}
}
if (await fs.pathExists(dir)) {
await walk(dir);
}
return count;
}
// 辅助函数:计算目录大小
async function getDirSize(dir) {
let size = 0;
async function walk(directory) {
const files = await fs.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stat = await fs.stat(filePath);
if (stat.isDirectory()) {
await walk(filePath);
} else {
size += stat.size;
}
}
}
if (await fs.pathExists(dir)) {
await walk(dir);
}
return size;
}
preparePackage().catch(console.error);