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.
- package/Cargo.toml +98 -98
- package/dist/cli.js +134 -92
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pack.js +1 -1
- package/dist/{roxify_native → roxify-cli} +0 -0
- package/dist/utils/decoder.js +59 -7
- package/dist/utils/encoder.js +12 -23
- package/dist/utils/optimization.js +2 -24
- package/dist/utils/rust-cli-wrapper.d.ts +3 -0
- package/dist/utils/rust-cli-wrapper.js +85 -55
- package/dist/utils/zstd.js +1 -1
- package/native/bwt.rs +56 -56
- package/native/context_mixing.rs +117 -117
- package/native/core.rs +423 -382
- package/native/encoder.rs +635 -629
- package/native/gpu.rs +116 -116
- package/native/hybrid.rs +287 -287
- package/native/lib.rs +489 -489
- package/native/main.rs +527 -534
- package/native/mtf.rs +106 -106
- package/native/packer.rs +447 -447
- package/native/png_utils.rs +538 -538
- package/native/rans_byte.rs +286 -286
- package/native/streaming.rs +212 -212
- package/native/streaming_encode.rs +7 -4
- package/native/test_small_bwt.rs +31 -31
- package/package.json +114 -114
- package/roxify_native-x86_64-unknown-linux-gnu.node +0 -0
package/dist/utils/encoder.js
CHANGED
|
@@ -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 =
|
|
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 (
|
|
40
|
+
if (canExecute(sameDirPath))
|
|
28
41
|
return sameDirPath;
|
|
29
42
|
const parentPath = join(baseDir, '..', name);
|
|
30
|
-
if (
|
|
43
|
+
if (canExecute(parentPath))
|
|
31
44
|
return parentPath;
|
|
32
45
|
const parentDistPath = join(baseDir, '..', 'dist', name);
|
|
33
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
124
|
+
if (canExecute(parentParentLocal))
|
|
112
125
|
return parentParentLocal;
|
|
113
126
|
const nodeModulesPath = join(baseDir, '..', '..', '..', '..', name);
|
|
114
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/utils/zstd.js
CHANGED
|
@@ -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 =
|
|
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
|
+
}
|