@kaviyarasan022/image-compressor-target 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +92 -0
- package/examples/example.js +6 -0
- package/images/photo_small.jpg +0 -0
- package/index.js +34 -0
- package/lib/compressor.js +50 -0
- package/package.json +18 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const yargs = require("yargs/yargs");
|
|
3
|
+
const { hideBin } = require("yargs/helpers");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const axios = require("axios");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const { compressToTarget } = require("../lib/compressor");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Download image from a URL to a temporary local file
|
|
12
|
+
* @param {string} url - direct image URL
|
|
13
|
+
* @returns {Promise<string>} - path to downloaded temp file
|
|
14
|
+
*/
|
|
15
|
+
async function downloadImage(url) {
|
|
16
|
+
const tmpPath = path.join(os.tmpdir(), "img_compress_temp_" + Date.now() + path.extname(url.split("?")[0]));
|
|
17
|
+
const writer = fs.createWriteStream(tmpPath);
|
|
18
|
+
const response = await axios({
|
|
19
|
+
url,
|
|
20
|
+
method: "GET",
|
|
21
|
+
responseType: "stream",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
response.data.pipe(writer);
|
|
25
|
+
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
writer.on("finish", () => resolve(tmpPath));
|
|
28
|
+
writer.on("error", reject);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
(async () => {
|
|
33
|
+
const argv = yargs(hideBin(process.argv))
|
|
34
|
+
.usage("Usage: node bin/cli.js <input> --target 300kb --out output.jpg")
|
|
35
|
+
.demandCommand(1, "Please provide an image path or URL")
|
|
36
|
+
.option("target", {
|
|
37
|
+
alias: "t",
|
|
38
|
+
type: "string",
|
|
39
|
+
describe: "Target file size (e.g., 300kb, 1mb)",
|
|
40
|
+
demandOption: true,
|
|
41
|
+
})
|
|
42
|
+
.option("out", {
|
|
43
|
+
alias: "o",
|
|
44
|
+
type: "string",
|
|
45
|
+
describe: "Output file path (default: input_compressed.jpg)",
|
|
46
|
+
})
|
|
47
|
+
.help()
|
|
48
|
+
.argv;
|
|
49
|
+
|
|
50
|
+
let inputPath = argv._[0];
|
|
51
|
+
let tempDownloaded = false;
|
|
52
|
+
|
|
53
|
+
const isURL = /^https?:\/\//i.test(inputPath);
|
|
54
|
+
if (isURL) {
|
|
55
|
+
console.log("🌐 Downloading image from URL...");
|
|
56
|
+
inputPath = await downloadImage(inputPath);
|
|
57
|
+
tempDownloaded = true;
|
|
58
|
+
} else {
|
|
59
|
+
// Check local file exists
|
|
60
|
+
if (!fs.existsSync(inputPath)) {
|
|
61
|
+
console.error("❌ Input file not found:", inputPath);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const outputPath =
|
|
67
|
+
argv.out ||
|
|
68
|
+
path.join(
|
|
69
|
+
path.dirname(inputPath),
|
|
70
|
+
path.basename(inputPath, path.extname(inputPath)) + "_compressed" + path.extname(inputPath)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// ✅ Ensure output folder exists
|
|
74
|
+
const outputFolder = path.dirname(outputPath);
|
|
75
|
+
if (!fs.existsSync(outputFolder)) {
|
|
76
|
+
fs.mkdirSync(outputFolder, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`🧠 Compressing ${inputPath} to around ${argv.target}...`);
|
|
80
|
+
try {
|
|
81
|
+
const result = await compressToTarget(inputPath, outputPath, argv.target);
|
|
82
|
+
console.log(`✅ Done! Final size: ${result.finalSizeKB} KB`);
|
|
83
|
+
console.log(`📁 Saved to: ${outputPath}`);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error("❌ Compression failed:", e.message);
|
|
86
|
+
} finally {
|
|
87
|
+
// Clean up temp file if downloaded
|
|
88
|
+
if (tempDownloaded && fs.existsSync(inputPath)) {
|
|
89
|
+
fs.unlinkSync(inputPath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const { compressToTarget } = require("./lib/compressor");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
|
|
7
|
+
*
|
|
8
|
+
* @param {string} inputPath - Path to input image file (jpg/png/webp)
|
|
9
|
+
* @param {string} outputPath - Path where compressed image will be saved
|
|
10
|
+
* @param {string|number} targetSize - e.g. "300kb", "1mb", or bytes number
|
|
11
|
+
* @returns {Promise<object>} - compression result info
|
|
12
|
+
*
|
|
13
|
+
* Example:
|
|
14
|
+
* const { compressToTarget } = require("image-compressor-target");
|
|
15
|
+
* await compressToTarget("photo.jpg", "photo_small.jpg", "300kb");
|
|
16
|
+
*/
|
|
17
|
+
module.exports = { compressToTarget };
|
|
18
|
+
|
|
19
|
+
// If executed directly (like `node index.js`), show quick usage help
|
|
20
|
+
if (require.main === module) {
|
|
21
|
+
console.log(`
|
|
22
|
+
🖼️ Image Compressor CLI
|
|
23
|
+
=============================
|
|
24
|
+
Usage:
|
|
25
|
+
node index.js <input> --target 300kb --out output.jpg
|
|
26
|
+
|
|
27
|
+
Or after linking:
|
|
28
|
+
img-compress ./photo.jpg --target 300kb --out photo_small.jpg
|
|
29
|
+
|
|
30
|
+
You can also import in JS:
|
|
31
|
+
const { compressToTarget } = require('image-compressor-target');
|
|
32
|
+
await compressToTarget('photo.jpg', 'photo_small.jpg', '300kb');
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require("fs").promises;
|
|
2
|
+
const sharp = require("sharp");
|
|
3
|
+
|
|
4
|
+
function parseSize(sizeStr) {
|
|
5
|
+
if (typeof sizeStr === "number") return sizeStr;
|
|
6
|
+
|
|
7
|
+
const s = String(sizeStr).trim().toLowerCase().replace(/\s+/g, "");
|
|
8
|
+
const match = s.match(/^(\d+(?:\.\d+)?)(b|kb|mb)?$/);
|
|
9
|
+
if (!match) throw new Error("Invalid size format. Use e.g. 300kb, 0.3mb, or 50000.");
|
|
10
|
+
|
|
11
|
+
const value = parseFloat(match[1]);
|
|
12
|
+
const unit = match[2] || "b";
|
|
13
|
+
|
|
14
|
+
if (unit === "b") return Math.round(value);
|
|
15
|
+
if (unit === "kb") return Math.round(value * 1024);
|
|
16
|
+
if (unit === "mb") return Math.round(value * 1024 * 1024);
|
|
17
|
+
|
|
18
|
+
return Math.round(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function compressToTarget(inputPath, outputPath, targetSizeStr) {
|
|
22
|
+
const targetBytes = parseSize(targetSizeStr);
|
|
23
|
+
const inputBuffer = await fs.readFile(inputPath);
|
|
24
|
+
let quality = 90;
|
|
25
|
+
let buffer = inputBuffer;
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < 10; i++) {
|
|
28
|
+
const { data } = await sharp(buffer)
|
|
29
|
+
.jpeg({ quality })
|
|
30
|
+
.toBuffer({ resolveWithObject: true });
|
|
31
|
+
|
|
32
|
+
if (data.length <= targetBytes || quality < 30) {
|
|
33
|
+
await fs.writeFile(outputPath, data);
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
finalSizeKB: (data.length / 1024).toFixed(2),
|
|
37
|
+
quality,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
quality -= 10;
|
|
42
|
+
buffer = data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new Error(
|
|
46
|
+
"Could not reach target size. Try a larger target or smaller image."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { compressToTarget };
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kaviyarasan022/image-compressor-target",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A Node.js CLI tool to compress images to a target size.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"image-compressor": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["image", "compressor", "cli", "tool", "image-optimization"],
|
|
13
|
+
"author": "Kaviyarasan M",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"axios": "^1.12.2"
|
|
17
|
+
}
|
|
18
|
+
}
|