roxify 1.13.5 → 1.13.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/Cargo.toml +1 -1
- package/README.md +30 -31
- package/dist/cli.js +36 -194
- package/dist/rox-macos-universal +0 -0
- package/dist/roxify_native +0 -0
- package/dist/roxify_native-macos-arm64 +0 -0
- package/dist/roxify_native-macos-x64 +0 -0
- package/dist/roxify_native.exe +0 -0
- package/dist/utils/decoder.d.ts +1 -21
- package/dist/utils/decoder.js +40 -1243
- package/dist/utils/encoder.d.ts +1 -13
- package/dist/utils/encoder.js +35 -565
- package/dist/utils/rust-cli-wrapper.d.ts +2 -2
- package/dist/utils/rust-cli-wrapper.js +8 -2
- package/native/io_advice.rs +1 -1
- package/native/io_ntfs_optimized.rs +99 -0
- package/native/main.rs +293 -33
- package/native/streaming_decode.rs +5 -1
- package/native/streaming_encode.rs +14 -4
- package/package.json +3 -3
- package/roxify_native-aarch64-apple-darwin.node +0 -0
- package/roxify_native-aarch64-pc-windows-msvc.node +0 -0
- package/roxify_native-aarch64-unknown-linux-gnu.node +0 -0
- package/roxify_native-i686-pc-windows-msvc.node +0 -0
- package/roxify_native-i686-unknown-linux-gnu.node +0 -0
- package/roxify_native-x86_64-apple-darwin.node +0 -0
- package/roxify_native-x86_64-pc-windows-msvc.node +0 -0
- package/roxify_native-x86_64-unknown-linux-gnu.node +0 -0
package/dist/utils/encoder.d.ts
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import { EncodeOptions } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Encode a buffer or array of buffers into a PNG image (ROX format).
|
|
4
|
+
* This function uses the Rust native implementation exclusively.
|
|
4
5
|
*
|
|
5
6
|
* @param input - The buffer or array of buffers to encode.
|
|
6
7
|
* @param opts - Optional encoding options.
|
|
7
8
|
* @returns A Promise that resolves to a PNG Buffer containing the encoded data.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```js
|
|
11
|
-
* import { encodeBinaryToPng } from 'roxify';
|
|
12
|
-
*
|
|
13
|
-
* const png = await encodeBinaryToPng(Buffer.from('hello'), {
|
|
14
|
-
* mode: 'screenshot',
|
|
15
|
-
* name: 'hello.txt',
|
|
16
|
-
* compressionLevel: 19,
|
|
17
|
-
* outputFormat: 'png',
|
|
18
|
-
* });
|
|
19
|
-
*
|
|
20
|
-
* * ```
|
|
21
9
|
*/
|
|
22
10
|
export declare function encodeBinaryToPng(input: Buffer | Buffer[], opts?: EncodeOptions): Promise<Buffer>;
|
package/dist/utils/encoder.js
CHANGED
|
@@ -1,586 +1,56 @@
|
|
|
1
|
-
import { createCipheriv, pbkdf2Sync, randomBytes } from 'crypto';
|
|
2
|
-
import * as zlib from 'zlib';
|
|
3
|
-
import { unpackBuffer } from '../pack.js';
|
|
4
|
-
import { bytesToWav } from './audio.js';
|
|
5
|
-
import { COMPRESSION_MARKERS, ENC_AES, ENC_NONE, ENC_XOR, MAGIC, MARKER_END, MARKER_START, PIXEL_MAGIC, PIXEL_MAGIC_BLOCK, PNG_HEADER, } from './constants.js';
|
|
6
|
-
import { crc32 } from './crc.js';
|
|
7
|
-
import { colorsToBytes } from './helpers.js';
|
|
8
1
|
import { native } from './native.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
function normalizeNativeFileList(fileList) {
|
|
3
|
+
if (!fileList)
|
|
4
|
+
return '[]';
|
|
5
|
+
return JSON.stringify(fileList.map((entry) => {
|
|
6
|
+
if (typeof entry === 'string') {
|
|
7
|
+
return { name: entry, size: 0 };
|
|
8
|
+
}
|
|
9
|
+
if (entry && typeof entry === 'object') {
|
|
10
|
+
if (entry.name)
|
|
11
|
+
return { name: entry.name, size: entry.size ?? 0 };
|
|
12
|
+
if (entry.path)
|
|
13
|
+
return { name: entry.path, size: entry.size ?? 0 };
|
|
14
|
+
}
|
|
15
|
+
return { name: String(entry), size: 0 };
|
|
16
|
+
}));
|
|
20
17
|
}
|
|
21
18
|
/**
|
|
22
19
|
* Encode a buffer or array of buffers into a PNG image (ROX format).
|
|
20
|
+
* This function uses the Rust native implementation exclusively.
|
|
23
21
|
*
|
|
24
22
|
* @param input - The buffer or array of buffers to encode.
|
|
25
23
|
* @param opts - Optional encoding options.
|
|
26
24
|
* @returns A Promise that resolves to a PNG Buffer containing the encoded data.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```js
|
|
30
|
-
* import { encodeBinaryToPng } from 'roxify';
|
|
31
|
-
*
|
|
32
|
-
* const png = await encodeBinaryToPng(Buffer.from('hello'), {
|
|
33
|
-
* mode: 'screenshot',
|
|
34
|
-
* name: 'hello.txt',
|
|
35
|
-
* compressionLevel: 19,
|
|
36
|
-
* outputFormat: 'png',
|
|
37
|
-
* });
|
|
38
|
-
*
|
|
39
|
-
* * ```
|
|
40
25
|
*/
|
|
41
26
|
export async function encodeBinaryToPng(input, opts = {}) {
|
|
42
|
-
let progressBar = null;
|
|
43
|
-
if (opts.showProgress) {
|
|
44
|
-
progressBar = {
|
|
45
|
-
start: () => { },
|
|
46
|
-
update: () => { },
|
|
47
|
-
stop: () => { },
|
|
48
|
-
};
|
|
49
|
-
const startTime = Date.now();
|
|
50
|
-
if (!opts.onProgress) {
|
|
51
|
-
opts.onProgress = (info) => {
|
|
52
|
-
let pct = 0;
|
|
53
|
-
if (info.phase === 'compress_progress' && info.loaded && info.total) {
|
|
54
|
-
pct = (info.loaded / info.total) * 50;
|
|
55
|
-
}
|
|
56
|
-
else if (info.phase === 'compress_done') {
|
|
57
|
-
pct = 50;
|
|
58
|
-
}
|
|
59
|
-
else if (info.phase === 'encrypt_done') {
|
|
60
|
-
pct = 80;
|
|
61
|
-
}
|
|
62
|
-
else if (info.phase === 'png_gen') {
|
|
63
|
-
pct = 90;
|
|
64
|
-
}
|
|
65
|
-
else if (info.phase === 'done') {
|
|
66
|
-
pct = 100;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
27
|
const compressionLevel = opts.compressionLevel ?? 19;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (opts.
|
|
80
|
-
|
|
81
|
-
const result =
|
|
82
|
-
|
|
83
|
-
});
|
|
84
|
-
if (opts.onProgress)
|
|
85
|
-
opts.onProgress({ phase: 'done' });
|
|
86
|
-
progressBar?.stop();
|
|
87
|
-
return result;
|
|
28
|
+
const inputBuf = Array.isArray(input) ? Buffer.concat(input) : input;
|
|
29
|
+
const fileName = opts.name || undefined;
|
|
30
|
+
const fileListJson = opts.includeFileList && opts.fileList
|
|
31
|
+
? normalizeNativeFileList(opts.fileList)
|
|
32
|
+
: undefined;
|
|
33
|
+
// --- PNG container via native Rust encoder ---
|
|
34
|
+
if (opts.container === 'sound') {
|
|
35
|
+
if (opts.passphrase) {
|
|
36
|
+
const encryptType = opts.encrypt && opts.encrypt !== 'auto' ? opts.encrypt : 'aes';
|
|
37
|
+
const result = native.nativeEncodeWavWithEncryptionNameAndFilelist(inputBuf, compressionLevel, opts.passphrase, encryptType, fileName, fileListJson);
|
|
38
|
+
return Buffer.from(result);
|
|
88
39
|
}
|
|
89
40
|
else {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
blockSize: opts.robustBlockSize ?? 4,
|
|
93
|
-
eccLevel: opts.eccLevel ?? 'medium',
|
|
94
|
-
});
|
|
95
|
-
if (opts.onProgress)
|
|
96
|
-
opts.onProgress({ phase: 'done' });
|
|
97
|
-
progressBar?.stop();
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// --- Native encoder fast path: let Rust handle compression/encryption/PNG ---
|
|
102
|
-
// This must be checked BEFORE TS compression to avoid double-compression.
|
|
103
|
-
if (typeof native.nativeEncodePngWithNameAndFilelist === 'function' &&
|
|
104
|
-
opts.includeFileList &&
|
|
105
|
-
opts.fileList &&
|
|
106
|
-
opts.compression !== 'bwt-ans') {
|
|
107
|
-
const fileName = opts.name || undefined;
|
|
108
|
-
const inputBuf = Array.isArray(input) ? Buffer.concat(input) : input;
|
|
109
|
-
let sizeMap = null;
|
|
110
|
-
try {
|
|
111
|
-
const unpack = unpackBuffer(inputBuf);
|
|
112
|
-
if (unpack) {
|
|
113
|
-
sizeMap = {};
|
|
114
|
-
for (const ef of unpack.files)
|
|
115
|
-
sizeMap[ef.path] = ef.buf.length;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (e) { }
|
|
119
|
-
const normalized = opts.fileList.map((f) => {
|
|
120
|
-
if (typeof f === 'string')
|
|
121
|
-
return { name: f, size: sizeMap && sizeMap[f] ? sizeMap[f] : 0 };
|
|
122
|
-
if (f && typeof f === 'object') {
|
|
123
|
-
if (f.name)
|
|
124
|
-
return { name: f.name, size: f.size ?? 0 };
|
|
125
|
-
if (f.path)
|
|
126
|
-
return { name: f.path, size: f.size ?? 0 };
|
|
127
|
-
}
|
|
128
|
-
return { name: String(f), size: 0 };
|
|
129
|
-
});
|
|
130
|
-
const fileListJson = JSON.stringify(normalized);
|
|
131
|
-
if (opts.onProgress)
|
|
132
|
-
opts.onProgress({ phase: 'compress_start', total: inputBuf.length });
|
|
133
|
-
// ── WAV container (--sound) via native Rust encoder ──
|
|
134
|
-
if (opts.container === 'sound') {
|
|
135
|
-
if (typeof native.nativeEncodeWavWithEncryptionNameAndFilelist === 'function' &&
|
|
136
|
-
opts.passphrase && opts.encrypt && opts.encrypt !== 'auto') {
|
|
137
|
-
const result = native.nativeEncodeWavWithEncryptionNameAndFilelist(inputBuf, compressionLevel, opts.passphrase, opts.encrypt, fileName, fileListJson);
|
|
138
|
-
if (opts.onProgress)
|
|
139
|
-
opts.onProgress({ phase: 'done' });
|
|
140
|
-
progressBar?.stop();
|
|
141
|
-
return Buffer.from(result);
|
|
142
|
-
}
|
|
143
|
-
else if (typeof native.nativeEncodeWavWithNameAndFilelist === 'function') {
|
|
144
|
-
const result = native.nativeEncodeWavWithNameAndFilelist(inputBuf, compressionLevel, fileName, fileListJson);
|
|
145
|
-
if (opts.onProgress)
|
|
146
|
-
opts.onProgress({ phase: 'done' });
|
|
147
|
-
progressBar?.stop();
|
|
148
|
-
return Buffer.from(result);
|
|
149
|
-
}
|
|
150
|
-
// fallthrough to TS WAV path below if native WAV not available
|
|
151
|
-
}
|
|
152
|
-
// ── PNG container (default) via native Rust encoder ──
|
|
153
|
-
if (opts.container !== 'sound') {
|
|
154
|
-
if (opts.passphrase && opts.encrypt && opts.encrypt !== 'auto') {
|
|
155
|
-
const result = native.nativeEncodePngWithEncryptionNameAndFilelist(inputBuf, compressionLevel, opts.passphrase, opts.encrypt, fileName, fileListJson);
|
|
156
|
-
if (opts.onProgress)
|
|
157
|
-
opts.onProgress({ phase: 'done' });
|
|
158
|
-
progressBar?.stop();
|
|
159
|
-
return Buffer.from(result);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
const result = native.nativeEncodePngWithNameAndFilelist(inputBuf, compressionLevel, fileName, fileListJson);
|
|
163
|
-
if (opts.onProgress)
|
|
164
|
-
opts.onProgress({ phase: 'done' });
|
|
165
|
-
progressBar?.stop();
|
|
166
|
-
return Buffer.from(result);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// --- TypeScript compression/encryption pipeline ---
|
|
171
|
-
let payloadInput;
|
|
172
|
-
let totalLen = 0;
|
|
173
|
-
if (Array.isArray(input)) {
|
|
174
|
-
payloadInput = [MAGIC, ...input];
|
|
175
|
-
totalLen = MAGIC.length + input.reduce((a, b) => a + b.length, 0);
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
payloadInput = [MAGIC, input];
|
|
179
|
-
totalLen = MAGIC.length + input.length;
|
|
180
|
-
}
|
|
181
|
-
if (opts.onProgress)
|
|
182
|
-
opts.onProgress({ phase: 'compress_start', total: totalLen });
|
|
183
|
-
let payload;
|
|
184
|
-
if (opts.compression === 'bwt-ans' && native?.hybridCompress) {
|
|
185
|
-
const flat = Array.isArray(payloadInput) ? Buffer.concat(payloadInput) : payloadInput;
|
|
186
|
-
if (opts.onProgress)
|
|
187
|
-
opts.onProgress({ phase: 'compress_progress', loaded: 0, total: 1 });
|
|
188
|
-
const compressed = Buffer.from(native.hybridCompress(flat));
|
|
189
|
-
payload = [compressed];
|
|
190
|
-
if (opts.onProgress)
|
|
191
|
-
opts.onProgress({ phase: 'compress_progress', loaded: 1, total: 1 });
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
payload = await parallelZstdCompress(payloadInput, compressionLevel, (loaded, total) => {
|
|
195
|
-
if (opts.onProgress) {
|
|
196
|
-
opts.onProgress({
|
|
197
|
-
phase: 'compress_progress',
|
|
198
|
-
loaded,
|
|
199
|
-
total,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}, opts.dict);
|
|
203
|
-
}
|
|
204
|
-
if (opts.onProgress)
|
|
205
|
-
opts.onProgress({ phase: 'compress_done', loaded: payload.length });
|
|
206
|
-
if (Array.isArray(input)) {
|
|
207
|
-
input.length = 0;
|
|
208
|
-
}
|
|
209
|
-
if (opts.passphrase && !opts.encrypt) {
|
|
210
|
-
opts.encrypt = 'aes';
|
|
211
|
-
}
|
|
212
|
-
if (opts.encrypt === 'auto' && !opts._skipAuto) {
|
|
213
|
-
const candidates = ['none', 'xor', 'aes'];
|
|
214
|
-
const candidateBufs = [];
|
|
215
|
-
for (const c of candidates) {
|
|
216
|
-
const testBuf = await encodeBinaryToPng(input, {
|
|
217
|
-
...opts,
|
|
218
|
-
encrypt: c,
|
|
219
|
-
_skipAuto: true,
|
|
220
|
-
});
|
|
221
|
-
candidateBufs.push({ enc: c, buf: testBuf });
|
|
222
|
-
}
|
|
223
|
-
candidateBufs.sort((a, b) => a.buf.length - b.buf.length);
|
|
224
|
-
return candidateBufs[0].buf;
|
|
225
|
-
}
|
|
226
|
-
if (opts.passphrase && opts.encrypt && opts.encrypt !== 'auto') {
|
|
227
|
-
const encChoice = opts.encrypt;
|
|
228
|
-
if (opts.onProgress)
|
|
229
|
-
opts.onProgress({ phase: 'encrypt_start' });
|
|
230
|
-
if (encChoice === 'aes') {
|
|
231
|
-
const salt = randomBytes(16);
|
|
232
|
-
const iv = randomBytes(12);
|
|
233
|
-
const PBKDF2_ITERS = 1000000;
|
|
234
|
-
const key = pbkdf2Sync(opts.passphrase, salt, PBKDF2_ITERS, 32, 'sha256');
|
|
235
|
-
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
236
|
-
const encParts = [];
|
|
237
|
-
for (const chunk of payload) {
|
|
238
|
-
encParts.push(cipher.update(chunk));
|
|
239
|
-
}
|
|
240
|
-
encParts.push(cipher.final());
|
|
241
|
-
const tag = cipher.getAuthTag();
|
|
242
|
-
payload = [Buffer.from([ENC_AES]), salt, iv, tag, ...encParts];
|
|
243
|
-
if (opts.onProgress)
|
|
244
|
-
opts.onProgress({ phase: 'encrypt_done' });
|
|
41
|
+
const result = native.nativeEncodeWavWithNameAndFilelist(inputBuf, compressionLevel, fileName, fileListJson);
|
|
42
|
+
return Buffer.from(result);
|
|
245
43
|
}
|
|
246
|
-
else if (encChoice === 'xor') {
|
|
247
|
-
const xoredParts = [];
|
|
248
|
-
let offset = 0;
|
|
249
|
-
const keyBuf = Buffer.from(opts.passphrase, 'utf8');
|
|
250
|
-
for (const chunk of payload) {
|
|
251
|
-
const out = Buffer.alloc(chunk.length);
|
|
252
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
253
|
-
out[i] = chunk[i] ^ keyBuf[(offset + i) % keyBuf.length];
|
|
254
|
-
}
|
|
255
|
-
offset += chunk.length;
|
|
256
|
-
xoredParts.push(out);
|
|
257
|
-
}
|
|
258
|
-
payload = [Buffer.from([ENC_XOR]), ...xoredParts];
|
|
259
|
-
if (opts.onProgress)
|
|
260
|
-
opts.onProgress({ phase: 'encrypt_done' });
|
|
261
|
-
}
|
|
262
|
-
else if (encChoice === 'none') {
|
|
263
|
-
payload = [Buffer.from([ENC_NONE]), ...payload];
|
|
264
|
-
if (opts.onProgress)
|
|
265
|
-
opts.onProgress({ phase: 'encrypt_done' });
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
payload = [Buffer.from([ENC_NONE]), ...payload];
|
|
270
|
-
}
|
|
271
|
-
const payloadTotalLen = payload.reduce((a, b) => a + b.length, 0);
|
|
272
|
-
if (opts.onProgress)
|
|
273
|
-
opts.onProgress({ phase: 'meta_prep_done', loaded: payloadTotalLen });
|
|
274
|
-
const metaParts = [];
|
|
275
|
-
const includeName = opts.includeName === undefined ? true : !!opts.includeName;
|
|
276
|
-
if (includeName && opts.name) {
|
|
277
|
-
const nameBuf = Buffer.from(opts.name, 'utf8');
|
|
278
|
-
metaParts.push(Buffer.from([nameBuf.length]));
|
|
279
|
-
metaParts.push(nameBuf);
|
|
280
44
|
}
|
|
281
45
|
else {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
let sizeMap = null;
|
|
287
|
-
if (!Array.isArray(input)) {
|
|
288
|
-
try {
|
|
289
|
-
const unpack = unpackBuffer(input);
|
|
290
|
-
if (unpack) {
|
|
291
|
-
sizeMap = {};
|
|
292
|
-
for (const ef of unpack.files)
|
|
293
|
-
sizeMap[ef.path] = ef.buf.length;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
catch (e) { }
|
|
297
|
-
}
|
|
298
|
-
const normalized = opts.fileList.map((f) => {
|
|
299
|
-
if (typeof f === 'string')
|
|
300
|
-
return { name: f, size: sizeMap && sizeMap[f] ? sizeMap[f] : 0 };
|
|
301
|
-
if (f && typeof f === 'object') {
|
|
302
|
-
if (f.name)
|
|
303
|
-
return { name: f.name, size: f.size ?? 0 };
|
|
304
|
-
if (f.path)
|
|
305
|
-
return { name: f.path, size: f.size ?? 0 };
|
|
306
|
-
}
|
|
307
|
-
return { name: String(f), size: 0 };
|
|
308
|
-
});
|
|
309
|
-
const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
|
|
310
|
-
const lenBuf = Buffer.alloc(4);
|
|
311
|
-
lenBuf.writeUInt32BE(jsonBuf.length, 0);
|
|
312
|
-
meta = [...meta, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
|
|
313
|
-
}
|
|
314
|
-
if (opts.output === 'rox') {
|
|
315
|
-
return Buffer.concat([MAGIC, ...meta]);
|
|
316
|
-
}
|
|
317
|
-
// ─── WAV container (TS fallback path) ──────────────────────────────────────
|
|
318
|
-
if (opts.container === 'sound') {
|
|
319
|
-
const nameBuf = opts.name ? Buffer.from(opts.name, 'utf8') : Buffer.alloc(0);
|
|
320
|
-
const nameLen = nameBuf.length;
|
|
321
|
-
const payloadLenBuf = writePayloadLength(payloadTotalLen);
|
|
322
|
-
const version = HEADER_VERSION_V2;
|
|
323
|
-
let wavPayload = [
|
|
324
|
-
PIXEL_MAGIC,
|
|
325
|
-
Buffer.from([version]),
|
|
326
|
-
Buffer.from([nameLen]),
|
|
327
|
-
nameBuf,
|
|
328
|
-
payloadLenBuf,
|
|
329
|
-
...payload,
|
|
330
|
-
];
|
|
331
|
-
if (opts.includeFileList && opts.fileList) {
|
|
332
|
-
let sizeMapW = null;
|
|
333
|
-
if (!Array.isArray(input)) {
|
|
334
|
-
try {
|
|
335
|
-
const unpack = unpackBuffer(input);
|
|
336
|
-
if (unpack) {
|
|
337
|
-
sizeMapW = {};
|
|
338
|
-
for (const ef of unpack.files)
|
|
339
|
-
sizeMapW[ef.path] = ef.buf.length;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
catch (e) { }
|
|
343
|
-
}
|
|
344
|
-
const normalizedW = opts.fileList.map((f) => {
|
|
345
|
-
if (typeof f === 'string')
|
|
346
|
-
return { name: f, size: sizeMapW && sizeMapW[f] ? sizeMapW[f] : 0 };
|
|
347
|
-
if (f && typeof f === 'object') {
|
|
348
|
-
if (f.name)
|
|
349
|
-
return { name: f.name, size: f.size ?? 0 };
|
|
350
|
-
if (f.path)
|
|
351
|
-
return { name: f.path, size: f.size ?? 0 };
|
|
352
|
-
}
|
|
353
|
-
return { name: String(f), size: 0 };
|
|
354
|
-
});
|
|
355
|
-
const jsonBufW = Buffer.from(JSON.stringify(normalizedW), 'utf8');
|
|
356
|
-
const lenBufW = Buffer.alloc(4);
|
|
357
|
-
lenBufW.writeUInt32BE(jsonBufW.length, 0);
|
|
358
|
-
wavPayload = [...wavPayload, Buffer.from('rXFL', 'utf8'), lenBufW, jsonBufW];
|
|
359
|
-
}
|
|
360
|
-
const wavData = bytesToWav(Buffer.concat(wavPayload));
|
|
361
|
-
payload.length = 0;
|
|
362
|
-
progressBar?.stop();
|
|
363
|
-
return wavData;
|
|
364
|
-
}
|
|
365
|
-
{
|
|
366
|
-
const nameBuf = opts.name ? Buffer.from(opts.name, 'utf8') : Buffer.alloc(0);
|
|
367
|
-
const nameLen = nameBuf.length;
|
|
368
|
-
const payloadLenBuf = writePayloadLength(payloadTotalLen);
|
|
369
|
-
const version = HEADER_VERSION_V2;
|
|
370
|
-
let metaPixel = [
|
|
371
|
-
Buffer.from([version]),
|
|
372
|
-
Buffer.from([nameLen]),
|
|
373
|
-
nameBuf,
|
|
374
|
-
payloadLenBuf,
|
|
375
|
-
...payload,
|
|
376
|
-
];
|
|
377
|
-
if (opts.includeFileList && opts.fileList) {
|
|
378
|
-
let sizeMap2 = null;
|
|
379
|
-
if (!Array.isArray(input)) {
|
|
380
|
-
try {
|
|
381
|
-
const unpack = unpackBuffer(input);
|
|
382
|
-
if (unpack) {
|
|
383
|
-
sizeMap2 = {};
|
|
384
|
-
for (const ef of unpack.files)
|
|
385
|
-
sizeMap2[ef.path] = ef.buf.length;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
catch (e) { }
|
|
389
|
-
}
|
|
390
|
-
const normalized = opts.fileList.map((f) => {
|
|
391
|
-
if (typeof f === 'string')
|
|
392
|
-
return { name: f, size: sizeMap2 && sizeMap2[f] ? sizeMap2[f] : 0 };
|
|
393
|
-
if (f && typeof f === 'object') {
|
|
394
|
-
if (f.name)
|
|
395
|
-
return { name: f.name, size: f.size ?? 0 };
|
|
396
|
-
if (f.path)
|
|
397
|
-
return { name: f.path, size: f.size ?? 0 };
|
|
398
|
-
}
|
|
399
|
-
return { name: String(f), size: 0 };
|
|
400
|
-
});
|
|
401
|
-
const jsonBuf = Buffer.from(JSON.stringify(normalized), 'utf8');
|
|
402
|
-
const lenBuf = Buffer.alloc(4);
|
|
403
|
-
lenBuf.writeUInt32BE(jsonBuf.length, 0);
|
|
404
|
-
metaPixel = [...metaPixel, Buffer.from('rXFL', 'utf8'), lenBuf, jsonBuf];
|
|
405
|
-
}
|
|
406
|
-
const useBlockEncoding = false;
|
|
407
|
-
const pixelMagic = useBlockEncoding ? PIXEL_MAGIC_BLOCK : PIXEL_MAGIC;
|
|
408
|
-
const dataWithoutMarkers = [pixelMagic, ...metaPixel];
|
|
409
|
-
const dataWithoutMarkersLen = dataWithoutMarkers.reduce((a, b) => a + b.length, 0);
|
|
410
|
-
const padding = (3 - (dataWithoutMarkersLen % 3)) % 3;
|
|
411
|
-
const paddedData = padding > 0 ?
|
|
412
|
-
[...dataWithoutMarkers, Buffer.alloc(padding)]
|
|
413
|
-
: dataWithoutMarkers;
|
|
414
|
-
const markerStartBytes = colorsToBytes(MARKER_START);
|
|
415
|
-
const compressionMarkerBytes = colorsToBytes(opts.compression === 'bwt-ans' ? COMPRESSION_MARKERS['bwt-ans'] : COMPRESSION_MARKERS.zstd);
|
|
416
|
-
const dataWithMarkers = [
|
|
417
|
-
markerStartBytes,
|
|
418
|
-
compressionMarkerBytes,
|
|
419
|
-
...paddedData,
|
|
420
|
-
];
|
|
421
|
-
const dataWithMarkersLen = dataWithMarkers.reduce((a, b) => a + b.length, 0);
|
|
422
|
-
let width;
|
|
423
|
-
let height;
|
|
424
|
-
let bufScr;
|
|
425
|
-
if (useBlockEncoding) {
|
|
426
|
-
const flatData = Buffer.concat(dataWithMarkers);
|
|
427
|
-
const blocksPerRow = Math.ceil(Math.sqrt(flatData.length));
|
|
428
|
-
const numRows = Math.ceil(flatData.length / blocksPerRow);
|
|
429
|
-
width = blocksPerRow * 2;
|
|
430
|
-
height = numRows * 2;
|
|
431
|
-
const rgbBuffer = Buffer.alloc(width * height * 3);
|
|
432
|
-
for (let i = 0; i < flatData.length; i++) {
|
|
433
|
-
const blockRow = Math.floor(i / blocksPerRow);
|
|
434
|
-
const blockCol = i % blocksPerRow;
|
|
435
|
-
const pixelRow = blockRow * 2;
|
|
436
|
-
const pixelCol = blockCol * 2;
|
|
437
|
-
const byte = flatData[i];
|
|
438
|
-
for (let dy = 0; dy < 2; dy++) {
|
|
439
|
-
for (let dx = 0; dx < 2; dx++) {
|
|
440
|
-
const px = (pixelRow + dy) * width + (pixelCol + dx);
|
|
441
|
-
rgbBuffer[px * 3] = byte;
|
|
442
|
-
rgbBuffer[px * 3 + 1] = byte;
|
|
443
|
-
rgbBuffer[px * 3 + 2] = byte;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
bufScr = Buffer.from(native.rgbToPng(rgbBuffer, width, height));
|
|
46
|
+
if (opts.passphrase) {
|
|
47
|
+
const encryptType = opts.encrypt && opts.encrypt !== 'auto' ? opts.encrypt : 'aes';
|
|
48
|
+
const result = native.nativeEncodePngWithEncryptionNameAndFilelist(inputBuf, compressionLevel, opts.passphrase, encryptType, fileName, fileListJson);
|
|
49
|
+
return Buffer.from(result);
|
|
448
50
|
}
|
|
449
51
|
else {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
const totalPixels = dataPixels + MARKER_END.length;
|
|
453
|
-
const maxWidth = 16384;
|
|
454
|
-
let side = Math.ceil(Math.sqrt(totalPixels));
|
|
455
|
-
if (side < MARKER_END.length)
|
|
456
|
-
side = MARKER_END.length;
|
|
457
|
-
let logicalWidth;
|
|
458
|
-
let logicalHeight;
|
|
459
|
-
if (side <= maxWidth) {
|
|
460
|
-
logicalWidth = side;
|
|
461
|
-
logicalHeight = side;
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
logicalWidth = Math.min(maxWidth, totalPixels);
|
|
465
|
-
logicalHeight = Math.ceil(totalPixels / logicalWidth);
|
|
466
|
-
}
|
|
467
|
-
const scale = 1;
|
|
468
|
-
width = logicalWidth * scale;
|
|
469
|
-
height = logicalHeight * scale;
|
|
470
|
-
const LARGE_IMAGE_PIXELS = 10000000;
|
|
471
|
-
const useManualPng = (width * height > LARGE_IMAGE_PIXELS || !!process.env.ROX_FAST_PNG) &&
|
|
472
|
-
opts.outputFormat !== 'webp';
|
|
473
|
-
if (process.env.ROX_DEBUG) {
|
|
474
|
-
console.log(`[DEBUG] Width=${width}, Height=${height}, Pixels=${width * height}`);
|
|
475
|
-
console.log(`[DEBUG] outputFormat=${opts.outputFormat}, useManualPng=${useManualPng}`);
|
|
476
|
-
}
|
|
477
|
-
const totalDataBytes = logicalWidth * logicalHeight * 3;
|
|
478
|
-
const markerEndPos = totalDataBytes - MARKER_END.length * 3;
|
|
479
|
-
const fullData = Buffer.alloc(totalDataBytes);
|
|
480
|
-
const flatData = Buffer.concat(dataWithMarkers);
|
|
481
|
-
flatData.copy(fullData, 0, 0, Math.min(flatData.length, markerEndPos));
|
|
482
|
-
let mOff = markerEndPos;
|
|
483
|
-
for (let i = 0; i < MARKER_END.length; i++) {
|
|
484
|
-
fullData[mOff++] = MARKER_END[i].r;
|
|
485
|
-
fullData[mOff++] = MARKER_END[i].g;
|
|
486
|
-
fullData[mOff++] = MARKER_END[i].b;
|
|
487
|
-
}
|
|
488
|
-
let raw;
|
|
489
|
-
let stride = 0;
|
|
490
|
-
if (useManualPng) {
|
|
491
|
-
stride = width * 3 + 1;
|
|
492
|
-
raw = Buffer.alloc(height * stride);
|
|
493
|
-
for (let row = 0; row < height; row++) {
|
|
494
|
-
raw[row * stride] = 0;
|
|
495
|
-
fullData.copy(raw, row * stride + 1, row * width * 3, (row + 1) * width * 3);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
raw = fullData;
|
|
500
|
-
}
|
|
501
|
-
if (opts.onProgress)
|
|
502
|
-
opts.onProgress({ phase: 'png_gen', loaded: 0, total: height });
|
|
503
|
-
if (useManualPng) {
|
|
504
|
-
const bytesPerRow = width * 3;
|
|
505
|
-
const scanlinesData = Buffer.alloc(height * (1 + bytesPerRow));
|
|
506
|
-
const progressStep = Math.max(1, Math.floor(height / 20));
|
|
507
|
-
for (let row = 0; row < height; row++) {
|
|
508
|
-
scanlinesData[row * (1 + bytesPerRow)] = 0;
|
|
509
|
-
const srcStart = row * stride + 1;
|
|
510
|
-
const dstStart = row * (1 + bytesPerRow) + 1;
|
|
511
|
-
raw.copy(scanlinesData, dstStart, srcStart, srcStart + bytesPerRow);
|
|
512
|
-
if (opts.onProgress && row % progressStep === 0) {
|
|
513
|
-
opts.onProgress({ phase: 'png_gen', loaded: row, total: height });
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
if (opts.onProgress)
|
|
517
|
-
opts.onProgress({ phase: 'png_compress', loaded: 0, total: 100 });
|
|
518
|
-
const idatData = zlib.deflateSync(scanlinesData, {
|
|
519
|
-
level: 0,
|
|
520
|
-
memLevel: 8,
|
|
521
|
-
strategy: zlib.constants.Z_FILTERED,
|
|
522
|
-
});
|
|
523
|
-
raw = Buffer.alloc(0);
|
|
524
|
-
const ihdrData = Buffer.alloc(13);
|
|
525
|
-
ihdrData.writeUInt32BE(width, 0);
|
|
526
|
-
ihdrData.writeUInt32BE(height, 4);
|
|
527
|
-
ihdrData[8] = 8;
|
|
528
|
-
ihdrData[9] = 2;
|
|
529
|
-
ihdrData[10] = 0;
|
|
530
|
-
ihdrData[11] = 0;
|
|
531
|
-
ihdrData[12] = 0;
|
|
532
|
-
const ihdrType = Buffer.from('IHDR', 'utf8');
|
|
533
|
-
const ihdrCrc = crc32(ihdrData, crc32(ihdrType));
|
|
534
|
-
const ihdrCrcBuf = Buffer.alloc(4);
|
|
535
|
-
ihdrCrcBuf.writeUInt32BE(ihdrCrc, 0);
|
|
536
|
-
const ihdrLen = Buffer.alloc(4);
|
|
537
|
-
ihdrLen.writeUInt32BE(ihdrData.length, 0);
|
|
538
|
-
const idatType = Buffer.from('IDAT', 'utf8');
|
|
539
|
-
const idatCrc = crc32(idatData, crc32(idatType));
|
|
540
|
-
const idatCrcBuf = Buffer.alloc(4);
|
|
541
|
-
idatCrcBuf.writeUInt32BE(idatCrc, 0);
|
|
542
|
-
const idatLen = Buffer.alloc(4);
|
|
543
|
-
idatLen.writeUInt32BE(idatData.length, 0);
|
|
544
|
-
const iendType = Buffer.from('IEND', 'utf8');
|
|
545
|
-
const iendCrc = crc32(Buffer.alloc(0), crc32(iendType));
|
|
546
|
-
const iendCrcBuf = Buffer.alloc(4);
|
|
547
|
-
iendCrcBuf.writeUInt32BE(iendCrc, 0);
|
|
548
|
-
const iendLen = Buffer.alloc(4);
|
|
549
|
-
iendLen.writeUInt32BE(0, 0);
|
|
550
|
-
bufScr = Buffer.concat([
|
|
551
|
-
PNG_HEADER,
|
|
552
|
-
ihdrLen,
|
|
553
|
-
ihdrType,
|
|
554
|
-
ihdrData,
|
|
555
|
-
ihdrCrcBuf,
|
|
556
|
-
idatLen,
|
|
557
|
-
idatType,
|
|
558
|
-
idatData,
|
|
559
|
-
idatCrcBuf,
|
|
560
|
-
iendLen,
|
|
561
|
-
iendType,
|
|
562
|
-
iendCrcBuf,
|
|
563
|
-
]);
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
const outputFormat = opts.outputFormat || 'png';
|
|
567
|
-
if (outputFormat === 'webp') {
|
|
568
|
-
throw new Error('WebP output format not supported with native backend');
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
bufScr = Buffer.from(native.rgbToPng(raw, width, height));
|
|
572
|
-
}
|
|
573
|
-
}
|
|
52
|
+
const result = native.nativeEncodePngWithNameAndFilelist(inputBuf, compressionLevel, fileName, fileListJson);
|
|
53
|
+
return Buffer.from(result);
|
|
574
54
|
}
|
|
575
|
-
payload.length = 0;
|
|
576
|
-
dataWithMarkers.length = 0;
|
|
577
|
-
metaPixel.length = 0;
|
|
578
|
-
meta.length = 0;
|
|
579
|
-
paddedData.length = 0;
|
|
580
|
-
dataWithoutMarkers.length = 0;
|
|
581
|
-
if (opts.onProgress)
|
|
582
|
-
opts.onProgress({ phase: 'png_compress', loaded: 100, total: 100 });
|
|
583
|
-
progressBar?.stop();
|
|
584
|
-
return bufScr;
|
|
585
55
|
}
|
|
586
56
|
}
|
|
@@ -2,7 +2,7 @@ declare function findRustBinary(): string | null;
|
|
|
2
2
|
export { findRustBinary };
|
|
3
3
|
export declare function isRustBinaryAvailable(): boolean;
|
|
4
4
|
export type ProgressCallback = (current: number, total: number, step: string) => void;
|
|
5
|
-
export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string, onProgress?: ProgressCallback): Promise<void>;
|
|
6
|
-
export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], dict?: string, onProgress?: ProgressCallback): Promise<void>;
|
|
5
|
+
export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string, ramBudgetMb?: number, onProgress?: ProgressCallback): Promise<void>;
|
|
6
|
+
export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], dict?: string, ramBudgetMb?: number, onProgress?: ProgressCallback): Promise<void>;
|
|
7
7
|
export declare function listWithRustCLI(inputPath: string): Promise<string>;
|
|
8
8
|
export declare function havepassphraseWithRustCLI(inputPath: string): Promise<string>;
|