image-video-optimizer 1.0.0 → 1.1.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/README.md CHANGED
@@ -141,4 +141,4 @@ Download from [ffmpeg.org](https://ffmpeg.org/download.html) and add to PATH
141
141
 
142
142
  ## License
143
143
 
144
- MIT
144
+ nirvána
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "image-video-optimizer",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "CLI tool to optimize images and videos with configurable resize and compression settings",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -25,11 +25,61 @@ class ImageProcessor {
25
25
  }
26
26
 
27
27
  const outputPath = this.generateOutputPath(inputPath, targetFormat);
28
+ const parsedPath = path.parse(inputPath);
28
29
 
29
30
  // Check if output path would be same as input path
30
31
  if (outputPath === inputPath) {
31
- console.log(`Output path same as input, skipping: ${path.basename(inputPath)}`);
32
- return { processed: false, reason: 'same_path' };
32
+ // Use temporary name for processing
33
+ const tempPath = path.join(parsedPath.dir, `${parsedPath.name}_temp.${targetFormat}`);
34
+ console.log(`Using temporary path for processing: ${path.basename(tempPath)}`);
35
+
36
+ let sharpInstance = sharp(inputPath);
37
+
38
+ if (needsResize) {
39
+ const newHeight = Math.round((maxWidth / metadata.width) * metadata.height);
40
+ sharpInstance = sharpInstance.resize(maxWidth, newHeight, {
41
+ fit: 'inside',
42
+ withoutEnlargement: true
43
+ });
44
+ console.log(`Resizing to: ${maxWidth}x${newHeight}`);
45
+ }
46
+
47
+ switch (targetFormat.toLowerCase()) {
48
+ case 'jpg':
49
+ case 'jpeg':
50
+ sharpInstance = sharpInstance.jpeg({ quality: 85 });
51
+ break;
52
+ case 'png':
53
+ sharpInstance = sharpInstance.png({ compressionLevel: 8 });
54
+ break;
55
+ case 'webp':
56
+ sharpInstance = sharpInstance.webp({ quality: 85 });
57
+ break;
58
+ default:
59
+ sharpInstance = sharpInstance.jpeg({ quality: 85 });
60
+ }
61
+
62
+ await sharpInstance.toFile(tempPath);
63
+
64
+ const compressionResult = this.checkCompression(inputPath, tempPath);
65
+
66
+ if (compressionResult.effective) {
67
+ // Replace original with processed file
68
+ fs.unlinkSync(inputPath);
69
+ fs.renameSync(tempPath, outputPath);
70
+ console.log(`✓ Processed: ${path.basename(inputPath)} (${compressionResult.compressionPercent}% reduction)`);
71
+ return {
72
+ processed: true,
73
+ outputPath,
74
+ originalSize: compressionResult.originalSize,
75
+ newSize: compressionResult.newSize,
76
+ compressionPercent: compressionResult.compressionPercent
77
+ };
78
+ } else {
79
+ console.log(`✗ Ineffective compression, keeping original: ${path.basename(inputPath)}`);
80
+ fs.unlinkSync(tempPath);
81
+ return { processed: false, reason: 'ineffective_compression' };
82
+ }
33
83
  }
34
84
 
35
85
  let sharpInstance = sharp(inputPath);
@@ -44,6 +44,74 @@ class VideoProcessor {
44
44
  return;
45
45
  }
46
46
 
47
+ const outputPath = this.generateOutputPath(inputPath);
48
+ const parsedPath = path.parse(inputPath);
49
+
50
+ // Check if output path would be same as input path
51
+ if (outputPath === inputPath) {
52
+ // Use temporary name for processing
53
+ const tempPath = path.join(parsedPath.dir, `${parsedPath.name}_temp.mp4`);
54
+ console.log(`Using temporary path for processing: ${path.basename(tempPath)}`);
55
+
56
+ let ffmpegCommand = ffmpeg(inputPath);
57
+
58
+ if (needsResize) {
59
+ const newHeight = Math.round((maxWidth / videoStream.width) * videoStream.height);
60
+ ffmpegCommand = ffmpegCommand.videoFilters(`scale=${maxWidth}:${newHeight}`);
61
+ console.log(`Resizing to: ${maxWidth}x${newHeight}`);
62
+ }
63
+
64
+ switch (encodeFormat.toLowerCase()) {
65
+ case 'h264':
66
+ ffmpegCommand = ffmpegCommand.videoCodec('libx264').audioCodec('aac');
67
+ break;
68
+ case 'h265':
69
+ ffmpegCommand = ffmpegCommand.videoCodec('libx265').audioCodec('aac');
70
+ break;
71
+ case 'vp9':
72
+ ffmpegCommand = ffmpegCommand.videoCodec('libvpx-vp9').audioCodec('libvorbis');
73
+ break;
74
+ default:
75
+ ffmpegCommand = ffmpegCommand.videoCodec('libx264').audioCodec('aac');
76
+ }
77
+
78
+ ffmpegCommand
79
+ .outputOptions('-crf 23')
80
+ .outputOptions('-preset medium')
81
+ .format('mp4')
82
+ .output(tempPath)
83
+ .on('end', () => {
84
+ const compressionResult = this.checkCompression(inputPath, tempPath);
85
+
86
+ if (compressionResult.effective) {
87
+ // Replace original with processed file
88
+ fs.unlinkSync(inputPath);
89
+ fs.renameSync(tempPath, outputPath);
90
+ console.log(`✓ Processed: ${path.basename(inputPath)} (${compressionResult.compressionPercent}% reduction)`);
91
+ resolve({
92
+ processed: true,
93
+ outputPath,
94
+ originalSize: compressionResult.originalSize,
95
+ newSize: compressionResult.newSize,
96
+ compressionPercent: compressionResult.compressionPercent
97
+ });
98
+ } else {
99
+ console.log(`✗ Ineffective compression, keeping original: ${path.basename(inputPath)}`);
100
+ fs.unlinkSync(tempPath);
101
+ resolve({ processed: false, reason: 'ineffective_compression' });
102
+ }
103
+ })
104
+ .on('error', (err) => {
105
+ console.error(`Error processing video ${inputPath}:`, err.message);
106
+ if (fs.existsSync(tempPath)) {
107
+ fs.unlinkSync(tempPath);
108
+ }
109
+ resolve({ processed: false, error: err.message });
110
+ })
111
+ .run();
112
+ return;
113
+ }
114
+
47
115
  let ffmpegCommand = ffmpeg(inputPath);
48
116
 
49
117
  if (needsResize) {
@@ -98,7 +166,6 @@ class VideoProcessor {
98
166
  })
99
167
  .run();
100
168
  });
101
-
102
169
  } catch (error) {
103
170
  console.error(`Error processing video ${inputPath}:`, error.message);
104
171
  resolve({ processed: false, error: error.message });