1+ import { spawn } from 'node:child_process' ;
12import fs from 'node:fs' ;
23import os from 'node:os' ;
34import path from 'node:path' ;
45
56import guetzli from '@343dev/guetzli' ;
6- import execBuffer from 'exec-buffer' ;
77import gifsicle from 'gifsicle' ;
88import pLimit from 'p-limit' ;
99import sharp from 'sharp' ;
@@ -164,28 +164,30 @@ async function processJpeg({ fileBuffer, config, isLossless }) {
164164 const sharpImage = sharp ( fileBuffer )
165165 . rotate ( ) ; // Rotate image using information from EXIF Orientation tag
166166
167- if ( isLossless ) {
168- const inputBuffer = await sharpImage
169- . toColorspace ( 'srgb' ) // Replace colorspace (guetzli works only with sRGB)
170- . jpeg ( { quality : 100 , optimizeCoding : false } ) // Applying maximum quality to minimize losses during image processing with sharp
167+ if ( ! isLossless ) {
168+ return sharpImage
169+ . jpeg ( config ?. jpeg ?. lossy || { } )
171170 . toBuffer ( ) ;
172-
173- return execBuffer ( {
174- bin : guetzli ,
175- args : [
176- ...optionsToArguments ( {
177- options : config ?. jpeg ?. lossless || { } ,
178- } ) ,
179- execBuffer . input ,
180- execBuffer . output ,
181- ] ,
182- input : inputBuffer ,
183- } ) ;
184171 }
185172
186- return sharpImage
187- . jpeg ( config ?. jpeg ?. lossy || { } )
173+ const inputBuffer = await sharpImage
174+ . toColorspace ( 'srgb' ) // Replace colorspace (guetzli works only with sRGB)
175+ . jpeg ( { quality : 100 , optimizeCoding : false } ) // Applying maximum quality to minimize losses during image processing with sharp
188176 . toBuffer ( ) ;
177+
178+ const commandOptions = [
179+ ...optionsToArguments ( {
180+ options : config ?. jpeg ?. lossless || { } ,
181+ } ) ,
182+ '-' ,
183+ '-' ,
184+ ] ;
185+
186+ return pipe ( {
187+ command : guetzli ,
188+ commandOptions,
189+ inputBuffer,
190+ } ) ;
189191}
190192
191193function processPng ( { fileBuffer, config, isLossless } ) {
@@ -195,20 +197,20 @@ function processPng({ fileBuffer, config, isLossless }) {
195197}
196198
197199function processGif ( { fileBuffer, config, isLossless } ) {
198- return execBuffer ( {
199- bin : gifsicle ,
200- args : [
201- ... optionsToArguments ( {
202- options : ( isLossless ? config ?. gif ?. lossless : config ?. gif ?. lossy ) || { } ,
203- concat : true ,
204- } ) ,
205- `--threads= ${ os . cpus ( ) . length } ` ,
206- '--no-warnings' ,
207- '--output' ,
208- execBuffer . output ,
209- execBuffer . input ,
210- ] ,
211- input : fileBuffer ,
200+ const commandOptions = [
201+ ... optionsToArguments ( {
202+ options : ( isLossless ? config ?. gif ?. lossless : config ?. gif ?. lossy ) || { } ,
203+ concat : true ,
204+ } ) ,
205+ `--threads= ${ os . cpus ( ) . length } ` ,
206+ '--no-warnings' ,
207+ '-' ,
208+ ] ;
209+
210+ return pipe ( {
211+ command : gifsicle ,
212+ commandOptions ,
213+ inputBuffer : fileBuffer ,
212214 } ) ;
213215}
214216
@@ -220,3 +222,31 @@ function processSvg({ fileBuffer, config }) {
220222 ) . data ,
221223 ) ;
222224}
225+
226+ function pipe ( { command, commandOptions, inputBuffer } ) {
227+ return new Promise ( ( resolve , reject ) => {
228+ const process = spawn ( command , commandOptions ) ;
229+
230+ process . stdin . write ( inputBuffer ) ;
231+ process . stdin . end ( ) ;
232+
233+ const stdoutChunks = [ ] ;
234+ process . stdout . on ( 'data' , chunk => {
235+ stdoutChunks . push ( chunk ) ;
236+ } ) ;
237+
238+ process . on ( 'error' , error => {
239+ reject ( new Error ( `Error processing image: ${ error . message } ` ) ) ;
240+ } ) ;
241+
242+ process . on ( 'close' , code => {
243+ if ( code !== 0 ) {
244+ reject ( new Error ( `Image optimization process exited with code ${ code } ` ) ) ;
245+ return ;
246+ }
247+
248+ const processedFileBuffer = Buffer . concat ( stdoutChunks ) ;
249+ resolve ( processedFileBuffer ) ;
250+ } ) ;
251+ } ) ;
252+ }
0 commit comments