image-video-optimizer 1.1.2 → 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.2",
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,102 +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
- ffmpegCommand
79
- .outputOptions('-crf 23')
80
- .outputOptions('-preset medium')
81
- .format('mp4')
82
- .output(tempPath)
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();
116
- 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}`);
117
60
  }
118
61
 
119
- let ffmpegCommand = ffmpeg(inputPath);
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();
116
+ }
120
117
 
118
+ let normalFfmpegCommand = ffmpeg(inputPath);
119
+
121
120
  if (needsResize) {
122
121
  const newHeight = Math.round((maxWidth / videoStream.width) * videoStream.height);
123
- ffmpegCommand = ffmpegCommand.videoFilters(`scale=${maxWidth}:${newHeight}`);
122
+ normalFfmpegCommand = normalFfmpegCommand.videoFilters(`scale=${maxWidth}:${newHeight}`);
124
123
  console.log(`Resizing to: ${maxWidth}x${newHeight}`);
125
124
  }
126
125
 
127
126
  switch (encodeFormat.toLowerCase()) {
128
127
  case 'h264':
129
- ffmpegCommand = ffmpegCommand.videoCodec('libx264').audioCodec('aac');
128
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libx264').audioCodec('aac');
130
129
  break;
131
130
  case 'h265':
132
- ffmpegCommand = ffmpegCommand.videoCodec('libx265').audioCodec('aac');
131
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libx265').audioCodec('aac');
133
132
  break;
134
133
  case 'vp9':
135
- ffmpegCommand = ffmpegCommand.videoCodec('libvpx-vp9').audioCodec('libvorbis');
134
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libvpx-vp9').audioCodec('libvorbis');
136
135
  break;
137
136
  default:
138
- ffmpegCommand = ffmpegCommand.videoCodec('libx264').audioCodec('aac');
137
+ normalFfmpegCommand = normalFfmpegCommand.videoCodec('libx264').audioCodec('aac');
139
138
  }
140
139
 
141
- ffmpegCommand
142
- .outputOptions('-crf 23')
143
- .outputOptions('-preset medium')
144
- .format('mp4')
145
- .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);
145
+
146
+ normalFfmpegCommand
146
147
  .on('start', (commandLine) => {
147
148
  console.log(`FFmpeg command: ${commandLine}`);
148
149
  })