node-red-contrib-hik-media-buffer 1.1.6 → 1.1.8
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/hik-media-buffer.js +41 -62
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
|
|
3
|
+
const https = require('https');
|
|
2
4
|
const fs = require('fs');
|
|
3
5
|
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
4
7
|
const { exec } = require('child_process');
|
|
5
8
|
|
|
6
9
|
module.exports = function(RED) {
|
|
@@ -17,61 +20,46 @@ module.exports = function(RED) {
|
|
|
17
20
|
node.camPass = config.camPass || config.pass;
|
|
18
21
|
node.cameras = config.cameras || [];
|
|
19
22
|
|
|
23
|
+
let streamRequest = null;
|
|
20
24
|
let isClosing = false;
|
|
21
25
|
let lastTriggerTime = 0;
|
|
22
26
|
let nvrOnline = true;
|
|
23
27
|
let statoCamera = {};
|
|
24
28
|
|
|
29
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
25
30
|
const EventList = ["FieldDetection", "LineDetection"];
|
|
26
31
|
|
|
27
|
-
// ---
|
|
32
|
+
// --- QUESTE RIGHE SERVONO PER IL SITO ---
|
|
28
33
|
const baseStorage = `C:\\Users\\APerucca\\Documents\\progetto-docker\\storage\\${node.name}`;
|
|
29
34
|
const imgDir = path.join(baseStorage, "allarmi");
|
|
30
35
|
const vidDir = path.join(baseStorage, "video");
|
|
31
|
-
|
|
32
36
|
if (!fs.existsSync(imgDir)) fs.mkdirSync(imgDir, { recursive: true });
|
|
33
37
|
if (!fs.existsSync(vidDir)) fs.mkdirSync(vidDir, { recursive: true });
|
|
34
38
|
|
|
35
|
-
// Client Digest per NVR e Camere
|
|
36
|
-
const nvrAuth = axiosDigest(node.user, node.pass);
|
|
37
|
-
|
|
38
39
|
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
39
40
|
|
|
40
41
|
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
41
42
|
|
|
42
|
-
async function getCameraName(cam) {
|
|
43
|
-
try {
|
|
44
|
-
const res = await nvrAuth.request({
|
|
45
|
-
method: 'GET',
|
|
46
|
-
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`
|
|
47
|
-
});
|
|
48
|
-
const match = res.data.toString().match(/<name>([^<]+)<\/name>/);
|
|
49
|
-
return match ? match[1] : `Cam_${cam.channel}`;
|
|
50
|
-
} catch (e) {
|
|
51
|
-
return `Camera_${cam.ip}`;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
43
|
async function checkCameras() {
|
|
56
44
|
if (isClosing) return;
|
|
57
45
|
for (let cam of node.cameras) {
|
|
46
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
58
47
|
try {
|
|
59
|
-
await
|
|
48
|
+
await camAuth.request({
|
|
60
49
|
method: 'GET',
|
|
61
|
-
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo
|
|
50
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
|
|
51
|
+
timeout: 5000,
|
|
52
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
62
53
|
});
|
|
63
|
-
|
|
64
54
|
if (statoCamera[cam.ip] === false) {
|
|
65
|
-
|
|
66
|
-
node.send({ payload: { status: "online", nomeCliente: node.name, nome_telecamera: nomeOnline, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
|
|
55
|
+
node.send({ payload: { status: "online", nomeCliente: node.name, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
|
|
67
56
|
statoCamera[cam.ip] = true;
|
|
68
57
|
} else if (statoCamera[cam.ip] === undefined) {
|
|
69
58
|
statoCamera[cam.ip] = true;
|
|
70
59
|
}
|
|
71
60
|
} catch (e) {
|
|
72
61
|
if (statoCamera[cam.ip] !== false) {
|
|
73
|
-
|
|
74
|
-
node.send({ payload: { status: "offline", nomeCliente: node.name, nome_telecamera: nomeOffline, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
|
|
62
|
+
node.send({ payload: { status: "offline", nomeCliente: node.name, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
|
|
75
63
|
statoCamera[cam.ip] = false;
|
|
76
64
|
}
|
|
77
65
|
}
|
|
@@ -100,51 +88,48 @@ module.exports = function(RED) {
|
|
|
100
88
|
if (nowTime - lastTriggerTime < 10000) return;
|
|
101
89
|
lastTriggerTime = nowTime;
|
|
102
90
|
|
|
103
|
-
const
|
|
104
|
-
const nomeCamera = await getCameraName(camera);
|
|
91
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
105
92
|
const referenceTime = new Date();
|
|
93
|
+
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
106
94
|
const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
|
|
107
95
|
const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
|
|
108
96
|
|
|
109
|
-
const camAuth = axiosDigest(node.user, node.camPass);
|
|
110
|
-
const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
|
|
111
|
-
|
|
112
97
|
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
113
98
|
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
114
99
|
|
|
115
|
-
|
|
100
|
+
const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
|
|
101
|
+
let output = { ip: camera.ip, nomeCliente: node.name, channel: channelID, event: evento, videoPath: null, imageBuffer: null, imagePath: null };
|
|
116
102
|
|
|
117
103
|
try {
|
|
118
104
|
const tracks = [{ id: "201" }, { id: "203" }];
|
|
119
105
|
for (let t of tracks) {
|
|
120
106
|
const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><maxResults>100</maxResults><metadataList><metadataDescriptor>//recordType.meta.std-cgi.com/${evento}</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
121
|
-
|
|
122
|
-
const resSearch = await camAuth.request({
|
|
123
|
-
method: 'POST', url: `${baseUrl}/search`, data: searchXml,
|
|
124
|
-
headers: { "Content-Type": "application/xml" }
|
|
107
|
+
|
|
108
|
+
const resSearch = await camAuth.request({
|
|
109
|
+
method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
|
|
125
110
|
});
|
|
126
111
|
|
|
127
|
-
let xml = resSearch.data.
|
|
112
|
+
let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
128
113
|
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
129
114
|
|
|
130
115
|
if (uriMatch) {
|
|
131
116
|
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
132
|
-
const resDown = await camAuth.request({
|
|
133
|
-
method: '
|
|
134
|
-
url: `${baseUrl}/download`,
|
|
135
|
-
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
136
|
-
responseType: 'arraybuffer'
|
|
137
|
-
headers: { "Content-Type": "application/xml" }
|
|
117
|
+
const resDown = await camAuth.request({
|
|
118
|
+
method: 'GET',
|
|
119
|
+
url: `${baseUrl}/download`,
|
|
120
|
+
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
121
|
+
responseType: 'arraybuffer'
|
|
138
122
|
});
|
|
139
123
|
|
|
140
124
|
let buffer = Buffer.from(resDown.data);
|
|
141
125
|
if (t.id === "203") {
|
|
142
|
-
// FOTO
|
|
126
|
+
// --- SALVA FOTO ---
|
|
143
127
|
const fullImgPath = path.join(imgDir, `img_${timestamp}.jpg`);
|
|
144
128
|
fs.writeFileSync(fullImgPath, buffer);
|
|
129
|
+
output.imageBuffer = buffer;
|
|
145
130
|
output.imagePath = fullImgPath;
|
|
146
131
|
} else {
|
|
147
|
-
// VIDEO
|
|
132
|
+
// --- SALVA VIDEO ---
|
|
148
133
|
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
149
134
|
const rawPath = path.join(vidDir, `raw_${timestamp}.mp4`);
|
|
150
135
|
const fixedPath = path.join(vidDir, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
@@ -170,19 +155,17 @@ module.exports = function(RED) {
|
|
|
170
155
|
updateNodeStatus();
|
|
171
156
|
}
|
|
172
157
|
|
|
158
|
+
// --- ALERT STREAM (TUTTO COME PRIMA) ---
|
|
173
159
|
function startAlertStream() {
|
|
174
160
|
if (isClosing) return;
|
|
161
|
+
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
175
162
|
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (!nvrOnline) {
|
|
180
|
-
node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } });
|
|
181
|
-
nvrOnline = true;
|
|
182
|
-
}
|
|
163
|
+
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
164
|
+
.then(response => {
|
|
165
|
+
streamRequest = response;
|
|
166
|
+
if (!nvrOnline) { node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } }); nvrOnline = true; }
|
|
183
167
|
updateNodeStatus();
|
|
184
|
-
|
|
185
|
-
res.data.on('data', (chunk) => {
|
|
168
|
+
response.data.on('data', (chunk) => {
|
|
186
169
|
const data = chunk.toString().toLowerCase();
|
|
187
170
|
if (data.includes("active")) {
|
|
188
171
|
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
@@ -193,27 +176,23 @@ module.exports = function(RED) {
|
|
|
193
176
|
}
|
|
194
177
|
}
|
|
195
178
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
res.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
|
|
179
|
+
response.data.on('error', () => handleNvrError());
|
|
180
|
+
response.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
|
|
199
181
|
}).catch(() => handleNvrError());
|
|
200
182
|
}
|
|
201
183
|
|
|
202
184
|
function handleNvrError() {
|
|
203
|
-
if (nvrOnline) {
|
|
204
|
-
node.send({ payload: { status: "offline", ip: node.host, msg: "NVR Offline" } });
|
|
205
|
-
nvrOnline = false;
|
|
206
|
-
}
|
|
185
|
+
if (nvrOnline) { node.send({ payload: { status: "offline", ip: node.host, msg: "NVR Offline" } }); nvrOnline = false; }
|
|
207
186
|
updateNodeStatus();
|
|
208
187
|
if (!isClosing) setTimeout(startAlertStream, 10000);
|
|
209
188
|
}
|
|
210
189
|
|
|
211
190
|
startAlertStream();
|
|
212
191
|
setTimeout(checkCameras, 2000);
|
|
213
|
-
|
|
214
192
|
node.on('close', (done) => {
|
|
215
193
|
isClosing = true;
|
|
216
194
|
clearInterval(heartbeatInterval);
|
|
195
|
+
if (streamRequest) streamRequest.data.destroy();
|
|
217
196
|
done();
|
|
218
197
|
});
|
|
219
198
|
}
|