163 lines
6.5 KiB
JavaScript
163 lines
6.5 KiB
JavaScript
import { readdir, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname, resolve } from 'path';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const wasmImports = {
|
|
/** @export */
|
|
__assert_fail: (condition, filename, line, func) => { console.log(condition, filename, line, func) },
|
|
/** @export */
|
|
emscripten_resize_heap: (size, old_size) => { console.log(size, old_size) },
|
|
/** @export */
|
|
fd_close: (fd) => { console.log(fd) },
|
|
/** @export */
|
|
fd_seek: (fd, offset, whence) => { console.log(fd, offset, whence) },
|
|
/** @export */
|
|
fd_write: (fd, buf, len, pos) => { console.log(fd, buf, len, pos) },
|
|
emscripten_memcpy_js: (dest, src, len) => { Module.HEAPU8.copyWithin(dest, src, src + len); },
|
|
}
|
|
var Module;
|
|
// 初始化 WASM 模块
|
|
async function initWasm() {
|
|
if (Module) {
|
|
return;
|
|
}
|
|
const wasmBinary = readFileSync(join(__dirname, './wasm/convert_image_to_webp.wasm'));
|
|
const info = {
|
|
'env': wasmImports,
|
|
'wasi_snapshot_preview1': wasmImports,
|
|
};
|
|
await WebAssembly.instantiate(wasmBinary, info).then(result => {
|
|
// console.log('wasmModule ok');
|
|
Module = {
|
|
...result.instance.exports,
|
|
HEAPU8: new Uint8Array(result.instance.exports.memory.buffer),
|
|
getValue: (ptr) => { return Module.HEAPU8[ptr] },
|
|
};
|
|
// processImages();
|
|
});
|
|
|
|
|
|
}
|
|
|
|
export function processImages(options) {
|
|
initWasm().then(async () => {
|
|
convertCount = 0;
|
|
failCount = 0;
|
|
await processDirectory(options.input, options.output, options);
|
|
//打印结束
|
|
console.log('Compression completed.');
|
|
console.log(`Converted: ${convertCount} Failed: ${failCount}`);
|
|
});
|
|
}
|
|
//转换个数
|
|
let convertCount = 0;
|
|
//失败个数
|
|
let failCount = 0;
|
|
// 新增处理目录的递归函数
|
|
function processDirectory(inputDir, outputDir, options, currentDepth = 0) {
|
|
return new Promise((resolve, reject) => {
|
|
readdir(inputDir, { withFileTypes: true }, async (err, entries) => {
|
|
if (err) {
|
|
console.log('Failed to scan directory: ' + err);
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 处理子目录
|
|
if (options.recursive && currentDepth < options.recursive) {
|
|
const dirPromises = entries
|
|
.filter(entry => entry.isDirectory())
|
|
.map(entry => {
|
|
const newInputPath = join(inputDir, entry.name);
|
|
if (resolve(newInputPath) === resolve(options.output)) {
|
|
return Promise.resolve();
|
|
}
|
|
const newOutputPath = join(outputDir, entry.name);
|
|
return processDirectory(newInputPath, newOutputPath, options, currentDepth + 1);
|
|
});
|
|
await Promise.all(dirPromises);
|
|
}
|
|
|
|
// 处理图片文件
|
|
const imageFiles = entries
|
|
.filter(entry => entry.isFile() && /\.(jpg|jpeg|png|gif|tga|bmp|psd|gif|hdr|pic)$/i.test(entry.name))
|
|
.map(entry => entry.name);
|
|
|
|
let completedFiles = 0;
|
|
let totalFiles = imageFiles.length;
|
|
|
|
// 压缩图片文件
|
|
imageFiles.forEach(file => {
|
|
const inputPath = join(inputDir, file);
|
|
const outputPath = join(outputDir, `${file.split('.')[0]}.webp`);
|
|
|
|
try {
|
|
// 读取图片文件
|
|
const inputBuffer = readFileSync(inputPath);
|
|
|
|
// 转换为 Uint8Array
|
|
const inputData = new Uint8Array(inputBuffer);
|
|
|
|
// 分配内存
|
|
const outputSizePtr = Module.malloc(4);
|
|
const inputDataPtr = Module.malloc(inputData.length);
|
|
Module.HEAPU8.set(inputData, inputDataPtr);
|
|
|
|
// 调用 WASM 函数进行转换(使用命令行指定的质量)
|
|
const webpPtr = Module.convert_image_to_webp(
|
|
inputDataPtr,
|
|
inputData.length,
|
|
0,
|
|
0,
|
|
options.quality, // 使用命令行指定的质量
|
|
outputSizePtr,
|
|
options.alpha ? 1 : 0
|
|
);
|
|
|
|
// 获取输出大小
|
|
const outputSize = Module.HEAPU8[outputSizePtr] |
|
|
(Module.HEAPU8[outputSizePtr + 1] << 8) |
|
|
(Module.HEAPU8[outputSizePtr + 2] << 16) |
|
|
(Module.HEAPU8[outputSizePtr + 3] << 24);;
|
|
|
|
// 获取 WebP 数据
|
|
const webpData = Buffer.from(
|
|
Module.HEAPU8.subarray(webpPtr, webpPtr + outputSize)
|
|
);
|
|
|
|
// 释放内存
|
|
Module.free(outputSizePtr);
|
|
Module.free(webpPtr);
|
|
Module.free(inputDataPtr);
|
|
|
|
// 保存文件
|
|
if (!existsSync(outputDir)) {
|
|
mkdirSync(outputDir, { recursive: true });
|
|
}
|
|
writeFileSync(outputPath, webpData);
|
|
|
|
// 更新进度
|
|
completedFiles++;
|
|
const originalSize = (inputBuffer.length / 1024).toFixed(2);
|
|
const compressedSize = (outputSize / 1024).toFixed(2);
|
|
console.log(`Compressed: ${file} ( - ${completedFiles}/${totalFiles})`);
|
|
console.log(`Compression ratio: ${originalSize}KB => ${compressedSize}KB =>${((compressedSize / originalSize) * 100).toFixed(2)}%`);
|
|
convertCount++;
|
|
} catch (error) {
|
|
console.error(`Error processing file ${file}:`, error);
|
|
failCount++;
|
|
}
|
|
});
|
|
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
}
|