amiudmodz 5.0.8 → 5.1.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/lib/Utils/messages-media.js +170 -81
- package/lib/Utils/messages.js +10 -1
- package/package.json +1 -1
|
@@ -103,43 +103,52 @@ const extractVideoThumb = async (path, destPath, time, size) => new Promise((res
|
|
|
103
103
|
});
|
|
104
104
|
const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
105
105
|
var _a, _b;
|
|
106
|
+
let tmpPath;
|
|
106
107
|
if (bufferOrFilePath instanceof stream_1.Readable) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
|
|
111
|
-
const img = lib.sharp.default(bufferOrFilePath);
|
|
112
|
-
const dimensions = await img.metadata();
|
|
113
|
-
const buffer = await img
|
|
114
|
-
.resize(width)
|
|
115
|
-
.jpeg({ quality: 50 })
|
|
116
|
-
.toBuffer();
|
|
117
|
-
return {
|
|
118
|
-
buffer,
|
|
119
|
-
original: {
|
|
120
|
-
width: dimensions.width,
|
|
121
|
-
height: dimensions.height,
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
|
|
126
|
-
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
|
|
127
|
-
const jimp = await read(bufferOrFilePath);
|
|
128
|
-
const dimensions = {
|
|
129
|
-
width: jimp.getWidth(),
|
|
130
|
-
height: jimp.getHeight()
|
|
131
|
-
};
|
|
132
|
-
const buffer = await jimp
|
|
133
|
-
.quality(50)
|
|
134
|
-
.resize(width, AUTO, RESIZE_BILINEAR)
|
|
135
|
-
.getBufferAsync(MIME_JPEG);
|
|
136
|
-
return {
|
|
137
|
-
buffer,
|
|
138
|
-
original: dimensions
|
|
139
|
-
};
|
|
108
|
+
// Spill stream to disk instead of buffering to RAM
|
|
109
|
+
tmpPath = await spillToDisk(bufferOrFilePath);
|
|
110
|
+
bufferOrFilePath = tmpPath;
|
|
140
111
|
}
|
|
141
|
-
|
|
142
|
-
|
|
112
|
+
try {
|
|
113
|
+
const lib = await getImageProcessingLibrary();
|
|
114
|
+
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
|
|
115
|
+
const img = lib.sharp.default(bufferOrFilePath);
|
|
116
|
+
const dimensions = await img.metadata();
|
|
117
|
+
const buffer = await img
|
|
118
|
+
.resize(width)
|
|
119
|
+
.jpeg({ quality: 50 })
|
|
120
|
+
.toBuffer();
|
|
121
|
+
return {
|
|
122
|
+
buffer,
|
|
123
|
+
original: {
|
|
124
|
+
width: dimensions.width,
|
|
125
|
+
height: dimensions.height,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
|
|
130
|
+
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
|
|
131
|
+
const jimp = await read(bufferOrFilePath);
|
|
132
|
+
const dimensions = {
|
|
133
|
+
width: jimp.getWidth(),
|
|
134
|
+
height: jimp.getHeight()
|
|
135
|
+
};
|
|
136
|
+
const buffer = await jimp
|
|
137
|
+
.quality(50)
|
|
138
|
+
.resize(width, AUTO, RESIZE_BILINEAR)
|
|
139
|
+
.getBufferAsync(MIME_JPEG);
|
|
140
|
+
return {
|
|
141
|
+
buffer,
|
|
142
|
+
original: dimensions
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
throw new boom_1.Boom('No image processing library available');
|
|
147
|
+
}
|
|
148
|
+
} finally {
|
|
149
|
+
if (tmpPath) {
|
|
150
|
+
await fs_1.promises.unlink(tmpPath).catch(() => {});
|
|
151
|
+
}
|
|
143
152
|
}
|
|
144
153
|
};
|
|
145
154
|
exports.extractImageThumb = extractImageThumb;
|
|
@@ -151,6 +160,7 @@ exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
|
|
|
151
160
|
const generateProfilePicture = async (mediaUpload) => {
|
|
152
161
|
var _a, _b;
|
|
153
162
|
let bufferOrFilePath;
|
|
163
|
+
let tmpPath;
|
|
154
164
|
if (Buffer.isBuffer(mediaUpload)) {
|
|
155
165
|
bufferOrFilePath = mediaUpload;
|
|
156
166
|
}
|
|
@@ -158,34 +168,41 @@ const generateProfilePicture = async (mediaUpload) => {
|
|
|
158
168
|
bufferOrFilePath = mediaUpload.url.toString();
|
|
159
169
|
}
|
|
160
170
|
else {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const lib = await getImageProcessingLibrary();
|
|
164
|
-
let img;
|
|
165
|
-
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
|
|
166
|
-
img = lib.sharp.default(bufferOrFilePath)
|
|
167
|
-
.resize(640, 640)
|
|
168
|
-
.jpeg({
|
|
169
|
-
quality: 50,
|
|
170
|
-
})
|
|
171
|
-
.toBuffer();
|
|
172
|
-
}
|
|
173
|
-
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
|
|
174
|
-
const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
|
|
175
|
-
const jimp = await read(bufferOrFilePath);
|
|
176
|
-
const min = Math.min(jimp.getWidth(), jimp.getHeight());
|
|
177
|
-
const cropped = jimp.crop(0, 0, min, min);
|
|
178
|
-
img = cropped
|
|
179
|
-
.quality(50)
|
|
180
|
-
.resize(640, 640, RESIZE_BILINEAR)
|
|
181
|
-
.getBufferAsync(MIME_JPEG);
|
|
171
|
+
tmpPath = await spillToDisk(mediaUpload.stream);
|
|
172
|
+
bufferOrFilePath = tmpPath;
|
|
182
173
|
}
|
|
183
|
-
|
|
184
|
-
|
|
174
|
+
try {
|
|
175
|
+
const lib = await getImageProcessingLibrary();
|
|
176
|
+
let img;
|
|
177
|
+
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
|
|
178
|
+
img = lib.sharp.default(bufferOrFilePath)
|
|
179
|
+
.resize(640, 640)
|
|
180
|
+
.jpeg({
|
|
181
|
+
quality: 50,
|
|
182
|
+
})
|
|
183
|
+
.toBuffer();
|
|
184
|
+
}
|
|
185
|
+
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
|
|
186
|
+
const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
|
|
187
|
+
const jimp = await read(bufferOrFilePath);
|
|
188
|
+
const min = Math.min(jimp.getWidth(), jimp.getHeight());
|
|
189
|
+
const cropped = jimp.crop(0, 0, min, min);
|
|
190
|
+
img = cropped
|
|
191
|
+
.quality(50)
|
|
192
|
+
.resize(640, 640, RESIZE_BILINEAR)
|
|
193
|
+
.getBufferAsync(MIME_JPEG);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
throw new boom_1.Boom('No image processing library available');
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
img: await img,
|
|
200
|
+
};
|
|
201
|
+
} finally {
|
|
202
|
+
if (tmpPath) {
|
|
203
|
+
await fs_1.promises.unlink(tmpPath).catch(() => {});
|
|
204
|
+
}
|
|
185
205
|
}
|
|
186
|
-
return {
|
|
187
|
-
img: await img,
|
|
188
|
-
};
|
|
189
206
|
};
|
|
190
207
|
exports.generateProfilePicture = generateProfilePicture;
|
|
191
208
|
/** gets the SHA256 of the given media message */
|
|
@@ -239,19 +256,14 @@ async function getAudioWaveform(buffer, logger) {
|
|
|
239
256
|
const { PassThrough } = require('stream');
|
|
240
257
|
const ff = require('fluent-ffmpeg');
|
|
241
258
|
|
|
242
|
-
|
|
259
|
+
const inputStream = new PassThrough();
|
|
243
260
|
if (Buffer.isBuffer(buffer)) {
|
|
244
|
-
|
|
261
|
+
inputStream.end(buffer);
|
|
245
262
|
} else if (typeof buffer === 'string') {
|
|
246
|
-
|
|
247
|
-
audioData = await exports.toBuffer(rStream);
|
|
263
|
+
(0, fs_1.createReadStream)(buffer).pipe(inputStream);
|
|
248
264
|
} else {
|
|
249
|
-
|
|
265
|
+
buffer.pipe(inputStream);
|
|
250
266
|
}
|
|
251
|
-
|
|
252
|
-
return await new Promise((resolve, reject) => {
|
|
253
|
-
const inputStream = new PassThrough();
|
|
254
|
-
inputStream.end(audioData);
|
|
255
267
|
const chunks = [];
|
|
256
268
|
const bars = 64;
|
|
257
269
|
|
|
@@ -280,7 +292,7 @@ async function getAudioWaveform(buffer, logger) {
|
|
|
280
292
|
const normalized = avg.map(v => Math.floor((v / max) * 100));
|
|
281
293
|
resolve(new Uint8Array(normalized));
|
|
282
294
|
})
|
|
283
|
-
.pipe()
|
|
295
|
+
.pipe(new PassThrough(), { end: true })
|
|
284
296
|
.on('data', chunk => chunks.push(chunk));
|
|
285
297
|
});
|
|
286
298
|
} catch (e) {
|
|
@@ -343,9 +355,31 @@ const toBuffer = async (stream) => {
|
|
|
343
355
|
return Buffer.concat(chunks);
|
|
344
356
|
};
|
|
345
357
|
exports.toBuffer = toBuffer;
|
|
358
|
+
/**
|
|
359
|
+
* Spills a Buffer or Readable stream to a temp file on disk so it is not
|
|
360
|
+
* held entirely in RAM. Returns the temp-file path so the caller can clean
|
|
361
|
+
* it up when done.
|
|
362
|
+
*/
|
|
363
|
+
const spillToDisk = async (source) => {
|
|
364
|
+
const tmpPath = (0, path_1.join)(getTmpFilesDirectory(), `spill-${(0, generics_1.generateMessageID)()}`);
|
|
365
|
+
const writeStream = (0, fs_1.createWriteStream)(tmpPath);
|
|
366
|
+
await new Promise((resolve, reject) => {
|
|
367
|
+
writeStream.on('error', reject);
|
|
368
|
+
writeStream.on('finish', resolve);
|
|
369
|
+
if (Buffer.isBuffer(source)) {
|
|
370
|
+
writeStream.end(source);
|
|
371
|
+
} else {
|
|
372
|
+
source.pipe(writeStream);
|
|
373
|
+
source.on('error', reject);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
return tmpPath;
|
|
377
|
+
};
|
|
346
378
|
const getStream = async (item, opts) => {
|
|
347
379
|
if (Buffer.isBuffer(item)) {
|
|
348
|
-
|
|
380
|
+
// Write the Buffer to a temp file so it can be GC'd from RAM
|
|
381
|
+
const tmpPath = await spillToDisk(item);
|
|
382
|
+
return { stream: (0, fs_1.createReadStream)(tmpPath), type: 'file', spilledPath: tmpPath };
|
|
349
383
|
}
|
|
350
384
|
if ('stream' in item) {
|
|
351
385
|
return { stream: item.stream, type: 'readable' };
|
|
@@ -479,15 +513,48 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
|
|
|
479
513
|
};
|
|
480
514
|
exports.prepareStream = prepareStream;
|
|
481
515
|
const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts, isPtt, forceOpus } = {}) => {
|
|
482
|
-
const { stream, type } = await (0, exports.getStream)(media, opts);
|
|
516
|
+
const { stream, type, spilledPath } = await (0, exports.getStream)(media, opts);
|
|
483
517
|
let finalStream = stream;
|
|
484
518
|
let opusConverted = false;
|
|
519
|
+
let opusSpillPath;
|
|
485
520
|
if (mediaType === 'audio' && (isPtt === true || forceOpus === true)) {
|
|
486
521
|
try {
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
522
|
+
// Write audio to temp file, convert via ffmpeg, write result to another temp file
|
|
523
|
+
const audioTmpPath = await spillToDisk(stream);
|
|
524
|
+
try {
|
|
525
|
+
const ff = require('fluent-ffmpeg');
|
|
526
|
+
opusSpillPath = (0, path_1.join)(getTmpFilesDirectory(), `opus-${(0, generics_1.generateMessageID)()}`);
|
|
527
|
+
const opusWriteStream = (0, fs_1.createWriteStream)(opusSpillPath);
|
|
528
|
+
|
|
529
|
+
await new Promise((resolve, reject) => {
|
|
530
|
+
ff(audioTmpPath)
|
|
531
|
+
.noVideo()
|
|
532
|
+
.audioCodec('libopus')
|
|
533
|
+
.format('ogg')
|
|
534
|
+
.audioBitrate('48k')
|
|
535
|
+
.audioChannels(1)
|
|
536
|
+
.audioFrequency(48000)
|
|
537
|
+
.outputOptions([
|
|
538
|
+
'-vn',
|
|
539
|
+
'-b:a 64k',
|
|
540
|
+
'-ac 2',
|
|
541
|
+
'-ar 48000',
|
|
542
|
+
'-map_metadata', '-1',
|
|
543
|
+
'-application', 'voip'
|
|
544
|
+
])
|
|
545
|
+
.on('error', (err) => {
|
|
546
|
+
opusWriteStream.destroy();
|
|
547
|
+
reject(err);
|
|
548
|
+
})
|
|
549
|
+
.on('end', resolve)
|
|
550
|
+
.pipe(opusWriteStream);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
finalStream = (0, fs_1.createReadStream)(opusSpillPath);
|
|
554
|
+
opusConverted = true;
|
|
555
|
+
} finally {
|
|
556
|
+
await fs_1.promises.unlink(audioTmpPath).catch(() => {});
|
|
557
|
+
}
|
|
491
558
|
}
|
|
492
559
|
catch (error) {
|
|
493
560
|
const { stream: newStream } = await (0, exports.getStream)(media, opts);
|
|
@@ -497,15 +564,16 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
497
564
|
const mediaKey = Crypto.randomBytes(32);
|
|
498
565
|
const { cipherKey, iv, macKey } = getMediaKeys(mediaKey, mediaType);
|
|
499
566
|
|
|
500
|
-
|
|
501
567
|
const encPath = (0, path_1.join)(getTmpFilesDirectory(), `enc-${mediaType}-${(0, generics_1.generateMessageID)()}`);
|
|
502
568
|
const encTempWriteStream = (0, fs_1.createWriteStream)(encPath);
|
|
503
569
|
|
|
504
570
|
let bodyPath;
|
|
505
571
|
let writeStream;
|
|
506
572
|
let didSaveToTmpPath = false;
|
|
573
|
+
// For 'file' type (includes our spill-to-disk buffers), use the path directly
|
|
507
574
|
if (type === 'file') {
|
|
508
|
-
|
|
575
|
+
// If spilled from a Buffer, spilledPath IS the original; use it as bodyPath
|
|
576
|
+
bodyPath = spilledPath || (media && media.url ? media.url : undefined);
|
|
509
577
|
}
|
|
510
578
|
else if (saveOriginalFileIfRequired) {
|
|
511
579
|
bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
|
|
@@ -556,15 +624,32 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
556
624
|
const fileSha256 = sha256Plain.digest();
|
|
557
625
|
const fileEncSha256 = sha256Enc.digest();
|
|
558
626
|
|
|
559
|
-
|
|
560
|
-
|
|
627
|
+
await new Promise((resolve, reject) => {
|
|
628
|
+
encTempWriteStream.end(resolve);
|
|
629
|
+
encTempWriteStream.once('error', reject);
|
|
630
|
+
});
|
|
631
|
+
if (writeStream) {
|
|
632
|
+
await new Promise((resolve, reject) => {
|
|
633
|
+
writeStream.end(resolve);
|
|
634
|
+
writeStream.once('error', reject);
|
|
635
|
+
});
|
|
636
|
+
}
|
|
561
637
|
finalStream.destroy();
|
|
638
|
+
// Clean up the opus spill temp file if used
|
|
639
|
+
if (opusSpillPath) {
|
|
640
|
+
await fs_1.promises.unlink(opusSpillPath).catch(() => {});
|
|
641
|
+
}
|
|
642
|
+
// Clean up the buffer-spill temp file — it's no longer needed once encrypted
|
|
643
|
+
// (the enc temp file is what gets uploaded; bodyPath/spilledPath is only
|
|
644
|
+
// needed for thumbnail/duration computation which happens in parallel)
|
|
645
|
+
// Caller (messages.js) handles cleanup of streamPath & bodyPath.
|
|
562
646
|
|
|
563
647
|
return {
|
|
564
648
|
mediaKey,
|
|
565
649
|
encWriteStream: (0, fs_1.createReadStream)(encPath),
|
|
566
650
|
bodyPath,
|
|
567
651
|
streamPath: encPath,
|
|
652
|
+
spilledPath, // expose so caller can clean up the buffer spill file
|
|
568
653
|
mac,
|
|
569
654
|
fileEncSha256,
|
|
570
655
|
fileSha256,
|
|
@@ -581,6 +666,9 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
581
666
|
sha256Plain.destroy();
|
|
582
667
|
sha256Enc.destroy();
|
|
583
668
|
finalStream.destroy();
|
|
669
|
+
if (opusSpillPath) {
|
|
670
|
+
await fs_1.promises.unlink(opusSpillPath).catch(() => {});
|
|
671
|
+
}
|
|
584
672
|
try {
|
|
585
673
|
await fs_1.promises.unlink(encPath);
|
|
586
674
|
} catch { }
|
|
@@ -746,6 +834,7 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
|
|
|
746
834
|
headers: {
|
|
747
835
|
...options.headers || {},
|
|
748
836
|
'Content-Type': 'application/octet-stream',
|
|
837
|
+
'Content-Length': bodyLength.toString(),
|
|
749
838
|
'Origin': Defaults_1.DEFAULT_ORIGIN
|
|
750
839
|
},
|
|
751
840
|
httpsAgent: fetchAgent,
|
package/lib/Utils/messages.js
CHANGED
|
@@ -149,7 +149,7 @@ const prepareWAMessageMedia = async (message, options) => {
|
|
|
149
149
|
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
|
|
150
150
|
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
|
|
151
151
|
|
|
152
|
-
const { mediaKey, encWriteStream, bodyPath, streamPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath, opusConverted } = await (options.newsletter ? messages_media_1.prepareStream : messages_media_1.encryptedStream)(uploadData.media, options.mediaTypeOverride || mediaType, {
|
|
152
|
+
const { mediaKey, encWriteStream, bodyPath, streamPath, spilledPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath, opusConverted } = await (options.newsletter ? messages_media_1.prepareStream : messages_media_1.encryptedStream)(uploadData.media, options.mediaTypeOverride || mediaType, {
|
|
153
153
|
logger,
|
|
154
154
|
saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
|
|
155
155
|
opts: options.options,
|
|
@@ -157,6 +157,9 @@ const prepareWAMessageMedia = async (message, options) => {
|
|
|
157
157
|
forceOpus: (mediaType === "audio" && uploadData.mimetype && uploadData.mimetype.includes('opus'))
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
// Nullify the media reference to help GC free the memory if it's a large Buffer
|
|
161
|
+
uploadData.media = undefined;
|
|
162
|
+
|
|
160
163
|
if (mediaType === 'audio' && opusConverted) {
|
|
161
164
|
uploadData.mimetype = 'audio/ogg; codecs=opus';
|
|
162
165
|
}
|
|
@@ -210,6 +213,12 @@ const prepareWAMessageMedia = async (message, options) => {
|
|
|
210
213
|
if (streamPath) {
|
|
211
214
|
await fs_1.promises.unlink(streamPath).catch(() => { });
|
|
212
215
|
}
|
|
216
|
+
// Clean up the buffer-spill temp file (if input was a Buffer, it was
|
|
217
|
+
// written to /tmp to avoid holding it in RAM; delete it now that we
|
|
218
|
+
// are done with thumbnail/duration computation)
|
|
219
|
+
if (spilledPath && spilledPath !== bodyPath) {
|
|
220
|
+
await fs_1.promises.unlink(spilledPath).catch(() => { });
|
|
221
|
+
}
|
|
213
222
|
logger === null || logger === void 0 ? void 0 : logger.debug('removed tmp files');
|
|
214
223
|
});
|
|
215
224
|
|