grandi 0.1.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/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +402 -0
- package/binding.gyp +200 -0
- package/lib/.clang-format +297 -0
- package/lib/grandi.cc +96 -0
- package/lib/grandi_find.cc +365 -0
- package/lib/grandi_find.h +41 -0
- package/lib/grandi_receive.cc +1155 -0
- package/lib/grandi_receive.h +63 -0
- package/lib/grandi_routing.cc +460 -0
- package/lib/grandi_routing.h +37 -0
- package/lib/grandi_send.cc +960 -0
- package/lib/grandi_send.h +45 -0
- package/lib/grandi_util.cc +323 -0
- package/lib/grandi_util.h +137 -0
- package/package.json +84 -0
- package/prebuilds/darwin-arm64/grandi.node +0 -0
- package/prebuilds/darwin-arm64/libndi.dylib +0 -0
- package/prebuilds/darwin-x64/grandi.node +0 -0
- package/prebuilds/darwin-x64/libndi.dylib +0 -0
- package/prebuilds/linux-arm/grandi.node +0 -0
- package/prebuilds/linux-arm/libndi.so.6 +0 -0
- package/prebuilds/linux-arm64/grandi.node +0 -0
- package/prebuilds/linux-arm64/libndi.so.6 +0 -0
- package/prebuilds/linux-ia32/grandi.node +0 -0
- package/prebuilds/linux-ia32/libndi.so.6 +0 -0
- package/prebuilds/linux-x64/grandi.node +0 -0
- package/prebuilds/linux-x64/libndi.so.6 +0 -0
- package/prebuilds/win32-ia32/Processing.NDI.Lib.x86.dll +0 -0
- package/prebuilds/win32-ia32/grandi.node +0 -0
- package/prebuilds/win32-x64/Processing.NDI.Lib.x64.dll +0 -0
- package/prebuilds/win32-x64/grandi.node +0 -0
- package/scripts/preinstall.mjs +429 -0
- package/src/index.ts +141 -0
- package/src/node-gyp-build.d.ts +5 -0
- package/src/types.ts +228 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
import { pipeline } from "node:stream/promises";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import zip from "cross-zip";
|
|
8
|
+
import { execa } from "execa";
|
|
9
|
+
import got from "got";
|
|
10
|
+
import shell from "shelljs";
|
|
11
|
+
import tmp from "tmp";
|
|
12
|
+
|
|
13
|
+
const isRepoCheckout = fs.existsSync(
|
|
14
|
+
path.join(path.dirname(fileURLToPath(import.meta.url)), "./../.git"),
|
|
15
|
+
);
|
|
16
|
+
const forceRebuild = process.env.NDI_FORCE?.toString() === "1";
|
|
17
|
+
|
|
18
|
+
if (!isRepoCheckout && !forceRebuild) {
|
|
19
|
+
console.log("[grandi] Skipping preinstall: running from packaged release.");
|
|
20
|
+
console.log("[grandi] Set NDI_FORCE=1 to run the downloader manually.");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Ensure tmp cleans up on process exit
|
|
25
|
+
tmp.setGracefulCleanup();
|
|
26
|
+
|
|
27
|
+
const platform = os.platform();
|
|
28
|
+
const arch = os.arch();
|
|
29
|
+
const supportsColor = process.stdout.isTTY && process.env.NO_COLOR !== "1";
|
|
30
|
+
const isInteractiveTerminal = process.stdout.isTTY && process.env.CI !== "true";
|
|
31
|
+
|
|
32
|
+
const color = (open) => (value) =>
|
|
33
|
+
supportsColor ? `${open}${value}\x1b[0m` : value;
|
|
34
|
+
|
|
35
|
+
const colors = {
|
|
36
|
+
cyan: color("\x1b[36m"),
|
|
37
|
+
magenta: color("\x1b[35m"),
|
|
38
|
+
green: color("\x1b[32m"),
|
|
39
|
+
yellow: color("\x1b[33m"),
|
|
40
|
+
red: color("\x1b[31m"),
|
|
41
|
+
gray: color("\x1b[90m"),
|
|
42
|
+
bold: color("\x1b[1m"),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const icons = {
|
|
46
|
+
heading: colors.bold("=="),
|
|
47
|
+
info: colors.cyan("[i]"),
|
|
48
|
+
step: colors.magenta("[>]"),
|
|
49
|
+
success: colors.green("[ok]"),
|
|
50
|
+
warn: colors.yellow("[!]"),
|
|
51
|
+
error: colors.red("[x]"),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const log = {
|
|
55
|
+
heading: (message) => console.log(`${icons.heading} ${colors.bold(message)}`),
|
|
56
|
+
info: (message) => console.log(`${icons.info} ${message}`),
|
|
57
|
+
step: (message) => console.log(`${icons.step} ${message}`),
|
|
58
|
+
success: (message) => console.log(`${icons.success} ${message}`),
|
|
59
|
+
warn: (message) => console.warn(`${icons.warn} ${message}`),
|
|
60
|
+
error: (message) => console.error(`${icons.error} ${message}`),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function formatBytes(bytes) {
|
|
64
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
65
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
66
|
+
let value = bytes;
|
|
67
|
+
let unitIndex = 0;
|
|
68
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
69
|
+
value /= 1024;
|
|
70
|
+
unitIndex++;
|
|
71
|
+
}
|
|
72
|
+
const precision = value >= 10 || unitIndex === 0 ? 0 : 1;
|
|
73
|
+
return `${value.toFixed(precision)} ${units[unitIndex]}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function deriveLabelFromUrl(url) {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = new URL(url);
|
|
79
|
+
const base = path.basename(parsed.pathname);
|
|
80
|
+
return base.length > 0 ? decodeURIComponent(base) : url;
|
|
81
|
+
} catch {
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function clearProgressLine() {
|
|
87
|
+
if (!isInteractiveTerminal) return;
|
|
88
|
+
readline.cursorTo(process.stdout, 0);
|
|
89
|
+
readline.clearLine(process.stdout, 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createDownloadTracker(label) {
|
|
93
|
+
let progressRendered = false;
|
|
94
|
+
const prefix = `${colors.cyan("[dl]")} ${label}`;
|
|
95
|
+
|
|
96
|
+
const render = (details) => {
|
|
97
|
+
if (!isInteractiveTerminal) return;
|
|
98
|
+
readline.cursorTo(process.stdout, 0);
|
|
99
|
+
readline.clearLine(process.stdout, 0);
|
|
100
|
+
process.stdout.write(`${prefix} ${details}`);
|
|
101
|
+
progressRendered = true;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
start() {
|
|
106
|
+
log.step(`Downloading ${label}`);
|
|
107
|
+
},
|
|
108
|
+
update(progress) {
|
|
109
|
+
if (!isInteractiveTerminal) return;
|
|
110
|
+
const percent =
|
|
111
|
+
typeof progress.percent === "number" &&
|
|
112
|
+
Number.isFinite(progress.percent)
|
|
113
|
+
? Math.min(progress.percent * 100, 100)
|
|
114
|
+
: null;
|
|
115
|
+
const transferred = formatBytes(progress.transferred);
|
|
116
|
+
const total =
|
|
117
|
+
typeof progress.total === "number" && progress.total > 0
|
|
118
|
+
? formatBytes(progress.total)
|
|
119
|
+
: null;
|
|
120
|
+
const parts = [];
|
|
121
|
+
if (percent !== null) {
|
|
122
|
+
parts.push(`${percent.toFixed(1)}%`);
|
|
123
|
+
}
|
|
124
|
+
parts.push(total ? `${transferred}/${total}` : transferred);
|
|
125
|
+
render(parts.join(" • "));
|
|
126
|
+
},
|
|
127
|
+
finish() {
|
|
128
|
+
if (progressRendered) {
|
|
129
|
+
clearProgressLine();
|
|
130
|
+
}
|
|
131
|
+
log.success(`Downloaded ${label}`);
|
|
132
|
+
},
|
|
133
|
+
fail(err) {
|
|
134
|
+
if (progressRendered) {
|
|
135
|
+
clearProgressLine();
|
|
136
|
+
}
|
|
137
|
+
const message =
|
|
138
|
+
err instanceof Error
|
|
139
|
+
? err.message
|
|
140
|
+
: err
|
|
141
|
+
? String(err)
|
|
142
|
+
: "Unknown error";
|
|
143
|
+
log.error(`Failed to download ${label}: ${message}`);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function downloadToFile(url, options = {}) {
|
|
149
|
+
let outFile;
|
|
150
|
+
let label;
|
|
151
|
+
|
|
152
|
+
if (typeof options === "string") {
|
|
153
|
+
outFile = options;
|
|
154
|
+
} else if (options && typeof options === "object") {
|
|
155
|
+
outFile = options.outFile;
|
|
156
|
+
label = options.label;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const filePath = outFile ?? tmp.tmpNameSync();
|
|
160
|
+
const tracker = createDownloadTracker(label ?? deriveLabelFromUrl(url));
|
|
161
|
+
|
|
162
|
+
tracker.start();
|
|
163
|
+
const downloadStream = got.stream(url, {
|
|
164
|
+
retry: { limit: 3 },
|
|
165
|
+
timeout: { request: 60_000 },
|
|
166
|
+
});
|
|
167
|
+
downloadStream.on("downloadProgress", (progress) => {
|
|
168
|
+
tracker.update(progress);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await pipeline(downloadStream, fs.createWriteStream(filePath));
|
|
173
|
+
tracker.finish();
|
|
174
|
+
return filePath;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
tracker.fail(error);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function ndiSubsetPresent() {
|
|
182
|
+
try {
|
|
183
|
+
// Basic sanity: headers exist and at least one lib directory has files
|
|
184
|
+
const header = path.join("ndi", "include", "Processing.NDI.Lib.h");
|
|
185
|
+
if (!fs.existsSync(header)) return false;
|
|
186
|
+
const libDirs = [
|
|
187
|
+
path.join("ndi", "lib", "win-x86"),
|
|
188
|
+
path.join("ndi", "lib", "win-x64"),
|
|
189
|
+
path.join("ndi", "lib", "macOS"),
|
|
190
|
+
path.join("ndi", "lib", "lnx-x86"),
|
|
191
|
+
path.join("ndi", "lib", "lnx-x64"),
|
|
192
|
+
path.join("ndi", "lib", "lnx-armv7l"),
|
|
193
|
+
path.join("ndi", "lib", "lnx-arm64"),
|
|
194
|
+
];
|
|
195
|
+
return libDirs.some(
|
|
196
|
+
(d) => fs.existsSync(d) && fs.readdirSync(d).length > 0,
|
|
197
|
+
);
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function main() {
|
|
204
|
+
log.heading("NDI SDK bootstrap");
|
|
205
|
+
log.info(`Detected platform: ${platform}/${arch}`);
|
|
206
|
+
|
|
207
|
+
const supportedPlatform =
|
|
208
|
+
platform === "darwin" ||
|
|
209
|
+
platform === "linux" ||
|
|
210
|
+
(platform === "win32" && ["ia32", "x64"].includes(arch));
|
|
211
|
+
|
|
212
|
+
if (!supportedPlatform) {
|
|
213
|
+
log.warn("Current platform is not supported; skipping NDI SDK setup.");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!forceRebuild && ndiSubsetPresent()) {
|
|
217
|
+
log.success(
|
|
218
|
+
"NDI SDK subset already present; skipping re-assembly (set NDI_FORCE=1 to force)",
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
log.step("Cleaning existing NDI SDK and build artifacts");
|
|
222
|
+
shell.rm("-rf", "ndi");
|
|
223
|
+
shell.rm("-rf", "build");
|
|
224
|
+
log.heading("Assembling NDI SDK distribution subset");
|
|
225
|
+
log.info("NDI SDK license: https://ndi.link/ndisdk_license");
|
|
226
|
+
|
|
227
|
+
if (platform === "win32") {
|
|
228
|
+
const innoUrl =
|
|
229
|
+
"https://constexpr.org/innoextract/files/innoextract-1.9-windows.zip";
|
|
230
|
+
const innoZip = await downloadToFile(innoUrl, {
|
|
231
|
+
label: "Innoextract utility (Windows)",
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
log.step("Extracting innoextract utility");
|
|
235
|
+
const innoDir = tmp.tmpNameSync();
|
|
236
|
+
zip.unzipSync(innoZip, innoDir);
|
|
237
|
+
|
|
238
|
+
const ndiUrl = "https://downloads.ndi.tv/SDK/NDI_SDK/NDI 6 SDK.exe";
|
|
239
|
+
const ndiExe = await downloadToFile(ndiUrl, {
|
|
240
|
+
label: "NDI SDK distribution (Windows)",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
log.step("Extracting NDI SDK distribution");
|
|
244
|
+
const extractDir = tmp.tmpNameSync();
|
|
245
|
+
shell.mkdir("-p", extractDir);
|
|
246
|
+
await execa(
|
|
247
|
+
path.join(innoDir, "innoextract.exe"),
|
|
248
|
+
["-s", "-d", extractDir, ndiExe],
|
|
249
|
+
{ stdio: "inherit" },
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
log.step("Assembling Windows NDI SDK subset");
|
|
253
|
+
shell.rm("-rf", "ndi");
|
|
254
|
+
shell.mkdir("-p", ["ndi/lib/win-x86", "ndi/lib/win-x64"]);
|
|
255
|
+
shell.cp("-rL", path.join(extractDir, "app", "Include/"), "ndi/");
|
|
256
|
+
shell.cp(
|
|
257
|
+
path.join(extractDir, "app/Lib/x86/Processing.NDI.Lib.x86.lib"),
|
|
258
|
+
"ndi/lib/win-x86/Processing.NDI.Lib.x86.lib",
|
|
259
|
+
);
|
|
260
|
+
shell.cp(
|
|
261
|
+
path.join(extractDir, "app/Bin/x86/Processing.NDI.Lib.x86.dll"),
|
|
262
|
+
"ndi/lib/win-x86/Processing.NDI.Lib.x86.dll",
|
|
263
|
+
);
|
|
264
|
+
shell.cp(
|
|
265
|
+
path.join(extractDir, "app/Lib/x64/Processing.NDI.Lib.x64.lib"),
|
|
266
|
+
"ndi/lib/win-x64/Processing.NDI.Lib.x64.lib",
|
|
267
|
+
);
|
|
268
|
+
shell.cp(
|
|
269
|
+
path.join(extractDir, "app/Bin/x64/Processing.NDI.Lib.x64.dll"),
|
|
270
|
+
"ndi/lib/win-x64/Processing.NDI.Lib.x64.dll",
|
|
271
|
+
);
|
|
272
|
+
shell.cp(
|
|
273
|
+
path.join(extractDir, "app/NDI SDK License Agreement.pdf"),
|
|
274
|
+
"ndi/lib/LICENSE.pdf",
|
|
275
|
+
);
|
|
276
|
+
shell.cp(
|
|
277
|
+
path.join(extractDir, "app/Bin/x64/Processing.NDI.Lib.Licenses.txt"),
|
|
278
|
+
"ndi/lib/libndi_licenses.txt",
|
|
279
|
+
);
|
|
280
|
+
log.step("Removing temporary files");
|
|
281
|
+
shell.rm("-f", innoZip);
|
|
282
|
+
shell.rm("-f", ndiExe);
|
|
283
|
+
shell.rm("-rf", innoDir);
|
|
284
|
+
shell.rm("-rf", extractDir);
|
|
285
|
+
} else if (platform === "darwin") {
|
|
286
|
+
const pkgUrl =
|
|
287
|
+
"https://downloads.ndi.tv/SDK/NDI_SDK_Mac/Install_NDI_SDK_v6_Apple.pkg";
|
|
288
|
+
const pkgFile = await downloadToFile(pkgUrl, {
|
|
289
|
+
label: "NDI SDK distribution (macOS)",
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
log.step("Extracting NDI SDK distribution");
|
|
293
|
+
const workDir = tmp.tmpNameSync();
|
|
294
|
+
shell.rm("-rf", workDir);
|
|
295
|
+
await execa("pkgutil", ["--expand", pkgFile, workDir], {
|
|
296
|
+
stdio: "inherit",
|
|
297
|
+
});
|
|
298
|
+
await execa(
|
|
299
|
+
"cpio",
|
|
300
|
+
["-idmu", "-F", path.join(workDir, "NDI_SDK_Component.pkg/Payload")],
|
|
301
|
+
{
|
|
302
|
+
cwd: workDir,
|
|
303
|
+
stdio: "ignore",
|
|
304
|
+
},
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
log.step("Assembling macOS NDI SDK subset");
|
|
308
|
+
shell.rm("-rf", "ndi");
|
|
309
|
+
shell.mkdir("-p", ["ndi/include", "ndi/lib/macOS"]);
|
|
310
|
+
shell.mv(
|
|
311
|
+
path.join(workDir, "NDI SDK for Apple/include/*.h"),
|
|
312
|
+
"ndi/include/",
|
|
313
|
+
);
|
|
314
|
+
shell.mv(
|
|
315
|
+
path.join(workDir, "NDI SDK for Apple/lib/macOS/*.dylib"),
|
|
316
|
+
"ndi/lib/macOS/",
|
|
317
|
+
);
|
|
318
|
+
shell.mv(
|
|
319
|
+
path.join(workDir, "NDI SDK for Apple/lib/macOS/libndi_licenses.txt"),
|
|
320
|
+
"ndi/lib/",
|
|
321
|
+
);
|
|
322
|
+
shell.mv(
|
|
323
|
+
path.join(workDir, "NDI SDK for Apple/NDI SDK License Agreement.pdf"),
|
|
324
|
+
"ndi/lib/LICENSE.pdf",
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
log.step("Removing temporary files");
|
|
328
|
+
shell.rm("-f", pkgFile);
|
|
329
|
+
shell.rm("-rf", workDir);
|
|
330
|
+
} else if (platform === "linux") {
|
|
331
|
+
const tarUrl =
|
|
332
|
+
"https://downloads.ndi.tv/SDK/NDI_SDK_Linux/Install_NDI_SDK_v6_Linux.tar.gz";
|
|
333
|
+
const tarFile = await downloadToFile(tarUrl, {
|
|
334
|
+
label: "NDI SDK distribution (Linux)",
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
log.step("Extracting NDI SDK distribution");
|
|
338
|
+
const workDir = tmp.tmpNameSync();
|
|
339
|
+
shell.mkdir("-p", workDir);
|
|
340
|
+
await execa("tar", ["-z", "-x", "-C", workDir, "-f", tarFile], {
|
|
341
|
+
stdio: "inherit",
|
|
342
|
+
});
|
|
343
|
+
await execa(
|
|
344
|
+
"sh",
|
|
345
|
+
["-c", `echo "y" | PAGER=cat sh Install_NDI_SDK_v6_Linux.sh`],
|
|
346
|
+
{
|
|
347
|
+
cwd: workDir,
|
|
348
|
+
stdio: "inherit",
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
log.step("Assembling Linux NDI SDK subset");
|
|
353
|
+
shell.rm("-rf", "ndi");
|
|
354
|
+
shell.mkdir("-p", [
|
|
355
|
+
"ndi/include",
|
|
356
|
+
"ndi/lib/lnx-x86",
|
|
357
|
+
"ndi/lib/lnx-x64",
|
|
358
|
+
"ndi/lib/lnx-armv7l",
|
|
359
|
+
"ndi/lib/lnx-arm64",
|
|
360
|
+
]);
|
|
361
|
+
shell.mv(
|
|
362
|
+
path.join(workDir, "NDI SDK for Linux/include/*.h"),
|
|
363
|
+
"ndi/include/",
|
|
364
|
+
);
|
|
365
|
+
shell.mv(
|
|
366
|
+
path.join(workDir, "NDI SDK for Linux/lib/i686-linux-gnu/*"),
|
|
367
|
+
"ndi/lib/lnx-x86/",
|
|
368
|
+
);
|
|
369
|
+
shell.mv(
|
|
370
|
+
path.join(workDir, "NDI SDK for Linux/lib/x86_64-linux-gnu/*"),
|
|
371
|
+
"ndi/lib/lnx-x64/",
|
|
372
|
+
);
|
|
373
|
+
shell.mv(
|
|
374
|
+
path.join(workDir, "NDI SDK for Linux/lib/arm-rpi4-linux-gnueabihf/*"),
|
|
375
|
+
"ndi/lib/lnx-armv7l/",
|
|
376
|
+
);
|
|
377
|
+
shell.mv(
|
|
378
|
+
path.join(
|
|
379
|
+
workDir,
|
|
380
|
+
"NDI SDK for Linux/lib/aarch64-rpi4-linux-gnueabi/*",
|
|
381
|
+
),
|
|
382
|
+
"ndi/lib/lnx-arm64/",
|
|
383
|
+
);
|
|
384
|
+
shell.mv(
|
|
385
|
+
path.join(workDir, "NDI SDK for Linux/NDI SDK License Agreement.txt"),
|
|
386
|
+
"ndi/lib/LICENSE",
|
|
387
|
+
);
|
|
388
|
+
shell.mv(
|
|
389
|
+
path.join(workDir, "NDI SDK for Linux/licenses/libndi_licenses.txt"),
|
|
390
|
+
"ndi/lib/",
|
|
391
|
+
);
|
|
392
|
+
const linuxArchDirs = [
|
|
393
|
+
"ndi/lib/lnx-x86",
|
|
394
|
+
"ndi/lib/lnx-x64",
|
|
395
|
+
"ndi/lib/lnx-armv7l",
|
|
396
|
+
"ndi/lib/lnx-arm64",
|
|
397
|
+
];
|
|
398
|
+
for (const d of linuxArchDirs) {
|
|
399
|
+
try {
|
|
400
|
+
const files = await fs.promises.readdir(d);
|
|
401
|
+
const real = files.find((f) => /^libndi\.so\.\d+\.\d+/.test(f));
|
|
402
|
+
if (real) {
|
|
403
|
+
const realPath = path.join(d, real);
|
|
404
|
+
const so6 = path.join(d, "libndi.so.6");
|
|
405
|
+
const so = path.join(d, "libndi.so");
|
|
406
|
+
if (fs.existsSync(so6)) {
|
|
407
|
+
await fs.promises.unlink(so6);
|
|
408
|
+
await fs.promises.copyFile(realPath, so6);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} catch (e) {
|
|
412
|
+
const message =
|
|
413
|
+
e instanceof Error ? e.message : e ? String(e) : "Unknown error";
|
|
414
|
+
log.error(`Failed to normalize Linux libraries in ${d}: ${message}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
log.step("Removing temporary files");
|
|
418
|
+
shell.rm("-f", tarFile);
|
|
419
|
+
shell.rm("-rf", workDir);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
main().catch((err) => {
|
|
425
|
+
const message =
|
|
426
|
+
err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
427
|
+
log.error(message);
|
|
428
|
+
process.exitCode = 1;
|
|
429
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import nodeGypBuild from "node-gyp-build";
|
|
3
|
+
|
|
4
|
+
import type * as T from "./types";
|
|
5
|
+
import {
|
|
6
|
+
AudioFormat,
|
|
7
|
+
Bandwidth,
|
|
8
|
+
ColorFormat,
|
|
9
|
+
FourCC,
|
|
10
|
+
FrameType,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
function isSupportedPlatform(): boolean {
|
|
14
|
+
return (
|
|
15
|
+
process.platform === "darwin" ||
|
|
16
|
+
process.platform === "linux" ||
|
|
17
|
+
(process.platform === "win32" && ["ia32", "x64"].includes(process.arch))
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const noopAddon: T.GrandiAddon = {
|
|
22
|
+
version() {
|
|
23
|
+
return "";
|
|
24
|
+
},
|
|
25
|
+
isSupportedCPU() {
|
|
26
|
+
return false;
|
|
27
|
+
},
|
|
28
|
+
initialize() {
|
|
29
|
+
return false;
|
|
30
|
+
},
|
|
31
|
+
destroy() {
|
|
32
|
+
return false;
|
|
33
|
+
},
|
|
34
|
+
send(_params) {
|
|
35
|
+
return Promise.reject(new Error("Unsupported platform or CPU"));
|
|
36
|
+
},
|
|
37
|
+
receive(_params) {
|
|
38
|
+
return Promise.reject(new Error("Unsupported platform or CPU"));
|
|
39
|
+
},
|
|
40
|
+
routing() {
|
|
41
|
+
return Promise.reject(new Error("Unsupported platform or CPU"));
|
|
42
|
+
},
|
|
43
|
+
find(_params) {
|
|
44
|
+
return Promise.reject(new Error("Unsupported platform or CPU"));
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const addon: T.GrandiAddon = isSupportedPlatform()
|
|
49
|
+
? (nodeGypBuild(path.join(__dirname, "..")) as T.GrandiAddon)
|
|
50
|
+
: noopAddon;
|
|
51
|
+
|
|
52
|
+
export function find(params: T.FindOptions = {}): Promise<T.Finder> {
|
|
53
|
+
return addon.find(params);
|
|
54
|
+
}
|
|
55
|
+
// Named runtime exports
|
|
56
|
+
export const version = addon.version;
|
|
57
|
+
export const isSupportedCPU = addon.isSupportedCPU;
|
|
58
|
+
export const initialize = addon.initialize;
|
|
59
|
+
export const destroy = addon.destroy;
|
|
60
|
+
export const send = addon.send;
|
|
61
|
+
export const receive = addon.receive;
|
|
62
|
+
export const routing = addon.routing;
|
|
63
|
+
|
|
64
|
+
// Re-export enums and types for convenient named imports
|
|
65
|
+
export { ColorFormat, AudioFormat, Bandwidth, FrameType, FourCC };
|
|
66
|
+
export type {
|
|
67
|
+
AudioFrame,
|
|
68
|
+
AudioReceiveOptions,
|
|
69
|
+
Finder,
|
|
70
|
+
FindOptions,
|
|
71
|
+
GrandiAddon,
|
|
72
|
+
PtpTimestamp,
|
|
73
|
+
ReceivedAudioFrame,
|
|
74
|
+
ReceivedMetadataFrame,
|
|
75
|
+
ReceivedVideoFrame,
|
|
76
|
+
ReceiveOptions,
|
|
77
|
+
ReceiverDataFrame,
|
|
78
|
+
ReceiverTallyState,
|
|
79
|
+
Routing,
|
|
80
|
+
Sender,
|
|
81
|
+
SenderTally,
|
|
82
|
+
Source,
|
|
83
|
+
SourceChangeEvent,
|
|
84
|
+
StatusChangeEvent,
|
|
85
|
+
Timecode,
|
|
86
|
+
VideoFrame,
|
|
87
|
+
} from "./types";
|
|
88
|
+
|
|
89
|
+
const grandi = {
|
|
90
|
+
version,
|
|
91
|
+
isSupportedCPU,
|
|
92
|
+
initialize,
|
|
93
|
+
destroy,
|
|
94
|
+
send,
|
|
95
|
+
receive,
|
|
96
|
+
routing,
|
|
97
|
+
find,
|
|
98
|
+
ColorFormat,
|
|
99
|
+
AudioFormat,
|
|
100
|
+
Bandwidth,
|
|
101
|
+
FrameType,
|
|
102
|
+
FourCC,
|
|
103
|
+
|
|
104
|
+
// Constants mapped to enum values
|
|
105
|
+
COLOR_FORMAT_BGRX_BGRA: ColorFormat.BGRX_BGRA,
|
|
106
|
+
COLOR_FORMAT_UYVY_BGRA: ColorFormat.UYVY_BGRA,
|
|
107
|
+
COLOR_FORMAT_RGBX_RGBA: ColorFormat.RGBX_RGBA,
|
|
108
|
+
COLOR_FORMAT_UYVY_RGBA: ColorFormat.UYVY_RGBA,
|
|
109
|
+
COLOR_FORMAT_FASTEST: ColorFormat.Fastest,
|
|
110
|
+
COLOR_FORMAT_BGRX_BGRA_FLIPPED: ColorFormat.BGRX_BGRA_FLIPPED,
|
|
111
|
+
|
|
112
|
+
BANDWIDTH_METADATA_ONLY: Bandwidth.MetadataOnly,
|
|
113
|
+
BANDWIDTH_AUDIO_ONLY: Bandwidth.AudioOnly,
|
|
114
|
+
BANDWIDTH_LOWEST: Bandwidth.Lowest,
|
|
115
|
+
BANDWIDTH_HIGHEST: Bandwidth.Highest,
|
|
116
|
+
|
|
117
|
+
FORMAT_TYPE_PROGRESSIVE: FrameType.Progressive,
|
|
118
|
+
FORMAT_TYPE_INTERLACED: FrameType.Interlaced,
|
|
119
|
+
FORMAT_TYPE_FIELD_0: FrameType.Field0,
|
|
120
|
+
FORMAT_TYPE_FIELD_1: FrameType.Field1,
|
|
121
|
+
|
|
122
|
+
AUDIO_FORMAT_FLOAT_32_SEPARATE: AudioFormat.Float32Separate,
|
|
123
|
+
AUDIO_FORMAT_FLOAT_32_INTERLEAVED: AudioFormat.Float32Interleaved,
|
|
124
|
+
AUDIO_FORMAT_INT_16_INTERLEAVED: AudioFormat.Int16Interleaved,
|
|
125
|
+
|
|
126
|
+
// FourCC helpers/constants
|
|
127
|
+
FOURCC_UYVY: FourCC.UYVY,
|
|
128
|
+
FOURCC_UYVA: FourCC.UYVA,
|
|
129
|
+
FOURCC_P216: FourCC.P216,
|
|
130
|
+
FOURCC_PA16: FourCC.PA16,
|
|
131
|
+
FOURCC_YV12: FourCC.YV12,
|
|
132
|
+
FOURCC_I420: FourCC.I420,
|
|
133
|
+
FOURCC_NV12: FourCC.NV12,
|
|
134
|
+
FOURCC_BGRA: FourCC.BGRA,
|
|
135
|
+
FOURCC_BGRX: FourCC.BGRX,
|
|
136
|
+
FOURCC_RGBA: FourCC.RGBA,
|
|
137
|
+
FOURCC_RGBX: FourCC.RGBX,
|
|
138
|
+
FOURCC_FLTp: FourCC.FLTp,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export default grandi;
|