diff --git a/scripts/optimize-images.ts b/scripts/optimize-images.ts index 9151bf8e..803c54ee 100644 --- a/scripts/optimize-images.ts +++ b/scripts/optimize-images.ts @@ -16,14 +16,16 @@ import { default as getImageDimensions } from "image-size"; const SOURCE_DIRECTORY = "images"; const DESTINATION_DIRECTORY = path.join("public", "images"); -// directory where Meet the Team headshots are stored, relative to the source directory +// directories are relative to SOURCE_DIRECTORY const TEAM_IMAGES_DIRECTORY = path.join("team", ""); +const EVENTS_IMAGES_DIRECTORY = path.join("events", ""); const IMAGE_MINIMUM_SIZE = 512; const GET_ENCODER_FROM_EXTENSION: { [imageExtension: string]: string } = { jpg: "mozjpeg", jpeg: "mozjpeg", + JPG: "mozjpeg", png: "oxipng", }; @@ -35,6 +37,8 @@ const ENCODER_OPTIONS: { [encoder: string]: Record } = { void optimizeImages(); export async function optimizeImages() { + const startTime = Date.now(); + const imagePaths = await getFilePathsInDirectory(SOURCE_DIRECTORY); await fse.emptyDir(DESTINATION_DIRECTORY); @@ -42,52 +46,73 @@ export async function optimizeImages() { const numberOfWorkers = Math.min(cpus().length, 8); const imagePool = new ImagePool(numberOfWorkers); - await Promise.all( - imagePaths.map(async (imagePath) => { - const sourcePath = path.join(SOURCE_DIRECTORY, imagePath); - const destinationPath = path.join(DESTINATION_DIRECTORY, imagePath); - const fileExtension = imagePath.split(".").pop() ?? ""; - const encoder = GET_ENCODER_FROM_EXTENSION[fileExtension]; + // process smaller batches in order to reduce memory usage + const batchSize = 32; - if (!encoder) { - await fse.copy(sourcePath, destinationPath); - return; - } + for (let i = 0; i < imagePaths.length; i += batchSize) { + await Promise.all( + imagePaths.slice(i, i + batchSize).map(async (imagePath) => { + const imageStartTime = Date.now(); - const rawImageFile = await fse.readFile(sourcePath); - const ingestedImage = imagePool.ingestImage(rawImageFile); - const { width, height } = getImageDimensions(rawImageFile); + const sourcePath = path.join(SOURCE_DIRECTORY, imagePath); + const destinationPath = path.join(DESTINATION_DIRECTORY, imagePath); + const fileExtension = imagePath.split(".").pop() ?? ""; + const encoder = GET_ENCODER_FROM_EXTENSION[fileExtension]; - await ingestedImage.decoded; + if (!encoder) { + await fse.copy(sourcePath, destinationPath); + console.log( + `Copied ${imagePath} in ${getElapsedSeconds(imageStartTime)}s` + ); + return; + } - const shouldResize = - imagePath.startsWith(TEAM_IMAGES_DIRECTORY) && - (width ?? 0) > IMAGE_MINIMUM_SIZE && - (height ?? 0) > IMAGE_MINIMUM_SIZE; + const rawImageFile = await fse.readFile(sourcePath); + const ingestedImage = imagePool.ingestImage(rawImageFile); + const { width, height } = getImageDimensions(rawImageFile); - if (width && height && shouldResize) { - const smallerDimension = width < height ? "width" : "height"; + await ingestedImage.decoded; - // specifying only one dimension maintains the aspect ratio - const preprocessOptions = { - resize: { - enabled: true, - [smallerDimension]: IMAGE_MINIMUM_SIZE, - }, - }; + const shouldResize = + (imagePath.startsWith(TEAM_IMAGES_DIRECTORY) || + imagePath.startsWith(EVENTS_IMAGES_DIRECTORY)) && + (width ?? 0) > IMAGE_MINIMUM_SIZE && + (height ?? 0) > IMAGE_MINIMUM_SIZE; - await ingestedImage.preprocess(preprocessOptions); - } + if (width && height && shouldResize) { + const smallerDimension = width < height ? "width" : "height"; - const encodeOptions = { [encoder]: ENCODER_OPTIONS[encoder] }; - await ingestedImage.encode(encodeOptions); + // specifying only one dimension maintains the aspect ratio + const preprocessOptions = { + resize: { + enabled: true, + [smallerDimension]: IMAGE_MINIMUM_SIZE, + }, + }; - const encodedImage = await ingestedImage.encodedWith[encoder]; - await fse.outputFile(destinationPath, encodedImage.binary); - }) - ); + await ingestedImage.preprocess(preprocessOptions); + + console.log( + `Resized ${sourcePath} in ${getElapsedSeconds(imageStartTime)}s` + ); + } + + const encodeOptions = { [encoder]: ENCODER_OPTIONS[encoder] }; + await ingestedImage.encode(encodeOptions); + + const encodedImage = await ingestedImage.encodedWith[encoder]; + await fse.outputFile(destinationPath, encodedImage.binary); + + console.log( + `Optimized ${sourcePath} in ${getElapsedSeconds(imageStartTime)}s` + ); + }) + ); + } await imagePool.close(); + + console.log(`TOTAL DURATION: ${getElapsedSeconds(startTime)}s`); } async function getFilePathsInDirectory(directory: string): Promise { @@ -105,3 +130,7 @@ async function getFilePathsInDirectory(directory: string): Promise { ) ).flat(); } + +function getElapsedSeconds(startTime: number) { + return (Date.now() - startTime) / 1000; +}