roxify 1.12.11 → 1.13.1

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.
@@ -467,40 +467,29 @@ export async function encodeBinaryToPng(input, opts = {}) {
467
467
  console.log(`[DEBUG] Width=${width}, Height=${height}, Pixels=${width * height}`);
468
468
  console.log(`[DEBUG] outputFormat=${opts.outputFormat}, useManualPng=${useManualPng}`);
469
469
  }
470
+ const totalDataBytes = logicalWidth * logicalHeight * 3;
471
+ const markerEndPos = totalDataBytes - MARKER_END.length * 3;
472
+ const fullData = Buffer.alloc(totalDataBytes);
473
+ const flatData = Buffer.concat(dataWithMarkers);
474
+ flatData.copy(fullData, 0, 0, Math.min(flatData.length, markerEndPos));
475
+ let mOff = markerEndPos;
476
+ for (let i = 0; i < MARKER_END.length; i++) {
477
+ fullData[mOff++] = MARKER_END[i].r;
478
+ fullData[mOff++] = MARKER_END[i].g;
479
+ fullData[mOff++] = MARKER_END[i].b;
480
+ }
470
481
  let raw;
471
482
  let stride = 0;
472
483
  if (useManualPng) {
473
484
  stride = width * 3 + 1;
474
485
  raw = Buffer.alloc(height * stride);
475
- const flatData = Buffer.concat(dataWithMarkers);
476
- const markerEndBytes = Buffer.alloc(MARKER_END.length * 3);
477
- for (let i = 0; i < MARKER_END.length; i++) {
478
- markerEndBytes[i * 3] = MARKER_END[i].r;
479
- markerEndBytes[i * 3 + 1] = MARKER_END[i].g;
480
- markerEndBytes[i * 3 + 2] = MARKER_END[i].b;
481
- }
482
- const totalDataBytes = logicalWidth * logicalHeight * 3;
483
- const fullData = Buffer.alloc(totalDataBytes);
484
- const markerEndPos = totalDataBytes - MARKER_END.length * 3;
485
- flatData.copy(fullData, 0, 0, Math.min(flatData.length, markerEndPos));
486
- markerEndBytes.copy(fullData, markerEndPos);
487
486
  for (let row = 0; row < height; row++) {
488
487
  raw[row * stride] = 0;
489
488
  fullData.copy(raw, row * stride + 1, row * width * 3, (row + 1) * width * 3);
490
489
  }
491
490
  }
492
491
  else {
493
- raw = Buffer.alloc(width * height * 3);
494
- const flatData = Buffer.concat(dataWithMarkers);
495
- flatData.copy(raw, 0, 0, Math.min(flatData.length, raw.length));
496
- const markerEndBytes = Buffer.alloc(MARKER_END.length * 3);
497
- for (let i = 0; i < MARKER_END.length; i++) {
498
- markerEndBytes[i * 3] = MARKER_END[i].r;
499
- markerEndBytes[i * 3 + 1] = MARKER_END[i].g;
500
- markerEndBytes[i * 3 + 2] = MARKER_END[i].b;
501
- }
502
- const markerEndPos = raw.length - MARKER_END.length * 3;
503
- markerEndBytes.copy(raw, markerEndPos);
492
+ raw = fullData;
504
493
  }
505
494
  if (opts.onProgress)
506
495
  opts.onProgress({ phase: 'png_gen', loaded: 0, total: height });
@@ -112,6 +112,7 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
112
112
  const row = raw.slice(rowStart, rowStart + rowBytes);
113
113
  let bestSum = Infinity;
114
114
  let bestFiltered = null;
