import fs from "fs"; import path from "path"; import { spawn } from "child_process"; import color from "picocolors"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const toktx = path.join(__dirname, "..", "..", "bin", "toktx.exe"); // 检查 toktx 是否存在 export function checkToktx() { if (!fs.existsSync(toktx)) { console.error("❌ 找不到 toktx.exe"); process.exit(1); } } // 扫描图片文件 export function scanImages(exts) { console.log("🔍 扫描目标文件中..."); const cwd = process.cwd(); const images = fs.readdirSync(cwd).filter(f => exts.some(ext => f.toLowerCase().endsWith("." + ext)) ); return { images, cwd }; } // 构建压缩参数 export function buildArgs(input, output, config) { const args = ["--t2"]; if (config.encoding === "uastc") { args.push("--encode", "uastc"); const zcmpLevel = { none: "0", standard: "10", high: "18", extreme: "22" }; args.push("--zcmp", zcmpLevel[config.quality] || "10"); } else if (config.encoding === "etc1s") { args.push("--encode", "etc1s"); } else if (config.encoding === "astc") { args.push("--encode", "astc"); const blkSize = { none: "8x8", standard: "6x6", high: "5x5", extreme: "4x4" }; args.push("--astc_blk_d", blkSize[config.quality] || "6x6"); } if (config.mipmap === "auto") { args.push("--genmipmap"); } args.push(output, input); return args; } // 压缩单个文件 export function compressFile(img, config, cwd, progress) { const baseName = img.replace(/\.[^.]+$/, ""); const out = baseName + ".ktx2"; // 点动画 let dots = 0; const dotAnim = setInterval(() => { const dotStr = ".".repeat(dots); process.stdout.write(`\r${progress} ${img} 正在转换中${dotStr} `); dots = dots >= 3 ? 0 : dots + 1; }, 300); process.stdout.write(`${progress} ${img} 正在转换中.`); const args = buildArgs(img, out, config); return new Promise((resolve) => { const proc = spawn(toktx, args, { cwd }); let stderr = ""; proc.stderr?.on("data", data => { stderr += data.toString(); }); proc.on("close", code => { clearInterval(dotAnim); if (code === 0) { console.log(`\r${progress} ${color.green("✓")} ${out} `); resolve({ success: true }); } else { console.log(`\r${progress} ${color.red("✗")} ${img} 失败 `); if (stderr) { console.log(color.dim(` 错误: ${stderr.trim()}`)); } resolve({ success: false, error: stderr }); } }); proc.on("error", err => { clearInterval(dotAnim); console.log(`\r${progress} ${color.red("✗")} ${img} 失败 `); console.log(color.dim(` 错误: ${err.message}`)); resolve({ success: false, error: err.message }); }); }); } // 批量压缩 export async function compressAll(images, config, cwd) { const total = images.length; let finished = 0; let failed = 0; for (const img of images) { finished++; const progress = `(${finished}/${total})`; const result = await compressFile(img, config, cwd, progress); if (!result.success) failed++; } return { total, failed }; }