UNPKG

diginext-img-magic-cli

Version:
320 lines • 13.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const yargs_1 = __importDefault(require("yargs")); const promises_1 = __importDefault(require("fs/promises")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const async_1 = require("async"); const diginext_img_magic_1 = require("diginext-img-magic"); const isImageSupported_1 = __importDefault(require("diginext-img-magic/dist/compress/isImageSupported")); const url_1 = require("diginext-utils/dist/string/url"); const getAllFiles_1 = __importDefault(require("./plugins/getAllFiles")); const { version } = require("../package.json"); class ImageConverter { constructor(options) { this.outputDirWebp = ""; this.outputDirThumb = ""; this.results = []; this.processedCount = 0; this.options = options; this.setupOutputDirectories(); } setupOutputDirectories() { const normalizedDir = this.options.dir.replace(/\\/g, "/"); const folderName = (0, url_1.getFileNameWithoutExtension)(normalizedDir); const parentDir = path_1.default.dirname(normalizedDir); this.outputDirWebp = path_1.default.join(parentDir, `${folderName}-webp`); this.outputDirThumb = path_1.default.join(parentDir, `${folderName}-thumb-webp`); } async ensureDirectoryExists(dirPath) { try { if (fs_1.default.existsSync(dirPath)) { await promises_1.default.rm(dirPath, { recursive: true }); } await promises_1.default.mkdir(dirPath, { recursive: true }); } catch (error) { throw new Error(`Failed to create directory ${dirPath}: ${error}`); } } async validateDirectory() { try { const stats = await promises_1.default.lstat(this.options.dir); if (!stats.isDirectory()) { throw new Error("Path is not a directory"); } } catch (error) { throw new Error(`Invalid directory: ${this.options.dir}`); } } checkForDuplicateNames(files) { const nameMap = new Map(); files.forEach((file) => { if ((0, isImageSupported_1.default)(file)) { const nameWithoutExt = (0, url_1.getFileNameWithoutExtension)(file); const relativePath = path_1.default.dirname(file.replace(this.options.dir, "")); const key = path_1.default.join(relativePath, nameWithoutExt); if (!nameMap.has(key)) { nameMap.set(key, []); } nameMap.get(key).push(file); } }); const duplicates = []; nameMap.forEach((files, name) => { if (files.length > 1) { duplicates.push(...files); console.warn(`āš ļø Duplicate names found for: ${name}`); console.warn(` Files: ${files.join(", ")}`); } }); return duplicates; } async convertSingleImage(task) { const { inputPath, index, total } = task; try { // console.log(`šŸ“ø [${index}/${total}] Processing: ${path.basename(inputPath)}`); const normalizedPath = inputPath.replace(/\\/g, "/"); const fileExt = (0, url_1.getFileExtension)(normalizedPath); const isSupported = (0, isImageSupported_1.default)(normalizedPath); // Handle non-image files if (!isSupported) { const outputPath = normalizedPath.replace(this.options.dir, this.outputDirWebp); await promises_1.default.mkdir(path_1.default.dirname(outputPath), { recursive: true }); await promises_1.default.copyFile(normalizedPath, outputPath); return { success: true, input: inputPath, output: outputPath, }; } // Convert image to WebP const result = await (0, diginext_img_magic_1.convertToWebpByPath)(normalizedPath, { maxSize: this.options.maxsize, small: this.options.thumb, }); // Handle main WebP file const webpOutputPath = normalizedPath.replace(this.options.dir, this.outputDirWebp).replace(`.${fileExt}`, ".webp"); await promises_1.default.mkdir(path_1.default.dirname(webpOutputPath), { recursive: true }); await promises_1.default.copyFile(result.large, webpOutputPath); await promises_1.default.rm(result.large); // Handle thumbnail if requested if (this.options.thumb && result.small) { const thumbOutputPath = normalizedPath.replace(this.options.dir, this.outputDirThumb).replace(`.${fileExt}`, ".webp"); await promises_1.default.mkdir(path_1.default.dirname(thumbOutputPath), { recursive: true }); await promises_1.default.copyFile(result.small, thumbOutputPath); await promises_1.default.rm(result.small); } return { success: true, input: inputPath, output: webpOutputPath, }; } catch (error) { return { success: false, input: inputPath, error: error instanceof Error ? error.message : String(error), }; } } createWorker() { return async (task) => { try { const result = await this.convertSingleImage(task); this.results.push(result); this.processedCount++; if (!result.success) { console.error(`āŒ Failed to convert ${task.inputPath}: ${result.error}`); } else { console.log(`āœ” [${this.processedCount}/${task.total}] Completed: ${path_1.default.basename(task.inputPath)}`); } } catch (error) { const errorResult = { success: false, input: task.inputPath, error: error instanceof Error ? error.message : String(error), }; this.results.push(errorResult); this.processedCount++; console.error(`āŒ Worker error for ${task.inputPath}:`, error); } }; } async processWithQueue(files) { return new Promise((resolve, reject) => { const concurrency = this.options.concurrency || 10; console.log(`šŸš€ Starting conversion of ${files.length} files with concurrency: ${concurrency}`); // Create the queue with our worker function const q = (0, async_1.queue)(this.createWorker(), concurrency); // Set up queue event handlers q.error((error, task) => { console.error("Queue error occurred:", error); console.error("Failed task:", task); }); q.drain(() => { console.log("\nšŸŽÆ All tasks completed!"); resolve(this.results); }); // Add progress monitoring // let lastProgress = 0; // q.saturated(() => { // console.log("šŸ”„ Queue is running at full capacity"); // }); q.empty(() => { console.log("šŸ“­ Queue is empty, waiting for workers to finish..."); }); // Create tasks and add them to the queue const tasks = files.map((file, index) => ({ inputPath: file, index: index + 1, total: files.length, })); // Add all tasks to the queue q.push(tasks, (error) => { if (error) { console.error("Task completion error:", error); } }); // Handle the case where there are no files if (files.length === 0) { resolve([]); } }); } async cleanup() { try { if (fs_1.default.existsSync(".temp")) { await promises_1.default.rm(".temp", { recursive: true }); } } catch (error) { console.warn("āš ļø Warning: Failed to cleanup temp directory:", error); } } printSummary(results) { const successful = results.filter((r) => r.success).length; const failed = results.filter((r) => !r.success).length; console.log("\n" + "=".repeat(50)); console.log("šŸ“Š CONVERSION SUMMARY"); console.log("=".repeat(50)); console.log(`āœ” Successful: ${successful}`); console.log(`āŒ Failed: ${failed}`); console.log(`šŸ“ Output directory: ${this.outputDirWebp}`); if (this.options.thumb) { console.log(`šŸ–¼ļø Thumbnail directory: ${this.outputDirThumb}`); } if (failed > 0) { console.log("\nāŒ Failed conversions:"); results.filter((r) => !r.success).forEach((r) => console.log(` • ${path_1.default.basename(r.input)}: ${r.error}`)); } console.log("=".repeat(50)); } async convert() { try { // Reset state this.results = []; this.processedCount = 0; // Validate input directory await this.validateDirectory(); // Get all files const files = (0, getAllFiles_1.default)(this.options.dir); if (files.length === 0) { console.log("šŸ“‚ No files found in the specified directory."); return; } // Check for duplicate names const duplicates = this.checkForDuplicateNames(files); if (duplicates.length > 0) { throw new Error("Cannot proceed: Found files with duplicate names but different extensions."); } // Setup output directories await this.ensureDirectoryExists(this.outputDirWebp); if (this.options.thumb) { await this.ensureDirectoryExists(this.outputDirThumb); } // Process files using async queue const results = await this.processWithQueue(files); // Cleanup and summary await this.cleanup(); this.printSummary(results); } catch (error) { console.error("šŸ’„ Conversion failed:", error instanceof Error ? error.message : String(error)); throw error; } } } async function parseArguments() { const argv = await (0, yargs_1.default)(process.argv.slice(2)) .usage("Usage: $0 [options]") .example('$0 --dir "./images" --thumb --maxsize 2048 --concurrency 5', "Convert images with 5 concurrent workers") .option("dir", { alias: "d", describe: "Source directory containing images", type: "string", demandOption: true, }) .option("thumb", { alias: "t", describe: "Generate thumbnails", type: "boolean", default: false, }) .option("maxsize", { alias: "m", describe: "Maximum size for converted images", type: "number", default: 4096, }) .option("concurrency", { alias: "c", describe: "Number of concurrent workers", type: "number", default: 12, }) .help("h") .alias("h", "help") .epilog(`Version: ${version}`) .parseAsync(); return { dir: argv.dir, thumb: argv.thumb, maxsize: argv.maxsize, concurrency: argv.concurrency, }; } async function main() { try { console.log(`šŸŽØ Image Converter v${version}`); console.log("=".repeat(30)); const options = await parseArguments(); console.log(`šŸ“ Source: ${options.dir}`); console.log(`šŸ“ Max size: ${options.maxsize}px`); console.log(`šŸ–¼ļø Generate thumbnails: ${options.thumb ? "Yes" : "No"}`); console.log(`⚔ Concurrency: ${options.concurrency}`); console.log("=".repeat(30)); const converter = new ImageConverter(options); await converter.convert(); console.log("šŸŽ‰ All done!"); } catch (error) { console.error("šŸ’„ Application error:", error instanceof Error ? error.message : String(error)); process.exit(1); } } // Handle unhandled promise rejections process.on("unhandledRejection", (reason, promise) => { console.error("Unhandled Rejection at:", promise, "reason:", reason); process.exit(1); }); // Run the application main(); //# sourceMappingURL=index.js.map