115
+ let chosenFilter = 0;
115
116
  for (let f = 0; f <= 4; f++) {
116
117
  const filtered = Buffer.alloc(rowBytes);
117
118
  let sum = 0;
@@ -145,33 +146,10 @@ export async function optimizePngBuffer(pngBuf, fast = false) {
145
146
  if (sum < bestSum) {
146
147
  bestSum = sum;
147
148
  bestFiltered = filtered;
148
- }
149
- }
150
- const rowBuf = Buffer.alloc(1 + rowBytes);
151
- let chosenFilter = 0;
152
- for (let f = 0; f <= 4; f++) {
153
- const filtered = Buffer.alloc(rowBytes);
154
- for (let i = 0; i < rowBytes; i++) {
155
- const val = row[i];
156
- const left = i - bytesPerPixel >= 0 ? row[i - bytesPerPixel] : 0;
157
- const up = prevRow ? prevRow[i] : 0;
158
- const upLeft = prevRow && i - bytesPerPixel >= 0 ? prevRow[i - bytesPerPixel] : 0;
159
- if (f === 0)
160
- filtered[i] = val;
161
- else if (f === 1)
162
- filtered[i] = (val - left + 256) & 0xff;
163
- else if (f === 2)
164
- filtered[i] = (val - up + 256) & 0xff;
165
- else if (f === 3)
166
- filtered[i] = (val - Math.floor((left + up) / 2) + 256) & 0xff;
167
- else
168
- filtered[i] = (val - paethPredict(left, up, upLeft) + 256) & 0xff;
169
- }
170
- if (filtered.equals(bestFiltered)) {
171
149
  chosenFilter = f;
172
- break;
173
150
  }
174
151
  }
152
+ const rowBuf = Buffer.alloc(1 + rowBytes);
175
153
  rowBuf[0] = chosenFilter;
176
154
  bestFiltered.copy(rowBuf, 1);
177
155
  outRows.push(rowBuf);
@@ -2,3 +2,6 @@ declare function findRustBinary(): string | null;
2
2
  export { findRustBinary };
3
3
  export declare function isRustBinaryAvailable(): boolean;
4
4
  export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string): Promise<void>;
5
+ export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], dict?: string): Promise<void>;
6
+ export declare function listWithRustCLI(inputPath: string): Promise<string>;
7
+ export declare function havepassphraseWithRustCLI(inputPath: string): Promise<string>;
@@ -1,5 +1,5 @@
1
1
  import { execSync, spawn } from 'child_process';
2
- import { existsSync } from 'fs';
2
+ import { accessSync, constants, existsSync } from 'fs';
3
3
  import { dirname, join } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  let moduleDir;
@@ -14,6 +14,19 @@ else {
14
14
  moduleDir = process.cwd();
15
15
  }
16
16
  }
