roxify 1.2.6 → 1.2.7
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/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pack.d.ts +4 -0
- package/dist/pack.js +5 -1
- package/dist/utils/crc.js +1 -1
- package/dist/utils/decoder.js +113 -74
- package/dist/utils/encoder.d.ts +1 -1
- package/dist/utils/encoder.js +188 -114
- package/dist/utils/optimization.js +4 -0
- package/dist/utils/zstd.d.ts +1 -1
- package/dist/utils/zstd.js +26 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,4 @@ export * from './utils/reconstitution.js';
|
|
|
10
10
|
export * from './utils/types.js';
|
|
11
11
|
export * from './utils/zstd.js';
|
|
12
12
|
export { decodeMinPng, encodeMinPng } from './minpng.js';
|
|
13
|
-
export { packPaths, unpackBuffer } from './pack.js';
|
|
13
|
+
export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
|
package/dist/index.js
CHANGED
|
@@ -10,4 +10,4 @@ export * from './utils/reconstitution.js';
|
|
|
10
10
|
export * from './utils/types.js';
|
|
11
11
|
export * from './utils/zstd.js';
|
|
12
12
|
export { decodeMinPng, encodeMinPng } from './minpng.js';
|
|
13
|
-
export { packPaths, unpackBuffer } from './pack.js';
|
|
13
|
+
export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
|
package/dist/pack.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface VFSIndexEntry {
|
|
|
10
10
|
offset: number;
|
|
11
11
|
size: number;
|
|
12
12
|
}
|
|
13
|
+
export declare function packPathsToParts(paths: string[], baseDir?: string, onProgress?: (readBytes: number, totalBytes: number, currentFile?: string) => void): {
|
|
14
|
+
parts: Buffer[];
|
|
15
|
+
list: string[];
|
|
16
|
+
};
|
|
13
17
|
export declare function packPaths(paths: string[], baseDir?: string, onProgress?: (readBytes: number, totalBytes: number, currentFile?: string) => void): {
|
|
14
18
|
buf: Buffer;
|
|
15
19
|
list: string[];
|
package/dist/pack.js
CHANGED
|
@@ -14,7 +14,7 @@ function* collectFilesGenerator(paths) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
export function
|
|
17
|
+
export function packPathsToParts(paths, baseDir, onProgress) {
|
|
18
18
|
const files = [];
|
|
19
19
|
for (const f of collectFilesGenerator(paths)) {
|
|
20
20
|
files.push(f);
|
|
@@ -48,6 +48,10 @@ export function packPaths(paths, baseDir, onProgress) {
|
|
|
48
48
|
header.writeUInt32BE(0x524f5850, 0);
|
|
49
49
|
header.writeUInt32BE(files.length, 4);
|
|
50
50
|
parts.unshift(header);
|
|
51
|
+
return { parts, list };
|
|
52
|
+
}
|
|
53
|
+
export function packPaths(paths, baseDir, onProgress) {
|
|
54
|
+
const { parts, list } = packPathsToParts(paths, baseDir, onProgress);
|
|
51
55
|
return { buf: Buffer.concat(parts), list };
|
|
52
56
|
}
|
|
53
57
|
export function unpackBuffer(buf, fileList) {
|
package/dist/utils/crc.js
CHANGED
|
@@ -16,7 +16,7 @@ export function crc32(buf, previous = 0) {
|
|
|
16
16
|
for (let i = 0; i < buf.length; i++) {
|
|
17
17
|
crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
|
18
18
|
}
|
|
19
|
-
return crc ^ 0xffffffff;
|
|
19
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
20
20
|
}
|
|
21
21
|
export function adler32(buf, prev = 1) {
|
|
22
22
|
let s1 = prev & 0xffff;
|
package/dist/utils/decoder.js
CHANGED
|
@@ -92,7 +92,7 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
92
92
|
try {
|
|
93
93
|
const info = await sharp(pngBuf).metadata();
|
|
94
94
|
if (info.width && info.height) {
|
|
95
|
-
const MAX_RAW_BYTES =
|
|
95
|
+
const MAX_RAW_BYTES = 1200 * 1024 * 1024;
|
|
96
96
|
const rawBytesEstimate = info.width * info.height * 4;
|
|
97
97
|
if (rawBytesEstimate > MAX_RAW_BYTES) {
|
|
98
98
|
throw new DataFormatError(`Image too large to decode in-process (${Math.round(rawBytesEstimate / 1024 / 1024)} MB). Increase Node heap or use a smaller image/compact mode.`);
|
|
@@ -225,17 +225,44 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
225
225
|
return { buf: payload, meta: { name } };
|
|
226
226
|
}
|
|
227
227
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
228
|
+
const metadata = await sharp(processedBuf).metadata();
|
|
229
|
+
const currentWidth = metadata.width;
|
|
230
|
+
const currentHeight = metadata.height;
|
|
231
|
+
const rawRGB = Buffer.allocUnsafe(currentWidth * currentHeight * 3);
|
|
232
|
+
let writeOffset = 0;
|
|
233
|
+
const rowsPerChunk = 2000;
|
|
234
|
+
for (let startRow = 0; startRow < currentHeight; startRow += rowsPerChunk) {
|
|
235
|
+
const endRow = Math.min(startRow + rowsPerChunk, currentHeight);
|
|
236
|
+
const chunkHeight = endRow - startRow;
|
|
237
|
+
const { data: chunkData, info: chunkInfo } = await sharp(processedBuf)
|
|
238
|
+
.extract({
|
|
239
|
+
left: 0,
|
|
240
|
+
top: startRow,
|
|
241
|
+
width: currentWidth,
|
|
242
|
+
height: chunkHeight,
|
|
243
|
+
})
|
|
244
|
+
.raw()
|
|
245
|
+
.toBuffer({ resolveWithObject: true });
|
|
246
|
+
const channels = chunkInfo.channels;
|
|
247
|
+
const pixelsInChunk = currentWidth * chunkHeight;
|
|
248
|
+
if (channels === 3) {
|
|
249
|
+
chunkData.copy(rawRGB, writeOffset);
|
|
250
|
+
writeOffset += pixelsInChunk * 3;
|
|
251
|
+
}
|
|
252
|
+
else if (channels === 4) {
|
|
253
|
+
for (let i = 0; i < pixelsInChunk; i++) {
|
|
254
|
+
rawRGB[writeOffset++] = chunkData[i * 4];
|
|
255
|
+
rawRGB[writeOffset++] = chunkData[i * 4 + 1];
|
|
256
|
+
rawRGB[writeOffset++] = chunkData[i * 4 + 2];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (opts.onProgress) {
|
|
260
|
+
opts.onProgress({
|
|
261
|
+
phase: 'extract_pixels',
|
|
262
|
+
loaded: endRow,
|
|
263
|
+
total: currentHeight,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
239
266
|
}
|
|
240
267
|
const firstPixels = [];
|
|
241
268
|
for (let i = 0; i < Math.min(MARKER_START.length, rawRGB.length / 3); i++) {
|
|
@@ -278,29 +305,25 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
278
305
|
else {
|
|
279
306
|
const reconstructed = await cropAndReconstitute(processedBuf, opts.debugDir);
|
|
280
307
|
const { data: rdata, info: rinfo } = await sharp(reconstructed)
|
|
281
|
-
.ensureAlpha()
|
|
282
308
|
.raw()
|
|
283
309
|
.toBuffer({ resolveWithObject: true });
|
|
284
310
|
logicalWidth = rinfo.width;
|
|
285
311
|
logicalHeight = rinfo.height;
|
|
286
312
|
logicalData = Buffer.alloc(rinfo.width * rinfo.height * 3);
|
|
287
|
-
|
|
288
|
-
logicalData
|
|
289
|
-
|
|
290
|
-
|
|
313
|
+
if (rinfo.channels === 3) {
|
|
314
|
+
rdata.copy(logicalData);
|
|
315
|
+
}
|
|
316
|
+
else if (rinfo.channels === 4) {
|
|
317
|
+
for (let i = 0; i < logicalWidth * logicalHeight; i++) {
|
|
318
|
+
logicalData[i * 3] = rdata[i * 4];
|
|
319
|
+
logicalData[i * 3 + 1] = rdata[i * 4 + 1];
|
|
320
|
+
logicalData[i * 3 + 2] = rdata[i * 4 + 2];
|
|
321
|
+
}
|
|
291
322
|
}
|
|
292
323
|
}
|
|
293
324
|
if (process.env.ROX_DEBUG) {
|
|
294
325
|
console.log('DEBUG: Logical grid reconstructed:', logicalWidth, 'x', logicalHeight, '=', logicalWidth * logicalHeight, 'pixels');
|
|
295
326
|
}
|
|
296
|
-
const finalGrid = [];
|
|
297
|
-
for (let i = 0; i < logicalData.length; i += 3) {
|
|
298
|
-
finalGrid.push({
|
|
299
|
-
r: logicalData[i],
|
|
300
|
-
g: logicalData[i + 1],
|
|
301
|
-
b: logicalData[i + 2],
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
327
|
if (hasPixelMagic) {
|
|
305
328
|
if (logicalData.length < 8 + PIXEL_MAGIC.length) {
|
|
306
329
|
throw new DataFormatError('Pixel mode data too short');
|
|
@@ -337,15 +360,15 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
337
360
|
payload = payload.slice(MAGIC.length);
|
|
338
361
|
return { buf: payload, meta: { name } };
|
|
339
362
|
}
|
|
363
|
+
const totalPixels = (logicalData.length / 3) | 0;
|
|
340
364
|
let startIdx = -1;
|
|
341
|
-
for (let i = 0; i <=
|
|
365
|
+
for (let i = 0; i <= totalPixels - MARKER_START.length; i++) {
|
|
342
366
|
let match = true;
|
|
343
367
|
for (let mi = 0; mi < MARKER_START.length && match; mi++) {
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
p.b !== MARKER_START[mi].b) {
|
|
368
|
+
const offset = (i + mi) * 3;
|
|
369
|
+
if (logicalData[offset] !== MARKER_START[mi].r ||
|
|
370
|
+
logicalData[offset + 1] !== MARKER_START[mi].g ||
|
|
371
|
+
logicalData[offset + 2] !== MARKER_START[mi].b) {
|
|
349
372
|
match = false;
|
|
350
373
|
}
|
|
351
374
|
}
|
|
@@ -356,7 +379,7 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
356
379
|
}
|
|
357
380
|
if (startIdx === -1) {
|
|
358
381
|
if (process.env.ROX_DEBUG) {
|
|
359
|
-
console.log('DEBUG: MARKER_START not found in grid of',
|
|
382
|
+
console.log('DEBUG: MARKER_START not found in grid of', totalPixels, 'pixels');
|
|
360
383
|
console.log('DEBUG: Trying 2D scan for START marker...');
|
|
361
384
|
}
|
|
362
385
|
let found2D = false;
|
|
@@ -417,17 +440,20 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
417
440
|
if (process.env.ROX_DEBUG) {
|
|
418
441
|
console.log(`DEBUG: Extracted rectangle: ${rectWidth}x${rectHeight} from (${x},${y})`);
|
|
419
442
|
}
|
|
420
|
-
|
|
443
|
+
const newDataLen = rectWidth * rectHeight * 3;
|
|
444
|
+
const newData = Buffer.allocUnsafe(newDataLen);
|
|
445
|
+
let writeIdx = 0;
|
|
421
446
|
for (let ry = y; ry <= endY; ry++) {
|
|
422
447
|
for (let rx = x; rx <= endX; rx++) {
|
|
423
448
|
const idx = (ry * logicalWidth + rx) * 3;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
b: logicalData[idx + 2],
|
|
428
|
-
});
|
|
449
|
+
newData[writeIdx++] = logicalData[idx];
|
|
450
|
+
newData[writeIdx++] = logicalData[idx + 1];
|
|
451
|
+
newData[writeIdx++] = logicalData[idx + 2];
|
|
429
452
|
}
|
|
430
453
|
}
|
|
454
|
+
logicalData = newData;
|
|
455
|
+
logicalWidth = rectWidth;
|
|
456
|
+
logicalHeight = rectHeight;
|
|
431
457
|
startIdx = 0;
|
|
432
458
|
found2D = true;
|
|
433
459
|
}
|
|
@@ -435,34 +461,43 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
435
461
|
}
|
|
436
462
|
if (!found2D) {
|
|
437
463
|
if (process.env.ROX_DEBUG) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
.
|
|
464
|
+
const first20 = [];
|
|
465
|
+
for (let i = 0; i < Math.min(20, totalPixels); i++) {
|
|
466
|
+
const offset = i * 3;
|
|
467
|
+
first20.push(`(${logicalData[offset]},${logicalData[offset + 1]},${logicalData[offset + 2]})`);
|
|
468
|
+
}
|
|
469
|
+
console.log('DEBUG: First 20 pixels:', first20.join(' '));
|
|
442
470
|
}
|
|
443
471
|
throw new Error('Marker START not found - image format not supported');
|
|
444
472
|
}
|
|
445
473
|
}
|
|
446
474
|
if (process.env.ROX_DEBUG && startIdx === 0) {
|
|
447
|
-
console.log(`DEBUG: MARKER_START at index ${startIdx}, grid size: ${
|
|
475
|
+
console.log(`DEBUG: MARKER_START at index ${startIdx}, grid size: ${totalPixels}`);
|
|
448
476
|
}
|
|
449
|
-
const
|
|
450
|
-
|
|
477
|
+
const dataStartPixel = startIdx + MARKER_START.length + 1;
|
|
478
|
+
const curTotalPixels = (logicalData.length / 3) | 0;
|
|
479
|
+
if (curTotalPixels < dataStartPixel + MARKER_END.length) {
|
|
451
480
|
if (process.env.ROX_DEBUG) {
|
|
452
|
-
console.log('DEBUG:
|
|
481
|
+
console.log('DEBUG: grid too small:', curTotalPixels, 'pixels');
|
|
453
482
|
}
|
|
454
483
|
throw new Error('Marker START or END not found - image format not supported');
|
|
455
484
|
}
|
|
456
485
|
for (let i = 0; i < MARKER_START.length; i++) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
486
|
+
const offset = (startIdx + i) * 3;
|
|
487
|
+
if (logicalData[offset] !== MARKER_START[i].r ||
|
|
488
|
+
logicalData[offset + 1] !== MARKER_START[i].g ||
|
|
489
|
+
logicalData[offset + 2] !== MARKER_START[i].b) {
|
|
460
490
|
throw new Error('Marker START not found - image format not supported');
|
|
461
491
|
}
|
|
462
492
|
}
|
|
463
493
|
let compression = 'zstd';
|
|
464
|
-
if (
|
|
465
|
-
const
|
|
494
|
+
if (curTotalPixels > startIdx + MARKER_START.length) {
|
|
495
|
+
const compOffset = (startIdx + MARKER_START.length) * 3;
|
|
496
|
+
const compPixel = {
|
|
497
|
+
r: logicalData[compOffset],
|
|
498
|
+
g: logicalData[compOffset + 1],
|
|
499
|
+
b: logicalData[compOffset + 2],
|
|
500
|
+
};
|
|
466
501
|
if (compPixel.r === 0 && compPixel.g === 255 && compPixel.b === 0) {
|
|
467
502
|
compression = 'zstd';
|
|
468
503
|
}
|
|
@@ -473,47 +508,51 @@ export async function decodePngToBinary(input, opts = {}) {
|
|
|
473
508
|
if (process.env.ROX_DEBUG) {
|
|
474
509
|
console.log(`DEBUG: Detected compression: ${compression}`);
|
|
475
510
|
}
|
|
476
|
-
let
|
|
511
|
+
let endStartPixel = -1;
|
|
477
512
|
const lastLineStart = (logicalHeight - 1) * logicalWidth;
|
|
478
513
|
const endMarkerStartCol = logicalWidth - MARKER_END.length;
|
|
479
|
-
if (lastLineStart + endMarkerStartCol <
|
|
514
|
+
if (lastLineStart + endMarkerStartCol < curTotalPixels) {
|
|
480
515
|
let matchEnd = true;
|
|
481
516
|
for (let mi = 0; mi < MARKER_END.length && matchEnd; mi++) {
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
517
|
+
const pixelIdx = lastLineStart + endMarkerStartCol + mi;
|
|
518
|
+
if (pixelIdx >= curTotalPixels) {
|
|
484
519
|
matchEnd = false;
|
|
485
520
|
break;
|
|
486
521
|
}
|
|
487
|
-
const
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
522
|
+
const offset = pixelIdx * 3;
|
|
523
|
+
if (logicalData[offset] !== MARKER_END[mi].r ||
|
|
524
|
+
logicalData[offset + 1] !== MARKER_END[mi].g ||
|
|
525
|
+
logicalData[offset + 2] !== MARKER_END[mi].b) {
|
|
491
526
|
matchEnd = false;
|
|
492
527
|
}
|
|
493
528
|
}
|
|
494
529
|
if (matchEnd) {
|
|
495
|
-
|
|
530
|
+
endStartPixel = lastLineStart + endMarkerStartCol - startIdx;
|
|
496
531
|
if (process.env.ROX_DEBUG) {
|
|
497
532
|
console.log(`DEBUG: Found END marker at last line, col ${endMarkerStartCol}`);
|
|
498
533
|
}
|
|
499
534
|
}
|
|
500
535
|
}
|
|
501
|
-
if (
|
|
536
|
+
if (endStartPixel === -1) {
|
|
502
537
|
if (process.env.ROX_DEBUG) {
|
|
503
538
|
console.log('DEBUG: END marker not found at expected position');
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
539
|
+
const lastLinePixels = [];
|
|
540
|
+
for (let i = Math.max(0, lastLineStart); i < curTotalPixels && i < lastLineStart + 20; i++) {
|
|
541
|
+
const offset = i * 3;
|
|
542
|
+
lastLinePixels.push(`(${logicalData[offset]},${logicalData[offset + 1]},${logicalData[offset + 2]})`);
|
|
543
|
+
}
|
|
544
|
+
console.log('DEBUG: Last line pixels:', lastLinePixels.join(' '));
|
|
545
|
+
}
|
|
546
|
+
endStartPixel = curTotalPixels - startIdx;
|
|
547
|
+
}
|
|
548
|
+
const dataPixelCount = endStartPixel - (MARKER_START.length + 1);
|
|
549
|
+
const pixelBytes = Buffer.allocUnsafe(dataPixelCount * 3);
|
|
550
|
+
for (let i = 0; i < dataPixelCount; i++) {
|
|
551
|
+
const srcOffset = (dataStartPixel + i) * 3;
|
|
552
|
+
const dstOffset = i * 3;
|
|
553
|
+
pixelBytes[dstOffset] = logicalData[srcOffset];
|
|
554
|
+
pixelBytes[dstOffset + 1] = logicalData[srcOffset + 1];
|
|
555
|
+
pixelBytes[dstOffset + 2] = logicalData[srcOffset + 2];
|
|
517
556
|
}
|
|
518
557
|
if (process.env.ROX_DEBUG) {
|
|
519
558
|
console.log('DEBUG: extracted len', pixelBytes.length);
|
package/dist/utils/encoder.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { EncodeOptions } from './types.js';
|
|
4
|
-
export declare function encodeBinaryToPng(input: Buffer, opts?: EncodeOptions): Promise<Buffer>;
|
|
4
|
+
export declare function encodeBinaryToPng(input: Buffer | Buffer[], opts?: EncodeOptions): Promise<Buffer>;
|
package/dist/utils/encoder.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import cliProgress from 'cli-progress';
|
|
2
2
|
import { createCipheriv, pbkdf2Sync, randomBytes } from 'crypto';
|
|
3
|
-
import encode from 'png-chunks-encode';
|
|
4
3
|
import sharp from 'sharp';
|
|
5
4
|
import * as zlib from 'zlib';
|
|
6
5
|
import { unpackBuffer } from '../pack.js';
|
|
7
|
-
import { COMPRESSION_MARKERS, ENC_AES, ENC_NONE, ENC_XOR, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PNG_HEADER,
|
|
8
|
-
import {
|
|
6
|
+
import { COMPRESSION_MARKERS, ENC_AES, ENC_NONE, ENC_XOR, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PNG_HEADER, } from './constants.js';
|
|
7
|
+
import { crc32 } from './crc.js';
|
|
8
|
+
import { colorsToBytes } from './helpers.js';
|
|
9
9
|
import { optimizePngBuffer } from './optimization.js';
|
|
10
10
|
import { parallelZstdCompress } from './zstd.js';
|
|
11
11
|
export async function encodeBinaryToPng(input, opts = {}) {
|
|
@@ -41,11 +41,19 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
let
|
|
44
|
+
let payloadInput;
|
|
45
|
+
let totalLen = 0;
|
|
46
|
+
if (Array.isArray(input)) {
|
|
47
|
+
payloadInput = [MAGIC, ...input];
|
|
48
|
+
totalLen = MAGIC.length + input.reduce((a, b) => a + b.length, 0);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
payloadInput = [MAGIC, input];
|
|
52
|
+
totalLen = MAGIC.length + input.length;
|
|
53
|
+
}
|
|
45
54
|
if (opts.onProgress)
|
|
46
|
-
opts.onProgress({ phase: 'compress_start', total:
|
|
47
|
-
|
|
48
|
-
payload = await parallelZstdCompress(deltaEncoded, 19, (loaded, total) => {
|
|
55
|
+
opts.onProgress({ phase: 'compress_start', total: totalLen });
|
|
56
|
+
let payload = await parallelZstdCompress(payloadInput, 15, (loaded, total) => {
|
|
49
57
|
if (opts.onProgress) {
|
|
50
58
|
opts.onProgress({
|
|
51
59
|
phase: 'compress_progress',
|
|
@@ -56,6 +64,9 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
56
64
|
});
|
|
57
65
|
if (opts.onProgress)
|
|
58
66
|
opts.onProgress({ phase: 'compress_done', loaded: payload.length });
|
|
67
|
+
if (Array.isArray(input)) {
|
|
68
|
+
input.length = 0;
|
|
69
|
+
}
|
|
59
70
|
if (opts.passphrase && !opts.encrypt) {
|
|
60
71
|
opts.encrypt = 'aes';
|
|
61
72
|
}
|
|
@@ -83,29 +94,44 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
83
94
|
const PBKDF2_ITERS = 1000000;
|
|
84
95
|
const key = pbkdf2Sync(opts.passphrase, salt, PBKDF2_ITERS, 32, 'sha256');
|
|
85
96
|
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
86
|
-
const
|
|
97
|
+
const encParts = [];
|
|
98
|
+
for (const chunk of payload) {
|
|
99
|
+
encParts.push(cipher.update(chunk));
|
|
100
|
+
}
|
|
101
|
+
encParts.push(cipher.final());
|
|
87
102
|
const tag = cipher.getAuthTag();
|
|
88
|
-
payload =
|
|
103
|
+
payload = [Buffer.from([ENC_AES]), salt, iv, tag, ...encParts];
|
|
89
104
|
if (opts.onProgress)
|
|
90
105
|
opts.onProgress({ phase: 'encrypt_done' });
|
|
91
106
|
}
|
|
92
107
|
else if (encChoice === 'xor') {
|
|
93
|
-
const
|
|
94
|
-
|
|
108
|
+
const xoredParts = [];
|
|
109
|
+
let offset = 0;
|
|
110
|
+
const keyBuf = Buffer.from(opts.passphrase, 'utf8');
|
|
111
|
+
for (const chunk of payload) {
|
|
112
|
+
const out = Buffer.alloc(chunk.length);
|
|
113
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
114
|
+
out[i] = chunk[i] ^ keyBuf[(offset + i) % keyBuf.length];
|
|
115
|
+
}
|
|
116
|
+
offset += chunk.length;
|
|
117
|
+
xoredParts.push(out);
|
|
118
|
+
}
|
|
119
|
+
payload = [Buffer.from([ENC_XOR]), ...xoredParts];
|
|
95
120
|
if (opts.onProgress)
|
|
96
121
|
opts.onProgress({ phase: 'encrypt_done' });
|
|
97
122
|
}
|
|
98
123
|
else if (encChoice === 'none') {
|
|
99
|
-
payload =
|
|
124
|
+
payload = [Buffer.from([ENC_NONE]), ...payload];
|
|
100
125
|
if (opts.onProgress)
|
|
101
126
|
opts.onProgress({ phase: 'encrypt_done' });
|
|
102
127
|
}
|
|
103
128
|
}
|
|
104
129
|
else {
|
|
105
|
-
payload =
|
|
130
|
+
payload = [Buffer.from([ENC_NONE]), ...payload];
|
|
106
131
|
}
|
|
132
|
+
const payloadTotalLen = payload.reduce((a, b) => a + b.length, 0);
|
|
107
133
|
if (opts.onProgress)
|
|
108
|
-
opts.onProgress({ phase: 'meta_prep_done', loaded:
|
|
134
|
+
opts.onProgress({ phase: 'meta_prep_done', loaded: payloadTotalLen });
|
|
109
135
|
const metaParts = [];
|
|
110
136
|
const includeName = opts.includeName === undefined ? true : !!opts.includeName;
|
|
111
137
|
if (includeName && opts.name) {
|
|
@@ -116,19 +142,20 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
116
142
|
else {
|
|
117
143
|
metaParts.push(Buffer.from([0]));
|
|
118
144
|
}
|
|
119
|
-
metaParts
|
|
120
|
-
let meta = Buffer.concat(metaParts);
|
|
145
|
+
let meta = [...metaParts, ...payload];
|
|
121
146
|
if (opts.includeFileList && opts.fileList) {
|
|
122
147
|
let sizeMap = null;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
if (!Array.isArray(input)) {
|
|
149
|
+
try {
|
|
150
|
+
const unpack = unpackBuffer(input);
|
|
151
|
+
if (unpack) {
|
|
152
|
+
sizeMap = {};
|
|
153
|
+
for (const ef of unpack.files)
|
|
154
|
+
sizeMap[ef.path] = ef.buf.length;
|
|
155
|
+
}
|
|
129
156
|
}
|
|
157
|
+
catch (e) { }
|
|
130
158
|
}
|
|
131
|
-
catch (e) { }
|
|
132
159
|
const normalized = opts.fileList.map((f) => {
|
|
133
160
|
if (typeof f === 'string')
|
|
134
161
|
return { name: f, size: sizeMap && sizeMap[f] ? sizeMap[f] : 0 };
|
|
@@ -143,10 +170,10 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
143
170
|
const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
|
|
144
171
|
const lenBuf = Buffer.alloc(4);
|
|
145
172
|
lenBuf.writeUInt32BE(jsonBuf.length, 0);
|
|
146
|
-
meta =
|
|
173
|
+
meta = [...meta, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
|
|
147
174
|
}
|
|
148
175
|
if (opts.output === 'rox') {
|
|
149
|
-
return Buffer.concat([MAGIC, meta]);
|
|
176
|
+
return Buffer.concat([MAGIC, ...meta]);
|
|
150
177
|
}
|
|
151
178
|
{
|
|
152
179
|
const nameBuf = opts.name
|
|
@@ -154,26 +181,28 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
154
181
|
: Buffer.alloc(0);
|
|
155
182
|
const nameLen = nameBuf.length;
|
|
156
183
|
const payloadLenBuf = Buffer.alloc(4);
|
|
157
|
-
payloadLenBuf.writeUInt32BE(
|
|
184
|
+
payloadLenBuf.writeUInt32BE(payloadTotalLen, 0);
|
|
158
185
|
const version = 1;
|
|
159
|
-
let metaPixel =
|
|
186
|
+
let metaPixel = [
|
|
160
187
|
Buffer.from([version]),
|
|
161
188
|
Buffer.from([nameLen]),
|
|
162
189
|
nameBuf,
|
|
163
190
|
payloadLenBuf,
|
|
164
|
-
payload,
|
|
165
|
-
]
|
|
191
|
+
...payload,
|
|
192
|
+
];
|
|
166
193
|
if (opts.includeFileList && opts.fileList) {
|
|
167
194
|
let sizeMap2 = null;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
195
|
+
if (!Array.isArray(input)) {
|
|
196
|
+
try {
|
|
197
|
+
const unpack = unpackBuffer(input);
|
|
198
|
+
if (unpack) {
|
|
199
|
+
sizeMap2 = {};
|
|
200
|
+
for (const ef of unpack.files)
|
|
201
|
+
sizeMap2[ef.path] = ef.buf.length;
|
|
202
|
+
}
|
|
174
203
|
}
|
|
204
|
+
catch (e) { }
|
|
175
205
|
}
|
|
176
|
-
catch (e) { }
|
|
177
206
|
const normalized = opts.fileList.map((f) => {
|
|
178
207
|
if (typeof f === 'string')
|
|
179
208
|
return { name: f, size: sizeMap2 && sizeMap2[f] ? sizeMap2[f] : 0 };
|
|
@@ -188,27 +217,24 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
188
217
|
const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
|
|
189
218
|
const lenBuf = Buffer.alloc(4);
|
|
190
219
|
lenBuf.writeUInt32BE(jsonBuf.length, 0);
|
|
191
|
-
metaPixel = Buffer.
|
|
192
|
-
metaPixel,
|
|
193
|
-
Buffer.from('rXFL', 'utf8'),
|
|
194
|
-
lenBuf,
|
|
195
|
-
jsonBuf,
|
|
196
|
-
]);
|
|
220
|
+
metaPixel = [...metaPixel, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
|
|
197
221
|
}
|
|
198
|
-
const dataWithoutMarkers =
|
|
199
|
-
const
|
|
222
|
+
const dataWithoutMarkers = [PIXEL_MAGIC, ...metaPixel];
|
|
223
|
+
const dataWithoutMarkersLen = dataWithoutMarkers.reduce((a, b) => a + b.length, 0);
|
|
224
|
+
const padding = (3 - (dataWithoutMarkersLen % 3)) % 3;
|
|
200
225
|
const paddedData = padding > 0
|
|
201
|
-
?
|
|
226
|
+
? [...dataWithoutMarkers, Buffer.alloc(padding)]
|
|
202
227
|
: dataWithoutMarkers;
|
|
203
228
|
const markerStartBytes = colorsToBytes(MARKER_START);
|
|
204
229
|
const compressionMarkerBytes = colorsToBytes(COMPRESSION_MARKERS.zstd);
|
|
205
|
-
const dataWithMarkers =
|
|
230
|
+
const dataWithMarkers = [
|
|
206
231
|
markerStartBytes,
|
|
207
232
|
compressionMarkerBytes,
|
|
208
|
-
paddedData,
|
|
209
|
-
]
|
|
233
|
+
...paddedData,
|
|
234
|
+
];
|
|
210
235
|
const bytesPerPixel = 3;
|
|
211
|
-
const
|
|
236
|
+
const dataWithMarkersLen = dataWithMarkers.reduce((a, b) => a + b.length, 0);
|
|
237
|
+
const dataPixels = Math.ceil(dataWithMarkersLen / 3);
|
|
212
238
|
const totalPixels = dataPixels + MARKER_END.length;
|
|
213
239
|
const maxWidth = 16384;
|
|
214
240
|
let side = Math.ceil(Math.sqrt(totalPixels));
|
|
@@ -227,8 +253,37 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
227
253
|
const scale = 1;
|
|
228
254
|
const width = logicalWidth * scale;
|
|
229
255
|
const height = logicalHeight * scale;
|
|
230
|
-
const
|
|
256
|
+
const LARGE_IMAGE_PIXELS = 50000000;
|
|
257
|
+
const useManualPng = width * height > LARGE_IMAGE_PIXELS || !!process.env.ROX_FAST_PNG;
|
|
258
|
+
let raw;
|
|
259
|
+
let stride = 0;
|
|
260
|
+
if (useManualPng) {
|
|
261
|
+
stride = width * 3 + 1;
|
|
262
|
+
raw = Buffer.alloc(height * stride);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
raw = Buffer.alloc(width * height * bytesPerPixel);
|
|
266
|
+
}
|
|
267
|
+
let currentBufIdx = 0;
|
|
268
|
+
let currentBufOffset = 0;
|
|
269
|
+
const getNextByte = () => {
|
|
270
|
+
while (currentBufIdx < dataWithMarkers.length) {
|
|
271
|
+
const buf = dataWithMarkers[currentBufIdx];
|
|
272
|
+
if (currentBufOffset < buf.length) {
|
|
273
|
+
return buf[currentBufOffset++];
|
|
274
|
+
}
|
|
275
|
+
currentBufIdx++;
|
|
276
|
+
currentBufOffset = 0;
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
};
|
|
231
280
|
for (let ly = 0; ly < logicalHeight; ly++) {
|
|
281
|
+
if (useManualPng) {
|
|
282
|
+
for (let sy = 0; sy < scale; sy++) {
|
|
283
|
+
const py = ly * scale + sy;
|
|
284
|
+
raw[py * stride] = 0;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
232
287
|
for (let lx = 0; lx < logicalWidth; lx++) {
|
|
233
288
|
const linearIdx = ly * logicalWidth + lx;
|
|
234
289
|
let r = 0, g = 0, b = 0;
|
|
@@ -240,107 +295,126 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
240
295
|
b = MARKER_END[markerIdx].b;
|
|
241
296
|
}
|
|
242
297
|
else if (linearIdx < dataPixels) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
srcIdx + 1 < dataWithMarkers.length
|
|
247
|
-
? dataWithMarkers[srcIdx + 1]
|
|
248
|
-
: 0;
|
|
249
|
-
b =
|
|
250
|
-
srcIdx + 2 < dataWithMarkers.length
|
|
251
|
-
? dataWithMarkers[srcIdx + 2]
|
|
252
|
-
: 0;
|
|
298
|
+
r = getNextByte();
|
|
299
|
+
g = getNextByte();
|
|
300
|
+
b = getNextByte();
|
|
253
301
|
}
|
|
254
302
|
for (let sy = 0; sy < scale; sy++) {
|
|
255
303
|
for (let sx = 0; sx < scale; sx++) {
|
|
256
304
|
const px = lx * scale + sx;
|
|
257
305
|
const py = ly * scale + sy;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
306
|
+
if (useManualPng) {
|
|
307
|
+
const dstIdx = py * stride + 1 + px * 3;
|
|
308
|
+
raw[dstIdx] = r;
|
|
309
|
+
raw[dstIdx + 1] = g;
|
|
310
|
+
raw[dstIdx + 2] = b;
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
const dstIdx = (py * width + px) * 3;
|
|
314
|
+
raw[dstIdx] = r;
|
|
315
|
+
raw[dstIdx + 1] = g;
|
|
316
|
+
raw[dstIdx + 2] = b;
|
|
317
|
+
}
|
|
262
318
|
}
|
|
263
319
|
}
|
|
264
320
|
}
|
|
265
321
|
}
|
|
322
|
+
payload.length = 0;
|
|
323
|
+
dataWithMarkers.length = 0;
|
|
324
|
+
metaPixel.length = 0;
|
|
325
|
+
meta.length = 0;
|
|
326
|
+
paddedData.length = 0;
|
|
327
|
+
dataWithoutMarkers.length = 0;
|
|
266
328
|
if (opts.onProgress)
|
|
267
|
-
opts.onProgress({ phase: 'png_gen', loaded: 0, total:
|
|
268
|
-
let loaded = 0;
|
|
269
|
-
const progressInterval = setInterval(() => {
|
|
270
|
-
loaded = Math.min(loaded + 2, 98);
|
|
271
|
-
if (opts.onProgress)
|
|
272
|
-
opts.onProgress({ phase: 'png_gen', loaded, total: 100 });
|
|
273
|
-
}, 50);
|
|
329
|
+
opts.onProgress({ phase: 'png_gen', loaded: 0, total: height });
|
|
274
330
|
let bufScr;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
331
|
+
if (useManualPng) {
|
|
332
|
+
const bytesPerRow = width * 3;
|
|
333
|
+
const scanlinesData = Buffer.alloc(height * (1 + bytesPerRow));
|
|
334
|
+
const progressStep = Math.max(1, Math.floor(height / 20));
|
|
335
|
+
for (let row = 0; row < height; row++) {
|
|
336
|
+
scanlinesData[row * (1 + bytesPerRow)] = 0;
|
|
337
|
+
const srcStart = row * stride + 1;
|
|
338
|
+
const dstStart = row * (1 + bytesPerRow) + 1;
|
|
339
|
+
raw.copy(scanlinesData, dstStart, srcStart, srcStart + bytesPerRow);
|
|
340
|
+
if (opts.onProgress && row % progressStep === 0) {
|
|
341
|
+
opts.onProgress({ phase: 'png_gen', loaded: row, total: height });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (opts.onProgress)
|
|
345
|
+
opts.onProgress({ phase: 'png_compress', loaded: 0, total: 100 });
|
|
346
|
+
const idatData = zlib.deflateSync(scanlinesData, {
|
|
347
|
+
level: 3,
|
|
279
348
|
memLevel: 8,
|
|
280
349
|
strategy: zlib.constants.Z_DEFAULT_STRATEGY,
|
|
281
350
|
});
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
351
|
+
raw = Buffer.alloc(0);
|
|
352
|
+
const ihdrData = Buffer.alloc(13);
|
|
353
|
+
ihdrData.writeUInt32BE(width, 0);
|
|
354
|
+
ihdrData.writeUInt32BE(height, 4);
|
|
355
|
+
ihdrData[8] = 8;
|
|
356
|
+
ihdrData[9] = 2;
|
|
357
|
+
ihdrData[10] = 0;
|
|
358
|
+
ihdrData[11] = 0;
|
|
359
|
+
ihdrData[12] = 0;
|
|
360
|
+
const ihdrType = Buffer.from('IHDR', 'utf8');
|
|
361
|
+
const ihdrCrc = crc32(ihdrData, crc32(ihdrType));
|
|
362
|
+
const ihdrCrcBuf = Buffer.alloc(4);
|
|
363
|
+
ihdrCrcBuf.writeUInt32BE(ihdrCrc, 0);
|
|
364
|
+
const ihdrLen = Buffer.alloc(4);
|
|
365
|
+
ihdrLen.writeUInt32BE(ihdrData.length, 0);
|
|
366
|
+
const idatType = Buffer.from('IDAT', 'utf8');
|
|
367
|
+
const idatCrc = crc32(idatData, crc32(idatType));
|
|
368
|
+
const idatCrcBuf = Buffer.alloc(4);
|
|
369
|
+
idatCrcBuf.writeUInt32BE(idatCrc, 0);
|
|
370
|
+
const idatLen = Buffer.alloc(4);
|
|
371
|
+
idatLen.writeUInt32BE(idatData.length, 0);
|
|
372
|
+
const iendType = Buffer.from('IEND', 'utf8');
|
|
373
|
+
const iendCrc = crc32(Buffer.alloc(0), crc32(iendType));
|
|
374
|
+
const iendCrcBuf = Buffer.alloc(4);
|
|
375
|
+
iendCrcBuf.writeUInt32BE(iendCrc, 0);
|
|
376
|
+
const iendLen = Buffer.alloc(4);
|
|
377
|
+
iendLen.writeUInt32BE(0, 0);
|
|
378
|
+
bufScr = Buffer.concat([
|
|
379
|
+
PNG_HEADER,
|
|
380
|
+
ihdrLen,
|
|
381
|
+
ihdrType,
|
|
382
|
+
ihdrData,
|
|
383
|
+
ihdrCrcBuf,
|
|
384
|
+
idatLen,
|
|
385
|
+
idatType,
|
|
386
|
+
idatData,
|
|
387
|
+
idatCrcBuf,
|
|
388
|
+
iendLen,
|
|
389
|
+
iendType,
|
|
390
|
+
iendCrcBuf,
|
|
391
|
+
]);
|
|
299
392
|
}
|
|
300
393
|
else {
|
|
301
394
|
bufScr = await sharp(raw, {
|
|
302
395
|
raw: { width, height, channels: 3 },
|
|
303
396
|
})
|
|
304
397
|
.png({
|
|
305
|
-
compressionLevel:
|
|
398
|
+
compressionLevel: 3,
|
|
306
399
|
palette: false,
|
|
307
400
|
effort: 1,
|
|
308
401
|
adaptiveFiltering: false,
|
|
309
402
|
})
|
|
310
403
|
.toBuffer();
|
|
311
404
|
}
|
|
405
|
+
raw = Buffer.alloc(0);
|
|
312
406
|
if (opts.onProgress)
|
|
313
|
-
opts.onProgress({ phase: '
|
|
407
|
+
opts.onProgress({ phase: 'png_compress', loaded: 100, total: 100 });
|
|
314
408
|
if (opts.onProgress)
|
|
315
409
|
opts.onProgress({ phase: 'optimizing', loaded: 0, total: 100 });
|
|
316
|
-
let optInterval = null;
|
|
317
|
-
if (opts.onProgress) {
|
|
318
|
-
let optLoaded = 0;
|
|
319
|
-
optInterval = setInterval(() => {
|
|
320
|
-
optLoaded = Math.min(optLoaded + 5, 95);
|
|
321
|
-
opts.onProgress?.({
|
|
322
|
-
phase: 'optimizing',
|
|
323
|
-
loaded: optLoaded,
|
|
324
|
-
total: 100,
|
|
325
|
-
});
|
|
326
|
-
}, 100);
|
|
327
|
-
}
|
|
328
410
|
try {
|
|
329
411
|
const optimized = await optimizePngBuffer(bufScr, true);
|
|
330
|
-
clearInterval(progressInterval);
|
|
331
|
-
if (optInterval) {
|
|
332
|
-
clearInterval(optInterval);
|
|
333
|
-
optInterval = null;
|
|
334
|
-
}
|
|
335
412
|
if (opts.onProgress)
|
|
336
413
|
opts.onProgress({ phase: 'optimizing', loaded: 100, total: 100 });
|
|
337
414
|
progressBar?.stop();
|
|
338
415
|
return optimized;
|
|
339
416
|
}
|
|
340
417
|
catch (e) {
|
|
341
|
-
clearInterval(progressInterval);
|
|
342
|
-
if (optInterval)
|
|
343
|
-
clearInterval(optInterval);
|
|
344
418
|
progressBar?.stop();
|
|
345
419
|
return bufScr;
|
|
346
420
|
}
|
|
@@ -7,6 +7,10 @@ import extract from 'png-chunks-extract';
|
|
|
7
7
|
import * as zlib from 'zlib';
|
|
8
8
|
import { PNG_HEADER, PNG_HEADER_HEX } from './constants.js';
|
|
9
9
|
export async function optimizePngBuffer(pngBuf, fast = false) {
|
|
10
|
+
const MAX_OPTIMIZE_SIZE = 50 * 1024 * 1024;
|
|
11
|
+
if (pngBuf.length > MAX_OPTIMIZE_SIZE) {
|
|
12
|
+
return pngBuf;
|
|
13
|
+
}
|
|
10
14
|
const runCommandAsync = (cmd, args, timeout = 120000) => {
|
|
11
15
|
return new Promise((resolve) => {
|
|
12
16
|
try {
|
package/dist/utils/zstd.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare function compressStream(stream: AsyncGenerator<Buffer>, level?: n
|
|
|
4
4
|
chunks: Buffer[];
|
|
5
5
|
totalLength: number;
|
|
6
6
|
}>;
|
|
7
|
-
export declare function parallelZstdCompress(payload: Buffer, level?: number, onProgress?: (loaded: number, total: number) => void): Promise<Buffer>;
|
|
7
|
+
export declare function parallelZstdCompress(payload: Buffer | Buffer[], level?: number, onProgress?: (loaded: number, total: number) => void): Promise<Buffer[]>;
|
|
8
8
|
export declare function parallelZstdDecompress(payload: Buffer, onProgress?: (info: {
|
|
9
9
|
phase: string;
|
|
10
10
|
loaded?: number;
|
package/dist/utils/zstd.js
CHANGED
|
@@ -26,22 +26,35 @@ export async function compressStream(stream, level = 19, onProgress) {
|
|
|
26
26
|
}
|
|
27
27
|
export async function parallelZstdCompress(payload, level = 19, onProgress) {
|
|
28
28
|
const chunkSize = 8 * 1024 * 1024;
|
|
29
|
-
if (payload.length <= chunkSize) {
|
|
30
|
-
if (onProgress)
|
|
31
|
-
onProgress(0, 1);
|
|
32
|
-
const result = await zstdCompress(payload, level);
|
|
33
|
-
if (onProgress)
|
|
34
|
-
onProgress(1, 1);
|
|
35
|
-
return Buffer.from(result);
|
|
36
|
-
}
|
|
37
29
|
const chunks = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
if (Array.isArray(payload)) {
|
|
31
|
+
for (const p of payload) {
|
|
32
|
+
if (p.length <= chunkSize) {
|
|
33
|
+
chunks.push(p);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
for (let i = 0; i < p.length; i += chunkSize) {
|
|
37
|
+
chunks.push(p.subarray(i, Math.min(i + chunkSize, p.length)));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (payload.length <= chunkSize) {
|
|
44
|
+
if (onProgress)
|
|
45
|
+
onProgress(0, 1);
|
|
46
|
+
const result = await zstdCompress(payload, level);
|
|
47
|
+
if (onProgress)
|
|
48
|
+
onProgress(1, 1);
|
|
49
|
+
return [Buffer.from(result)];
|
|
50
|
+
}
|
|
51
|
+
for (let i = 0; i < payload.length; i += chunkSize) {
|
|
52
|
+
chunks.push(payload.subarray(i, Math.min(i + chunkSize, payload.length)));
|
|
53
|
+
}
|
|
41
54
|
}
|
|
42
55
|
const totalChunks = chunks.length;
|
|
43
56
|
let completedChunks = 0;
|
|
44
|
-
const concurrency = Math.max(1, Math.min(
|
|
57
|
+
const concurrency = Math.max(1, Math.min(4, cpus().length));
|
|
45
58
|
const compressedChunks = new Array(totalChunks);
|
|
46
59
|
let idx = 0;
|
|
47
60
|
const worker = async () => {
|
|
@@ -65,7 +78,7 @@ export async function parallelZstdCompress(payload, level = 19, onProgress) {
|
|
|
65
78
|
const header = Buffer.alloc(8);
|
|
66
79
|
header.writeUInt32BE(0x5a535444, 0);
|
|
67
80
|
header.writeUInt32BE(compressedChunks.length, 4);
|
|
68
|
-
return
|
|
81
|
+
return [header, chunkSizes, ...compressedChunks];
|
|
69
82
|
}
|
|
70
83
|
export async function parallelZstdDecompress(payload, onProgress) {
|
|
71
84
|
if (payload.length < 8) {
|
package/package.json
CHANGED