@stagetimerio/grandiose 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/README.md +220 -0
- package/binding.gyp +92 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +101 -0
- package/index.d.ts +142 -0
- package/index.js +104 -0
- package/package.json +61 -0
- package/scripts/dist.js +45 -0
- package/scripts/ndi.js +159 -0
- package/src/grandiose.cc +96 -0
- package/src/grandiose_find.cc +344 -0
- package/src/grandiose_find.h +42 -0
- package/src/grandiose_receive.cc +923 -0
- package/src/grandiose_receive.h +60 -0
- package/src/grandiose_routing.cc +434 -0
- package/src/grandiose_routing.h +38 -0
- package/src/grandiose_send.cc +792 -0
- package/src/grandiose_send.h +47 -0
- package/src/grandiose_util.cc +282 -0
- package/src/grandiose_util.h +120 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Copyright 2018 Streampunk Media Ltd.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require("path")
|
|
17
|
+
|
|
18
|
+
const addon = require(path.join(__dirname, "grandiose.node"));
|
|
19
|
+
|
|
20
|
+
const COLOR_FORMAT_BGRX_BGRA = 0; // No alpha channel: BGRX, Alpha channel: BGRA
|
|
21
|
+
const COLOR_FORMAT_UYVY_BGRA = 1; // No alpha channel: UYVY, Alpha channel: BGRA
|
|
22
|
+
const COLOR_FORMAT_RGBX_RGBA = 2; // No alpha channel: RGBX, Alpha channel: RGBA
|
|
23
|
+
const COLOR_FORMAT_UYVY_RGBA = 3; // No alpha channel: UYVY, Alpha channel: RGBA
|
|
24
|
+
|
|
25
|
+
const NDI_LIB_FOURCC = (ch0, ch1, ch2, ch3) =>
|
|
26
|
+
(ch0.charCodeAt(0) | (ch1.charCodeAt(0) << 8) | (ch2.charCodeAt(0) << 16) | (ch3.charCodeAt(0) << 24))
|
|
27
|
+
|
|
28
|
+
const FOURCC_UYVY = NDI_LIB_FOURCC("U", "Y", "V", "Y")
|
|
29
|
+
const FOURCC_UYVA = NDI_LIB_FOURCC("U", "Y", "V", "A")
|
|
30
|
+
const FOURCC_P216 = NDI_LIB_FOURCC("P", "2", "1", "6")
|
|
31
|
+
const FOURCC_PA16 = NDI_LIB_FOURCC("P", "A", "1", "6")
|
|
32
|
+
const FOURCC_YV12 = NDI_LIB_FOURCC("Y", "V", "1", "2")
|
|
33
|
+
const FOURCC_I420 = NDI_LIB_FOURCC("I", "4", "2", "0")
|
|
34
|
+
const FOURCC_NV12 = NDI_LIB_FOURCC("N", "V", "1", "2")
|
|
35
|
+
const FOURCC_BGRA = NDI_LIB_FOURCC("B", "G", "R", "A")
|
|
36
|
+
const FOURCC_BGRX = NDI_LIB_FOURCC("B", "G", "R", "X")
|
|
37
|
+
const FOURCC_RGBA = NDI_LIB_FOURCC("R", "G", "B", "A")
|
|
38
|
+
const FOURCC_RGBX = NDI_LIB_FOURCC("R", "G", "B", "X")
|
|
39
|
+
const FOURCC_FLTp = NDI_LIB_FOURCC("F", "L", "T", "p")
|
|
40
|
+
|
|
41
|
+
// On Windows there are some APIs that require bottom to top images in RGBA format. Specifying
|
|
42
|
+
// this format will return images in this format. The image data pointer will still point to the
|
|
43
|
+
// "top" of the image, althought he stride will be negative. You can get the "bottom" line of the image
|
|
44
|
+
// using : video_data.p_data + (video_data.yres - 1)*video_data.line_stride_in_bytes
|
|
45
|
+
const COLOR_FORMAT_BGRX_BGRA_FLIPPED = 200;
|
|
46
|
+
|
|
47
|
+
const COLOR_FORMAT_FASTEST = 100;
|
|
48
|
+
|
|
49
|
+
const BANDWIDTH_METADATA_ONLY = -10; // Receive metadata.
|
|
50
|
+
const BANDWIDTH_AUDIO_ONLY = 10; // Receive metadata, audio.
|
|
51
|
+
const BANDWIDTH_LOWEST = 0; // Receive metadata, audio, video at a lower bandwidth and resolution.
|
|
52
|
+
const BANDWIDTH_HIGHEST = 100; // Receive metadata, audio, video at full resolution.
|
|
53
|
+
|
|
54
|
+
const FORMAT_TYPE_PROGRESSIVE = 1;
|
|
55
|
+
const FORMAT_TYPE_INTERLACED = 0;
|
|
56
|
+
const FORMAT_TYPE_FIELD_0 = 2;
|
|
57
|
+
const FORMAT_TYPE_FIELD_1 = 3;
|
|
58
|
+
|
|
59
|
+
// Default NDI audio format
|
|
60
|
+
// Channels stored one after the other in each block - 32-bit floating point values
|
|
61
|
+
const AUDIO_FORMAT_FLOAT_32_SEPARATE = 0;
|
|
62
|
+
// Alternative NDI audio foramt
|
|
63
|
+
// Channels stored as channel-interleaved 32-bit floating point values
|
|
64
|
+
const AUDIO_FORMAT_FLOAT_32_INTERLEAVED = 1;
|
|
65
|
+
// Alternative NDI audio format
|
|
66
|
+
// Channels stored as channel-interleaved 16-bit integer values
|
|
67
|
+
const AUDIO_FORMAT_INT_16_INTERLEAVED = 2;
|
|
68
|
+
|
|
69
|
+
let find = function (...args) {
|
|
70
|
+
if (args.length === 0) return addon.find();
|
|
71
|
+
if (Array.isArray(args[0].groups)) {
|
|
72
|
+
args[0].groups = args[0].groups.reduce((x, y) => x + ',' + y);
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(args[0].extraIPs)) {
|
|
75
|
+
args[0].extraIPs = args[0].extraIPs.reduce((x, y) => x + ',' + y);
|
|
76
|
+
}
|
|
77
|
+
return addon.find.apply(null, args);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
version: addon.version,
|
|
82
|
+
isSupportedCPU: addon.isSupportedCPU,
|
|
83
|
+
initialize: addon.initialize,
|
|
84
|
+
destroy: addon.destroy,
|
|
85
|
+
find: find,
|
|
86
|
+
receive: addon.receive,
|
|
87
|
+
send: addon.send,
|
|
88
|
+
routing: addon.routing,
|
|
89
|
+
COLOR_FORMAT_BGRX_BGRA, COLOR_FORMAT_UYVY_BGRA,
|
|
90
|
+
COLOR_FORMAT_RGBX_RGBA, COLOR_FORMAT_UYVY_RGBA,
|
|
91
|
+
COLOR_FORMAT_BGRX_BGRA_FLIPPED, COLOR_FORMAT_FASTEST,
|
|
92
|
+
FOURCC_UYVY, FOURCC_UYVA, FOURCC_P216, FOURCC_PA16, FOURCC_YV12,
|
|
93
|
+
FOURCC_I420, FOURCC_NV12, FOURCC_BGRA, FOURCC_BGRX, FOURCC_RGBA, FOURCC_RGBX,
|
|
94
|
+
FOURCC_FLTp,
|
|
95
|
+
BANDWIDTH_METADATA_ONLY, BANDWIDTH_AUDIO_ONLY,
|
|
96
|
+
BANDWIDTH_LOWEST, BANDWIDTH_HIGHEST,
|
|
97
|
+
FORMAT_TYPE_PROGRESSIVE, FORMAT_TYPE_INTERLACED,
|
|
98
|
+
FORMAT_TYPE_FIELD_0, FORMAT_TYPE_FIELD_1,
|
|
99
|
+
AUDIO_FORMAT_FLOAT_32_SEPARATE, AUDIO_FORMAT_FLOAT_32_INTERLEAVED,
|
|
100
|
+
AUDIO_FORMAT_INT_16_INTERLEAVED
|
|
101
|
+
};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export interface AudioFrame {
|
|
2
|
+
type: 'audio'
|
|
3
|
+
audioFormat: AudioFormat
|
|
4
|
+
referenceLevel: number
|
|
5
|
+
sampleRate: number // Hz
|
|
6
|
+
channels: number
|
|
7
|
+
samples: number
|
|
8
|
+
channelStrideInBytes: number
|
|
9
|
+
timestamp: [number, number] // PTP timestamp
|
|
10
|
+
timecode: [number, number] // timecode as PTP value
|
|
11
|
+
data: Buffer
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface VideoFrame {
|
|
15
|
+
type: 'video'
|
|
16
|
+
xres: number
|
|
17
|
+
yres: number
|
|
18
|
+
frameRateN: number
|
|
19
|
+
frameRateD: number
|
|
20
|
+
fourCC: FourCC
|
|
21
|
+
pictureAspectRatio: number
|
|
22
|
+
timestamp: [ number, number ] // PTP timestamp
|
|
23
|
+
frameFormatType: FrameType
|
|
24
|
+
timecode: [ number, number ] // Measured in nanoseconds
|
|
25
|
+
lineStrideBytes: number
|
|
26
|
+
data: Buffer
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Receiver {
|
|
30
|
+
embedded: unknown
|
|
31
|
+
video: (timeout?: number) => Promise<VideoFrame>
|
|
32
|
+
audio: (params: {
|
|
33
|
+
audioFormat: AudioFormat
|
|
34
|
+
referenceLevel: number
|
|
35
|
+
}, timeout?: number) => Promise<AudioFrame>
|
|
36
|
+
metadata: any
|
|
37
|
+
data: any
|
|
38
|
+
source: Source
|
|
39
|
+
colorFormat: ColorFormat
|
|
40
|
+
bandwidth: Bandwidth
|
|
41
|
+
allowVideoFields: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Sender {
|
|
45
|
+
embedded: unknown
|
|
46
|
+
destroy: () => Promise<void>
|
|
47
|
+
video: (frame: VideoFrame) => Promise<void>
|
|
48
|
+
audio: (frame: AudioFrame) => Promise<void>
|
|
49
|
+
connections: () => number
|
|
50
|
+
tally: () => { onProgram: boolean, onPreview: boolean }
|
|
51
|
+
sourcename: () => string
|
|
52
|
+
name: string
|
|
53
|
+
groups?: string | string[]
|
|
54
|
+
clockVideo: boolean
|
|
55
|
+
clockAudio: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface Routing {
|
|
59
|
+
name: string
|
|
60
|
+
groups?: string
|
|
61
|
+
embedded: unknown
|
|
62
|
+
destroy: () => Promise<void>
|
|
63
|
+
change: (Source) => number
|
|
64
|
+
clear: () => boolean
|
|
65
|
+
connections: () => number
|
|
66
|
+
sourcename: () => string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface Source {
|
|
70
|
+
name: string
|
|
71
|
+
urlAddress?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const enum FrameType {
|
|
75
|
+
Progressive = 1,
|
|
76
|
+
Interlaced = 0,
|
|
77
|
+
Field0 = 2,
|
|
78
|
+
Field1 = 3,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const enum ColorFormat {
|
|
82
|
+
BGRX_BGRA = 0,
|
|
83
|
+
UYVY_BGRA = 1,
|
|
84
|
+
RGBX_RGBA = 2,
|
|
85
|
+
UYVY_RGBA = 3,
|
|
86
|
+
Fastest = 100,
|
|
87
|
+
Best = 101
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const enum FourCC {
|
|
91
|
+
UYVY = 1498831189,
|
|
92
|
+
UYVA = 1096178005,
|
|
93
|
+
P216 = 909193808,
|
|
94
|
+
PA16 = 909197648,
|
|
95
|
+
YV12 = 842094169,
|
|
96
|
+
I420 = 808596553,
|
|
97
|
+
NV12 = 842094158,
|
|
98
|
+
BGRA = 1095911234,
|
|
99
|
+
BGRX = 1481787202,
|
|
100
|
+
RGBA = 1094862674,
|
|
101
|
+
RGBX = 1480738642
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const enum AudioFormat {
|
|
105
|
+
Float32Separate = 0,
|
|
106
|
+
Float32Interleaved = 1,
|
|
107
|
+
Int16Interleaved = 2
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const enum Bandwidth {
|
|
111
|
+
MetadataOnly = -10,
|
|
112
|
+
AudioOnly = 10,
|
|
113
|
+
Lowest = 0,
|
|
114
|
+
Highest = 100
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function find(params: {
|
|
118
|
+
showLocalSources?: boolean
|
|
119
|
+
groups?: string | string[]
|
|
120
|
+
extraIPs?: string | string[]
|
|
121
|
+
}): Promise<Source[]>
|
|
122
|
+
|
|
123
|
+
export function receive(params: {
|
|
124
|
+
source: Source
|
|
125
|
+
colorFormat?: ColorFormat
|
|
126
|
+
bandwidth?: Bandwidth
|
|
127
|
+
allowVideoFields?: boolean
|
|
128
|
+
name?: string
|
|
129
|
+
}): Receiver
|
|
130
|
+
|
|
131
|
+
export function send(params: {
|
|
132
|
+
name: string
|
|
133
|
+
groups?: string | string[]
|
|
134
|
+
clockVideo?: boolean
|
|
135
|
+
clockAudio?: boolean
|
|
136
|
+
}): Promise<Sender>
|
|
137
|
+
|
|
138
|
+
export function routing(params: {
|
|
139
|
+
name: string
|
|
140
|
+
groups?: string | string[]
|
|
141
|
+
}): Routing
|
|
142
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* Copyright 2018 Streampunk Media Ltd.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require("path")
|
|
17
|
+
|
|
18
|
+
const addon = require('bindings')({
|
|
19
|
+
bindings: "grandiose",
|
|
20
|
+
module_root: path.resolve(__dirname)
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const COLOR_FORMAT_BGRX_BGRA = 0; // No alpha channel: BGRX, Alpha channel: BGRA
|
|
24
|
+
const COLOR_FORMAT_UYVY_BGRA = 1; // No alpha channel: UYVY, Alpha channel: BGRA
|
|
25
|
+
const COLOR_FORMAT_RGBX_RGBA = 2; // No alpha channel: RGBX, Alpha channel: RGBA
|
|
26
|
+
const COLOR_FORMAT_UYVY_RGBA = 3; // No alpha channel: UYVY, Alpha channel: RGBA
|
|
27
|
+
|
|
28
|
+
const NDI_LIB_FOURCC = (ch0, ch1, ch2, ch3) =>
|
|
29
|
+
(ch0.charCodeAt(0) | (ch1.charCodeAt(0) << 8) | (ch2.charCodeAt(0) << 16) | (ch3.charCodeAt(0) << 24))
|
|
30
|
+
|
|
31
|
+
const FOURCC_UYVY = NDI_LIB_FOURCC("U", "Y", "V", "Y")
|
|
32
|
+
const FOURCC_UYVA = NDI_LIB_FOURCC("U", "Y", "V", "A")
|
|
33
|
+
const FOURCC_P216 = NDI_LIB_FOURCC("P", "2", "1", "6")
|
|
34
|
+
const FOURCC_PA16 = NDI_LIB_FOURCC("P", "A", "1", "6")
|
|
35
|
+
const FOURCC_YV12 = NDI_LIB_FOURCC("Y", "V", "1", "2")
|
|
36
|
+
const FOURCC_I420 = NDI_LIB_FOURCC("I", "4", "2", "0")
|
|
37
|
+
const FOURCC_NV12 = NDI_LIB_FOURCC("N", "V", "1", "2")
|
|
38
|
+
const FOURCC_BGRA = NDI_LIB_FOURCC("B", "G", "R", "A")
|
|
39
|
+
const FOURCC_BGRX = NDI_LIB_FOURCC("B", "G", "R", "X")
|
|
40
|
+
const FOURCC_RGBA = NDI_LIB_FOURCC("R", "G", "B", "A")
|
|
41
|
+
const FOURCC_RGBX = NDI_LIB_FOURCC("R", "G", "B", "X")
|
|
42
|
+
const FOURCC_FLTp = NDI_LIB_FOURCC("F", "L", "T", "p")
|
|
43
|
+
|
|
44
|
+
// On Windows there are some APIs that require bottom to top images in RGBA format. Specifying
|
|
45
|
+
// this format will return images in this format. The image data pointer will still point to the
|
|
46
|
+
// "top" of the image, althought he stride will be negative. You can get the "bottom" line of the image
|
|
47
|
+
// using : video_data.p_data + (video_data.yres - 1)*video_data.line_stride_in_bytes
|
|
48
|
+
const COLOR_FORMAT_BGRX_BGRA_FLIPPED = 200;
|
|
49
|
+
|
|
50
|
+
const COLOR_FORMAT_FASTEST = 100;
|
|
51
|
+
|
|
52
|
+
const BANDWIDTH_METADATA_ONLY = -10; // Receive metadata.
|
|
53
|
+
const BANDWIDTH_AUDIO_ONLY = 10; // Receive metadata, audio.
|
|
54
|
+
const BANDWIDTH_LOWEST = 0; // Receive metadata, audio, video at a lower bandwidth and resolution.
|
|
55
|
+
const BANDWIDTH_HIGHEST = 100; // Receive metadata, audio, video at full resolution.
|
|
56
|
+
|
|
57
|
+
const FORMAT_TYPE_PROGRESSIVE = 1;
|
|
58
|
+
const FORMAT_TYPE_INTERLACED = 0;
|
|
59
|
+
const FORMAT_TYPE_FIELD_0 = 2;
|
|
60
|
+
const FORMAT_TYPE_FIELD_1 = 3;
|
|
61
|
+
|
|
62
|
+
// Default NDI audio format
|
|
63
|
+
// Channels stored one after the other in each block - 32-bit floating point values
|
|
64
|
+
const AUDIO_FORMAT_FLOAT_32_SEPARATE = 0;
|
|
65
|
+
// Alternative NDI audio foramt
|
|
66
|
+
// Channels stored as channel-interleaved 32-bit floating point values
|
|
67
|
+
const AUDIO_FORMAT_FLOAT_32_INTERLEAVED = 1;
|
|
68
|
+
// Alternative NDI audio format
|
|
69
|
+
// Channels stored as channel-interleaved 16-bit integer values
|
|
70
|
+
const AUDIO_FORMAT_INT_16_INTERLEAVED = 2;
|
|
71
|
+
|
|
72
|
+
let find = function (...args) {
|
|
73
|
+
if (args.length === 0) return addon.find();
|
|
74
|
+
if (Array.isArray(args[0].groups)) {
|
|
75
|
+
args[0].groups = args[0].groups.reduce((x, y) => x + ',' + y);
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(args[0].extraIPs)) {
|
|
78
|
+
args[0].extraIPs = args[0].extraIPs.reduce((x, y) => x + ',' + y);
|
|
79
|
+
}
|
|
80
|
+
return addon.find.apply(null, args);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
version: addon.version,
|
|
85
|
+
isSupportedCPU: addon.isSupportedCPU,
|
|
86
|
+
initialize: addon.initialize,
|
|
87
|
+
destroy: addon.destroy,
|
|
88
|
+
find: find,
|
|
89
|
+
receive: addon.receive,
|
|
90
|
+
send: addon.send,
|
|
91
|
+
routing: addon.routing,
|
|
92
|
+
COLOR_FORMAT_BGRX_BGRA, COLOR_FORMAT_UYVY_BGRA,
|
|
93
|
+
COLOR_FORMAT_RGBX_RGBA, COLOR_FORMAT_UYVY_RGBA,
|
|
94
|
+
COLOR_FORMAT_BGRX_BGRA_FLIPPED, COLOR_FORMAT_FASTEST,
|
|
95
|
+
FOURCC_UYVY, FOURCC_UYVA, FOURCC_P216, FOURCC_PA16, FOURCC_YV12,
|
|
96
|
+
FOURCC_I420, FOURCC_NV12, FOURCC_BGRA, FOURCC_BGRX, FOURCC_RGBA, FOURCC_RGBX,
|
|
97
|
+
FOURCC_FLTp,
|
|
98
|
+
BANDWIDTH_METADATA_ONLY, BANDWIDTH_AUDIO_ONLY,
|
|
99
|
+
BANDWIDTH_LOWEST, BANDWIDTH_HIGHEST,
|
|
100
|
+
FORMAT_TYPE_PROGRESSIVE, FORMAT_TYPE_INTERLACED,
|
|
101
|
+
FORMAT_TYPE_FIELD_0, FORMAT_TYPE_FIELD_1,
|
|
102
|
+
AUDIO_FORMAT_FLOAT_32_SEPARATE, AUDIO_FORMAT_FLOAT_32_INTERLEAVED,
|
|
103
|
+
AUDIO_FORMAT_INT_16_INTERLEAVED
|
|
104
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stagetimerio/grandiose",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js N-API native addon wrapping the NDI SDK.",
|
|
5
|
+
"homepage": "https://github.com/stagetimerio/grandiose#readme",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"NDI",
|
|
8
|
+
"video",
|
|
9
|
+
"streaming",
|
|
10
|
+
"napi",
|
|
11
|
+
"native"
|
|
12
|
+
],
|
|
13
|
+
"author": "Lukas Hermann <lukas@stagetimer.io>",
|
|
14
|
+
"contributors": [
|
|
15
|
+
"Dr. Ralf S. Engelschall <rse@engelschall.com> (NDI SDK 6, audio send, routing)",
|
|
16
|
+
"Ian Shade (NDI sender, TypeScript types)",
|
|
17
|
+
"Dan Jenkins (macOS fixes)",
|
|
18
|
+
"Streampunk Media (original implementation)"
|
|
19
|
+
],
|
|
20
|
+
"license": "Apache-2.0",
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"gypfile": true,
|
|
24
|
+
"files": [
|
|
25
|
+
"src/",
|
|
26
|
+
"scripts/",
|
|
27
|
+
"index.js",
|
|
28
|
+
"index.d.ts",
|
|
29
|
+
"binding.gyp"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/stagetimerio/grandiose.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/stagetimerio/grandiose/issues"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"registry": "https://registry.npmjs.org"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"got": "14.6.6",
|
|
43
|
+
"mkdirp": "3.0.1",
|
|
44
|
+
"tmp": "0.2.5",
|
|
45
|
+
"execa": "9.6.1",
|
|
46
|
+
"cross-zip": "4.0.1",
|
|
47
|
+
"shelljs": "0.10.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"bindings": "1.5.0",
|
|
51
|
+
"shx": "0.4.0",
|
|
52
|
+
"vitest": "^4.1.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"install": "node scripts/ndi.js && node-gyp rebuild && node scripts/dist.js",
|
|
56
|
+
"build": "node-gyp rebuild && node scripts/dist.js",
|
|
57
|
+
"dist": "node scripts/dist.js",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"clean": "shx rm -rf ndi build dist"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/scripts/dist.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const fs = require("fs")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
|
|
4
|
+
const root = path.resolve(__dirname, "..")
|
|
5
|
+
const buildDir = path.join(root, "build", "Release")
|
|
6
|
+
const distDir = path.join(root, "dist")
|
|
7
|
+
|
|
8
|
+
/* clean and create dist/ */
|
|
9
|
+
fs.rmSync(distDir, { recursive: true, force: true })
|
|
10
|
+
fs.mkdirSync(distDir)
|
|
11
|
+
|
|
12
|
+
/* copy grandiose.node */
|
|
13
|
+
const nodePath = path.join(buildDir, "grandiose.node")
|
|
14
|
+
if (!fs.existsSync(nodePath))
|
|
15
|
+
throw new Error("build/Release/grandiose.node not found — run node-gyp rebuild first")
|
|
16
|
+
fs.copyFileSync(nodePath, path.join(distDir, "grandiose.node"))
|
|
17
|
+
console.log(" copied grandiose.node")
|
|
18
|
+
|
|
19
|
+
/* copy NDI shared libraries (.dylib, .so, .so.*, .dll) */
|
|
20
|
+
const libPattern = /\.(dylib|so(\.\d+(\.\d+)*)?|dll)$/
|
|
21
|
+
const libs = fs.readdirSync(buildDir).filter(f => libPattern.test(f))
|
|
22
|
+
if (libs.length === 0)
|
|
23
|
+
throw new Error("No NDI shared library found in build/Release/")
|
|
24
|
+
for (const lib of libs) {
|
|
25
|
+
fs.copyFileSync(path.join(buildDir, lib), path.join(distDir, lib))
|
|
26
|
+
console.log(` copied ${lib}`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* generate dist/index.js from root index.js with rewritten require */
|
|
30
|
+
let indexSrc = fs.readFileSync(path.join(root, "index.js"), "utf8")
|
|
31
|
+
const needle = /const addon = require\('bindings'\)\(\{[\s\S]*?\}\);?/
|
|
32
|
+
if (!needle.test(indexSrc))
|
|
33
|
+
throw new Error("Could not find bindings require in index.js — has the format changed?")
|
|
34
|
+
indexSrc = indexSrc.replace(
|
|
35
|
+
needle,
|
|
36
|
+
'const addon = require(path.join(__dirname, "grandiose.node"));'
|
|
37
|
+
)
|
|
38
|
+
fs.writeFileSync(path.join(distDir, "index.js"), indexSrc)
|
|
39
|
+
console.log(" generated index.js")
|
|
40
|
+
|
|
41
|
+
/* copy index.d.ts */
|
|
42
|
+
fs.copyFileSync(path.join(root, "index.d.ts"), path.join(distDir, "index.d.ts"))
|
|
43
|
+
console.log(" copied index.d.ts")
|
|
44
|
+
|
|
45
|
+
console.log("dist/ ready")
|
package/scripts/ndi.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2021 Dr. Ralf S. Engelschall
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/* external requirements */
|
|
18
|
+
const fs = require("fs")
|
|
19
|
+
const path = require("path")
|
|
20
|
+
const os = require("os")
|
|
21
|
+
const shell = require("shelljs")
|
|
22
|
+
const { execa } = require("execa")
|
|
23
|
+
const zip = require("cross-zip")
|
|
24
|
+
const { got } = require("got")
|
|
25
|
+
const mkdirp = require("mkdirp")
|
|
26
|
+
const tmp = require("tmp")
|
|
27
|
+
|
|
28
|
+
/* establish asynchronous environment */
|
|
29
|
+
;(async () => {
|
|
30
|
+
console.log("++ ad-hoc assembling NDK SDK distribution subset from original sources")
|
|
31
|
+
if (os.platform() === "win32") {
|
|
32
|
+
/* download innoextract utility */
|
|
33
|
+
const url1 = "https://constexpr.org/innoextract/files/innoextract-1.9-windows.zip"
|
|
34
|
+
console.log("-- downloading innoextract utility")
|
|
35
|
+
const data1 = await got.get(url1, { responseType: "buffer" })
|
|
36
|
+
const file1 = tmp.tmpNameSync()
|
|
37
|
+
await fs.promises.writeFile(file1, data1.body, { encoding: null })
|
|
38
|
+
|
|
39
|
+
/* extract innoextract utility */
|
|
40
|
+
console.log("-- extracting innoextract utility")
|
|
41
|
+
const dir1 = tmp.tmpNameSync()
|
|
42
|
+
zip.unzipSync(file1, dir1)
|
|
43
|
+
|
|
44
|
+
/* download NDI SDK distribution */
|
|
45
|
+
const url2 = "https://downloads.ndi.tv/SDK/NDI_SDK/NDI%206%20SDK.exe"
|
|
46
|
+
console.log("-- dowloading NDI SDK distribution")
|
|
47
|
+
const data2 = await got.get(url2, { responseType: "buffer" })
|
|
48
|
+
const file2 = tmp.tmpNameSync()
|
|
49
|
+
await fs.promises.writeFile(file2, data2.body, { encoding: null })
|
|
50
|
+
|
|
51
|
+
/* extract NDI SDK distribution */
|
|
52
|
+
console.log("-- extracting NDI SDK distribution")
|
|
53
|
+
const dir2 = tmp.tmpNameSync()
|
|
54
|
+
shell.mkdir("-p", dir2)
|
|
55
|
+
await execa(path.join(dir1, "innoextract.exe"), [ "-s", "-d", dir2, file2 ],
|
|
56
|
+
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
|
57
|
+
|
|
58
|
+
/* assemble NDI SDK subset */
|
|
59
|
+
console.log("-- assembling NDI SDK subset")
|
|
60
|
+
shell.rm("-rf", "ndi")
|
|
61
|
+
shell.mkdir("-p", "ndi")
|
|
62
|
+
shell.mkdir("-p", "ndi/lib/win-x86")
|
|
63
|
+
shell.mkdir("-p", "ndi/lib/win-x64")
|
|
64
|
+
shell.mv(path.join(dir2, "app/Include"), "ndi/include")
|
|
65
|
+
shell.mv(path.join(dir2, "app/Lib/x86/Processing.NDI.Lib.x86.lib"), "ndi/lib/win-x86/Processing.NDI.Lib.x86.lib")
|
|
66
|
+
shell.mv(path.join(dir2, "app/Bin/x86/Processing.NDI.Lib.x86.dll"), "ndi/lib/win-x86/Processing.NDI.Lib.x86.dll")
|
|
67
|
+
shell.mv(path.join(dir2, "app/Lib/x64/Processing.NDI.Lib.x64.lib"), "ndi/lib/win-x64/Processing.NDI.Lib.x64.lib")
|
|
68
|
+
shell.mv(path.join(dir2, "app/Bin/x64/Processing.NDI.Lib.x64.dll"), "ndi/lib/win-x64/Processing.NDI.Lib.x64.dll")
|
|
69
|
+
|
|
70
|
+
/* remove temporary files */
|
|
71
|
+
console.log("-- removing temporary files")
|
|
72
|
+
shell.rm("-f", file1)
|
|
73
|
+
shell.rm("-f", file2)
|
|
74
|
+
shell.rm("-rf", dir1)
|
|
75
|
+
shell.rm("-rf", dir2)
|
|
76
|
+
}
|
|
77
|
+
else if (os.platform() === "darwin") {
|
|
78
|
+
/* download NDI SDK distribution */
|
|
79
|
+
const url1 = "https://downloads.ndi.tv/SDK/NDI_SDK_Mac/Install_NDI_SDK_v6_Apple.pkg"
|
|
80
|
+
console.log("-- dowloading NDI SDK distribution")
|
|
81
|
+
const data1 = await got.get(url1, { responseType: "buffer" })
|
|
82
|
+
const file1 = tmp.tmpNameSync()
|
|
83
|
+
await fs.promises.writeFile(file1, data1.body, { encoding: null })
|
|
84
|
+
|
|
85
|
+
/* extract NDI SDK distribution */
|
|
86
|
+
console.log("-- extracting NDI SDK distribution")
|
|
87
|
+
const dir1 = tmp.tmpNameSync()
|
|
88
|
+
shell.rm("-rf", dir1)
|
|
89
|
+
await execa("pkgutil", [ "--expand", file1, dir1 ],
|
|
90
|
+
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
|
91
|
+
await execa("cpio", [ "-idmu", "-F", path.join(dir1, "NDI_SDK_Component.pkg/Payload") ],
|
|
92
|
+
{ cwd: dir1, stdin: "inherit", stdout: "ignore", stderr: "ignore" })
|
|
93
|
+
|
|
94
|
+
/* assemble NDI SDK subset */
|
|
95
|
+
console.log("-- assembling NDI SDK subset")
|
|
96
|
+
shell.rm("-rf", "ndi")
|
|
97
|
+
shell.mkdir("-p", "ndi/include")
|
|
98
|
+
shell.mkdir("-p", "ndi/lib/mac-a64")
|
|
99
|
+
shell.mkdir("-p", "ndi/lib/mac-x64")
|
|
100
|
+
shell.mv(path.join(dir1, "NDI SDK for Apple/include/*.h"), "ndi/include/")
|
|
101
|
+
shell.mv(path.join(dir1, "NDI SDK for Apple/lib/macOS/*.dylib"), "ndi/lib/mac-a64/")
|
|
102
|
+
shell.mv(path.join(dir1, "NDI SDK for Apple/lib/macOS/*.dylib"), "ndi/lib/mac-x64/")
|
|
103
|
+
|
|
104
|
+
/* remove temporary files */
|
|
105
|
+
console.log("-- removing temporary files")
|
|
106
|
+
shell.rm("-f", file1)
|
|
107
|
+
shell.rm("-rf", dir1)
|
|
108
|
+
}
|
|
109
|
+
else if (os.platform() === "linux") {
|
|
110
|
+
/* download NDI SDK distribution */
|
|
111
|
+
const url1 = "https://downloads.ndi.tv/SDK/NDI_SDK_Linux/Install_NDI_SDK_v6_Linux.tar.gz"
|
|
112
|
+
console.log("-- dowloading NDI SDK distribution")
|
|
113
|
+
const data1 = await got.get(url1, { responseType: "buffer" })
|
|
114
|
+
const file1 = tmp.tmpNameSync()
|
|
115
|
+
await fs.promises.writeFile(file1, data1.body, { encoding: null })
|
|
116
|
+
|
|
117
|
+
/* extract NDI SDK distribution */
|
|
118
|
+
console.log("-- extracting NDI SDK distribution")
|
|
119
|
+
const dir1 = tmp.tmpNameSync()
|
|
120
|
+
shell.mkdir("-p", dir1)
|
|
121
|
+
await execa("tar", [ "-z", "-x", "-C", dir1, "-f", file1 ],
|
|
122
|
+
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
|
123
|
+
await execa("sh", [ "-c", `echo "y" | PAGER=cat sh Install_NDI_SDK_v6_Linux.sh` ],
|
|
124
|
+
{ cwd: dir1, stdin: "inherit", stdout: "ignore", stderr: "inherit" })
|
|
125
|
+
|
|
126
|
+
/* assemble NDI SDK subset */
|
|
127
|
+
console.log("-- assembling NDI SDK subset")
|
|
128
|
+
shell.rm("-rf", "ndi")
|
|
129
|
+
shell.mkdir("-p", "ndi/include")
|
|
130
|
+
shell.mkdir("-p", "ndi/lib/lnx-x86")
|
|
131
|
+
shell.mkdir("-p", "ndi/lib/lnx-x64")
|
|
132
|
+
shell.mkdir("-p", "ndi/lib/lnx-a64")
|
|
133
|
+
shell.mv(path.join(dir1, "NDI SDK for Linux/include/*.h"), "ndi/include/")
|
|
134
|
+
shell.mv(path.join(dir1, "NDI SDK for Linux/lib/i686-linux-gnu/*"), "ndi/lib/lnx-x86/")
|
|
135
|
+
shell.mv(path.join(dir1, "NDI SDK for Linux/lib/x86_64-linux-gnu/*"), "ndi/lib/lnx-x64/")
|
|
136
|
+
shell.mv(path.join(dir1, "NDI SDK for Linux/lib/aarch64-rpi4-linux-gnueabi/*"), "ndi/lib/lnx-a64/")
|
|
137
|
+
|
|
138
|
+
/* dereference symlinks (Linux NDI SDK ships symlink chains) */
|
|
139
|
+
for (const sub of ["lnx-x86", "lnx-x64", "lnx-a64"]) {
|
|
140
|
+
const dir = path.join("ndi", "lib", sub)
|
|
141
|
+
for (const f of fs.readdirSync(dir)) {
|
|
142
|
+
const p = path.join(dir, f)
|
|
143
|
+
if (fs.lstatSync(p).isSymbolicLink()) {
|
|
144
|
+
const real = fs.realpathSync(p)
|
|
145
|
+
fs.unlinkSync(p)
|
|
146
|
+
fs.copyFileSync(real, p)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* remove temporary files */
|
|
152
|
+
console.log("-- removing temporary files")
|
|
153
|
+
shell.rm("-f", file1)
|
|
154
|
+
shell.rm("-rf", dir1)
|
|
155
|
+
}
|
|
156
|
+
})().catch((err) => {
|
|
157
|
+
console.log(`** ERROR: ${err}`)
|
|
158
|
+
})
|
|
159
|
+
|