b2b-platform-utils 1.1.56 → 1.1.57

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.
Files changed (2) hide show
  1. package/optimizeMedia.js +178 -1
  2. package/package.json +1 -1
package/optimizeMedia.js CHANGED
@@ -82,8 +82,185 @@ async function saveGameImageBufferWEBPWithOptions(fileDataBuffer, options = {})
82
82
  .toBuffer();
83
83
  }
84
84
 
85
+ const WEBP_CONVERSION_FORMATS = new Set(['jpeg', 'png', 'webp']);
86
+
87
+ function normalizeImageFileType(originalFileType) {
88
+ if (!originalFileType) {
89
+ return null;
90
+ }
91
+
92
+ let type = String(originalFileType).toLowerCase().replace(/^\./, '');
93
+
94
+ if (type === 'jpg') {
95
+ type = 'jpeg';
96
+ }
97
+
98
+ return type;
99
+ }
100
+
101
+ function isKnownFormat(fileType) {
102
+ return fileType === 'gif' || fileType === 'avif' || WEBP_CONVERSION_FORMATS.has(fileType);
103
+ }
104
+
105
+ function hasResizeOptions(options) {
106
+ const { maxWidth, width, height } = options;
107
+
108
+ return width != null || height != null || maxWidth != null;
109
+ }
110
+
111
+ function buildResizeOptions(options) {
112
+ const {
113
+ maxWidth,
114
+ width,
115
+ height,
116
+ fit = 'inside',
117
+ withoutEnlargement = true
118
+ } = options;
119
+
120
+ const resizeOptions = { fit, withoutEnlargement };
121
+
122
+ if (width != null) {
123
+ resizeOptions.width = width;
124
+ } else if (maxWidth != null) {
125
+ resizeOptions.width = maxWidth;
126
+ }
127
+
128
+ if (height != null) {
129
+ resizeOptions.height = height;
130
+ }
131
+
132
+ return resizeOptions;
133
+ }
134
+
135
+ async function resizePipelineIfNeeded(pipeline, fileDataBuffer, options) {
136
+ const { resize = 1 } = options;
137
+
138
+ if (hasResizeOptions(options)) {
139
+ return pipeline.resize(buildResizeOptions(options));
140
+ }
141
+
142
+ if (resize !== 1) {
143
+ const metadata = await sharp(fileDataBuffer).metadata();
144
+ const newWidth = Math.round(metadata.width * resize);
145
+ const newHeight = Math.round(metadata.height * resize);
146
+
147
+ return pipeline.resize(newWidth, newHeight);
148
+ }
149
+
150
+ return pipeline;
151
+ }
152
+
153
+ async function encodeWebpBuffer(fileDataBuffer, options) {
154
+ let pipeline = sharp(fileDataBuffer);
155
+ pipeline = await resizePipelineIfNeeded(pipeline, fileDataBuffer, options);
156
+
157
+ return pipeline
158
+ .webp({
159
+ quality: options.quality,
160
+ lossless: false,
161
+ smartSubsample: options.smartSubsample,
162
+ effort: options.effort,
163
+ alphaQuality: options.alphaQuality
164
+ })
165
+ .toBuffer();
166
+ }
167
+
168
+ /**
169
+ * Format-aware image optimization: preserves GIF/AVIF, converts JPEG/PNG/WebP to WebP, safe fallback on failure.
170
+ * @param {Buffer} fileDataBuffer
171
+ * @param {string} originalFileType
172
+ * @param {object} [options]
173
+ * @returns {Promise<{ buffer: Buffer, fileType: string, optimized: boolean }>}
174
+ */
175
+ async function optimizeImageBufferByFormat(fileDataBuffer, originalFileType, options = {}) {
176
+ const opts = {
177
+ quality: 80,
178
+ resize: 1,
179
+ alphaQuality: 80,
180
+ maxWidth: null,
181
+ width: null,
182
+ height: null,
183
+ fit: 'inside',
184
+ withoutEnlargement: true,
185
+ effort: 5,
186
+ smartSubsample: true,
187
+ ...options
188
+ };
189
+
190
+ let normalizedType = normalizeImageFileType(originalFileType);
191
+
192
+ try {
193
+ if (normalizedType === 'gif') {
194
+ return { buffer: fileDataBuffer, fileType: 'gif', optimized: false };
195
+ }
196
+
197
+ if (!normalizedType || !isKnownFormat(normalizedType)) {
198
+ const metadata = await sharp(fileDataBuffer).metadata();
199
+ const metadataType = normalizeImageFileType(metadata.format);
200
+
201
+ if (metadataType) {
202
+ normalizedType = metadataType;
203
+ }
204
+ }
205
+
206
+ if (normalizedType === 'gif') {
207
+ return { buffer: fileDataBuffer, fileType: 'gif', optimized: false };
208
+ }
209
+
210
+ if (normalizedType === 'avif') {
211
+ try {
212
+ let pipeline = sharp(fileDataBuffer);
213
+
214
+ if (hasResizeOptions(opts)) {
215
+ pipeline = pipeline.resize(buildResizeOptions(opts));
216
+ }
217
+
218
+ const buffer = await pipeline
219
+ .avif({
220
+ quality: opts.quality,
221
+ effort: opts.effort
222
+ })
223
+ .toBuffer();
224
+
225
+ return { buffer, fileType: 'avif', optimized: true };
226
+ } catch {
227
+ return { buffer: fileDataBuffer, fileType: 'avif', optimized: false };
228
+ }
229
+ }
230
+
231
+ if (WEBP_CONVERSION_FORMATS.has(normalizedType)) {
232
+ try {
233
+ const buffer = await encodeWebpBuffer(fileDataBuffer, opts);
234
+
235
+ return { buffer, fileType: 'webp', optimized: true };
236
+ } catch {
237
+ return { buffer: fileDataBuffer, fileType: normalizedType, optimized: false };
238
+ }
239
+ }
240
+
241
+ try {
242
+ const buffer = await encodeWebpBuffer(fileDataBuffer, opts);
243
+
244
+ return { buffer, fileType: 'webp', optimized: true };
245
+ } catch {
246
+ return {
247
+ buffer: fileDataBuffer,
248
+ fileType: normalizedType || 'bin',
249
+ optimized: false
250
+ };
251
+ }
252
+ } catch {
253
+ return {
254
+ buffer: fileDataBuffer,
255
+ fileType: normalizedType || normalizeImageFileType(originalFileType) || 'bin',
256
+ optimized: false
257
+ };
258
+ }
259
+ }
260
+
85
261
  module.exports = {
86
262
  optimizeImageBufferWEBP,
87
263
  saveGameImageBufferWEBP,
88
- saveGameImageBufferWEBPWithOptions
264
+ saveGameImageBufferWEBPWithOptions,
265
+ optimizeImageBufferByFormat
89
266
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b2b-platform-utils",
3
- "version": "1.1.56",
3
+ "version": "1.1.57",
4
4
  "description": "Shared utilities for Node.js microservices: errors map, local cache, logger, numbers, dates, filesystem, media optimization, paginator, slugger, crypto wrapper, sanitize HTML, sorting.",
5
5
  "type": "commonjs",
6
6
  "license": "KingSizer",