NodeJs 批量图片瘦身,重设尺寸和图片质量并保存到指定目录

源代码(image-resize.js)

/**
 * NodeJs 批量图片瘦身,重设尺寸和图片质量并保存到指定目录
 * 功能:批量图片瘦身,可指定宽度和图片质量并输出保存到指定目录
 * 使用:node image-resize.js
 * 扩展包:npm install images
 */

// 安装并引用 images 模块处理图片瘦身
const NmImages = require('images')
// 引用 fs 文件系统模块
const NmFs = require('fs')
// 引用 path 路径处理模块
const NmPath = require('path')

// 配置信息
const config = {
    // 图片格式后缀
    image_exts: ['jpg', 'png', 'gif', 'jpeg', 'webp', 'tiff'],
    // 图片大小与压缩质量配置
    quantity_config: {
        // 是否启用
        enable: true,
        // 注意:顺序必须从大到小
        values: [
            // 50 MB 以上 30
            {
                size: 52428800,
                quantity: 30
            },
            // 30 MB 以上 50
            {
                size: 31457280,
                quantity: 50
            },
            // 20 MB 以上 60
            {
                size: 20971520,
                quantity: 60
            },
            // 15 MB 以上 70
            {
                size: 15728640,
                quantity: 70
            },
            // 10 MB 以上 80
            {
                size: 10485760,
                quantity: 80
            },
            // 默认 80
            {
                size: 0,
                quantity: 80
            },
        ]
    }
}

/**
 * 批量生成缩略图到指定目录(目标目录的层级结构和来源目录保持一致)
 * @param {String} fromDir 来源目录
 * @param {String} toDir 目标目录
 * @param {Boolean} isDebug 是否调试模式。调试模式只会在控制台输出信息,不会真正操作文件
 * @param {Boolean} isSkipExists 是否跳过已存在的目标文件
 * @param {Number} width 图片宽度。最小 1080, 最大 3048
 * @param {Number} quality 图像质量
 */
async function resizeImages(fromDir, toDir, isDebug = true, isSkipExists = true, width = 0, quality = 80) {
    if (!NmFs.existsSync(fromDir)) {
        console.log('path not exists: ', fromDir);
        return;
    }
    // 自动创建目标路径
    if (!isDebug && !NmFs.existsSync(toDir)) {
        NmFs.mkdirSync(toDir, {
            recursive: true
        });
    }
    // 自动补齐路径符
    const SEP = NmPath.sep;
    if (!fromDir.endsWith(SEP)) {
        fromDir += SEP;
    }
    if (!toDir.endsWith(SEP)) {
        toDir += SEP;
    }
    // 打开目录
    const dir = await NmFs.promises.opendir(fromDir);
    // 声明变量,优化内存
    let ext = '',
        newPath = '',
        currentPath = '',
        fromFileSize = '0 B',
        toFileSize = '0 B';
    for await (const dirent of dir) {
        // 当前路径
        currentPath = fromDir + dirent.name;
        newPath = toDir + dirent.name;
        // 处理目录
        if (dirent.isDirectory()) {
            // 如果当前路径是目录,则进入递归模式
            resizeImages(currentPath + SEP, newPath + SEP, isDebug, isSkipExists, width, quality);
            continue;
        }
        // 处理文件
        ext = NmPath.extname(dirent.name); // .jpg
        if (!ext) {
            continue;
        }
        ext = ext.substring(1).toLowerCase();
        // 过滤非图片格式的文件
        if (!config.image_exts.includes(ext)) {
            continue;
        }
        // 自动图片质量
        fromFileSize = NmFs.statSync(currentPath).size;
        if (config.quantity_config.enable) {
            for (let kv of config.quantity_config.values) {
                if (fromFileSize > kv.size) {
                    quantity = kv.quantity;
                    break;
                }
            }
        }
        fromFileSize = bytesFormatter(fromFileSize);
        // 重设图片尺寸并保存
        if (isDebug) {
            console.log(`[缩略图] [${fromFileSize}/${toFileSize}/Q${quantity}]`, currentPath, '=>', newPath);
            continue;
        }
        // 判断是否过滤已存在的目标文件
        if (isSkipExists && NmFs.existsSync(newPath)) {
            toFileSize = bytesFormatter(NmFs.statSync(newPath).size);
            console.log(`[已存在] [${fromFileSize}/${toFileSize}/Q${quantity}]`, currentPath, `=>`, newPath);
            continue;
        }
        // 照片瘦身
        try {
            if (width > 0) {
                NmImages(currentPath).resize(width).save(newPath, {
                    quality: quality
                });
            } else {
                NmImages(currentPath).save(newPath, {
                    quality: quality
                });
            }
            toFileSize = bytesFormatter(NmFs.statSync(newPath).size);
            console.log(`[缩略图] [${fromFileSize}/${toFileSize}/Q${quantity}]`, currentPath, '=>', newPath);
        } catch (error) {
            console.log(`[错误] [${fromFileSize}/${toFileSize}/Q${quantity}]`, currentPath, '=>', newPath);
        }
    }
}

//文件大小换算
function bytesFormatter(bytes = 0) {
    if (bytes === 0) {
        return '0 B';
    }
    let k = 1024,
        sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
        i = Math.floor(Math.log(bytes) / Math.log(k));
    let num = bytes / Math.pow(k, i);
    // 优化:1023.999 KB 显示为 0.9xx MB
    if (num > 1000) {
        i += 1;
        num = bytes / Math.pow(k, i);
    }
    return num.toPrecision(3) + ' ' + sizes[i];
}

// 执行批量照片瘦身功能
// 注意,会内存溢出,是image插件的问题,暂时没有解决办法,只能报错后重新运行,循环接力直到完成
// NmImages.setGCThreshold(1024 * 1024 * 1024); // 设置内存自动回收阈值为 1GB。会出错!
const FROM_PATH = 'F:\\Downloads\\Images';
const TO_PATH = FROM_PATH + ' Lite';
const IS_DEBUG = false;
const IS_SKIP_EXISTS = true;
resizeImages(FROM_PATH, TO_PATH, IS_DEBUG, IS_SKIP_EXISTS).catch(err => console.log(err))

执行

node image-resize.js
posted on 2021-01-17 19:09  sochishun  阅读(1237)  评论(0编辑  收藏  举报