UNPKG

sharp-pic

Version:

A tool for image compression

149 lines (127 loc) 4.37 kB
const fs = require('fs'); const path = require('path'); const jsonfile = require('jsonfile'); const pLimit = require('p-limit'); const sharp = require('sharp'); // 配置项 const exts = ['.jpg', '.png', '.jpeg']; const maxSize = 10 * 1024 * 1024; // 5MB const compressRecord = {}; // 压缩记录缓存 const limit = pLimit(3); // 并发限制 /** * 本地压缩函数 */ async function compressFile(inputPath, outputPath) { try { const meta = await sharp(inputPath).metadata(); let processor = sharp(inputPath); // 根据格式设置参数 if (meta.format === 'jpeg') { processor = processor.jpeg({ quality: 70, mozjpeg: true }); } else if (meta.format === 'png') { processor = processor.png({ quality: 75, compressionLevel: 9 }); } // 执行压缩 await processor.toFile(outputPath); // 计算压缩率 const origSize = fs.statSync(inputPath).size; const compressedSize = fs.statSync(outputPath).size; return { input: { size: origSize }, output: { size: compressedSize, ratio: (1 - compressedSize / origSize).toFixed(2) } }; } catch (err) { throw new Error(`压缩失败: ${err.message}`); } } /** * 处理单个文件(修复重复压缩) */ async function processFile(filePath, compressRecordPath) { // 获取相对路径作为唯一标识(替代MD5) const relativePath = path.relative(compressRecordPath, filePath) .replace(/\\/g, '/'); // 统一路径格式 // 检查是否已压缩过 if (compressRecord[compressRecordPath][relativePath]) { // console.log(`⏩ 跳过已处理文件: ${relativePath}`); return 'skip'; } let retries = 3; while (retries > 0) { try { const tempPath = `${filePath}.tmp${Date.now()}`; const data = await compressFile(filePath, tempPath); // 替换原文件 fs.unlinkSync(filePath); fs.renameSync(tempPath, filePath); // 更新记录(使用相对路径) compressRecord[compressRecordPath][relativePath] = { ratio: data.output.ratio, date: new Date().toISOString() }; console.log(`✅ [${relativePath}] 压缩成功,节省 ${data.output.ratio * 100}%`); return 'success'; } catch (error) { retries--; if (retries === 0) { console.log(`❌ 失败: ${relativePath}`, error.message); return 'fail'; } await new Promise(resolve => setTimeout(resolve, 300)); } } } /** * 递归处理目录(保持不变) */ async function processDirectory(folder, recordPath) { const files = fs.readdirSync(folder); let total = 0, success = 0, fail = 0; await Promise.all(files.map(file => limit(async () => { const filePath = path.join(folder, file); const stat = fs.statSync(filePath); if (stat.isFile() && exts.includes(path.extname(filePath).toLowerCase())) { if (stat.size > maxSize) { console.log(`⏭️ 跳过大文件: ${file} (${(stat.size / 1024 / 1024).toFixed(2)}MB)`); return; } total++; const result = await processFile(filePath, recordPath); if (result === 'success') success++; else if (result === 'fail') fail++; } else if (stat.isDirectory()) { const subResult = await processDirectory(filePath, recordPath); total += subResult.total; success += subResult.success; fail += subResult.fail; } }))); return { total, success, fail }; } /** * 入口函数(修复记录加载) */ async function startCompressImage(imagePath) { console.log('🖼️ 开始本地图片压缩...'); const recordFile = path.join(imagePath, 'tiny.json'); // 加载压缩记录 try { compressRecord[imagePath] = jsonfile.readFileSync(recordFile); console.log('🔍 找到历史压缩记录'); } catch (e) { compressRecord[imagePath] = {}; } // 执行压缩 const { total, success, fail } = await processDirectory(imagePath, imagePath); // 保存记录(使用相对路径) jsonfile.writeFileSync(recordFile, compressRecord[imagePath], { spaces: 2 }); console.log('\n📊 统计:'); console.log(`📂 文件总数: ${total}`); console.log(`✅ 成功: ${success}`); console.log(`❌ 失败: ${fail}`); console.log(`💾 记录文件: ${recordFile}`); } module.exports = startCompressImage;