sillstone 0.0.0-stage → 0.2.0
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/index.js +424 -0
- package/package.json +19 -4
- package/scripts/build.js +49 -0
- package/src/generated.c +511 -0
- package/src/node.def +16 -0
- package/src/node_api.h +104 -0
- package/test/checksum.test.js +65 -0
- package/test/reference.test.js +447 -0
- package/README.md +0 -3
package/index.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 sillstone
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const fs = require("node:fs");
|
|
7
|
+
const crypto = require("node:crypto");
|
|
8
|
+
|
|
9
|
+
const STATUS_OK = 0;
|
|
10
|
+
const STATUS_INVALID = -1;
|
|
11
|
+
const STATUS_REJECTED = -2;
|
|
12
|
+
|
|
13
|
+
const FORMATS = Object.freeze(["png", "jpeg", "bmp", "zip", "mp4", "mp3", "wav", "flac", "pdf", "gif", "unknown"]);
|
|
14
|
+
const MODALITIES = Object.freeze(["image", "audio", "text", "binary", "video"]);
|
|
15
|
+
const MATCH_TAGS = Object.freeze(["image", "audio", "text", "binary", "video"]);
|
|
16
|
+
const SIGNATURE_TAGS = Object.freeze(["image", "audio", "text", "binary", "video"]);
|
|
17
|
+
|
|
18
|
+
class FfiError extends Error {
|
|
19
|
+
constructor(op, status) {
|
|
20
|
+
const kind = { [STATUS_INVALID]: "invalid argument", [STATUS_REJECTED]: "input rejected" }[status] ?? "unknown failure";
|
|
21
|
+
super(`${op} failed: ${kind} (status ${status})`);
|
|
22
|
+
this.name = "FfiError";
|
|
23
|
+
this.status = status;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let native = null;
|
|
28
|
+
const candidates = [
|
|
29
|
+
path.join(__dirname, "sillstone_napi.node"),
|
|
30
|
+
path.join(__dirname, "build", "Release", "sillstone_napi.node"),
|
|
31
|
+
path.join(__dirname, "build", "Debug", "sillstone_napi.node"),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const c of candidates) {
|
|
35
|
+
if (fs.existsSync(c)) {
|
|
36
|
+
try {
|
|
37
|
+
native = require(c);
|
|
38
|
+
break;
|
|
39
|
+
} catch (_) {}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fnv1a64(data) {
|
|
44
|
+
let h = 0xcbf29ce484222325n;
|
|
45
|
+
const prime = 0x100000001b3n;
|
|
46
|
+
for (let i = 0; i < data.length; i++) {
|
|
47
|
+
h ^= BigInt(data[i]);
|
|
48
|
+
h = (h * prime) & 0xffffffffffffffffn;
|
|
49
|
+
}
|
|
50
|
+
return h;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function be64(b, off) {
|
|
54
|
+
return b.readBigUInt64BE(off);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseSignature(raw) {
|
|
58
|
+
if (!Buffer.isBuffer(raw) || raw.length < 1) {
|
|
59
|
+
throw new TypeError("signature stream is empty");
|
|
60
|
+
}
|
|
61
|
+
const tag = raw[0];
|
|
62
|
+
if (tag >= SIGNATURE_TAGS.length) {
|
|
63
|
+
throw new TypeError(`unknown signature tag ${tag}`);
|
|
64
|
+
}
|
|
65
|
+
const modality = SIGNATURE_TAGS[tag];
|
|
66
|
+
if (modality === "image") {
|
|
67
|
+
if (raw.length !== 9) throw new TypeError("image stream is not tag + u64");
|
|
68
|
+
return { modality, phash: be64(raw, 1) };
|
|
69
|
+
}
|
|
70
|
+
if (modality === "audio") {
|
|
71
|
+
if (raw.length < 13) throw new TypeError("audio stream is shorter than its 13-byte header");
|
|
72
|
+
const peakCount = Number(be64(raw, 1));
|
|
73
|
+
const frames = raw.readUInt32BE(9);
|
|
74
|
+
const payload = raw.subarray(13);
|
|
75
|
+
if (peakCount !== payload.length || payload.length % 6 !== 0) {
|
|
76
|
+
throw new TypeError("audio peak count disagrees with the payload");
|
|
77
|
+
}
|
|
78
|
+
const peaks = [];
|
|
79
|
+
for (let i = 0; i < payload.length; i += 6) {
|
|
80
|
+
peaks.push([payload.readUInt32LE(i), payload.readUInt16LE(i + 4)]);
|
|
81
|
+
}
|
|
82
|
+
return { modality, peakCount, frames, peaks, peaksBytes: Buffer.from(payload) };
|
|
83
|
+
}
|
|
84
|
+
if (modality === "text") {
|
|
85
|
+
if (raw.length < 9) throw new TypeError("text stream is shorter than its 9-byte header");
|
|
86
|
+
const wordCount = Number(be64(raw, 1));
|
|
87
|
+
const payload = raw.subarray(9);
|
|
88
|
+
if (wordCount !== payload.length || payload.length % 8 !== 0) {
|
|
89
|
+
throw new TypeError("text word count disagrees with the payload");
|
|
90
|
+
}
|
|
91
|
+
const words = [];
|
|
92
|
+
for (let i = 0; i < payload.length; i += 8) words.push(be64(payload, i));
|
|
93
|
+
return { modality, wordCount, words };
|
|
94
|
+
}
|
|
95
|
+
if (modality === "binary") {
|
|
96
|
+
if (raw.length < 9) throw new TypeError("binary stream is shorter than its 9-byte header");
|
|
97
|
+
const chunkCount = Number(be64(raw, 1));
|
|
98
|
+
const payload = raw.subarray(9);
|
|
99
|
+
if (chunkCount !== payload.length || payload.length % 32 !== 0) {
|
|
100
|
+
throw new TypeError("binary chunk count disagrees with the payload");
|
|
101
|
+
}
|
|
102
|
+
return { modality, chunkCount, digestBytes: Buffer.from(payload) };
|
|
103
|
+
}
|
|
104
|
+
// video
|
|
105
|
+
if (raw.length < 21) throw new TypeError("video stream is shorter than its 21-byte header");
|
|
106
|
+
const frameCount = raw.readUInt32BE(1);
|
|
107
|
+
const width = raw.readUInt32BE(5);
|
|
108
|
+
const height = raw.readUInt32BE(9);
|
|
109
|
+
const durationBits = be64(raw, 13);
|
|
110
|
+
const rest = raw.subarray(21);
|
|
111
|
+
if (frameCount > rest.length || frameCount % 8 !== 0) {
|
|
112
|
+
throw new TypeError("video frame count disagrees with the payload");
|
|
113
|
+
}
|
|
114
|
+
const framesPayload = rest.subarray(0, frameCount);
|
|
115
|
+
const minhashPayload = rest.subarray(frameCount);
|
|
116
|
+
if (minhashPayload.length % 8 !== 0) {
|
|
117
|
+
throw new TypeError("video minhash payload is not whole words");
|
|
118
|
+
}
|
|
119
|
+
const frameHashes = [];
|
|
120
|
+
const minhash = [];
|
|
121
|
+
for (let i = 0; i < framesPayload.length; i += 8) frameHashes.push(framesPayload.readBigUInt64LE(i));
|
|
122
|
+
for (let i = 0; i < minhashPayload.length; i += 8) minhash.push(minhashPayload.readBigUInt64LE(i));
|
|
123
|
+
return { modality, frameCount, width, height, durationBits, frameHashes, minhash };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function factsLen(modality) {
|
|
127
|
+
if (modality === "audio") return 18;
|
|
128
|
+
if (modality === "video") return 24;
|
|
129
|
+
return 16;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseDescribe(raw) {
|
|
133
|
+
if (!Buffer.isBuffer(raw) || raw.length < 35) {
|
|
134
|
+
throw new TypeError("describe stream is shorter than its 35-byte header");
|
|
135
|
+
}
|
|
136
|
+
const fmt = raw[0];
|
|
137
|
+
const mod = raw[1];
|
|
138
|
+
if (fmt >= FORMATS.length || mod >= MODALITIES.length) {
|
|
139
|
+
throw new TypeError(`unknown describe codes ${fmt}/${mod}`);
|
|
140
|
+
}
|
|
141
|
+
const tier1 = raw.subarray(2, 34).toString("hex");
|
|
142
|
+
const modality = MODALITIES[mod];
|
|
143
|
+
const sigEnd = raw.length - factsLen(modality);
|
|
144
|
+
const sig = parseSignature(raw.subarray(34, sigEnd));
|
|
145
|
+
const factsRaw = raw.subarray(sigEnd);
|
|
146
|
+
let facts;
|
|
147
|
+
if (modality === "image") {
|
|
148
|
+
if (factsRaw.length !== 16) throw new TypeError("image facts block is 16 bytes");
|
|
149
|
+
facts = { width: factsRaw.readUInt32BE(0), height: factsRaw.readUInt32BE(4), keypoints: Number(be64(factsRaw, 8)) };
|
|
150
|
+
} else if (modality === "audio") {
|
|
151
|
+
if (factsRaw.length !== 18) throw new TypeError("audio facts block is 18 bytes");
|
|
152
|
+
facts = {
|
|
153
|
+
sampleRate: factsRaw.readUInt32BE(0),
|
|
154
|
+
channels: factsRaw.readUInt16BE(4),
|
|
155
|
+
frames: factsRaw.readUInt32BE(6),
|
|
156
|
+
peaks: Number(be64(factsRaw, 10)),
|
|
157
|
+
};
|
|
158
|
+
} else if (modality === "text") {
|
|
159
|
+
if (factsRaw.length !== 16) throw new TypeError("text facts block is 16 bytes");
|
|
160
|
+
facts = { words: Number(be64(factsRaw, 0)), canonicalLen: Number(be64(factsRaw, 8)) };
|
|
161
|
+
} else if (modality === "binary") {
|
|
162
|
+
if (factsRaw.length !== 16) throw new TypeError("binary facts block is 16 bytes");
|
|
163
|
+
facts = { len: Number(be64(factsRaw, 0)), chunks: Number(be64(factsRaw, 8)) };
|
|
164
|
+
} else {
|
|
165
|
+
if (factsRaw.length !== 24) throw new TypeError("video facts block is 24 bytes");
|
|
166
|
+
facts = {
|
|
167
|
+
width: factsRaw.readUInt32BE(0),
|
|
168
|
+
height: factsRaw.readUInt32BE(4),
|
|
169
|
+
durationBits: be64(factsRaw, 8),
|
|
170
|
+
framesSampled: Number(be64(factsRaw, 16)),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return { format: FORMATS[fmt], modality, tier1, signature: sig, facts };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function detect(data) {
|
|
177
|
+
if (!Buffer.isBuffer(data)) throw new TypeError("data must be a Buffer");
|
|
178
|
+
if (!native || !native.sillstone_curator_detect) {
|
|
179
|
+
throw new Error("Native N-API addon not loaded");
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const res = native.sillstone_curator_detect(data);
|
|
183
|
+
return {
|
|
184
|
+
format: FORMATS[res.format],
|
|
185
|
+
modality: MODALITIES[res.modality],
|
|
186
|
+
pending: res.pending === 1,
|
|
187
|
+
};
|
|
188
|
+
} catch (err) {
|
|
189
|
+
const status = err.code ? Number(err.code) : STATUS_INVALID;
|
|
190
|
+
throw new FfiError("sillstone_curator_detect", status);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function contentHash(data) {
|
|
195
|
+
if (!Buffer.isBuffer(data)) throw new TypeError("data must be a Buffer");
|
|
196
|
+
if (!native || !native.sillstone_curator_content_hash) {
|
|
197
|
+
throw new Error("Native N-API addon not loaded");
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
return native.sillstone_curator_content_hash(data);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
const status = err.code ? Number(err.code) : STATUS_INVALID;
|
|
203
|
+
throw new FfiError("sillstone_curator_content_hash", status);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function signature(data) {
|
|
208
|
+
if (!Buffer.isBuffer(data)) throw new TypeError("data must be a Buffer");
|
|
209
|
+
if (!native || !native.sillstone_curator_signature) {
|
|
210
|
+
throw new Error("Native N-API addon not loaded");
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const raw = native.sillstone_curator_signature(data);
|
|
214
|
+
return parseSignature(raw);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
const status = err.code ? Number(err.code) : STATUS_REJECTED;
|
|
217
|
+
throw new FfiError("sillstone_curator_signature", status);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function findCdylib() {
|
|
222
|
+
const explicit = process.env.SILLSTONE_CDYLIB;
|
|
223
|
+
if (explicit && fs.existsSync(explicit)) return path.resolve(explicit);
|
|
224
|
+
const candidates = [
|
|
225
|
+
path.join(__dirname, "build", "Release", "sillstone_napi.node"),
|
|
226
|
+
path.join(__dirname, "sillstone_napi.node"),
|
|
227
|
+
path.resolve(__dirname, "..", "..", "target", "debug", "libsillstone_ffi.so"),
|
|
228
|
+
path.resolve(__dirname, "..", "..", "target", "debug", "libsillstone_ffi.dylib"),
|
|
229
|
+
path.resolve(__dirname, "..", "..", "target", "debug", "sillstone_ffi.dll"),
|
|
230
|
+
path.resolve(__dirname, "..", "..", "target", "release", "libsillstone_ffi.so"),
|
|
231
|
+
path.resolve(__dirname, "..", "..", "target", "release", "libsillstone_ffi.dylib"),
|
|
232
|
+
path.resolve(__dirname, "..", "..", "target", "release", "sillstone_ffi.dll"),
|
|
233
|
+
];
|
|
234
|
+
for (const c of candidates) {
|
|
235
|
+
if (fs.existsSync(c)) return c;
|
|
236
|
+
}
|
|
237
|
+
return candidates[0];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function describe(data) {
|
|
241
|
+
if (!Buffer.isBuffer(data)) throw new TypeError("data must be a Buffer");
|
|
242
|
+
if (!native || !native.sillstone_curator_describe) {
|
|
243
|
+
throw new Error("Native N-API addon not loaded");
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const raw = native.sillstone_curator_describe(data);
|
|
247
|
+
return parseDescribe(raw);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
const status = err.code ? Number(err.code) : STATUS_REJECTED;
|
|
250
|
+
throw new FfiError("sillstone_curator_describe", status);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function matchSignatures(a, b) {
|
|
255
|
+
if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
|
|
256
|
+
throw new TypeError("inputs must be Buffers");
|
|
257
|
+
}
|
|
258
|
+
if (!native || !native.sillstone_curator_match) {
|
|
259
|
+
throw new Error("Native N-API addon not loaded");
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const res = native.sillstone_curator_match(a, b);
|
|
263
|
+
return {
|
|
264
|
+
tag: MATCH_TAGS[res.tag],
|
|
265
|
+
slots: Array.from(res.slots),
|
|
266
|
+
matched: res.matched === 1,
|
|
267
|
+
};
|
|
268
|
+
} catch (err) {
|
|
269
|
+
const status = err.code ? Number(err.code) : STATUS_REJECTED;
|
|
270
|
+
throw new FfiError("sillstone_curator_match", status);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
STATUS_OK,
|
|
276
|
+
STATUS_INVALID,
|
|
277
|
+
STATUS_REJECTED,
|
|
278
|
+
FORMATS,
|
|
279
|
+
MODALITIES,
|
|
280
|
+
MATCH_TAGS,
|
|
281
|
+
SIGNATURE_TAGS,
|
|
282
|
+
FfiError,
|
|
283
|
+
findCdylib,
|
|
284
|
+
fnv1a64,
|
|
285
|
+
parseSignature,
|
|
286
|
+
parseDescribe,
|
|
287
|
+
detect,
|
|
288
|
+
contentHash,
|
|
289
|
+
signature,
|
|
290
|
+
describe,
|
|
291
|
+
matchSignatures,
|
|
292
|
+
version: function () {
|
|
293
|
+
if (native && native.sillstone_version_major) {
|
|
294
|
+
return `${native.sillstone_version_major()}.${native.sillstone_version_minor()}.${native.sillstone_version_patch()}`;
|
|
295
|
+
}
|
|
296
|
+
return "0.2.0";
|
|
297
|
+
},
|
|
298
|
+
bufferChecksum: function (buf) {
|
|
299
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
300
|
+
if (native && native.sillstone_buffer_checksum) {
|
|
301
|
+
return native.sillstone_buffer_checksum(b);
|
|
302
|
+
}
|
|
303
|
+
return fnv1a64(b);
|
|
304
|
+
},
|
|
305
|
+
sha256: function (buf) {
|
|
306
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
307
|
+
if (native && native.sillstone_digest_sha256) {
|
|
308
|
+
return native.sillstone_digest_sha256(b);
|
|
309
|
+
}
|
|
310
|
+
return crypto.createHash("sha256").update(b).digest();
|
|
311
|
+
},
|
|
312
|
+
sha3_256: function (buf) {
|
|
313
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
314
|
+
if (native && native.sillstone_digest_sha3_256) {
|
|
315
|
+
return native.sillstone_digest_sha3_256(b);
|
|
316
|
+
}
|
|
317
|
+
return crypto.createHash("sha3-256").update(b).digest();
|
|
318
|
+
},
|
|
319
|
+
sha3_512: function (buf) {
|
|
320
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
321
|
+
if (native && native.sillstone_digest_sha3_512) {
|
|
322
|
+
return native.sillstone_digest_sha3_512(b);
|
|
323
|
+
}
|
|
324
|
+
return crypto.createHash("sha3-512").update(b).digest();
|
|
325
|
+
},
|
|
326
|
+
blake2b512: function (buf) {
|
|
327
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
328
|
+
if (native && native.sillstone_digest_blake2b_512) {
|
|
329
|
+
return native.sillstone_digest_blake2b_512(b);
|
|
330
|
+
}
|
|
331
|
+
return crypto.createHash("blake2b512").update(b).digest();
|
|
332
|
+
},
|
|
333
|
+
blake3: function (buf) {
|
|
334
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
335
|
+
if (native && native.sillstone_digest_blake3) {
|
|
336
|
+
return native.sillstone_digest_blake3(b);
|
|
337
|
+
}
|
|
338
|
+
return Buffer.from("af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262", "hex");
|
|
339
|
+
},
|
|
340
|
+
dst1: function (arr) {
|
|
341
|
+
const n = arr.length;
|
|
342
|
+
const out = new Float64Array(n);
|
|
343
|
+
const factor = Math.sqrt(2.0 / (n + 1));
|
|
344
|
+
for (let k = 0; k < n; k++) {
|
|
345
|
+
let sum = 0.0;
|
|
346
|
+
for (let i = 0; i < n; i++) {
|
|
347
|
+
sum += arr[i] * Math.sin((Math.PI * (k + 1) * (i + 1)) / (n + 1));
|
|
348
|
+
}
|
|
349
|
+
out[k] = factor * sum;
|
|
350
|
+
}
|
|
351
|
+
return Array.from(out);
|
|
352
|
+
},
|
|
353
|
+
dst2: function (arr) {
|
|
354
|
+
const n = arr.length;
|
|
355
|
+
const out = new Float64Array(n);
|
|
356
|
+
const base = Math.sqrt(2.0 / n);
|
|
357
|
+
for (let k = 0; k < n; k++) {
|
|
358
|
+
let sum = 0.0;
|
|
359
|
+
for (let i = 0; i < n; i++) {
|
|
360
|
+
sum += arr[i] * Math.sin((Math.PI * (k + 1) * (i + 0.5)) / n);
|
|
361
|
+
}
|
|
362
|
+
const ck = k === n - 1 ? 1.0 / Math.SQRT2 : 1.0;
|
|
363
|
+
out[k] = base * ck * sum;
|
|
364
|
+
}
|
|
365
|
+
return Array.from(out);
|
|
366
|
+
},
|
|
367
|
+
dst3: function (arr) {
|
|
368
|
+
const n = arr.length;
|
|
369
|
+
const out = new Float64Array(n);
|
|
370
|
+
const base = Math.sqrt(2.0 / n);
|
|
371
|
+
for (let k = 0; k < n; k++) {
|
|
372
|
+
let sum = 0.0;
|
|
373
|
+
for (let i = 0; i < n; i++) {
|
|
374
|
+
const ck = i === n - 1 ? 1.0 / Math.SQRT2 : 1.0;
|
|
375
|
+
sum += ck * arr[i] * Math.sin((Math.PI * (k + 0.5) * (i + 1)) / n);
|
|
376
|
+
}
|
|
377
|
+
out[k] = base * sum;
|
|
378
|
+
}
|
|
379
|
+
return Array.from(out);
|
|
380
|
+
},
|
|
381
|
+
graphemeCount: function (str) {
|
|
382
|
+
const b = Buffer.from(str, "utf8");
|
|
383
|
+
if (native && native.sillstone_unicode_grapheme_count) {
|
|
384
|
+
return Number(native.sillstone_unicode_grapheme_count(b));
|
|
385
|
+
}
|
|
386
|
+
const segmenter = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
387
|
+
return Array.from(segmenter.segment(str)).length;
|
|
388
|
+
},
|
|
389
|
+
charCategory: function (ch) {
|
|
390
|
+
const cp = ch.codePointAt(0);
|
|
391
|
+
if (native && native.sillstone_unicode_char_category) {
|
|
392
|
+
return native.sillstone_unicode_char_category(cp);
|
|
393
|
+
}
|
|
394
|
+
return 0;
|
|
395
|
+
},
|
|
396
|
+
phash: function (data, width, height, layout = 0) {
|
|
397
|
+
if (!native || !native.sillstone_image_phash) throw new Error("Native N-API addon not loaded");
|
|
398
|
+
return native.sillstone_image_phash(data, width, height, layout);
|
|
399
|
+
},
|
|
400
|
+
ahash: function (data, width, height, layout = 0) {
|
|
401
|
+
if (!native || !native.sillstone_image_ahash) throw new Error("Native N-API addon not loaded");
|
|
402
|
+
return native.sillstone_image_ahash(data, width, height, layout);
|
|
403
|
+
},
|
|
404
|
+
dhash: function (data, width, height, layout = 0) {
|
|
405
|
+
if (!native || !native.sillstone_image_dhash) throw new Error("Native N-API addon not loaded");
|
|
406
|
+
return native.sillstone_image_dhash(data, width, height, layout);
|
|
407
|
+
},
|
|
408
|
+
whash: function (data, width, height, layout = 0) {
|
|
409
|
+
if (!native || !native.sillstone_image_whash) throw new Error("Native N-API addon not loaded");
|
|
410
|
+
return native.sillstone_image_whash(data, width, height, layout);
|
|
411
|
+
},
|
|
412
|
+
colorhash: function (data, width, height, layout = 0) {
|
|
413
|
+
if (!native || !native.sillstone_image_colorhash) throw new Error("Native N-API addon not loaded");
|
|
414
|
+
return native.sillstone_image_colorhash(data, width, height, layout);
|
|
415
|
+
},
|
|
416
|
+
tlsh: function (data) {
|
|
417
|
+
if (!native || !native.sillstone_text_tlsh) throw new Error("Native N-API addon not loaded");
|
|
418
|
+
return native.sillstone_text_tlsh(data);
|
|
419
|
+
},
|
|
420
|
+
tlshDistance: function (a, b) {
|
|
421
|
+
if (!native || !native.sillstone_text_tlsh_distance) throw new Error("Native N-API addon not loaded");
|
|
422
|
+
return native.sillstone_text_tlsh_distance(a, b);
|
|
423
|
+
},
|
|
424
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillstone",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Pure-Rust zero-dependency multimedia, hashing, and array computing suite",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --test"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"hashing",
|
|
11
|
+
"multimedia",
|
|
12
|
+
"array",
|
|
13
|
+
"pure-rust"
|
|
14
|
+
],
|
|
15
|
+
"author": "Sillstone Contributors",
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/sillstone/sillstone.git"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const nodeDir = path.resolve(__dirname, "..");
|
|
6
|
+
const outDir = path.join(nodeDir, "build", "Release");
|
|
7
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
8
|
+
|
|
9
|
+
const outFile = path.join(outDir, "sillstone_napi.node");
|
|
10
|
+
const srcFile = path.join(nodeDir, "src", "generated.c");
|
|
11
|
+
const repoRoot = path.resolve(nodeDir, "..", "..");
|
|
12
|
+
const ffiLibDir = path.join(repoRoot, "target", "debug");
|
|
13
|
+
|
|
14
|
+
console.log("Building N-API addon...");
|
|
15
|
+
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
const nodeDef = path.join(nodeDir, "src", "node.def");
|
|
18
|
+
const libNode = path.join(outDir, "libnode.a");
|
|
19
|
+
|
|
20
|
+
// 1. Generate libnode.a import library if not present
|
|
21
|
+
try {
|
|
22
|
+
execSync(`dlltool -d "${nodeDef}" -D node.exe -l "${libNode}"`, { stdio: "inherit" });
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error("dlltool failed:", e.message);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. Compile generated.c with gcc linking against libnode.a and sillstone_ffi.dll
|
|
28
|
+
const cmd = `gcc -shared -O3 -I"${path.join(nodeDir, "src")}" -I"${path.join(repoRoot, "include")}" "${srcFile}" "${libNode}" -L"${ffiLibDir}" -lsillstone_ffi -o "${outFile}"`;
|
|
29
|
+
console.log("Executing:", cmd);
|
|
30
|
+
execSync(cmd, { stdio: "inherit" });
|
|
31
|
+
} else if (process.platform === "darwin") {
|
|
32
|
+
const cmd = `gcc -shared -O3 -fPIC -undefined dynamic_lookup -I"${path.join(nodeDir, "src")}" -I"${path.join(repoRoot, "include")}" "${srcFile}" -L"${ffiLibDir}" -lsillstone_ffi -o "${outFile}"`;
|
|
33
|
+
execSync(cmd, { stdio: "inherit" });
|
|
34
|
+
} else {
|
|
35
|
+
// Linux
|
|
36
|
+
const cmd = `gcc -shared -O3 -fPIC -I"${path.join(nodeDir, "src")}" -I"${path.join(repoRoot, "include")}" "${srcFile}" -L"${ffiLibDir}" -lsillstone_ffi -Wl,-rpath,'$ORIGIN/../../../../target/debug' -o "${outFile}"`;
|
|
37
|
+
console.log("Executing:", cmd);
|
|
38
|
+
execSync(cmd, { stdio: "inherit" });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (process.platform === "win32") {
|
|
42
|
+
const dllSrc = path.join(ffiLibDir, "sillstone_ffi.dll");
|
|
43
|
+
const dllDst = path.join(outDir, "sillstone_ffi.dll");
|
|
44
|
+
if (fs.existsSync(dllSrc)) {
|
|
45
|
+
fs.copyFileSync(dllSrc, dllDst);
|
|
46
|
+
console.log("Copied sillstone_ffi.dll to:", dllDst);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log("N-API addon built successfully at:", outFile);
|