node-red-contrib-hik-media-buffer 1.1.82 → 1.1.84
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 +59 -89
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
|
|
2
3
|
const fs = require('fs');
|
|
3
4
|
const path = require('path');
|
|
4
5
|
const os = require('os');
|
|
@@ -16,15 +17,13 @@ module.exports = function(RED) {
|
|
|
16
17
|
node.user = config.user;
|
|
17
18
|
node.pass = config.pass;
|
|
18
19
|
|
|
19
|
-
let
|
|
20
|
+
let streamRequest = null;
|
|
20
21
|
let isClosing = false;
|
|
21
22
|
let lastTriggerTime = {};
|
|
22
23
|
let nvrOnline = true;
|
|
23
|
-
let statoCamera = {};
|
|
24
|
+
let statoCamera = {};
|
|
24
25
|
|
|
25
26
|
const EventList = ["FieldDetection", "LineDetection"];
|
|
26
|
-
|
|
27
|
-
// CARTELLA TEMPORANEA
|
|
28
27
|
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
29
28
|
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
30
29
|
|
|
@@ -32,24 +31,18 @@ module.exports = function(RED) {
|
|
|
32
31
|
|
|
33
32
|
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
34
33
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
prefixUrl: `${node.protocol}://${node.host}:${node.port}/ISAPI`,
|
|
38
|
-
username: node.user,
|
|
39
|
-
password: node.pass,
|
|
40
|
-
authentication: 'digest',
|
|
41
|
-
retry: 0,
|
|
42
|
-
https: {
|
|
43
|
-
rejectUnauthorized: false
|
|
44
|
-
}
|
|
45
|
-
});
|
|
34
|
+
// Unica libreria di autenticazione usata per tutto il flusso
|
|
35
|
+
const nvrAuthAxios = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
46
36
|
|
|
47
|
-
// --- PRENDE IL NOME DELLA TELECAMERA
|
|
37
|
+
// --- PRENDE IL NOME DELLA TELECAMERA DALL'NVR ---
|
|
48
38
|
async function getCameraName(channelID) {
|
|
49
39
|
try {
|
|
50
|
-
const res = await
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
const res = await nvrAuthAxios.request({
|
|
41
|
+
method: 'GET',
|
|
42
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}/overlays/channelNameOverlay`,
|
|
43
|
+
timeout: 5000
|
|
44
|
+
});
|
|
45
|
+
const match = res.data.toString().match(/<name>([^<]+)<\/name>/i);
|
|
53
46
|
if (match && match[1]) return match[1].trim();
|
|
54
47
|
return `Canale_${channelID}`;
|
|
55
48
|
} catch (e) {
|
|
@@ -61,11 +54,12 @@ module.exports = function(RED) {
|
|
|
61
54
|
async function checkCameras() {
|
|
62
55
|
if (isClosing || !nvrOnline) return;
|
|
63
56
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
const res = await nvrAuthAxios.request({
|
|
58
|
+
method: 'GET',
|
|
59
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels/status`,
|
|
60
|
+
timeout: 8000
|
|
61
|
+
});
|
|
62
|
+
const xml = res.data.toString();
|
|
69
63
|
const statusBlocks = xml.match(/<InputProxyChannelStatus>[\s\S]*?<\/InputProxyChannelStatus>/gi) || [];
|
|
70
64
|
|
|
71
65
|
for (let block of statusBlocks) {
|
|
@@ -77,13 +71,11 @@ module.exports = function(RED) {
|
|
|
77
71
|
const isOnlineNow = statusMatch[1].trim().toLowerCase() === 'true';
|
|
78
72
|
|
|
79
73
|
if (isOnlineNow && statoCamera[ch] === false) {
|
|
80
|
-
// La telecamera è tornata online!
|
|
81
74
|
const nomeOnline = await getCameraName(ch);
|
|
82
75
|
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, channel: ch, msg: "Camera ripristinata" } });
|
|
83
76
|
statoCamera[ch] = true;
|
|
84
77
|
} else if (!isOnlineNow && statoCamera[ch] !== false && statoCamera[ch] !== undefined) {
|
|
85
|
-
|
|
86
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera_${ch}`, channel: ch, msg: "Camera scollegata o non raggiungibile dall'NVR" } });
|
|
78
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera_${ch}`, channel: ch, msg: "Camera scollegata dall'NVR" } });
|
|
87
79
|
statoCamera[ch] = false;
|
|
88
80
|
} else if (statoCamera[ch] === undefined) {
|
|
89
81
|
statoCamera[ch] = isOnlineNow;
|
|
@@ -91,7 +83,7 @@ module.exports = function(RED) {
|
|
|
91
83
|
}
|
|
92
84
|
}
|
|
93
85
|
} catch (e) {
|
|
94
|
-
|
|
86
|
+
// Silenzioso
|
|
95
87
|
}
|
|
96
88
|
updateNodeStatus();
|
|
97
89
|
}
|
|
@@ -109,7 +101,7 @@ module.exports = function(RED) {
|
|
|
109
101
|
|
|
110
102
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
111
103
|
|
|
112
|
-
// --- DOWNLOAD MEDIA
|
|
104
|
+
// --- DOWNLOAD MEDIA UTILIZZANDO SOLO AXIOS ---
|
|
113
105
|
async function downloadMedia(evento, channelID) {
|
|
114
106
|
const chStr = channelID.toString();
|
|
115
107
|
const nowTime = Date.now();
|
|
@@ -127,51 +119,46 @@ module.exports = function(RED) {
|
|
|
127
119
|
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
128
120
|
|
|
129
121
|
let output = {
|
|
130
|
-
tipo_messaggio: "evento",
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
ip_telecamera: null,
|
|
134
|
-
tipo_evento: evento,
|
|
135
|
-
timestamp_epoch: timestamp,
|
|
136
|
-
stato_telecamera: "ONLINE",
|
|
137
|
-
channel: chStr,
|
|
138
|
-
foto_base64: null,
|
|
139
|
-
video_base64: null
|
|
122
|
+
tipo_messaggio: "evento", nome_cliente: node.name, nome_telecamera: nomeCamera,
|
|
123
|
+
ip_telecamera: null, tipo_evento: evento, timestamp_epoch: timestamp,
|
|
124
|
+
stato_telecamera: "ONLINE", channel: chStr, foto_base64: null, video_base64: null
|
|
140
125
|
};
|
|
141
126
|
|
|
142
127
|
let fileDaCancellare = [];
|
|
143
128
|
|
|
144
129
|
try {
|
|
130
|
+
try { await nvrAuthAxios.request({ method: 'POST', url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/search`, data: "" }); } catch(e) {}
|
|
145
131
|
const trackV = (channelID * 100 + 1).toString();
|
|
146
132
|
const trackI = (channelID * 100 + 3).toString();
|
|
147
133
|
const tracks = [{ id: trackV }, { id: trackI }];
|
|
148
|
-
|
|
134
|
+
let searchXml = ``;
|
|
149
135
|
for (let t of tracks) {
|
|
150
|
-
|
|
136
|
+
|
|
151
137
|
if (t.id === trackV) {
|
|
152
138
|
searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackList><trackID>${t.id}</trackID></trackList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><maxResults>100</maxResults><searchResultPostion>0</searchResultPostion><metadataList><metadataDescriptor>//recordType.meta.std-cgi.com</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
153
139
|
} else {
|
|
154
140
|
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><contentTypeList><contentType>metadata</contentType></contentTypeList><maxResults>30</maxResults><searchResultPostion>0</searchResultPostion><metadataList><metadataDescriptor>recordType.meta.hikvision.com/${evento}</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
155
141
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
const resSearch = await nvrAuthAxios.request({
|
|
143
|
+
method: 'POST',
|
|
144
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/search`,
|
|
145
|
+
data: searchXml,
|
|
146
|
+
headers: { "Content-Type": "application/xml; charset=UTF-8" }
|
|
160
147
|
});
|
|
161
148
|
|
|
162
|
-
let xml = resSearch.
|
|
149
|
+
let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
163
150
|
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
164
151
|
|
|
165
152
|
if (uriMatch) {
|
|
166
153
|
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
167
154
|
|
|
168
|
-
const resDown = await
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
155
|
+
const resDown = await nvrAuthAxios.request({
|
|
156
|
+
method: 'GET',
|
|
157
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/download?playbackURI=${encodeURIComponent(rawUri)}`,
|
|
158
|
+
responseType: 'arraybuffer'
|
|
172
159
|
});
|
|
173
160
|
|
|
174
|
-
let buffer = resDown.
|
|
161
|
+
let buffer = Buffer.from(resDown.data);
|
|
175
162
|
if (t.id === trackI) {
|
|
176
163
|
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
177
164
|
fs.writeFileSync(fullImgPath, buffer);
|
|
@@ -201,7 +188,6 @@ module.exports = function(RED) {
|
|
|
201
188
|
|
|
202
189
|
if (output.foto_base64 || output.video_base64) {
|
|
203
190
|
node.send({ payload: output });
|
|
204
|
-
|
|
205
191
|
setTimeout(() => {
|
|
206
192
|
for (let file of fileDaCancellare) {
|
|
207
193
|
try { if (fs.existsSync(file)) fs.unlinkSync(file); } catch (err) {}
|
|
@@ -215,48 +201,34 @@ module.exports = function(RED) {
|
|
|
215
201
|
updateNodeStatus();
|
|
216
202
|
}
|
|
217
203
|
|
|
218
|
-
// --- ALERT STREAM ---
|
|
204
|
+
// --- ALERT STREAM CON AXIOS ---
|
|
219
205
|
function startAlertStream() {
|
|
220
206
|
if (isClosing) return;
|
|
221
|
-
|
|
222
|
-
const streamUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
207
|
+
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
223
208
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
authentication: 'digest',
|
|
228
|
-
https: { rejectUnauthorized: false }
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
activeStream = downloadStream;
|
|
232
|
-
|
|
233
|
-
downloadStream.on('response', () => {
|
|
209
|
+
nvrAuthAxios.request({ method: 'GET', url: url, responseType: 'stream' })
|
|
210
|
+
.then(response => {
|
|
211
|
+
streamRequest = response;
|
|
234
212
|
if (!nvrOnline) {
|
|
235
213
|
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } });
|
|
236
214
|
nvrOnline = true;
|
|
237
215
|
}
|
|
238
216
|
updateNodeStatus();
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
217
|
+
|
|
218
|
+
response.data.on('data', (chunk) => {
|
|
219
|
+
const data = chunk.toString().toLowerCase();
|
|
220
|
+
if (data.includes("active")) {
|
|
221
|
+
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
222
|
+
if (chMatch) {
|
|
223
|
+
let ev = "Unknown";
|
|
224
|
+
for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
|
|
225
|
+
downloadMedia(ev, chMatch[1]);
|
|
226
|
+
}
|
|
249
227
|
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!isClosing) handleNvrError();
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
downloadStream.on('end', () => {
|
|
258
|
-
if (!isClosing) setTimeout(startAlertStream, 5000);
|
|
259
|
-
});
|
|
228
|
+
});
|
|
229
|
+
response.data.on('error', () => handleNvrError());
|
|
230
|
+
response.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
|
|
231
|
+
}).catch(() => handleNvrError());
|
|
260
232
|
}
|
|
261
233
|
|
|
262
234
|
function handleNvrError() {
|
|
@@ -274,9 +246,7 @@ module.exports = function(RED) {
|
|
|
274
246
|
node.on('close', (done) => {
|
|
275
247
|
isClosing = true;
|
|
276
248
|
clearInterval(heartbeatInterval);
|
|
277
|
-
if (
|
|
278
|
-
try { activeStream.destroy(); } catch(e){}
|
|
279
|
-
}
|
|
249
|
+
if (streamRequest) streamRequest.data.destroy();
|
|
280
250
|
done();
|
|
281
251
|
});
|
|
282
252
|
}
|