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.
- package/optimizeMedia.js +178 -1
- 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.
|
|
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",
|