17
+ function canExecute(p) {
18
+ if (!existsSync(p))
19
+ return false;
20
+ if (process.platform === 'win32')
21
+ return true;
22
+ try {
23
+ accessSync(p, constants.X_OK);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
17
30
  function findRustBinary() {
18
31
  const platformBins = {
19
32
  win32: ['roxify_native.exe', 'roxify-cli.exe', 'roxify_cli.exe'],
@@ -24,13 +37,13 @@ function findRustBinary() {
24
37
  const baseDir = moduleDir;
25
38
  for (const name of binNames) {
26
39
  const sameDirPath = join(baseDir, name);
27
- if (existsSync(sameDirPath))
40
+ if (canExecute(sameDirPath))
28
41
  return sameDirPath;
29
42
  const parentPath = join(baseDir, '..', name);
30
- if (existsSync(parentPath))
43
+ if (canExecute(parentPath))
31
44
  return parentPath;
32
45
  const parentDistPath = join(baseDir, '..', 'dist', name);
33
- if (existsSync(parentDistPath))
46
+ if (canExecute(parentDistPath))
34
47
  return parentDistPath;
35
48
  }
36
49
  if (process.pkg) {
@@ -42,7 +55,7 @@ function findRustBinary() {
42
55
  for (const basePath of snapshotPaths) {
43
56
  for (const name of binNames) {
44
57
  const binPath = join(basePath, name);
45
- if (existsSync(binPath))
58
+ if (canExecute(binPath))
46
59
  return binPath;
47
60
  }
48
61
  }
@@ -58,7 +71,7 @@ function findRustBinary() {
58
71
  for (const c of execCandidates) {
59
72
  for (const name of binNames) {
60
73
  const p = join(c, name);
61
- if (existsSync(p))
74
+ if (canExecute(p))
62
75
  return p;
63
76
  }
64
77
  }
@@ -97,7 +110,7 @@ function findRustBinary() {
97
110
  for (const c of candidates) {
98
111
  for (const name of binNames) {
99
112
  const candidate = join(c, name);
100
- if (existsSync(candidate))
113
+ if (canExecute(candidate))
101
114
  return candidate;
102
115
  }
103
116
  }
@@ -108,16 +121,16 @@ function findRustBinary() {
108
121
  catch { }
109
122
  for (const name of binNames) {
110
123
  const parentParentLocal = join(baseDir, '..', '..', name);
111
- if (existsSync(parentParentLocal))
124
+ if (canExecute(parentParentLocal))
112
125
  return parentParentLocal;
113
126
  const nodeModulesPath = join(baseDir, '..', '..', '..', '..', name);
114
- if (existsSync(nodeModulesPath))
127
+ if (canExecute(nodeModulesPath))
115
128
  return nodeModulesPath;
116
129
  }
117
130
  const targetRelease = join(baseDir, '..', '..', 'target', 'release');
118
131
  for (const name of binNames) {
119
132
  const targetPath = join(targetRelease, name);
120
- if (existsSync(targetPath))
133
+ if (canExecute(targetPath))
121
134
  return targetPath;
122
135
  }
123
136
  return null;
@@ -128,52 +141,32 @@ export function isRustBinaryAvailable() {
128
141
  }
129
142
  import { chmodSync, mkdtempSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
130
143
  import { tmpdir } from 'os';
131
- export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
144
+ function extractToTemp(pathToRead) {
145
+ const buf = readFileSync(pathToRead);
146
+ const tmp = mkdtempSync(join(tmpdir(), 'roxify-'));
147
+ const dest = join(tmp, pathToRead.replace(/.*[\\/]/, ''));
148
+ writeFileSync(dest, buf);
149
+ try {
150
+ chmodSync(dest, 0o755);
151
+ }
152
+ catch (e) { }
153
+ return dest;
154
+ }
155
+ function spawnRustCLI(args, options) {
132
156
  const cliPath = findRustBinary();
133
- if (!cliPath) {
157
+ if (!cliPath)
134
158
  throw new Error('Rust CLI binary not found');
135
- }
136
- function extractToTemp(pathToRead) {
137
- const buf = readFileSync(pathToRead);
138
- const tmp = mkdtempSync(join(tmpdir(), 'roxify-'));
139
- const dest = join(tmp, pathToRead.replace(/.*[\\/]/, ''));
140
- writeFileSync(dest, buf);
141
- try {
142
- chmodSync(dest, 0o755);
143
- }
144
- catch (e) { }
145
- return dest;
146
- }
147
159
  return new Promise((resolve, reject) => {
148
- const args = ['encode', '--level', String(compressionLevel)];
149
- let supportsName = false;
150
- if (name) {
151
- try {
152
- const helpOut = execSync(`"${cliPath}" --help`, {
153
- encoding: 'utf8',
154
- timeout: 2000,
155
- });
156
- if (helpOut && helpOut.includes('--name'))
157
- supportsName = true;
158
- }
159
- catch (e) {
160
- supportsName = false;
161
- }
162
- if (supportsName) {
163
- args.push('--name', name);
164
- }
165
- }
166
- if (passphrase) {
167
- args.push('--passphrase', passphrase);
168
- args.push('--encrypt', encryptType);
169
- }
170
- args.push(inputPath, outputPath);
171
160
  let triedExtract = false;
172
161
  let tempExe;
162
+ let stdout = '';
173
163
  const runSpawn = (exePath) => {
174
164
  let proc;
165
+ const stdio = options?.collectStdout
166
+ ? ['pipe', 'pipe', 'inherit']
167
+ : 'inherit';
175
168
  try {
176
- proc = spawn(exePath, args, { stdio: 'inherit' });
169
+ proc = spawn(exePath, args, { stdio });
177
170
  }
178
171
  catch (err) {
179
172
  if (!triedExtract) {
@@ -188,6 +181,9 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
188
181
  }
189
182
  return reject(err);
190
183
  }
184
+ if (options?.collectStdout && proc.stdout) {
185
+ proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
186
+ }
191
187
  proc.on('error', (err) => {
192
188
  if (!triedExtract) {
193
189
  triedExtract = true;
@@ -201,21 +197,55 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
201
197
  }
202
198
  reject(err);
203
199
  });
204
- proc.on('close', (code) => {
200
+ proc.on('close', (code, signal) => {
205
201
  if (tempExe) {
206
202
  try {
207
203
  unlinkSync(tempExe);
208
204
  }
209
205
  catch (e) { }
210
206
  }
211
- if (code === 0) {
212
- resolve();
213
- }
214
- else {
215
- reject(new Error(`Rust encoder exited with status ${code}`));
216
- }
207
+ if (code === 0 || (code === null && signal === null))
208
+ resolve(stdout);
209
+ else
210
+ reject(new Error(`Rust CLI exited with status ${code ?? signal}`));
217
211
  });
218
212
  };
219
213
  runSpawn(cliPath);
220
214
  });
221
215
  }
216
+ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
217
+ const cliPath = findRustBinary();
218
+ if (!cliPath)
219
+ throw new Error('Rust CLI binary not found');
220
+ const args = ['encode', '--level', String(compressionLevel)];
221
+ if (name) {
222
+ try {
223
+ const helpOut = execSync(`"${cliPath}" --help`, { encoding: 'utf8', timeout: 2000 });
224
+ if (helpOut && helpOut.includes('--name'))
225
+ args.push('--name', name);
226
+ }
227
+ catch (e) { }
228
+ }
229
+ if (passphrase) {
230
+ args.push('--passphrase', passphrase);
231
+ args.push('--encrypt', encryptType);
232
+ }
233
+ args.push(inputPath, outputPath);
234
+ await spawnRustCLI(args);
235
+ }
236
+ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict) {
237
+ const args = ['decompress', inputPath, outputPath];
238
+ if (passphrase)
239
+ args.push('--passphrase', passphrase);
240
+ if (files && files.length > 0)
241
+ args.push('--files', JSON.stringify(files));
242
+ if (dict)
243
+ args.push('--dict', dict);
244
+ await spawnRustCLI(args);
245
+ }
246
+ export async function listWithRustCLI(inputPath) {
247
+ return spawnRustCLI(['list', inputPath], { collectStdout: true });
248
+ }
249
+ export async function havepassphraseWithRustCLI(inputPath) {
250
+ return spawnRustCLI(['havepassphrase', inputPath], { collectStdout: true });
251
+ }
@@ -45,7 +45,7 @@ export async function compressStream(stream, level = 19, onProgress, dict) {
45
45
  };
46
46
  }
47
47
  export async function parallelZstdCompress(payload, level = 19, onProgress, dict) {
48
- const chunkSize = 8 * 1024 * 1024;
48
+ const chunkSize = 32 * 1024 * 1024;
49
49
  // For small payloads (< chunkSize), concatenate and compress as single frame
50
50
  // to avoid multi-chunk overhead (16+ bytes header per chunk boundary).
51
51
  let flat = null;
package/native/bwt.rs CHANGED
@@ -1,56 +1,56 @@
1
- use anyhow::Result;
2
- use libsais::bwt::Bwt;
3
- use libsais::typestate::OwnedBuffer;
4
- use libsais::BwtConstruction;
5
- use rayon::prelude::*;
6
-
7
- pub struct BwtResult {
8
- pub transformed: Vec<u8>,
9
- pub primary_index: u32,
10
- }
11
-
12
- pub fn bwt_encode(data: &[u8]) -> Result<BwtResult> {
13
- let n = data.len();
14
- if n == 0 {
15
- return Ok(BwtResult { transformed: Vec::new(), primary_index: 0 });
16
- }
17
-
18
- let bwt_result = BwtConstruction::for_text(data)
19
- .with_owned_temporary_array_buffer32()
20
- .single_threaded()
21
- .run()
22
- .map_err(|e| anyhow::anyhow!("libsais BWT: {:?}", e))?;
23
-
24
- let primary_index = bwt_result.primary_index() as u32;
25
- let transformed = bwt_result.bwt().to_vec();
26
-
27
- Ok(BwtResult { transformed, primary_index })
28
- }
29
-
30
- pub fn bwt_decode(bwt_data: &[u8], primary_index: u32) -> Result<Vec<u8>> {
31
- if bwt_data.is_empty() {
32
- return Ok(Vec::new());
33
- }
34
-
35
- let bwt_obj: Bwt<'static, u8, OwnedBuffer> =
36
- unsafe { Bwt::from_parts(bwt_data.to_vec(), primary_index as usize) };
37
-
38
- let text = bwt_obj
39
- .unbwt()
40
- .with_owned_temporary_array_buffer32()
41
- .single_threaded()
42
- .run()
43
- .map_err(|e| anyhow::anyhow!("libsais UnBWT: {:?}", e))?;
44
-
45
- Ok(text.as_slice().to_vec())
46
- }
47
-
48
- pub fn bwt_encode_streaming(block_size: usize, data: &[u8]) -> Result<Vec<(BwtResult, usize)>> {
49
- data.par_chunks(block_size)
50
- .enumerate()
51
- .map(|(i, chunk)| {
52
- let result = bwt_encode(chunk)?;
53
- Ok((result, i * block_size))
54
- })
55
- .collect()
56
- }
1
+ use anyhow::Result;
2
+ use libsais::bwt::Bwt;
3
+ use libsais::typestate::OwnedBuffer;
4
+ use libsais::BwtConstruction;
5
+ use rayon::prelude::*;
6
+
7
+ pub struct BwtResult {
8
+ pub transformed: Vec<u8>,
9
+ pub primary_index: u32,
10
+ }
11
+
12
+ pub fn bwt_encode(data: &[u8]) -> Result<BwtResult> {
13
+ let n = data.len();
14
+ if n == 0 {
15
+ return Ok(BwtResult { transformed: Vec::new(), primary_index: 0 });
16
+ }
17
+
18
+ let bwt_result = BwtConstruction::for_text(data)
19
+ .with_owned_temporary_array_buffer32()
20
+ .single_threaded()
21
+ .run()
22
+ .map_err(|e| anyhow::anyhow!("libsais BWT: {:?}", e))?;
23
+
24
+ let primary_index = bwt_result.primary_index() as u32;
25
+ let transformed = bwt_result.bwt().to_vec();
26
+
27
+ Ok(BwtResult { transformed, primary_index })
28
+ }
29
+
30
+ pub fn bwt_decode(bwt_data: &[u8], primary_index: u32) -> Result<Vec<u8>> {
31
+ if bwt_data.is_empty() {
32
+ return Ok(Vec::new());
33
+ }
34
+
35
+ let bwt_obj: Bwt<'static, u8, OwnedBuffer> =
36
+ unsafe { Bwt::from_parts(bwt_data.to_vec(), primary_index as usize) };
37
+
38
+ let text = bwt_obj
39
+ .unbwt()
40
+ .with_owned_temporary_array_buffer32()
41
+ .single_threaded()
42
+ .run()
43
+ .map_err(|e| anyhow::anyhow!("libsais UnBWT: {:?}", e))?;
44
+
45
+ Ok(text.as_slice().to_vec())
46
+ }
47
+
48
+ pub fn bwt_encode_streaming(block_size: usize, data: &[u8]) -> Result<Vec<(BwtResult, usize)>> {
49
+ data.par_chunks(block_size)
50
+ .enumerate()
51
+ .map(|(i, chunk)| {
52
+ let result = bwt_encode(chunk)?;
53
+ Ok((result, i * block_size))
54
+ })
55
+ .collect()
56
+ }