image-video-optimizer 3.3.33 ā 3.5.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/.image-video-optimizer-status.json +6 -0
- package/.image-video-optimizer.conf +9 -4
- package/README.md +13 -8
- package/bin/cli.js +11 -6
- package/package.json +1 -1
- package/src/imageProcessor.js +123 -56
- package/src/index.js +38 -13
- package/src/config.js +0 -57
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# Image Video Optimizer Configuration
|
|
2
|
+
# Generated automatically
|
|
3
|
+
|
|
4
|
+
max_image_width=720 # Compress images wider than this (pixels)
|
|
5
|
+
img_format=null # Target format for image conversion (null=no conversion)
|
|
6
|
+
video_max_width=720 # Maximum width for videos (pixels)
|
|
7
|
+
video_encode=h264 # Video encoding format
|
|
8
|
+
audio_ext=mp3 # Audio extension for audio files
|
|
9
|
+
pdf_compress=true # Enable/disable PDF compression
|
package/README.md
CHANGED
|
@@ -25,7 +25,12 @@ npm install -g image-video-optimizer
|
|
|
25
25
|
### Basic Usage
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
image-video-optimizer
|
|
28
|
+
image-video-optimizer . # will proceed the current directory
|
|
29
|
+
image-video-optimizer /path/to/directory # will proceed the specified directory
|
|
30
|
+
image-video-optimizer /path/to/directory/file.jpg # will proceed the specified image file
|
|
31
|
+
image-video-optimizer /path/to/directory/file.pdf # will proceed the specified pdf file
|
|
32
|
+
image-video-optimizer /path/to/directory/file.mp4 # will proceed the specified video file
|
|
33
|
+
image-video-optimizer /path/to/directory/file.mp3 # will proceed the specified audio file
|
|
29
34
|
```
|
|
30
35
|
|
|
31
36
|
### Options
|
|
@@ -34,7 +39,7 @@ image-video-optimizer /path/to/directory
|
|
|
34
39
|
image-video-optimizer /path/to/directory [options]
|
|
35
40
|
```
|
|
36
41
|
|
|
37
|
-
- `<
|
|
42
|
+
- `<target>`: Target directory or file to optimize (required)
|
|
38
43
|
- `-r, --reset`: Reset status file and start fresh
|
|
39
44
|
- `-v, --verbose`: Enable verbose logging
|
|
40
45
|
- `-V, --version`: Show version number
|
|
@@ -46,7 +51,7 @@ Create a `.image-video-optimizer.conf` file in your target directory to customiz
|
|
|
46
51
|
|
|
47
52
|
```ini
|
|
48
53
|
# Image settings
|
|
49
|
-
img_max_width=
|
|
54
|
+
img_max_width=720 # Maximum width for images (pixels)
|
|
50
55
|
img_format=jpg # Target format for image conversion
|
|
51
56
|
|
|
52
57
|
# Video settings
|
|
@@ -63,7 +68,7 @@ pdf_compress=true # Enable/disable PDF compression
|
|
|
63
68
|
### Default Configuration
|
|
64
69
|
|
|
65
70
|
If no configuration file is found, these defaults are used:
|
|
66
|
-
- `img_max_width`:
|
|
71
|
+
- `img_max_width`: 720
|
|
67
72
|
- `img_format`: jpg
|
|
68
73
|
- `video_max_width`: 720
|
|
69
74
|
- `video_encode`: h264
|
|
@@ -74,7 +79,7 @@ If no configuration file is found, these defaults are used:
|
|
|
74
79
|
|
|
75
80
|
### Images
|
|
76
81
|
- Input: jpg, jpeg, png, gif, bmp, tiff, webp
|
|
77
|
-
- Output: jpg, png, webp (
|
|
82
|
+
- Output: jpg, png, webp (as it is but compressed with better algorithms like avif webp)
|
|
78
83
|
|
|
79
84
|
### Videos
|
|
80
85
|
- Input: avi, mov, wmv, flv, webm, mkv, m4v, mp4
|
|
@@ -143,10 +148,10 @@ image-video-optimizer ./photos --verbose
|
|
|
143
148
|
### Custom configuration
|
|
144
149
|
Create `.image-video-optimizer.conf`:
|
|
145
150
|
```ini
|
|
146
|
-
img_max_width=
|
|
151
|
+
img_max_width=720
|
|
147
152
|
img_format=webp
|
|
148
|
-
video_max_width=
|
|
149
|
-
video_encode=
|
|
153
|
+
video_max_width=720
|
|
154
|
+
video_encode=h264
|
|
150
155
|
audio_ext=mp3
|
|
151
156
|
pdf_compress=true
|
|
152
157
|
```
|
package/bin/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
5
6
|
const { optimize, resetStatus } = require('../src/index');
|
|
6
7
|
|
|
7
8
|
const program = new Command();
|
|
@@ -9,25 +10,29 @@ const program = new Command();
|
|
|
9
10
|
program
|
|
10
11
|
.name('image-video-optimizer')
|
|
11
12
|
.description('CLI tool to optimize and compress images, videos, audio, and PDFs')
|
|
12
|
-
.version('3.
|
|
13
|
+
.version('3.4.0');
|
|
13
14
|
|
|
14
15
|
program
|
|
15
|
-
.argument('[
|
|
16
|
+
.argument('[target]', 'Target directory or file to optimize', process.cwd())
|
|
16
17
|
.option('-r, --reset', 'Reset status file and start fresh')
|
|
17
18
|
.option('-v, --verbose', 'Show detailed processing information')
|
|
18
|
-
.action(async (
|
|
19
|
+
.action(async (target, options) => {
|
|
19
20
|
try {
|
|
20
|
-
const
|
|
21
|
+
const targetPath = path.resolve(target);
|
|
21
22
|
|
|
22
23
|
if (options.reset) {
|
|
24
|
+
// For reset, we need to determine if it's a file or directory
|
|
25
|
+
const targetDir = fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory()
|
|
26
|
+
? targetPath
|
|
27
|
+
: path.dirname(targetPath);
|
|
23
28
|
resetStatus(targetDir);
|
|
24
29
|
return;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
console.log('\nš Image Video Optimizer\n');
|
|
28
|
-
console.log('Target
|
|
33
|
+
console.log('Target: ' + targetPath);
|
|
29
34
|
|
|
30
|
-
await optimize(
|
|
35
|
+
await optimize(targetPath, options.verbose);
|
|
31
36
|
|
|
32
37
|
console.log('\nā
Optimization complete!\n');
|
|
33
38
|
} catch (error) {
|
package/package.json
CHANGED
package/src/imageProcessor.js
CHANGED
|
@@ -3,98 +3,165 @@ const sharp = require('sharp');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Get compression options for a specific format
|
|
7
|
+
* @param {string} format - Output format (jpg, png, webp, avif)
|
|
8
|
+
* @returns {object} Sharp compression options
|
|
9
|
+
*/
|
|
10
|
+
function getCompressionOptions(format) {
|
|
11
|
+
const options = {
|
|
12
|
+
jpg: { quality: 80, progressive: true },
|
|
13
|
+
jpeg: { quality: 80, progressive: true },
|
|
14
|
+
png: { compressionLevel: 9 },
|
|
15
|
+
webp: { quality: 80 },
|
|
16
|
+
avif: { quality: 75 }
|
|
17
|
+
};
|
|
18
|
+
return options[format.toLowerCase()] || options.jpg;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compress image to a specific format
|
|
23
|
+
* @param {object} image - Sharp image object
|
|
24
|
+
* @param {string} format - Target format
|
|
25
|
+
* @returns {object} Sharp pipeline with format applied
|
|
26
|
+
*/
|
|
27
|
+
function applyFormat(image, format) {
|
|
28
|
+
const formatLower = format.toLowerCase();
|
|
29
|
+
const options = getCompressionOptions(formatLower);
|
|
30
|
+
|
|
31
|
+
switch (formatLower) {
|
|
32
|
+
case 'jpg':
|
|
33
|
+
case 'jpeg':
|
|
34
|
+
return image.jpeg(options);
|
|
35
|
+
case 'png':
|
|
36
|
+
return image.png(options);
|
|
37
|
+
case 'webp':
|
|
38
|
+
return image.webp(options);
|
|
39
|
+
case 'avif':
|
|
40
|
+
return image.avif(options);
|
|
41
|
+
case 'gif':
|
|
42
|
+
return image.gif();
|
|
43
|
+
case 'tiff':
|
|
44
|
+
return image.tiff();
|
|
45
|
+
default:
|
|
46
|
+
return image.jpeg(options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Process image with multiple formats and keep the smallest
|
|
7
52
|
* @param {string} imagePath - Path to the image
|
|
8
53
|
* @param {object} config - Configuration object
|
|
9
|
-
* @returns {Promise<{success: boolean, originalSize: number, optimizedSize: number, message: string}>}
|
|
54
|
+
* @returns {Promise<{success: boolean, originalSize: number, optimizedSize: number, message: string, filePath: string}>}
|
|
10
55
|
*/
|
|
11
56
|
async function processImage(imagePath, config) {
|
|
12
57
|
try {
|
|
13
58
|
const stat = await fs.promises.stat(imagePath);
|
|
14
59
|
const originalSize = stat.size;
|
|
15
|
-
const tmpPath = imagePath + '.tmp';
|
|
16
60
|
const ext = path.extname(imagePath).toLowerCase();
|
|
17
61
|
const directory = path.dirname(imagePath);
|
|
18
62
|
const filename = path.basename(imagePath, ext);
|
|
19
63
|
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
const outputExt = outputFormat === 'jpg' ? '.jpg' : `.${outputFormat}`;
|
|
23
|
-
const finalPath = path.join(directory, filename + outputExt);
|
|
24
|
-
|
|
25
|
-
// Check if format conversion is needed
|
|
26
|
-
const needsFormatConversion = ext !== outputExt && ext !== (outputFormat === 'jpg' ? '.jpeg' : outputExt);
|
|
64
|
+
// Get max_image_width from config (default to 720)
|
|
65
|
+
const maxImageWidth = config.max_image_width || 720;
|
|
27
66
|
|
|
28
|
-
// Read and
|
|
67
|
+
// Read and get metadata
|
|
29
68
|
let image = sharp(imagePath);
|
|
30
|
-
|
|
31
|
-
// Get image metadata
|
|
32
69
|
const metadata = await image.metadata();
|
|
33
70
|
|
|
34
|
-
//
|
|
35
|
-
let
|
|
36
|
-
if (metadata.width >
|
|
37
|
-
image = image.resize(
|
|
38
|
-
withoutEnlargement: true
|
|
71
|
+
// Step 1: Apply conditional crop/resize based on max_image_width
|
|
72
|
+
let wasCropped = false;
|
|
73
|
+
if (metadata.width > maxImageWidth) {
|
|
74
|
+
image = image.resize(maxImageWidth, null, {
|
|
75
|
+
withoutEnlargement: true,
|
|
76
|
+
fit: 'inside'
|
|
39
77
|
});
|
|
40
|
-
|
|
78
|
+
wasCropped = true;
|
|
41
79
|
}
|
|
42
80
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
81
|
+
// Step 2: Compress original image format to temp file
|
|
82
|
+
const originalTmpPath = path.join(directory, `${filename}.original.tmp`);
|
|
83
|
+
const originalFormatImage = applyFormat(image.clone(), ext.replace('.', ''));
|
|
84
|
+
await originalFormatImage.toFile(originalTmpPath);
|
|
85
|
+
const originalTmpStat = await fs.promises.stat(originalTmpPath);
|
|
86
|
+
const originalCompressedSize = originalTmpStat.size;
|
|
87
|
+
|
|
88
|
+
// Step 3: Compress to AVIF and WebP formats to temp files
|
|
89
|
+
const avifTmpPath = path.join(directory, `${filename}.avif.tmp`);
|
|
90
|
+
const webpTmpPath = path.join(directory, `${filename}.webp.tmp`);
|
|
91
|
+
|
|
92
|
+
const avifImage = applyFormat(image.clone(), 'avif');
|
|
93
|
+
await avifImage.toFile(avifTmpPath);
|
|
94
|
+
const avifTmpStat = await fs.promises.stat(avifTmpPath);
|
|
95
|
+
const avifCompressedSize = avifTmpStat.size;
|
|
58
96
|
|
|
59
|
-
|
|
60
|
-
await
|
|
97
|
+
const webpImage = applyFormat(image.clone(), 'webp');
|
|
98
|
+
await webpImage.toFile(webpTmpPath);
|
|
99
|
+
const webpTmpStat = await fs.promises.stat(webpTmpPath);
|
|
100
|
+
const webpCompressedSize = webpTmpStat.size;
|
|
61
101
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
|
|
102
|
+
// Step 4: Compare sizes and determine best format
|
|
103
|
+
const formatComparison = [
|
|
104
|
+
{ format: 'original', size: originalCompressedSize, tmpPath: originalTmpPath },
|
|
105
|
+
{ format: 'avif', size: avifCompressedSize, tmpPath: avifTmpPath },
|
|
106
|
+
{ format: 'webp', size: webpCompressedSize, tmpPath: webpTmpPath }
|
|
107
|
+
];
|
|
65
108
|
|
|
66
|
-
//
|
|
67
|
-
|
|
109
|
+
// Sort by size to find the smallest
|
|
110
|
+
formatComparison.sort((a, b) => a.size - b.size);
|
|
111
|
+
const bestFormat = formatComparison[0];
|
|
112
|
+
|
|
113
|
+
// Step 5: Calculate compression savings
|
|
114
|
+
const optimizedSize = bestFormat.size;
|
|
115
|
+
const savedSize = originalSize - optimizedSize;
|
|
116
|
+
const savedPercent = ((savedSize / originalSize) * 100).toFixed(1);
|
|
117
|
+
|
|
118
|
+
// Step 6: Decide whether to replace
|
|
119
|
+
const shouldReplace = optimizedSize < originalSize || wasCropped;
|
|
68
120
|
|
|
69
121
|
if (shouldReplace) {
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
await fs.promises.
|
|
122
|
+
// Read the best file and save it with original filename + extension
|
|
123
|
+
const bestFileContent = await fs.promises.readFile(bestFormat.tmpPath);
|
|
124
|
+
const finalPath = imagePath; // Keep original filename and extension
|
|
125
|
+
|
|
126
|
+
await fs.promises.writeFile(finalPath, bestFileContent);
|
|
75
127
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
128
|
+
// Clean up temp files
|
|
129
|
+
await Promise.all([
|
|
130
|
+
fs.promises.unlink(originalTmpPath).catch(() => {}),
|
|
131
|
+
fs.promises.unlink(avifTmpPath).catch(() => {}),
|
|
132
|
+
fs.promises.unlink(webpTmpPath).catch(() => {})
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
const compressionInfo = wasCropped ? `[CROPPED to ${maxImageWidth}px] ` : '';
|
|
136
|
+
const sizeInfo = `${(originalSize / 1024).toFixed(1)}KB ā ${(optimizedSize / 1024).toFixed(1)}KB (${savedPercent}% saved)`;
|
|
137
|
+
const formatInfo = bestFormat.format !== 'original' ? ` [${bestFormat.format.toUpperCase()} codec]` : '';
|
|
80
138
|
|
|
81
139
|
return {
|
|
82
140
|
success: true,
|
|
83
141
|
originalSize,
|
|
84
142
|
optimizedSize,
|
|
85
|
-
|
|
86
|
-
|
|
143
|
+
savedSize,
|
|
144
|
+
savedPercent: parseFloat(savedPercent),
|
|
145
|
+
message: `ā ${path.basename(imagePath)} ${compressionInfo}${sizeInfo}${formatInfo}`,
|
|
146
|
+
filePath: finalPath,
|
|
147
|
+
usedFormat: bestFormat.format,
|
|
148
|
+
wasCropped
|
|
87
149
|
};
|
|
88
150
|
} else {
|
|
89
|
-
//
|
|
90
|
-
await
|
|
151
|
+
// Clean up temp files
|
|
152
|
+
await Promise.all([
|
|
153
|
+
fs.promises.unlink(originalTmpPath).catch(() => {}),
|
|
154
|
+
fs.promises.unlink(avifTmpPath).catch(() => {}),
|
|
155
|
+
fs.promises.unlink(webpTmpPath).catch(() => {})
|
|
156
|
+
]);
|
|
91
157
|
|
|
92
158
|
return {
|
|
93
159
|
success: false,
|
|
94
160
|
originalSize,
|
|
95
|
-
optimizedSize,
|
|
161
|
+
optimizedSize: originalSize,
|
|
96
162
|
message: `ā ${path.basename(imagePath)} - Already optimized (${(originalSize / 1024).toFixed(1)}KB)`,
|
|
97
|
-
filePath: imagePath
|
|
163
|
+
filePath: imagePath,
|
|
164
|
+
usedFormat: 'original'
|
|
98
165
|
};
|
|
99
166
|
}
|
|
100
167
|
} catch (error) {
|
package/src/index.js
CHANGED
|
@@ -7,8 +7,7 @@ const audioProcessor = require('./audioProcessor');
|
|
|
7
7
|
const pdfProcessor = require('./pdfProcessor');
|
|
8
8
|
|
|
9
9
|
const DEFAULT_CONFIG = {
|
|
10
|
-
|
|
11
|
-
img_format: 'jpg',
|
|
10
|
+
max_image_width: 720,
|
|
12
11
|
video_max_width: 720,
|
|
13
12
|
video_encode: 'h264',
|
|
14
13
|
audio_ext: 'mp3',
|
|
@@ -70,8 +69,7 @@ function ensureConfigFile(targetDir) {
|
|
|
70
69
|
const configContent = `# Image Video Optimizer Configuration
|
|
71
70
|
# Generated automatically
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
img_format=${DEFAULT_CONFIG.img_format} # Target format for image conversion
|
|
72
|
+
max_image_width=${DEFAULT_CONFIG.max_image_width} # Compress images wider than this (pixels)
|
|
75
73
|
video_max_width=${DEFAULT_CONFIG.video_max_width} # Maximum width for videos (pixels)
|
|
76
74
|
video_encode=${DEFAULT_CONFIG.video_encode} # Video encoding format
|
|
77
75
|
audio_ext=${DEFAULT_CONFIG.audio_ext} # Audio extension for audio files
|
|
@@ -178,15 +176,19 @@ function markFailed(filePath, error, status, targetDir) {
|
|
|
178
176
|
|
|
179
177
|
/**
|
|
180
178
|
* Main optimization function
|
|
181
|
-
* @param {string}
|
|
179
|
+
* @param {string} targetPath - Target directory or file to optimize
|
|
182
180
|
* @param {boolean} verbose - Show detailed processing information
|
|
183
181
|
*/
|
|
184
|
-
async function optimize(
|
|
185
|
-
// Validate
|
|
186
|
-
if (!fs.existsSync(
|
|
187
|
-
throw new Error(`
|
|
182
|
+
async function optimize(targetPath, verbose = false) {
|
|
183
|
+
// Validate target exists
|
|
184
|
+
if (!fs.existsSync(targetPath)) {
|
|
185
|
+
throw new Error(`Target does not exist: ${targetPath}`);
|
|
188
186
|
}
|
|
189
187
|
|
|
188
|
+
const stats = fs.statSync(targetPath);
|
|
189
|
+
const isFile = stats.isFile();
|
|
190
|
+
const targetDir = isFile ? path.dirname(targetPath) : targetPath;
|
|
191
|
+
|
|
190
192
|
// Ensure config file exists
|
|
191
193
|
ensureConfigFile(targetDir);
|
|
192
194
|
|
|
@@ -196,8 +198,7 @@ async function optimize(targetDir, verbose = false) {
|
|
|
196
198
|
const status = loadStatus(targetDir);
|
|
197
199
|
|
|
198
200
|
console.log('Configuration:');
|
|
199
|
-
console.log(` Image Max Width: ${config.
|
|
200
|
-
console.log(` Image Format: ${config.img_format}`);
|
|
201
|
+
console.log(` Image Max Width: ${config.max_image_width}px`);
|
|
201
202
|
console.log(` Video Max Width: ${config.video_max_width}px`);
|
|
202
203
|
console.log(` Video Encode: ${config.video_encode}`);
|
|
203
204
|
console.log(` Audio Extension: ${config.audio_ext}`);
|
|
@@ -208,9 +209,33 @@ async function optimize(targetDir, verbose = false) {
|
|
|
208
209
|
}
|
|
209
210
|
console.log('');
|
|
210
211
|
|
|
211
|
-
// Search for media files
|
|
212
|
+
// Search for media files or process single file
|
|
212
213
|
console.log('š Searching for media files...');
|
|
213
|
-
|
|
214
|
+
|
|
215
|
+
let images = [], videos = [], audio = [], documents = [];
|
|
216
|
+
|
|
217
|
+
if (isFile) {
|
|
218
|
+
// Handle single file
|
|
219
|
+
const ext = path.extname(targetPath).toLowerCase().slice(1);
|
|
220
|
+
if (fileSearcher.SUPPORTED_IMAGES.includes(ext)) {
|
|
221
|
+
images = [targetPath];
|
|
222
|
+
} else if (fileSearcher.SUPPORTED_VIDEOS.includes(ext)) {
|
|
223
|
+
videos = [targetPath];
|
|
224
|
+
} else if (fileSearcher.SUPPORTED_AUDIO.includes(ext)) {
|
|
225
|
+
audio = [targetPath];
|
|
226
|
+
} else if (fileSearcher.SUPPORTED_DOCUMENTS.includes(ext)) {
|
|
227
|
+
documents = [targetPath];
|
|
228
|
+
} else {
|
|
229
|
+
throw new Error(`Unsupported file type: ${ext}`);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
// Handle directory
|
|
233
|
+
const result = await fileSearcher.searchMediaFiles(targetDir, { verbose });
|
|
234
|
+
images = result.images;
|
|
235
|
+
videos = result.videos;
|
|
236
|
+
audio = result.audio;
|
|
237
|
+
documents = result.documents;
|
|
238
|
+
}
|
|
214
239
|
|
|
215
240
|
// Filter out already processed files
|
|
216
241
|
const pendingImages = images.filter(img => !isProcessed(img, status));
|
package/src/config.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const DEFAULT_CONFIG = {
|
|
5
|
-
img_max_width: 1080,
|
|
6
|
-
img_format: 'jpg',
|
|
7
|
-
video_max_width: 720,
|
|
8
|
-
video_encode: 'h264'
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
class Config {
|
|
12
|
-
constructor(targetDir) {
|
|
13
|
-
this.targetDir = targetDir;
|
|
14
|
-
this.configPath = path.join(targetDir, '.image-video-optimizer.conf');
|
|
15
|
-
this.config = { ...DEFAULT_CONFIG };
|
|
16
|
-
this.loadConfig();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
loadConfig() {
|
|
20
|
-
try {
|
|
21
|
-
if (fs.existsSync(this.configPath)) {
|
|
22
|
-
const configContent = fs.readFileSync(this.configPath, 'utf8');
|
|
23
|
-
const lines = configContent.split('\n');
|
|
24
|
-
|
|
25
|
-
lines.forEach(line => {
|
|
26
|
-
line = line.trim();
|
|
27
|
-
if (line && !line.startsWith('#')) {
|
|
28
|
-
const [key, value] = line.split('=').map(s => s.trim());
|
|
29
|
-
if (key && value) {
|
|
30
|
-
if (key.includes('width') || key.includes('max_width')) {
|
|
31
|
-
this.config[key] = parseInt(value, 10);
|
|
32
|
-
} else {
|
|
33
|
-
this.config[key] = value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
console.log(`Loaded configuration from ${this.configPath}`);
|
|
40
|
-
} else {
|
|
41
|
-
console.log('Using default configuration');
|
|
42
|
-
}
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.warn('Error loading configuration file, using defaults:', error.message);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
get(key) {
|
|
49
|
-
return this.config[key];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getAll() {
|
|
53
|
-
return { ...this.config };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
module.exports = Config;
|