Skip to content

Commit

Permalink
Image worker added
Browse files Browse the repository at this point in the history
  • Loading branch information
darsan-in committed Jun 26, 2024
1 parent 0b76d58 commit ff4690b
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 126 deletions.
247 changes: 247 additions & 0 deletions lib/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import { ImagePool } from "@squoosh/lib";
import { appendFileSync, readFileSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { cpus } from "os";
import { basename, dirname, extname, join, relative } from "path";
import { optimize } from "svgo";

import configurations from "../configLoader";
import {
ImageWorkerOutputTypes,
avifEncodeOptions,
jpgEncodeOptions,
webpEncodeOptions,
} from "./options";
import { currentTime, makeDirf } from "./utils";
const {
jpgEncodeOptions,
webpEncodeOptions,
avifEncodeOptions,
svgOptions,
cpuAllocation,
} = configurations.encodeOptions;

function _writeBinaryImage(
encodeResult: Record<string, any>,
filePath: string,
): Promise<void> {
const fileType: string = Object.keys(encodeResult.encodedWith)[0];
const extension: string = encodeResult.encodedWith[fileType].extension;

const outputPath: string = join(
configurations.destPath,
dirname(relative(process.cwd(), filePath)),
`${basename(filePath, extname(filePath))}.${extension}`,
);

//make dir
makeDirf(dirname(outputPath));

const outputImage: BinaryType =
encodeResult.encodedWith[fileType].binary;

return new Promise((resolve, reject) => {
writeFile(outputPath, outputImage, {
encoding: "binary",
})
.then(() => {
resolve();
})
.catch((error: Error) => {
reject(`Error writing output: ${outputPath}\n` + error.message);
});
});
}

function _encodeImages(
images: string[],
encodeOptions: Record<string, any>,
): Promise<void> {
//number of concurrent process.
/* 50 percentage of core count If there is no cpu allocation in Settings */
const threadCount: number =
cpuAllocation ?? Math.round((50 * cpus().length) / 100);

const pool = new ImagePool(threadCount);

/* Ingest images in pool */
const imagesRecords: {
encodeResult: Record<string, any>;
filePath: string;
}[] = images.map((filePath: string) => {
return {
encodeResult: pool.ingestImage(readFileSync(filePath)),
filePath: filePath,
};
});

/* Encode ingested images and save it */
const outputPromises: Promise<void>[] = imagesRecords.map(
(imagesRecord): Promise<void> => {
return new Promise((resolve, reject) => {
let { encodeResult, filePath } = imagesRecord;

encodeResult
.encode(encodeOptions)
.then(() => {
_writeBinaryImage(encodeResult, filePath)
.then(() => {
resolve();
})
.catch((err: Error) => {
reject(err.message);
});
})
.catch((err: Error) => {
reject(err.message);
});
});
},
);

return new Promise((resolve, reject) => {
Promise.all(outputPromises)
.then(() => {
//closing image pool
pool
.close()
.then(() => {
resolve();
})
.catch((err: Error) => {
reject(err.message);
});
})
.catch((err: Error) => {
reject(err.message);
});
});
}

async function _svgBatchHandler(
outputPromises: (() => Promise<void>)[],
): Promise<void> {
const promiseBatches: (() => Promise<void>)[][] = [];

const batchSize: number = cpuAllocation * 4;

for (let i = 0; i < outputPromises.length; i += batchSize) {
promiseBatches.push(outputPromises.slice(i, i + batchSize));
}

for (const batch of promiseBatches) {
const activatedBatch: Promise<void>[] = batch.map((func) => func());

await Promise.all(activatedBatch);
}
}

async function _svgWorker(svgImagePaths: string[]): Promise<void> {
const outputPromises: (() => Promise<void>)[] = svgImagePaths.map(
(svgPath: string) => {
return (): Promise<void> => {
return new Promise((resolve, reject) => {
readFile(svgPath, { encoding: "utf8" })
.then((svgData) => {
let outputSvgData: string = "";

let failed: boolean = false;

try {
outputSvgData = optimize(svgData, svgOptions as any)?.data;
} catch (err: any) {
const logMessage: string = `${svgPath} => ${err}`;

appendFileSync(
join(process.cwd(), "minomax.err.log"),
logMessage,
);

failed = true;
}

if (!failed) {
const outputPath: string = join(
configurations.destPath,
relative(process.cwd(), svgPath),
);

//Create directory Recursively
makeDirf(dirname(outputPath));

writeFile(outputPath, outputSvgData, {
encoding: "utf8",
})
.then(() => {
resolve();
})
.catch((error: Error) => {
reject(
`\nError writing svg file ${outputPath}\n` + error,
);
});
} else {
resolve();
}
})
.catch((error: Error) => {
reject(`\nError: ${basename(svgPath)}\n` + error);
});
});
};
},
);

try {
await _svgBatchHandler(outputPromises);
} catch (err: any) {
console.log(err);
process.exit(1);
}
}

export async function imageWorker(
imagePaths: string[],
targetFormat: ImageWorkerOutputTypes,
): Promise<void> {
process.on("SIGINT", () => {
process.exit();
});

console.log(`Number of images: ${imagePaths.length}`);

console.log(`[${currentTime()}] +++> Image Encoding Started`);

if (targetFormat === "svg") {
await _svgWorker(imagePaths);
console.log(
`[${currentTime()}] ===> Images are optimised with SVG format.`,
);
} else {
let encodeOptions:
| jpgEncodeOptions
| avifEncodeOptions
| webpEncodeOptions;

if (targetFormat === "avif") {
//avifEncodeOptions
encodeOptions = avifEncodeOptions;
} else if (targetFormat === "webp") {
//webpEncodeOptions
encodeOptions = webpEncodeOptions;
} else if (targetFormat === "jpg") {
//jpgEncodeOptions
encodeOptions = jpgEncodeOptions;
} else {
console.log("Provided type is not supported");
process.exit(1);
}

//encoding for jpg/avif/webp
await _encodeImages(imagePaths, encodeOptions);

console.log(
`[${currentTime()}] ===> Images are optimised with ${targetFormat.toUpperCase()} format.`,
);
}
}
38 changes: 21 additions & 17 deletions lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ interface mozjpegOptions {
chroma_quality: number; //set only if seperate cq is enabled
}

interface jpgEncodeOptions {
mozjpeg: mozjpegOptions;
}

interface webpOption {
method: number; //effort 0-6
quality: number; //0-100
Expand All @@ -38,10 +34,6 @@ interface webpOption {
partitions: number; //0-3
}

interface webpEncodeOptions {
webp: webpOption;
}

interface avifOptions {
cqLevel: number; //quality (62-0)
subsample: number; //subsample chroma (1 / 0)
Expand All @@ -53,7 +45,15 @@ interface avifOptions {
speed: number; //max effort (10-0)
}

interface avifEncodeOptions {
export interface jpgEncodeOptions {
mozjpeg: mozjpegOptions;
}

export interface webpEncodeOptions {
webp: webpOption;
}

export interface avifEncodeOptions {
avif: avifOptions;
}

Expand Down Expand Up @@ -124,28 +124,32 @@ interface svgOptions {
plugins: Partial<svgoPlugings>[];
}

type screenSizesOptions = "1x" | "2x" | "3x" | "4x" | "5x" | "6x";

interface imageSetConfigurations {
/*
Image set generator settings.
Sizes are width dependent.
Pixel unit is used in size.
Size always a upperlimit for each set (Example: x1:600) where 600px is upper limit
*/
screenSizes: {
"1X": number;
"2X": number;
"3X": number;
"4X": number;
"5X": number;
}; //Screen sizes upper-limits
screenSizes: Partial<Record<screenSizesOptions, number>>; //Screen sizes upper-limits
upscaleLevel: "l1" | "l2" | "l3";
fileSuffix: string;
}

export interface ConfigurationOptions {
interface encodeOptions {
jpgEncodeOptions: jpgEncodeOptions;
webpEncodeOptions: webpEncodeOptions;
avifEncodeOptions: avifEncodeOptions;
svgOptions: svgOptions;
cpuAllocation: number;
}

export interface ConfigurationOptions {
encodeOptions: encodeOptions;
imageSetConfigurations: imageSetConfigurations;
destPath: string;
}

export type ImageWorkerOutputTypes = "jpg" | "avif" | "webp" | "svg";
10 changes: 10 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { mkdirSync } from "fs";

export function makeDirf(dirPath: string): void {
mkdirSync(dirPath, { recursive: true });
}

export function currentTime(): string {
const currentDate = new Date();
return `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`;
}
Loading

0 comments on commit ff4690b

Please sign in to comment.