picslim 0.0.6 → 0.0.8
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/config.json +8 -0
- package/package.json +23 -2
- package/picslim.js +147 -102
package/config.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "picslim",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Picslim is a Node.js package that allows you to optimize images in a specified directory.",
|
|
5
5
|
"main": "picslim.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"source": "picslim.js",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"start": "node picslim.js -q 80"
|
|
11
|
+
"start": "node picslim.js -q 80",
|
|
12
|
+
"format": "standard --fix",
|
|
13
|
+
"prepare": "husky install"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
14
16
|
"optimization",
|
|
@@ -26,11 +28,30 @@
|
|
|
26
28
|
"license": "MIT",
|
|
27
29
|
"files": [
|
|
28
30
|
"picslim.js",
|
|
31
|
+
"config.json",
|
|
29
32
|
"package.json",
|
|
30
33
|
"README.md"
|
|
31
34
|
],
|
|
35
|
+
"eslintConfig": {
|
|
36
|
+
"extends": "./node_modules/standard/eslintrc.json"
|
|
37
|
+
},
|
|
38
|
+
"husky": {
|
|
39
|
+
"hooks": {
|
|
40
|
+
"pre-commit": "lint-staged"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"lint-staged": {
|
|
44
|
+
"*.{js}": [
|
|
45
|
+
"standard --fix"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
32
48
|
"dependencies": {
|
|
33
49
|
"sharp": "^0.32.6",
|
|
34
50
|
"yargs": "^17.7.2"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"husky": "^8.0.0",
|
|
54
|
+
"lint-staged": "^15.0.2",
|
|
55
|
+
"standard": "^17.1.0"
|
|
35
56
|
}
|
|
36
57
|
}
|
package/picslim.js
CHANGED
|
@@ -1,119 +1,164 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const fs = require(
|
|
3
|
-
const sharp = require(
|
|
4
|
-
const yargs = require(
|
|
2
|
+
const fs = require('fs').promises
|
|
3
|
+
const sharp = require('sharp')
|
|
4
|
+
const yargs = require('yargs')
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Parses command line arguments using yargs library.
|
|
8
|
-
*
|
|
9
|
-
* @typedef {Object} Argv
|
|
10
|
-
* @property {number} quality - Image quality (1 to 100)
|
|
11
|
-
* @property {number} width - Maximum width allowed
|
|
12
|
-
* @property {number} compressionLevel - PNG compression level (0 to 9)
|
|
13
8
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
describe: "Maximum width allowed",
|
|
26
|
-
demandOption: false,
|
|
27
|
-
type: "number",
|
|
28
|
-
default: null,
|
|
29
|
-
},
|
|
30
|
-
mh: {
|
|
31
|
-
alias: "maxHeight",
|
|
32
|
-
describe: "Maximum height allowed",
|
|
33
|
-
demandOption: false,
|
|
34
|
-
type: "number",
|
|
35
|
-
default: null,
|
|
36
|
-
},
|
|
37
|
-
c: {
|
|
38
|
-
alias: "compressionLevel",
|
|
39
|
-
describe: "PNG compression level (0 to 9)",
|
|
40
|
-
demandOption: false,
|
|
41
|
-
type: "number",
|
|
42
|
-
default: 9,
|
|
43
|
-
},
|
|
44
|
-
}).argv;
|
|
45
|
-
|
|
46
|
-
const inputDir = "./";
|
|
47
|
-
const outputDir = "./min";
|
|
48
|
-
const quality = argv.quality;
|
|
49
|
-
const maxWidth = argv.maxWidth;
|
|
50
|
-
const maxHeight = argv.maxHeight;
|
|
51
|
-
const compressionLevel = argv.compressionLevel;
|
|
9
|
+
function parseArguments () {
|
|
10
|
+
return yargs.options({
|
|
11
|
+
c: {
|
|
12
|
+
alias: 'config',
|
|
13
|
+
describe: 'Path to the configuration file',
|
|
14
|
+
demandOption: false,
|
|
15
|
+
type: 'string',
|
|
16
|
+
default: 'config.json'
|
|
17
|
+
}
|
|
18
|
+
}).argv
|
|
19
|
+
}
|
|
52
20
|
|
|
53
21
|
/**
|
|
54
22
|
* Verifies if the output directory exists; if not, creates it.
|
|
55
23
|
*
|
|
56
24
|
* @param {string} dir - The directory to verify.
|
|
57
25
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
fs.
|
|
63
|
-
if (err) {
|
|
64
|
-
console.error("Error reading input directory: ", err);
|
|
65
|
-
return;
|
|
26
|
+
async function createOutputDirectory (dir) {
|
|
27
|
+
try {
|
|
28
|
+
await fs.access(dir)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
await fs.mkdir(dir)
|
|
66
31
|
}
|
|
32
|
+
}
|
|
67
33
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Process and optimize an image file.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} inputPath - Path to the input image file.
|
|
38
|
+
* @param {string} outputPath - Path to the output image file.
|
|
39
|
+
* @param {string} file - The file name.
|
|
40
|
+
* @param {number} maxWidth - Maximum width allowed.
|
|
41
|
+
* @param {number} maxHeight - Maximum height allowed.
|
|
42
|
+
* @param {number} quality - Image quality.
|
|
43
|
+
* @param {number} compressionLevel - PNG compression level.
|
|
44
|
+
*/
|
|
45
|
+
async function processImage (inputPath, outputPath, file, maxWidth, maxHeight, quality, compressionLevel) {
|
|
46
|
+
try {
|
|
47
|
+
const image = sharp(inputPath)
|
|
48
|
+
const metadata = await image.metadata()
|
|
49
|
+
const originalWidth = metadata.width
|
|
50
|
+
const originalHeight = metadata.height
|
|
51
|
+
const imageProcessor = image.resize(
|
|
52
|
+
originalWidth > maxWidth ? maxWidth : null,
|
|
53
|
+
originalHeight > maxHeight ? maxHeight : null
|
|
54
|
+
)
|
|
71
55
|
|
|
72
56
|
if (file.match(/\.(jpg|jpeg)$/i)) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.then((metadata) => {
|
|
76
|
-
const originalWidth = metadata.width;
|
|
77
|
-
const originalHeight = metadata.height;
|
|
78
|
-
sharp(inputPath)
|
|
79
|
-
.resize(
|
|
80
|
-
originalWidth > maxWidth ? maxWidth : null,
|
|
81
|
-
originalHeight > maxHeight ? maxHeight : null,
|
|
82
|
-
)
|
|
83
|
-
.jpeg({
|
|
84
|
-
quality,
|
|
85
|
-
})
|
|
86
|
-
.toFile(outputPath, (err, info) => {
|
|
87
|
-
if (err) {
|
|
88
|
-
console.error(`Optimization error ${file}: `, err);
|
|
89
|
-
} else {
|
|
90
|
-
console.log(`Optimized PNG image: ${file}`);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
});
|
|
57
|
+
await imageProcessor.jpeg({ quality }).toFile(outputPath)
|
|
58
|
+
console.log(`Optimized JPEG image: ${file}`)
|
|
94
59
|
} else if (file.match(/\.(png)$/i)) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
60
|
+
await imageProcessor.png({ quality, compressionLevel }).toFile(outputPath)
|
|
61
|
+
console.log(`Optimized PNG image: ${file}`)
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`Optimization error ${file}: `, error)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load settings from a configuration file.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} configFile - Path to the configuration file.
|
|
72
|
+
*/
|
|
73
|
+
async function loadConfig (configFile) {
|
|
74
|
+
try {
|
|
75
|
+
const configData = await fs.readFile(configFile, 'utf8')
|
|
76
|
+
|
|
77
|
+
if (configData.trim() === '') {
|
|
78
|
+
console.error('Configuration file is empty.')
|
|
79
|
+
process.exit(1)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!validateJson(configData)) {
|
|
83
|
+
console.error('Configuration file is not a valid JSON object.')
|
|
84
|
+
process.exit(1)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const config = JSON.parse(configData)
|
|
88
|
+
|
|
89
|
+
validateConfig(config)
|
|
90
|
+
|
|
91
|
+
if (typeof config !== 'object' || Array.isArray(config)) {
|
|
92
|
+
console.error('Configuration file is not a valid JSON object.')
|
|
93
|
+
process.exit(1)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!config.inputDir || !config.outputDir) {
|
|
97
|
+
console.error("Configuration error: 'inputDir' and 'outputDir' are required fields.")
|
|
98
|
+
process.exit(1)
|
|
117
99
|
}
|
|
118
|
-
|
|
119
|
-
|
|
100
|
+
|
|
101
|
+
return config
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('Error loading or parsing the configuration file: ', error)
|
|
104
|
+
process.exit(1)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateJson (json) {
|
|
109
|
+
const isJson = (str) => {
|
|
110
|
+
try {
|
|
111
|
+
JSON.parse(str)
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// Error
|
|
114
|
+
// JSON is not okay
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return isJson(json)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Validate the configuration object.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} config - The loaded configuration object.
|
|
128
|
+
*/
|
|
129
|
+
function validateConfig (config) {
|
|
130
|
+
if (!config.inputDir || !config.outputDir) {
|
|
131
|
+
console.error("Configuration error: 'inputDir' and 'outputDir' are required fields.")
|
|
132
|
+
process.exit(1)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The main function that processes all image files in the input directory.
|
|
138
|
+
*/
|
|
139
|
+
async function main () {
|
|
140
|
+
const argv = parseArguments()
|
|
141
|
+
const config = await loadConfig(argv.config)
|
|
142
|
+
|
|
143
|
+
const inputDir = config.inputDir
|
|
144
|
+
const outputDir = config.outputDir
|
|
145
|
+
const quality = config.quality
|
|
146
|
+
const maxWidth = config.maxWidth
|
|
147
|
+
const maxHeight = config.maxHeight
|
|
148
|
+
const compressionLevel = config.compressionLevel
|
|
149
|
+
|
|
150
|
+
await createOutputDirectory(outputDir)
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const files = await fs.readdir(inputDir)
|
|
154
|
+
for (const file of files) {
|
|
155
|
+
const inputPath = `${inputDir}/${file}`
|
|
156
|
+
const outputPath = `${outputDir}/${file}`
|
|
157
|
+
await processImage(inputPath, outputPath, file, maxWidth, maxHeight, quality, compressionLevel)
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Error reading input directory: ', error)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main()
|