roxify 1.12.6 → 1.12.8
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 +98 -0
- package/dist/cli.js +104 -130
- package/dist/stub-progress.d.ts +5 -10
- package/dist/stub-progress.js +7 -20
- package/dist/utils/encoder.js +1 -1
- package/dist/utils/rust-cli-wrapper.d.ts +1 -4
- package/dist/utils/rust-cli-wrapper.js +3 -69
- package/native/archive.rs +220 -0
- package/native/audio.rs +151 -0
- package/native/bench_hybrid.rs +145 -0
- package/native/bwt.rs +56 -0
- package/native/context_mixing.rs +117 -0
- package/native/core.rs +382 -0
- package/native/crypto.rs +204 -0
- package/native/encoder.rs +690 -0
- package/native/gpu.rs +116 -0
- package/native/hybrid.rs +287 -0
- package/native/image_utils.rs +82 -0
- package/native/lib.rs +489 -0
- package/native/main.rs +534 -0
- package/native/mtf.rs +106 -0
- package/native/packer.rs +447 -0
- package/native/png_utils.rs +538 -0
- package/native/pool.rs +101 -0
- package/native/progress.rs +43 -0
- package/native/rans.rs +149 -0
- package/native/rans_byte.rs +286 -0
- package/native/reconstitution.rs +623 -0
- package/native/streaming.rs +214 -0
- package/native/streaming_decode.rs +338 -0
- package/native/streaming_encode.rs +494 -0
- package/native/test_small_bwt.rs +31 -0
- package/native/test_stages.rs +70 -0
- package/package.json +111 -113
package/Cargo.toml
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "roxify_native"
|
|
3
|
+
version = "1.12.8"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
publish = false
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "roxify_native"
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
path = "native/lib.rs"
|
|
11
|
+
|
|
12
|
+
[[bin]]
|
|
13
|
+
name = "roxify_native"
|
|
14
|
+
path = "native/main.rs"
|
|
15
|
+
|
|
16
|
+
[[bin]]
|
|
17
|
+
name = "bench_hybrid"
|
|
18
|
+
path = "native/bench_hybrid.rs"
|
|
19
|
+
|
|
20
|
+
[[bin]]
|
|
21
|
+
name = "test_stages"
|
|
22
|
+
path = "native/test_stages.rs"
|
|
23
|
+
|
|
24
|
+
[[bin]]
|
|
25
|
+
name = "test_small_bwt"
|
|
26
|
+
path = "native/test_small_bwt.rs"
|
|
27
|
+
|
|
28
|
+
[dev-dependencies]
|
|
29
|
+
|
|
30
|
+
[dependencies]
|
|
31
|
+
napi = "2"
|
|
32
|
+
napi-derive = "2"
|
|
33
|
+
rayon = "1.7"
|
|
34
|
+
zstd = { version = "0.11", features = ["zstdmt"] }
|
|
35
|
+
crc32fast = "1.3"
|
|
36
|
+
num_cpus = "1.16"
|
|
37
|
+
clap = { version = "4", features = ["derive"] }
|
|
38
|
+
serde_json = "1.0"
|
|
39
|
+
anyhow = "1.0"
|
|
40
|
+
png = "0.18.0"
|
|
41
|
+
image = { version = "0.25", default-features = false, features = ["png"] }
|
|
42
|
+
# indicatif removed from default deps to reduce heavy build graph
|
|
43
|
+
walkdir = "2.5.0"
|
|
44
|
+
tar = "0.4"
|
|
45
|
+
aes-gcm = "0.10"
|
|
46
|
+
aes = "0.8"
|
|
47
|
+
ctr = "0.9"
|
|
48
|
+
cipher = { version = "0.4", features = ["std"] }
|
|
49
|
+
hmac = "0.12"
|
|
50
|
+
pbkdf2 = "0.12"
|
|
51
|
+
rand = "0.8"
|
|
52
|
+
sha2 = "0.10"
|
|
53
|
+
mimalloc = "0.1"
|
|
54
|
+
|
|
55
|
+
wgpu = { version = "0.19", optional = true }
|
|
56
|
+
memmap2 = "0.9"
|
|
57
|
+
bytemuck = { version = "1.14", features = ["derive"] }
|
|
58
|
+
# tokio is optional now; enable via the 'async' feature
|
|
59
|
+
tokio = { version = "1", features = ["sync", "rt"], optional = true }
|
|
60
|
+
parking_lot = "0.12"
|
|
61
|
+
pollster = { version = "0.3", optional = true }
|
|
62
|
+
libsais = { version = "0.2.0", default-features = false }
|
|
63
|
+
|
|
64
|
+
[features]
|
|
65
|
+
# default is intentionally empty so the crate compiles fast for local checks.
|
|
66
|
+
# Enable 'gpu' to pull in the WGPU and pollster dependencies (heavy).
|
|
67
|
+
# Enable 'async' to include tokio runtime (optional).
|
|
68
|
+
# Example: `cargo build -p roxify_native --features gpu`
|
|
69
|
+
default = []
|
|
70
|
+
|
|
71
|
+
gpu = ["wgpu", "pollster"]
|
|
72
|
+
async = ["tokio"]
|
|
73
|
+
full = ["gpu", "async"]
|
|
74
|
+
|
|
75
|
+
[profile.release]
|
|
76
|
+
opt-level = 3
|
|
77
|
+
lto = "fat"
|
|
78
|
+
codegen-units = 1
|
|
79
|
+
strip = true
|
|
80
|
+
panic = "abort"
|
|
81
|
+
|
|
82
|
+
[profile.release-size]
|
|
83
|
+
inherits = "release"
|
|
84
|
+
opt-level = "z"
|
|
85
|
+
lto = true
|
|
86
|
+
strip = true
|
|
87
|
+
|
|
88
|
+
[profile.fastdev]
|
|
89
|
+
# Fast development profile for minimal user CPU and fast compilation.
|
|
90
|
+
# Lower optimization and high codegen units to parallelize compilation work,
|
|
91
|
+
# enable incremental to speed up subsequent incremental builds.
|
|
92
|
+
inherits = "release"
|
|
93
|
+
opt-level = 1
|
|
94
|
+
lto = false
|
|
95
|
+
codegen-units = 16
|
|
96
|
+
debug = false
|
|
97
|
+
incremental = true
|
|
98
|
+
panic = "abort"
|
package/dist/cli.js
CHANGED
|
@@ -5,8 +5,8 @@ import { basename, dirname, join, resolve } from 'path';
|
|
|
5
5
|
import { DataFormatError, decodePngToBinary, encodeBinaryToPng, hasPassphraseInPng, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
|
|
6
6
|
import { packPathsGenerator, unpackBuffer } from './pack.js';
|
|
7
7
|
import * as cliProgress from './stub-progress.js';
|
|
8
|
-
import {
|
|
9
|
-
const VERSION = '1.12.
|
|
8
|
+
import { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
|
|
9
|
+
const VERSION = '1.12.0';
|
|
10
10
|
function getDirectorySize(dirPath) {
|
|
11
11
|
let totalSize = 0;
|
|
12
12
|
try {
|
|
@@ -51,54 +51,54 @@ async function readLargeFile(filePath) {
|
|
|
51
51
|
return Buffer.concat(chunks);
|
|
52
52
|
}
|
|
53
53
|
function showHelp() {
|
|
54
|
-
console.log(`
|
|
55
|
-
ROX CLI — Encode/decode binary in PNG or WAV
|
|
56
|
-
|
|
57
|
-
Usage:
|
|
58
|
-
npx rox <command> [options]
|
|
59
|
-
|
|
60
|
-
Commands:
|
|
61
|
-
encode <input>... [output] Encode one or more files/directories
|
|
62
|
-
decode <input> [output] Decode PNG/WAV to original file
|
|
63
|
-
list <input> List files in a Rox archive
|
|
64
|
-
havepassphrase <input> Check whether the archive requires a passphrase
|
|
65
|
-
|
|
66
|
-
Options:
|
|
67
|
-
--image Use PNG container (default)
|
|
68
|
-
--sound Use WAV audio container (smaller overhead, faster)
|
|
69
|
-
--bwt-ans Use BWT-ANS compression instead of Zstd
|
|
70
|
-
-p, --passphrase <pass> Use passphrase (AES-256-GCM)
|
|
71
|
-
-m, --mode <mode> Mode: screenshot (default)
|
|
72
|
-
-e, --encrypt <type> auto|aes|xor|none
|
|
73
|
-
--no-compress Disable compression
|
|
74
|
-
--dict <file> Use zstd dictionary when compressing
|
|
75
|
-
--force-ts Force TypeScript encoder (slower but supports encryption)
|
|
76
|
-
-o, --output <path> Output file path
|
|
77
|
-
-s, --sizes Show file sizes in 'list' output (default)
|
|
78
|
-
--no-sizes Disable file size reporting in 'list'
|
|
79
|
-
--files <list> Extract only specified files (comma-separated)
|
|
80
|
-
--view-reconst Export the reconstituted PNG for debugging
|
|
81
|
-
--debug Export debug images (doubled.png, reconstructed.png)
|
|
82
|
-
-v, --verbose Show detailed errors
|
|
83
|
-
|
|
84
|
-
Lossy-Resilient Encoding:
|
|
85
|
-
--lossy-resilient Enable lossy-resilient mode (survives JPEG/MP3)
|
|
86
|
-
--ecc-level <level> ECC redundancy: low|medium|quartile|high (default: medium)
|
|
87
|
-
--block-size <n> Robust image block size: 2-8 pixels (default: 4)
|
|
88
|
-
|
|
89
|
-
When --lossy-resilient is active, data is encoded with Reed-Solomon ECC
|
|
90
|
-
and rendered as a QR-code-style grid (image) or MFSK tones (audio).
|
|
91
|
-
Use --sound or --image to choose the container format.
|
|
92
|
-
|
|
93
|
-
Examples:
|
|
94
|
-
npx rox encode secret.pdf Encode to PNG
|
|
95
|
-
npx rox encode secret.pdf --sound Encode to WAV
|
|
96
|
-
npx rox encode secret.pdf --lossy-resilient Lossy-resilient PNG
|
|
97
|
-
npx rox encode secret.pdf --lossy-resilient --sound --ecc-level high
|
|
98
|
-
npx rox decode secret.pdf.png Decode back
|
|
99
|
-
npx rox decode secret.pdf.wav Decode WAV back
|
|
100
|
-
|
|
101
|
-
Run "npx rox help" for this message.
|
|
54
|
+
console.log(`
|
|
55
|
+
ROX CLI — Encode/decode binary in PNG or WAV
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
npx rox <command> [options]
|
|
59
|
+
|
|
60
|
+
Commands:
|
|
61
|
+
encode <input>... [output] Encode one or more files/directories
|
|
62
|
+
decode <input> [output] Decode PNG/WAV to original file
|
|
63
|
+
list <input> List files in a Rox archive
|
|
64
|
+
havepassphrase <input> Check whether the archive requires a passphrase
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
--image Use PNG container (default)
|
|
68
|
+
--sound Use WAV audio container (smaller overhead, faster)
|
|
69
|
+
--bwt-ans Use BWT-ANS compression instead of Zstd
|
|
70
|
+
-p, --passphrase <pass> Use passphrase (AES-256-GCM)
|
|
71
|
+
-m, --mode <mode> Mode: screenshot (default)
|
|
72
|
+
-e, --encrypt <type> auto|aes|xor|none
|
|
73
|
+
--no-compress Disable compression
|
|
74
|
+
--dict <file> Use zstd dictionary when compressing
|
|
75
|
+
--force-ts Force TypeScript encoder (slower but supports encryption)
|
|
76
|
+
-o, --output <path> Output file path
|
|
77
|
+
-s, --sizes Show file sizes in 'list' output (default)
|
|
78
|
+
--no-sizes Disable file size reporting in 'list'
|
|
79
|
+
--files <list> Extract only specified files (comma-separated)
|
|
80
|
+
--view-reconst Export the reconstituted PNG for debugging
|
|
81
|
+
--debug Export debug images (doubled.png, reconstructed.png)
|
|
82
|
+
-v, --verbose Show detailed errors
|
|
83
|
+
|
|
84
|
+
Lossy-Resilient Encoding:
|
|
85
|
+
--lossy-resilient Enable lossy-resilient mode (survives JPEG/MP3)
|
|
86
|
+
--ecc-level <level> ECC redundancy: low|medium|quartile|high (default: medium)
|
|
87
|
+
--block-size <n> Robust image block size: 2-8 pixels (default: 4)
|
|
88
|
+
|
|
89
|
+
When --lossy-resilient is active, data is encoded with Reed-Solomon ECC
|
|
90
|
+
and rendered as a QR-code-style grid (image) or MFSK tones (audio).
|
|
91
|
+
Use --sound or --image to choose the container format.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
npx rox encode secret.pdf Encode to PNG
|
|
95
|
+
npx rox encode secret.pdf --sound Encode to WAV
|
|
96
|
+
npx rox encode secret.pdf --lossy-resilient Lossy-resilient PNG
|
|
97
|
+
npx rox encode secret.pdf --lossy-resilient --sound --ecc-level high
|
|
98
|
+
npx rox decode secret.pdf.png Decode back
|
|
99
|
+
npx rox decode secret.pdf.wav Decode WAV back
|
|
100
|
+
|
|
101
|
+
Run "npx rox help" for this message.
|
|
102
102
|
`);
|
|
103
103
|
}
|
|
104
104
|
function parseArgs(args) {
|
|
@@ -303,35 +303,22 @@ async function encodeCommand(args) {
|
|
|
303
303
|
const startTime = Date.now();
|
|
304
304
|
const encodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
305
305
|
let barValue = 0;
|
|
306
|
-
encodeBar.start(100, 0, { step: '
|
|
307
|
-
const
|
|
308
|
-
barValue = Math.
|
|
306
|
+
encodeBar.start(100, 0, { step: 'Encoding', elapsed: '0' });
|
|
307
|
+
const progressInterval = setInterval(() => {
|
|
308
|
+
barValue = Math.min(barValue + 1, 99);
|
|
309
309
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
else if (pct < 100)
|
|
316
|
-
step = 'Writing PNG';
|
|
317
|
-
else
|
|
318
|
-
step = 'Done';
|
|
319
|
-
encodeBar.update(Math.min(barValue, 99), { step, elapsed: String(elapsed) });
|
|
320
|
-
};
|
|
321
|
-
const smoothInterval = setInterval(() => {
|
|
322
|
-
if (barValue < 99) {
|
|
323
|
-
barValue = Math.min(barValue + 1, 99);
|
|
324
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
325
|
-
encodeBar.update(barValue, { step: barValue < 30 ? 'Packing files' : barValue < 80 ? 'Compressing' : 'Writing PNG', elapsed: String(elapsed) });
|
|
326
|
-
}
|
|
327
|
-
}, 2000);
|
|
310
|
+
encodeBar.update(barValue, {
|
|
311
|
+
step: 'Encoding',
|
|
312
|
+
elapsed: String(elapsed),
|
|
313
|
+
});
|
|
314
|
+
}, 500);
|
|
328
315
|
const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
|
|
329
316
|
const fileName = basename(inputPaths[0]);
|
|
330
|
-
await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput,
|
|
331
|
-
clearInterval(
|
|
317
|
+
await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 19, parsed.passphrase, encryptType, fileName);
|
|
318
|
+
clearInterval(progressInterval);
|
|
332
319
|
const encodeTime = Date.now() - startTime;
|
|
333
320
|
encodeBar.update(100, {
|
|
334
|
-
step: '
|
|
321
|
+
step: 'done',
|
|
335
322
|
elapsed: String(Math.floor(encodeTime / 1000)),
|
|
336
323
|
});
|
|
337
324
|
encodeBar.stop();
|
|
@@ -373,18 +360,28 @@ async function encodeCommand(args) {
|
|
|
373
360
|
const encodeBar = new cliProgress.SingleBar({
|
|
374
361
|
format: ' {bar} {percentage}% | {step} | {elapsed}s',
|
|
375
362
|
}, cliProgress.Presets.shades_classic);
|
|
363
|
+
let barStarted = false;
|
|
376
364
|
const startEncode = Date.now();
|
|
377
365
|
let currentEncodeStep = 'Starting';
|
|
378
366
|
let displayedPct = 0;
|
|
379
367
|
let targetPct = 0;
|
|
380
368
|
const TICK_MS = 100;
|
|
381
369
|
const PCT_STEP = 1;
|
|
382
|
-
encodeBar.start(100, 0, { step: currentEncodeStep, elapsed: '0' });
|
|
383
370
|
const encodeHeartbeat = setInterval(() => {
|
|
384
371
|
const elapsed = Date.now() - startEncode;
|
|
372
|
+
if (!barStarted) {
|
|
373
|
+
encodeBar.start(100, Math.floor(displayedPct), {
|
|
374
|
+
step: currentEncodeStep,
|
|
375
|
+
elapsed: '0',
|
|
376
|
+
});
|
|
377
|
+
barStarted = true;
|
|
378
|
+
}
|
|
385
379
|
if (displayedPct < targetPct) {
|
|
386
380
|
displayedPct = Math.min(displayedPct + PCT_STEP, targetPct);
|
|
387
381
|
}
|
|
382
|
+
else if (displayedPct < 99) {
|
|
383
|
+
displayedPct = Math.min(displayedPct + PCT_STEP, 99);
|
|
384
|
+
}
|
|
388
385
|
encodeBar.update(Math.floor(displayedPct), {
|
|
389
386
|
step: currentEncodeStep,
|
|
390
387
|
elapsed: String(Math.floor(elapsed / 1000)),
|
|
@@ -395,7 +392,7 @@ async function encodeCommand(args) {
|
|
|
395
392
|
mode,
|
|
396
393
|
name: parsed.outputName || 'archive',
|
|
397
394
|
skipOptimization: false,
|
|
398
|
-
compressionLevel:
|
|
395
|
+
compressionLevel: 6,
|
|
399
396
|
outputFormat: 'auto',
|
|
400
397
|
container: containerMode,
|
|
401
398
|
});
|
|
@@ -541,11 +538,13 @@ async function encodeCommand(args) {
|
|
|
541
538
|
const output = await encodeBinaryToPng(inputBuffer, options);
|
|
542
539
|
const encodeTime = Date.now() - startEncode;
|
|
543
540
|
clearInterval(encodeHeartbeat);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
541
|
+
if (barStarted) {
|
|
542
|
+
encodeBar.update(100, {
|
|
543
|
+
step: 'done',
|
|
544
|
+
elapsed: String(Math.floor(encodeTime / 1000)),
|
|
545
|
+
});
|
|
546
|
+
encodeBar.stop();
|
|
547
|
+
}
|
|
549
548
|
writeFileSync(resolvedOutput, output);
|
|
550
549
|
const outputSize = (output.length / 1024 / 1024).toFixed(2);
|
|
551
550
|
const inputSize = (inputSizeVal / 1024 / 1024).toFixed(2);
|
|
@@ -576,42 +575,6 @@ async function decodeCommand(args) {
|
|
|
576
575
|
}
|
|
577
576
|
const resolvedInput = resolve(inputPath);
|
|
578
577
|
const resolvedOutput = parsed.output || outputPath || 'decoded.bin';
|
|
579
|
-
const inputFileSize = statSync(resolvedInput).size;
|
|
580
|
-
if (isRustBinaryAvailable() && !parsed.dict && inputFileSize > 10 * 1024 * 1024) {
|
|
581
|
-
try {
|
|
582
|
-
console.log(' ');
|
|
583
|
-
console.log(`Decoding... (Using native Rust decoder)\n`);
|
|
584
|
-
const decodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
|
|
585
|
-
const startDecode = Date.now();
|
|
586
|
-
let barValue = 0;
|
|
587
|
-
decodeBar.start(100, 0, { step: 'Reading PNG', elapsed: '0' });
|
|
588
|
-
const onProgress = (pct) => {
|
|
589
|
-
barValue = Math.max(barValue, pct);
|
|
590
|
-
const elapsed = Math.floor((Date.now() - startDecode) / 1000);
|
|
591
|
-
let step = 'Reading PNG';
|
|
592
|
-
if (pct >= 10 && pct < 60)
|
|
593
|
-
step = 'Decompressing';
|
|
594
|
-
else if (pct >= 60 && pct < 100)
|
|
595
|
-
step = 'Extracting files';
|
|
596
|
-
else if (pct >= 100)
|
|
597
|
-
step = 'Done';
|
|
598
|
-
decodeBar.update(Math.min(barValue, 99), { step, elapsed: String(elapsed) });
|
|
599
|
-
};
|
|
600
|
-
await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, onProgress);
|
|
601
|
-
const decodeTime = Date.now() - startDecode;
|
|
602
|
-
decodeBar.update(100, { step: 'Done', elapsed: String(Math.floor(decodeTime / 1000)) });
|
|
603
|
-
decodeBar.stop();
|
|
604
|
-
console.log(`\nSuccess!`);
|
|
605
|
-
console.log(` Time: ${decodeTime}ms`);
|
|
606
|
-
console.log(` Output: ${resolve(resolvedOutput)}`);
|
|
607
|
-
console.log(' ');
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
catch (err) {
|
|
611
|
-
console.warn('\nRust decoder failed, falling back to TypeScript decoder...');
|
|
612
|
-
console.warn(`Reason: ${err.message}\n`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
578
|
try {
|
|
616
579
|
const options = {};
|
|
617
580
|
if (parsed.passphrase) {
|
|
@@ -638,19 +601,28 @@ async function decodeCommand(args) {
|
|
|
638
601
|
const decodeBar = new cliProgress.SingleBar({
|
|
639
602
|
format: ' {bar} {percentage}% | {step} | {elapsed}s',
|
|
640
603
|
}, cliProgress.Presets.shades_classic);
|
|
604
|
+
let barStarted = false;
|
|
641
605
|
const startDecode = Date.now();
|
|
642
606
|
let currentPct = 0;
|
|
643
607
|
let targetPct = 0;
|
|
644
|
-
let currentStep = '
|
|
645
|
-
decodeBar.start(100, 0, { step: currentStep, elapsed: '0' });
|
|
608
|
+
let currentStep = 'Decoding';
|
|
646
609
|
const heartbeat = setInterval(() => {
|
|
647
610
|
if (currentPct < targetPct) {
|
|
648
611
|
currentPct = Math.min(currentPct + 2, targetPct);
|
|
649
612
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
613
|
+
if (!barStarted && targetPct > 0) {
|
|
614
|
+
decodeBar.start(100, Math.floor(currentPct), {
|
|
615
|
+
step: currentStep,
|
|
616
|
+
elapsed: String(Math.floor((Date.now() - startDecode) / 1000)),
|
|
617
|
+
});
|
|
618
|
+
barStarted = true;
|
|
619
|
+
}
|
|
620
|
+
else if (barStarted) {
|
|
621
|
+
decodeBar.update(Math.floor(currentPct), {
|
|
622
|
+
step: currentStep,
|
|
623
|
+
elapsed: String(Math.floor((Date.now() - startDecode) / 1000)),
|
|
624
|
+
});
|
|
625
|
+
}
|
|
654
626
|
}, 100);
|
|
655
627
|
options.onProgress = (info) => {
|
|
656
628
|
if (info.phase === 'decompress_start') {
|
|
@@ -661,7 +633,7 @@ async function decodeCommand(args) {
|
|
|
661
633
|
info.loaded &&
|
|
662
634
|
info.total) {
|
|
663
635
|
targetPct = 50 + Math.floor((info.loaded / info.total) * 40);
|
|
664
|
-
currentStep =
|
|
636
|
+
currentStep = `Decompressing (${info.loaded}/${info.total})`;
|
|
665
637
|
}
|
|
666
638
|
else if (info.phase === 'decompress_done') {
|
|
667
639
|
targetPct = 90;
|
|
@@ -676,12 +648,14 @@ async function decodeCommand(args) {
|
|
|
676
648
|
const result = await decodePngToBinary(inputBuffer, options);
|
|
677
649
|
const decodeTime = Date.now() - startDecode;
|
|
678
650
|
clearInterval(heartbeat);
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
651
|
+
if (barStarted) {
|
|
652
|
+
currentPct = 100;
|
|
653
|
+
decodeBar.update(100, {
|
|
654
|
+
step: 'done',
|
|
655
|
+
elapsed: String(Math.floor(decodeTime / 1000)),
|
|
656
|
+
});
|
|
657
|
+
decodeBar.stop();
|
|
658
|
+
}
|
|
685
659
|
if (result.files) {
|
|
686
660
|
const baseDir = parsed.output || outputPath || '.';
|
|
687
661
|
const totalBytes = result.files.reduce((s, f) => s + f.buf.length, 0);
|
package/dist/stub-progress.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import cliProgress from 'cli-progress';
|
|
2
1
|
export declare class SingleBar {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
stop(): void;
|
|
2
|
+
constructor(...args: any[]);
|
|
3
|
+
start(...args: any[]): void;
|
|
4
|
+
update(...args: any[]): void;
|
|
5
|
+
stop(...args: any[]): void;
|
|
8
6
|
}
|
|
9
7
|
export declare const Presets: {
|
|
10
|
-
|
|
11
|
-
rect: cliProgress.Preset;
|
|
12
|
-
shades_classic: cliProgress.Preset;
|
|
13
|
-
shades_grey: cliProgress.Preset;
|
|
8
|
+
shades_classic: {};
|
|
14
9
|
};
|
package/dist/stub-progress.js
CHANGED
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
import cliProgress from 'cli-progress';
|
|
2
1
|
export class SingleBar {
|
|
3
|
-
constructor(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
forceRedraw: true,
|
|
8
|
-
barCompleteChar: '\u2588',
|
|
9
|
-
barIncompleteChar: '\u2591',
|
|
10
|
-
}, preset || cliProgress.Presets.shades_classic);
|
|
11
|
-
}
|
|
12
|
-
start(total, startValue, payload) {
|
|
13
|
-
this.bar.start(total, startValue, payload);
|
|
14
|
-
}
|
|
15
|
-
update(value, payload) {
|
|
16
|
-
this.bar.update(value, payload);
|
|
17
|
-
}
|
|
18
|
-
stop() {
|
|
19
|
-
this.bar.stop();
|
|
20
|
-
}
|
|
2
|
+
constructor(...args) { }
|
|
3
|
+
start(...args) { }
|
|
4
|
+
update(...args) { }
|
|
5
|
+
stop(...args) { }
|
|
21
6
|
}
|
|
22
|
-
export const Presets =
|
|
7
|
+
export const Presets = {
|
|
8
|
+
shades_classic: {},
|
|
9
|
+
};
|
package/dist/utils/encoder.js
CHANGED
|
@@ -59,7 +59,7 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
const compressionLevel = opts.compressionLevel ??
|
|
62
|
+
const compressionLevel = opts.compressionLevel ?? 19;
|
|
63
63
|
// ─── Lossy-resilient encoding fast path ────────────────────────────────────
|
|
64
64
|
// When lossyResilient is true, use QR-code-style block encoding with
|
|
65
65
|
// Reed-Solomon FEC. This produces output that survives lossy compression.
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
declare function findRustBinary(): string | null;
|
|
2
2
|
export { findRustBinary };
|
|
3
3
|
export declare function isRustBinaryAvailable(): boolean;
|
|
4
|
-
export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string
|
|
5
|
-
export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], onProgress?: (pct: number) => void): Promise<{
|
|
6
|
-
usedRust: boolean;
|
|
7
|
-
}>;
|
|
4
|
+
export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string): Promise<void>;
|
|
@@ -128,7 +128,7 @@ export function isRustBinaryAvailable() {
|
|
|
128
128
|
}
|
|
129
129
|
import { chmodSync, mkdtempSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
|
|
130
130
|
import { tmpdir } from 'os';
|
|
131
|
-
export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name
|
|
131
|
+
export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
|
|
132
132
|
const cliPath = findRustBinary();
|
|
133
133
|
if (!cliPath) {
|
|
134
134
|
throw new Error('Rust CLI binary not found');
|
|
@@ -145,7 +145,7 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
145
145
|
return dest;
|
|
146
146
|
}
|
|
147
147
|
return new Promise((resolve, reject) => {
|
|
148
|
-
const args = ['encode', '--level', String(compressionLevel)
|
|
148
|
+
const args = ['encode', '--level', String(compressionLevel)];
|
|
149
149
|
let supportsName = false;
|
|
150
150
|
if (name) {
|
|
151
151
|
try {
|
|
@@ -173,9 +173,7 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
173
173
|
const runSpawn = (exePath) => {
|
|
174
174
|
let proc;
|
|
175
175
|
try {
|
|
176
|
-
proc = spawn(exePath, args, {
|
|
177
|
-
stdio: ['inherit', 'inherit', 'pipe'],
|
|
178
|
-
});
|
|
176
|
+
proc = spawn(exePath, args, { stdio: 'inherit' });
|
|
179
177
|
}
|
|
180
178
|
catch (err) {
|
|
181
179
|
if (!triedExtract) {
|
|
@@ -190,20 +188,6 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
190
188
|
}
|
|
191
189
|
return reject(err);
|
|
192
190
|
}
|
|
193
|
-
if (proc.stderr && onProgress) {
|
|
194
|
-
let stderrBuf = '';
|
|
195
|
-
proc.stderr.on('data', (chunk) => {
|
|
196
|
-
stderrBuf += chunk.toString();
|
|
197
|
-
const lines = stderrBuf.split('\n');
|
|
198
|
-
stderrBuf = lines.pop() || '';
|
|
199
|
-
for (const line of lines) {
|
|
200
|
-
const match = line.match(/PROGRESS:(\d+)/);
|
|
201
|
-
if (match) {
|
|
202
|
-
onProgress(parseInt(match[1], 10));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
191
|
proc.on('error', (err) => {
|
|
208
192
|
if (!triedExtract) {
|
|
209
193
|
triedExtract = true;
|
|
@@ -235,53 +219,3 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
|
|
|
235
219
|
runSpawn(cliPath);
|
|
236
220
|
});
|
|
237
221
|
}
|
|
238
|
-
export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, onProgress) {
|
|
239
|
-
const cliPath = findRustBinary();
|
|
240
|
-
if (!cliPath) {
|
|
241
|
-
throw new Error('Rust CLI binary not found');
|
|
242
|
-
}
|
|
243
|
-
return new Promise((resolve, reject) => {
|
|
244
|
-
const args = ['decompress', '--progress'];
|
|
245
|
-
if (passphrase) {
|
|
246
|
-
args.push('--passphrase', passphrase);
|
|
247
|
-
}
|
|
248
|
-
if (files && files.length > 0) {
|
|
249
|
-
args.push('--files', JSON.stringify(files));
|
|
250
|
-
}
|
|
251
|
-
args.push(inputPath, outputPath);
|
|
252
|
-
const proc = spawn(cliPath, args, {
|
|
253
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
254
|
-
});
|
|
255
|
-
let stdout = '';
|
|
256
|
-
if (proc.stdout) {
|
|
257
|
-
proc.stdout.on('data', (chunk) => {
|
|
258
|
-
const text = chunk.toString();
|
|
259
|
-
stdout += text;
|
|
260
|
-
process.stdout.write(text);
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
if (proc.stderr && onProgress) {
|
|
264
|
-
let stderrBuf = '';
|
|
265
|
-
proc.stderr.on('data', (chunk) => {
|
|
266
|
-
stderrBuf += chunk.toString();
|
|
267
|
-
const lines = stderrBuf.split('\n');
|
|
268
|
-
stderrBuf = lines.pop() || '';
|
|
269
|
-
for (const line of lines) {
|
|
270
|
-
const match = line.match(/PROGRESS:(\d+)/);
|
|
271
|
-
if (match) {
|
|
272
|
-
onProgress(parseInt(match[1], 10));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
proc.on('error', (err) => reject(err));
|
|
278
|
-
proc.on('close', (code) => {
|
|
279
|
-
if (code === 0) {
|
|
280
|
-
resolve({ usedRust: true });
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
reject(new Error(`Rust decoder exited with status ${code}`));
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
}
|