roxify 1.16.12 → 1.16.14
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 +17 -30
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/utils/encoder.js +6 -20
- package/dist/utils/types.d.ts +0 -2
- package/package.json +1 -1
- package/dist/utils/audio.d.ts +0 -23
- package/dist/utils/audio.js +0 -98
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ async function loadJsEngine() {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
// Keep in sync with package.json#version.
|
|
24
|
-
const VERSION = '1.16.
|
|
24
|
+
const VERSION = '1.16.14';
|
|
25
25
|
function getDirectorySize(dirPath) {
|
|
26
26
|
let totalSize = 0;
|
|
27
27
|
try {
|
|
@@ -67,20 +67,18 @@ async function readLargeFile(filePath) {
|
|
|
67
67
|
}
|
|
68
68
|
function showHelp() {
|
|
69
69
|
console.log(`
|
|
70
|
-
ROX CLI — Encode/decode binary in PNG
|
|
70
|
+
ROX CLI — Encode/decode binary in PNG
|
|
71
71
|
|
|
72
72
|
Usage:
|
|
73
73
|
npx rox <command> [options]
|
|
74
74
|
|
|
75
75
|
Commands:
|
|
76
76
|
encode <input>... [output] Encode one or more files/directories
|
|
77
|
-
decode <input> [output] Decode PNG
|
|
77
|
+
decode <input> [output] Decode PNG to original file
|
|
78
78
|
list <input> List files in a Rox archive
|
|
79
79
|
havepassphrase <input> Check whether the archive requires a passphrase
|
|
80
80
|
|
|
81
81
|
Options:
|
|
82
|
-
--image Use PNG container (default)
|
|
83
|
-
--sound Use WAV audio container (smaller overhead, faster)
|
|
84
82
|
-p, --passphrase <pass> Use passphrase (AES-256-GCM)
|
|
85
83
|
-m, --mode <mode> Mode: screenshot (default)
|
|
86
84
|
-e, --encrypt <type> auto|aes|xor|none
|
|
@@ -101,17 +99,10 @@ Lossy-Resilient Encoding:
|
|
|
101
99
|
--ecc-level <level> ECC redundancy: low|medium|quartile|high (default: medium)
|
|
102
100
|
--block-size <n> Robust image block size: 2-8 pixels (default: 4)
|
|
103
101
|
|
|
104
|
-
When --lossy-resilient is active, data is encoded with Reed-Solomon ECC
|
|
105
|
-
and rendered as a QR-code-style grid (image) or MFSK tones (audio).
|
|
106
|
-
Use --sound or --image to choose the container format.
|
|
107
|
-
|
|
108
102
|
Examples:
|
|
109
103
|
npx rox encode secret.pdf Encode to PNG
|
|
110
|
-
npx rox encode secret.pdf --sound Encode to WAV
|
|
111
104
|
npx rox encode secret.pdf --lossy-resilient Lossy-resilient PNG
|
|
112
|
-
npx rox encode secret.pdf --lossy-resilient --sound --ecc-level high
|
|
113
105
|
npx rox decode secret.pdf.png Decode back
|
|
114
|
-
npx rox decode secret.pdf.wav Decode WAV back
|
|
115
106
|
|
|
116
107
|
Run "npx rox help" for this message.
|
|
117
108
|
`);
|
|
@@ -173,21 +164,18 @@ function parseArgs(args) {
|
|
|
173
164
|
parsed.blockSize = bs;
|
|
174
165
|
i += 2;
|
|
175
166
|
}
|
|
176
|
-
else if (key === 'sound') {
|
|
177
|
-
parsed.container = 'sound';
|
|
178
|
-
i++;
|
|
179
|
-
}
|
|
180
|
-
else if (key === 'image') {
|
|
181
|
-
parsed.container = 'image';
|
|
182
|
-
i++;
|
|
183
|
-
}
|
|
184
167
|
else if (key === 'debug-dir') {
|
|
185
168
|
parsed.debugDir = args[i + 1];
|
|
186
169
|
i += 2;
|
|
187
170
|
}
|
|
188
171
|
else if (key === 'files') {
|
|
189
|
-
|
|
190
|
-
|
|
172
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
173
|
+
parsed.files = args[i + 1].split(',');
|
|
174
|
+
i += 2;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
i++;
|
|
178
|
+
}
|
|
191
179
|
}
|
|
192
180
|
else if (key === 'dict') {
|
|
193
181
|
parsed.dict = args[i + 1];
|
|
@@ -276,14 +264,13 @@ async function encodeCommand(args) {
|
|
|
276
264
|
safeCwd = '/';
|
|
277
265
|
}
|
|
278
266
|
const resolvedInputs = inputPaths.map((p) => resolve(safeCwd, p));
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
let outputName = inputPaths.length === 1 ? basename(firstInput) : 'archive';
|
|
267
|
+
const outputNameBase = inputPaths.length === 1 ? basename(firstInput) : 'archive';
|
|
268
|
+
let outputName = outputNameBase;
|
|
282
269
|
if (inputPaths.length === 1 && !statSync(resolvedInputs[0]).isDirectory()) {
|
|
283
|
-
outputName =
|
|
270
|
+
outputName = outputNameBase.replace(/(\.[^.]+)?$/, '.png');
|
|
284
271
|
}
|
|
285
272
|
else {
|
|
286
|
-
outputName
|
|
273
|
+
outputName = outputNameBase + '.png';
|
|
287
274
|
}
|
|
288
275
|
let resolvedOutput;
|
|
289
276
|
try {
|
|
@@ -312,7 +299,7 @@ async function encodeCommand(args) {
|
|
|
312
299
|
}
|
|
313
300
|
}
|
|
314
301
|
catch (e) { }
|
|
315
|
-
if (isRustBinaryAvailable() && !parsed.forceTs
|
|
302
|
+
if (isRustBinaryAvailable() && !parsed.forceTs) {
|
|
316
303
|
try {
|
|
317
304
|
console.log(`Encoding to ${resolvedOutput} (Using native Rust encoder)\n`);
|
|
318
305
|
const startTime = Date.now();
|
|
@@ -410,7 +397,6 @@ async function encodeCommand(args) {
|
|
|
410
397
|
skipOptimization: false,
|
|
411
398
|
compressionLevel: 6,
|
|
412
399
|
outputFormat: 'auto',
|
|
413
|
-
container: containerMode,
|
|
414
400
|
});
|
|
415
401
|
if (parsed.verbose)
|
|
416
402
|
options.verbose = true;
|
|
@@ -427,7 +413,7 @@ async function encodeCommand(args) {
|
|
|
427
413
|
if (parsed.blockSize)
|
|
428
414
|
options.robustBlockSize = parsed.blockSize;
|
|
429
415
|
}
|
|
430
|
-
console.log(`Encoding to ${resolvedOutput}
|
|
416
|
+
console.log(`Encoding to ${resolvedOutput}\n`);
|
|
431
417
|
let inputData;
|
|
432
418
|
let inputSizeVal = 0;
|
|
433
419
|
let displayName;
|
|
@@ -763,6 +749,7 @@ async function main() {
|
|
|
763
749
|
await encodeCommand(commandArgs);
|
|
764
750
|
break;
|
|
765
751
|
case 'decode':
|
|
752
|
+
case 'decompress':
|
|
766
753
|
await decodeCommand(commandArgs);
|
|
767
754
|
break;
|
|
768
755
|
case 'list':
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/utils/encoder.js
CHANGED
|
@@ -43,28 +43,14 @@ export async function encodeBinaryToPng(input, opts = {}) {
|
|
|
43
43
|
const fileListJson = opts.includeFileList && opts.fileList
|
|
44
44
|
? normalizeNativeFileList(opts.fileList)
|
|
45
45
|
: undefined;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const result = native.nativeEncodeWavWithEncryptionNameAndFilelist(inputBuf, compressionLevel, opts.passphrase, encryptType, fileName, fileListJson);
|
|
51
|
-
return Buffer.from(result);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
const result = native.nativeEncodeWavWithNameAndFilelist(inputBuf, compressionLevel, fileName, fileListJson);
|
|
55
|
-
return Buffer.from(result);
|
|
56
|
-
}
|
|
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);
|
|
57
50
|
}
|
|
58
51
|
else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const result = native.nativeEncodePngWithEncryptionNameAndFilelist(inputBuf, compressionLevel, opts.passphrase, encryptType, fileName, fileListJson);
|
|
62
|
-
return Buffer.from(result);
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const result = native.nativeEncodePngWithNameAndFilelist(inputBuf, compressionLevel, fileName, fileListJson);
|
|
66
|
-
return Buffer.from(result);
|
|
67
|
-
}
|
|
52
|
+
const result = native.nativeEncodePngWithNameAndFilelist(inputBuf, compressionLevel, fileName, fileListJson);
|
|
53
|
+
return Buffer.from(result);
|
|
68
54
|
}
|
|
69
55
|
}
|
|
70
56
|
function normalizeNativeFileList(fileList) {
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -13,8 +13,6 @@ export interface EncodeOptions {
|
|
|
13
13
|
_skipAuto?: boolean;
|
|
14
14
|
output?: 'auto' | 'png' | 'rox';
|
|
15
15
|
outputFormat?: 'png' | 'webp';
|
|
16
|
-
/** Container format: 'image' (PNG, default) or 'sound' (WAV) */
|
|
17
|
-
container?: 'image' | 'sound';
|
|
18
16
|
/**
|
|
19
17
|
* Enable lossy-resilient encoding. When true, the output survives lossy
|
|
20
18
|
* compression (MP3/AAC for audio, JPEG/WebP for image) using QR-code-like
|
package/package.json
CHANGED
package/dist/utils/audio.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WAV container for binary data.
|
|
3
|
-
*
|
|
4
|
-
* Encodes raw bytes as 8-bit unsigned PCM mono samples (44100 Hz).
|
|
5
|
-
* Header is exactly 44 bytes. Total container overhead: 44 bytes (constant).
|
|
6
|
-
*
|
|
7
|
-
* Compared to PNG (stored deflate): PNG overhead grows with data size
|
|
8
|
-
* (zlib framing, filter bytes, chunk CRCs). WAV overhead is constant.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Pack raw bytes into a WAV file (8-bit PCM, mono, 44100 Hz).
|
|
12
|
-
* The bytes are stored directly as unsigned PCM samples.
|
|
13
|
-
*/
|
|
14
|
-
export declare function bytesToWav(data: Buffer): Buffer;
|
|
15
|
-
/**
|
|
16
|
-
* Extract raw bytes from a WAV file.
|
|
17
|
-
* Returns the PCM data (the original bytes).
|
|
18
|
-
*/
|
|
19
|
-
export declare function wavToBytes(wav: Buffer): Buffer;
|
|
20
|
-
/**
|
|
21
|
-
* Check if a buffer starts with a RIFF/WAVE header.
|
|
22
|
-
*/
|
|
23
|
-
export declare function isWav(buf: Buffer): boolean;
|
package/dist/utils/audio.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WAV container for binary data.
|
|
3
|
-
*
|
|
4
|
-
* Encodes raw bytes as 8-bit unsigned PCM mono samples (44100 Hz).
|
|
5
|
-
* Header is exactly 44 bytes. Total container overhead: 44 bytes (constant).
|
|
6
|
-
*
|
|
7
|
-
* Compared to PNG (stored deflate): PNG overhead grows with data size
|
|
8
|
-
* (zlib framing, filter bytes, chunk CRCs). WAV overhead is constant.
|
|
9
|
-
*/
|
|
10
|
-
const WAV_HEADER_SIZE = 44;
|
|
11
|
-
const SAMPLE_RATE = 44100;
|
|
12
|
-
const BITS_PER_SAMPLE = 8;
|
|
13
|
-
const NUM_CHANNELS = 1;
|
|
14
|
-
/**
|
|
15
|
-
* Pack raw bytes into a WAV file (8-bit PCM, mono, 44100 Hz).
|
|
16
|
-
* The bytes are stored directly as unsigned PCM samples.
|
|
17
|
-
*/
|
|
18
|
-
export function bytesToWav(data) {
|
|
19
|
-
const dataSize = data.length;
|
|
20
|
-
const fileSize = WAV_HEADER_SIZE - 8 + dataSize;
|
|
21
|
-
const byteRate = SAMPLE_RATE * NUM_CHANNELS * (BITS_PER_SAMPLE / 8);
|
|
22
|
-
const blockAlign = NUM_CHANNELS * (BITS_PER_SAMPLE / 8);
|
|
23
|
-
const wav = Buffer.alloc(WAV_HEADER_SIZE + dataSize);
|
|
24
|
-
let offset = 0;
|
|
25
|
-
// RIFF header
|
|
26
|
-
wav.write('RIFF', offset, 'ascii');
|
|
27
|
-
offset += 4;
|
|
28
|
-
wav.writeUInt32LE(fileSize, offset);
|
|
29
|
-
offset += 4;
|
|
30
|
-
wav.write('WAVE', offset, 'ascii');
|
|
31
|
-
offset += 4;
|
|
32
|
-
// fmt sub-chunk
|
|
33
|
-
wav.write('fmt ', offset, 'ascii');
|
|
34
|
-
offset += 4;
|
|
35
|
-
wav.writeUInt32LE(16, offset);
|
|
36
|
-
offset += 4; // sub-chunk size (PCM = 16)
|
|
37
|
-
wav.writeUInt16LE(1, offset);
|
|
38
|
-
offset += 2; // audio format (1 = PCM)
|
|
39
|
-
wav.writeUInt16LE(NUM_CHANNELS, offset);
|
|
40
|
-
offset += 2;
|
|
41
|
-
wav.writeUInt32LE(SAMPLE_RATE, offset);
|
|
42
|
-
offset += 4;
|
|
43
|
-
wav.writeUInt32LE(byteRate, offset);
|
|
44
|
-
offset += 4;
|
|
45
|
-
wav.writeUInt16LE(blockAlign, offset);
|
|
46
|
-
offset += 2;
|
|
47
|
-
wav.writeUInt16LE(BITS_PER_SAMPLE, offset);
|
|
48
|
-
offset += 2;
|
|
49
|
-
// data sub-chunk
|
|
50
|
-
wav.write('data', offset, 'ascii');
|
|
51
|
-
offset += 4;
|
|
52
|
-
wav.writeUInt32LE(dataSize, offset);
|
|
53
|
-
offset += 4;
|
|
54
|
-
data.copy(wav, offset);
|
|
55
|
-
return wav;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Extract raw bytes from a WAV file.
|
|
59
|
-
* Returns the PCM data (the original bytes).
|
|
60
|
-
*/
|
|
61
|
-
export function wavToBytes(wav) {
|
|
62
|
-
if (wav.length < WAV_HEADER_SIZE) {
|
|
63
|
-
throw new Error('WAV data too short');
|
|
64
|
-
}
|
|
65
|
-
if (wav.toString('ascii', 0, 4) !== 'RIFF') {
|
|
66
|
-
throw new Error('Not a RIFF file');
|
|
67
|
-
}
|
|
68
|
-
if (wav.toString('ascii', 8, 12) !== 'WAVE') {
|
|
69
|
-
throw new Error('Not a WAVE file');
|
|
70
|
-
}
|
|
71
|
-
// Find the "data" sub-chunk
|
|
72
|
-
let offset = 12;
|
|
73
|
-
while (offset + 8 <= wav.length) {
|
|
74
|
-
const chunkId = wav.toString('ascii', offset, offset + 4);
|
|
75
|
-
const chunkSize = wav.readUInt32LE(offset + 4);
|
|
76
|
-
if (chunkId === 'data') {
|
|
77
|
-
const dataStart = offset + 8;
|
|
78
|
-
const dataEnd = dataStart + chunkSize;
|
|
79
|
-
if (dataEnd > wav.length) {
|
|
80
|
-
return wav.subarray(dataStart);
|
|
81
|
-
}
|
|
82
|
-
return wav.subarray(dataStart, dataEnd);
|
|
83
|
-
}
|
|
84
|
-
offset += 8 + chunkSize;
|
|
85
|
-
if (chunkSize % 2 !== 0)
|
|
86
|
-
offset += 1; // RIFF word alignment
|
|
87
|
-
}
|
|
88
|
-
throw new Error('data chunk not found in WAV');
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Check if a buffer starts with a RIFF/WAVE header.
|
|
92
|
-
*/
|
|
93
|
-
export function isWav(buf) {
|
|
94
|
-
return (buf.length >= 12 &&
|
|
95
|
-
buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && // RIFF
|
|
96
|
-
buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45 // WAVE
|
|
97
|
-
);
|
|
98
|
-
}
|