diff --git a/optimize.js b/optimize.js index dc201d4..8d2042f 100644 --- a/optimize.js +++ b/optimize.js @@ -1,9 +1,9 @@ +import { spawn } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import guetzli from '@343dev/guetzli'; -import execBuffer from 'exec-buffer'; import gifsicle from 'gifsicle'; import pLimit from 'p-limit'; import sharp from 'sharp'; @@ -164,28 +164,30 @@ async function processJpeg({ fileBuffer, config, isLossless }) { const sharpImage = sharp(fileBuffer) .rotate(); // Rotate image using information from EXIF Orientation tag - if (isLossless) { - const inputBuffer = await sharpImage - .toColorspace('srgb') // Replace colorspace (guetzli works only with sRGB) - .jpeg({ quality: 100, optimizeCoding: false }) // Applying maximum quality to minimize losses during image processing with sharp + if (!isLossless) { + return sharpImage + .jpeg(config?.jpeg?.lossy || {}) .toBuffer(); - - return execBuffer({ - bin: guetzli, - args: [ - ...optionsToArguments({ - options: config?.jpeg?.lossless || {}, - }), - execBuffer.input, - execBuffer.output, - ], - input: inputBuffer, - }); } - return sharpImage - .jpeg(config?.jpeg?.lossy || {}) + const inputBuffer = await sharpImage + .toColorspace('srgb') // Replace colorspace (guetzli works only with sRGB) + .jpeg({ quality: 100, optimizeCoding: false }) // Applying maximum quality to minimize losses during image processing with sharp .toBuffer(); + + const commandOptions = [ + ...optionsToArguments({ + options: config?.jpeg?.lossless || {}, + }), + '-', + '-', + ]; + + return pipe({ + command: guetzli, + commandOptions, + inputBuffer, + }); } function processPng({ fileBuffer, config, isLossless }) { @@ -195,20 +197,20 @@ function processPng({ fileBuffer, config, isLossless }) { } function processGif({ fileBuffer, config, isLossless }) { - return execBuffer({ - bin: gifsicle, - args: [ - ...optionsToArguments({ - options: (isLossless ? config?.gif?.lossless : config?.gif?.lossy) || {}, - concat: true, - }), - `--threads=${os.cpus().length}`, - '--no-warnings', - '--output', - execBuffer.output, - execBuffer.input, - ], - input: fileBuffer, + const commandOptions = [ + ...optionsToArguments({ + options: (isLossless ? config?.gif?.lossless : config?.gif?.lossy) || {}, + concat: true, + }), + `--threads=${os.cpus().length}`, + '--no-warnings', + '-', + ]; + + return pipe({ + command: gifsicle, + commandOptions, + inputBuffer: fileBuffer, }); } @@ -220,3 +222,31 @@ function processSvg({ fileBuffer, config }) { ).data, ); } + +function pipe({ command, commandOptions, inputBuffer }) { + return new Promise((resolve, reject) => { + const process = spawn(command, commandOptions); + + process.stdin.write(inputBuffer); + process.stdin.end(); + + const stdoutChunks = []; + process.stdout.on('data', chunk => { + stdoutChunks.push(chunk); + }); + + process.on('error', error => { + reject(new Error(`Error processing image: ${error.message}`)); + }); + + process.on('close', code => { + if (code !== 0) { + reject(new Error(`Image optimization process exited with code ${code}`)); + return; + } + + const processedFileBuffer = Buffer.concat(stdoutChunks); + resolve(processedFileBuffer); + }); + }); +} diff --git a/package-lock.json b/package-lock.json index 4837dec..909a4cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,9 @@ "version": "10.0.0", "license": "MIT", "dependencies": { - "@343dev/guetzli": "^1.0.1", + "@343dev/guetzli": "^1.1.0", "cli-progress": "^3.11.0", "commander": "^12.1.0", - "exec-buffer": "^3.2.0", "fdir": "^6.4.0", "gifsicle": "^7.0.1", "p-limit": "^6.1.0", @@ -56,9 +55,9 @@ } }, "node_modules/@343dev/guetzli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@343dev/guetzli/-/guetzli-1.0.1.tgz", - "integrity": "sha512-M+hjr0IIuXrmAlUO/Qz92jE9ZkPUSMg28J8hu2JscCEH7SUDUL4G1uiWlNVGZwh7K3G+9Ln0nLctF9WGWy/D8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@343dev/guetzli/-/guetzli-1.1.0.tgz", + "integrity": "sha512-mcarbcAG2RnguYhQsbKmjQpvsRsCmvcjukqfy5MDIGVepj5BNOd6maFjoR45ePcyO7eqVYt2S6h34XGxc09DUA==", "hasInstallScript": true, "license": "Apache-2.0", "bin": { @@ -2453,6 +2452,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -2815,6 +2815,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3360,6 +3361,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/config-chain": { @@ -4509,22 +4511,6 @@ "dev": true, "license": "MIT" }, - "node_modules/exec-buffer": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", - "license": "MIT", - "dependencies": { - "execa": "^0.7.0", - "p-finally": "^1.0.0", - "pify": "^3.0.0", - "rimraf": "^2.5.4", - "tempfile": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -4907,6 +4893,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -5180,6 +5167,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -5522,6 +5510,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -7453,6 +7442,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -7965,6 +7955,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8627,19 +8618,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 68e4884..9a43925 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,9 @@ "node": ">=18.18" }, "dependencies": { - "@343dev/guetzli": "^1.0.1", + "@343dev/guetzli": "^1.1.0", "cli-progress": "^3.11.0", "commander": "^12.1.0", - "exec-buffer": "^3.2.0", "fdir": "^6.4.0", "gifsicle": "^7.0.1", "p-limit": "^6.1.0", diff --git a/tests/cli.test.js b/tests/cli.test.js index d3818cd..d9f3608 100644 --- a/tests/cli.test.js +++ b/tests/cli.test.js @@ -88,7 +88,7 @@ describe('CLI', () => { const stdout = runCliWithParameters(`--lossless ${workDirectory}${file}`); expectFileRatio({ - stdout, file, maxRatio: 50, minRatio: 45, + stdout, file, maxRatio: 55, minRatio: 45, }); });