image-video-optimizer 1.1.3 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "image-video-optimizer",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
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": {
@@ -27,60 +27,9 @@ class ImageProcessor {
27
27
  const outputPath = this.generateOutputPath(inputPath, targetFormat);
28
28
  const parsedPath = path.parse(inputPath);
29
29
 
30
- // Check if output path would be same as input path
31
- if (outputPath === inputPath) {
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
- }
83
- }
30
+ // Always use temporary file for processing
31
+ const tempPath = path.join(parsedPath.dir, `${parsedPath.name}_temp.${targetFormat}`);
32
+ console.log(`Using temporary path for processing: ${path.basename(tempPath)}`);
84
33
 
85
34
  let sharpInstance = sharp(inputPath);
86
35
 
@@ -108,11 +57,14 @@ class ImageProcessor {
108
57
  sharpInstance = sharpInstance.jpeg({ quality: 85 });
109
58
  }
110
59
 
111
- await sharpInstance.toFile(outputPath);
60
+ await sharpInstance.toFile(tempPath);
112
61
 
113
- const compressionResult = this.checkCompression(inputPath, outputPath);
62
+ const compressionResult = this.checkCompression(inputPath, tempPath);
114
63
 
115
64
  if (compressionResult.effective) {
65
+ // Replace original with processed file
66
+ fs.unlinkSync(inputPath);
67
+ fs.renameSync(tempPath, outputPath);
116
68
  console.log(`✓ Processed: ${path.basename(inputPath)} (${compressionResult.compressionPercent}% reduction)`);
117
69
  return {
118
70
  processed: true,
@@ -123,7 +75,7 @@ class ImageProcessor {
123
75
  };
124
76
  } else {
125
77
  console.log(`✗ Ineffective compression, keeping original: ${path.basename(inputPath)}`);
126
- fs.unlinkSync(outputPath);
78
+ fs.unlinkSync(tempPath);
127
79
  return { processed: false, reason: 'ineffective_compression' };
128
80
  }
129
81
 
@@ -47,105 +47,103 @@ class VideoProcessor {
47
47
  const outputPath = this.generateOutputPath(inputPath);
48
48
  const parsedPath = path.parse(inputPath);
49
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
- // Add input/output options with proper quoting
79
- ffmpegCommand = ffmpegCommand.inputOptions(['-fflags', '+genpts']);
80
- ffmpegCommand = ffmpegCommand.outputOptions(['-crf', '23', '-preset', 'medium']);
81
- ffmpegCommand = ffmpegCommand.format('mp4');
82
- ffmpegCommand = ffmpegCommand.output(tempPath);
83
-
84
- ffmpegCommand
85
- .on('start', (commandLine) => {
86
- console.log(`FFmpeg command: ${commandLine}`);
87
- })
88
- .on('end', () => {
89
- const compressionResult = this.checkCompression(inputPath, tempPath);
90
-
91
- if (compressionResult.effective) {
92
- // Replace original with processed file
93
- fs.unlinkSync(inputPath);
94
- fs.renameSync(tempPath, outputPath);
95
- console.log(`✓ Processed: ${path.basename(inputPath)} (${compressionResult.compressionPercent}% reduction)`);
96
- resolve({
97
- processed: true,
98
- outputPath,
99
- originalSize: compressionResult.originalSize,
100
- newSize: compressionResult.newSize,
101
- compressionPercent: compressionResult.compressionPercent
102
- });
103
- } else {
104
- console.log(`✗ Ineffective compression, keeping original: ${path.basename(inputPath)}`);
105
- fs.unlinkSync(tempPath);
106
- resolve({ processed: false, reason: 'ineffective_compression' });
107
- }
108
- })
109
- .on('error', (err) => {
110
- console.error(`Error processing video ${inputPath}:`, err.message);
111
- console.error(`FFmpeg stderr: ${err.stderr || 'No stderr available'}`);
112
- if (fs.existsSync(tempPath)) {
113
- fs.unlinkSync(tempPath);
114
- }
115
- resolve({ processed: false, error: err.message });
116
- })
117
- .run();
118
- return;
50
+ // Always use temporary file for processing
51
+ const tempPath = path.join(parsedPath.dir, `${parsedPath.name}_temp.mp4`);
52
+ console.log(`Using temporary path for processing: ${path.basename(tempPath)}`);
53
+
54
+ let tempFfmpegCommand = ffmpeg(inputPath);
55
+
56
+ if (needsResize) {
57
+ const newHeight = Math.round((maxWidth / videoStream.width) * videoStream.height);
58
+ tempFfmpegCommand = tempFfmpegCommand.videoFilters(`scale=${maxWidth}:${newHeight}`);
59
+ console.log(`Resizing to: ${maxWidth}x${newHeight}`);
60
+ }
61
+
62
+ switch (encodeFormat.toLowerCase()) {
63
+ case 'h264':
64
+ tempFfmpegCommand = tempFfmpegCommand.videoCodec('libx264').audioCodec('aac');
65
+ break;
66
+ case 'h265':
67
+ tempFfmpegCommand = tempFfmpegCommand.videoCodec('libx265').audioCodec('aac');
68
+ break;
69
+ case 'vp9':
70
+ tempFfmpegCommand = tempFfmpegCommand.videoCodec('libvpx-vp9').audioCodec('libvorbis');
71
+ break;
72
+ default:
73
+ tempFfmpegCommand = tempFfmpegCommand.videoCodec('libx264').audioCodec('aac');
74
+ }
75
+
76
+ // Add input/output options with proper quoting
77
+ tempFfmpegCommand = tempFfmpegCommand.inputOptions(['-fflags', '+genpts']);
78
+ tempFfmpegCommand = tempFfmpegCommand.outputOptions(['-crf', '23', '-preset', 'medium', '-y']);
79
+ tempFfmpegCommand = tempFfmpegCommand.format('mp4');
80
+ tempFfmpegCommand = tempFfmpegCommand.output(tempPath);
81
+
82
+ tempFfmpegCommand
83
+ .on('start', (commandLine) => {
84
+ console.log(`FFmpeg command: ${commandLine}`);
85
+ })
86
+ .on('end', () => {
87
+ const compressionResult = this.checkCompression(inputPath, tempPath);
88
+
89
+ if (compressionResult.effective) {
90
+ // Replace original with processed file
91
+ fs.unlinkSync(inputPath);
92
+ fs.renameSync(tempPath, outputPath);
93
+ console.log(`✓ Processed: ${path.basename(inputPath)} (${compressionResult.compressionPercent}% reduction)`);
94
+ resolve({
95
+ processed: true,
96
+ outputPath,
97
+ originalSize: compressionResult.originalSize,
98
+ newSize: compressionResult.newSize,
99
+ compressionPercent: compressionResult.compressionPercent
100
+ });
101
+ } else {
102
+ console.log(`✗ Ineffective compression, keeping original: ${path.basename(inputPath)}`);
103
+ fs.unlinkSync(tempPath);
104
+ resolve({ processed: false, reason: 'ineffective_compression' });
105
+ }
106
+ })
107
+ .on('error', (err) => {
108
+ console.error(`Error processing video ${inputPath}:`, err.message);
109
+ console.error(`FFmpeg stderr: ${err.stderr || 'No stderr available'}`);
110
+ if (fs.existsSync(tempPath)) {
111
+ fs.unlinkSync(tempPath);
112
+ }
113
+ resolve({ processed: false, error: err.message });
114
+ })
115
+ .run();
119
116
  }
120
117
 
121
- let ffmpegCommand = ffmpeg(inputPath);
118
+ let normalFfmpegCommand = ffmpeg(inputPath);
122
119
 
123
120
  if (needsResize) {
124
121
  const newHeight = Math.round((maxWidth / videoStream.width) * videoStream.height);
125
- ffmpegCommand = ffmpegCommand.videoFilters(`scale=${maxWidth}:${newHeight}`);
122
+ normalFfmpegCommand = normalFfmpegCommand.videoFilters(`scale=${maxWidth}:${newHeight}`);
126
123
  console.log(`Resizing to: ${maxWidth}x${newHeight}`);
127
124
  }
128
125
 
129
126
  switch (encodeFormat.toLowerCase()) {
130
127
  case 'h264':
131
- ffmpegCommand = ffmpegCommand.videoCodec('libx264').audioCodec('aac');
128
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libx264').audioCodec('aac');
132
129
  break;
133
130
  case 'h265':
134
- ffmpegCommand = ffmpegCommand.videoCodec('libx265').audioCodec('aac');
131
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libx265').audioCodec('aac');
135
132
  break;
136
133
  case 'vp9':
137
- ffmpegCommand = ffmpegCommand.videoCodec('libvpx-vp9').audioCodec('libvorbis');
134
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libvpx-vp9').audioCodec('libvorbis');
138
135
  break;
139
136
  default:
140
- ffmpegCommand = ffmpegCommand.videoCodec('libx264').audioCodec('aac');
137
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libx264').audioCodec('aac');
141
138
  }
142
139
 
143
- ffmpegCommand = ffmpegCommand.inputOptions(['-fflags', '+genpts']);
144
- ffmpegCommand = ffmpegCommand.outputOptions(['-crf', '23', '-preset', 'medium']);
145
- ffmpegCommand = ffmpegCommand.format('mp4');
146
- ffmpegCommand = ffmpegCommand.output(outputPath);
140
+ // Add input/output options with proper quoting
141
+ normalFfmpegCommand = normalFfmpegCommand.inputOptions(['-fflags', '+genpts']);
142
+ normalFfmpegCommand = normalFfmpegCommand.outputOptions(['-crf', '23', '-preset', 'medium', '-y']);
143
+ normalFfmpegCommand = normalFfmpegCommand.format('mp4');
144
+ normalFfmpegCommand = normalFfmpegCommand.output(outputPath);
147
145
 
148
- ffmpegCommand
146
+ normalFfmpegCommand
149
147
  .on('start', (commandLine) => {
150
148
  console.log(`FFmpeg command: ${commandLine}`);
151
149
  })