node-red-contrib-hik-media-buffer 1.1.45 → 1.1.46
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 +25 -38
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -23,7 +23,6 @@ module.exports = function(RED) {
|
|
|
23
23
|
let lastTriggerTime = {};
|
|
24
24
|
let nvrOnline = true;
|
|
25
25
|
|
|
26
|
-
// Strutture dinamiche per monitorare canali e stati senza liste fisse
|
|
27
26
|
let statoCanale = {};
|
|
28
27
|
let ultimoAllarmeTempo = {};
|
|
29
28
|
|
|
@@ -65,7 +64,6 @@ module.exports = function(RED) {
|
|
|
65
64
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
66
65
|
|
|
67
66
|
try {
|
|
68
|
-
// Interroghiamo l'NVR sullo stato globale dei suoi canali proxy
|
|
69
67
|
const res = await nvrAuth.request({
|
|
70
68
|
method: 'GET',
|
|
71
69
|
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels/status`,
|
|
@@ -76,22 +74,19 @@ module.exports = function(RED) {
|
|
|
76
74
|
nvrOnline = true;
|
|
77
75
|
const xmlData = res.data.toString();
|
|
78
76
|
|
|
79
|
-
// Regex globale per estrarre TUTTI i blocchi <InputProxyChannelStatus> presenti nell'XML dell'NVR
|
|
80
77
|
const bloccoRegex = /<InputProxyChannelStatus[\s\\S]*?>([\s\S]*?)<\/InputProxyChannelStatus>/gi;
|
|
81
78
|
let matchBlocco;
|
|
82
79
|
|
|
83
80
|
while ((matchBlocco = bloccoRegex.exec(xmlData)) !== null) {
|
|
84
81
|
const bloccoContenuto = matchBlocco[1];
|
|
85
82
|
|
|
86
|
-
// Estraiamo l'ID del canale e il suo stato dal blocco attuale
|
|
87
83
|
const idMatch = bloccoContenuto.match(/<id>(\d+)<\/id>/i);
|
|
88
|
-
const statusMatch = bloccoContenuto.match(/<status>([^<]+)</i);
|
|
84
|
+
const statusMatch = bloccoContenuto.match(/<status>([^<]+)</i);
|
|
89
85
|
|
|
90
86
|
if (idMatch && statusMatch) {
|
|
91
87
|
const ch = idMatch[1];
|
|
92
88
|
const statusStr = statusMatch[1].toLowerCase();
|
|
93
89
|
|
|
94
|
-
// Determiniamo se per l'NVR la telecamera su questo canale è attiva
|
|
95
90
|
const isOnline = statusStr.includes("online") || statusStr.includes("recording");
|
|
96
91
|
|
|
97
92
|
if (isOnline && statoCanale[ch] === false) {
|
|
@@ -102,13 +97,12 @@ module.exports = function(RED) {
|
|
|
102
97
|
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Canale_${ch}`, ip_nvr: node.host, channel: ch.toString(), msg: "Camera scollegata o offline su NVR" } });
|
|
103
98
|
statoCanale[ch] = false;
|
|
104
99
|
} else if (statoCanale[ch] === undefined) {
|
|
105
|
-
// Primo avvio: memorizziamo lo stato iniziale
|
|
106
100
|
statoCanale[ch] = isOnline;
|
|
107
101
|
}
|
|
108
102
|
}
|
|
109
103
|
}
|
|
110
104
|
} catch (e) {
|
|
111
|
-
nvrOnline = false;
|
|
105
|
+
nvrOnline = false;
|
|
112
106
|
}
|
|
113
107
|
updateNodeStatus();
|
|
114
108
|
}
|
|
@@ -126,23 +120,24 @@ module.exports = function(RED) {
|
|
|
126
120
|
|
|
127
121
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
128
122
|
|
|
129
|
-
// --- DOWNLOAD
|
|
123
|
+
// --- DOWNLOAD MULTIMEDIALE DA NVR CON LOGICA STRUTTURALE ORIGINALE ---
|
|
130
124
|
async function downloadMedia(evento, channelID) {
|
|
131
125
|
const nowTime = Date.now();
|
|
132
126
|
if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
|
|
133
127
|
if (nowTime - lastTriggerTime[channelID] < 5000) return;
|
|
134
128
|
lastTriggerTime[channelID] = nowTime;
|
|
135
129
|
|
|
136
|
-
|
|
130
|
+
// ISTANZIAMO camAuth SULL'NVR: Risolve il problema delle credenziali vuote nel vecchio blocco
|
|
131
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
132
|
+
|
|
137
133
|
const referenceTime = new Date();
|
|
138
134
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
139
|
-
const startTime = toHikDate(new Date(referenceTime.getTime() - (
|
|
140
|
-
const endTime = toHikDate(new Date(referenceTime.getTime() + (
|
|
135
|
+
const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
|
|
136
|
+
const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
|
|
141
137
|
|
|
142
138
|
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
143
139
|
const nomeCamera = await getCameraName(channelID);
|
|
144
140
|
|
|
145
|
-
// Ritardo tecnico per dare tempo all'NVR di chiudere e indicizzare il file su HDD
|
|
146
141
|
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
147
142
|
const baseUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt`;
|
|
148
143
|
|
|
@@ -150,7 +145,7 @@ module.exports = function(RED) {
|
|
|
150
145
|
tipo_messaggio: "evento",
|
|
151
146
|
nome_cliente: node.name,
|
|
152
147
|
nome_telecamera: nomeCamera,
|
|
153
|
-
|
|
148
|
+
ip_telecamera: node.host,
|
|
154
149
|
tipo_evento: evento,
|
|
155
150
|
timestamp_epoch: timestamp,
|
|
156
151
|
stato_telecamera: "ONLINE",
|
|
@@ -159,17 +154,19 @@ module.exports = function(RED) {
|
|
|
159
154
|
video_base64: null
|
|
160
155
|
};
|
|
161
156
|
|
|
162
|
-
let fileDaCancellare = [];
|
|
163
157
|
const parsedChannel = parseInt(channelID, 10);
|
|
164
|
-
const trackVideo = (parsedChannel * 100) + 1; // Formula
|
|
165
|
-
const trackFoto = (parsedChannel * 100) + 3; // Formula
|
|
158
|
+
const trackVideo = (parsedChannel * 100) + 1; // Formula NVR (*100+1)
|
|
159
|
+
const trackFoto = (parsedChannel * 100) + 3; // Formula NVR (*100+3)
|
|
166
160
|
|
|
167
|
-
const tracks = [{ id: trackVideo.toString()
|
|
161
|
+
const tracks = [{ id: trackVideo.toString() }, { id: trackFoto.toString() }];
|
|
162
|
+
let fileDaCancellare = [];
|
|
168
163
|
|
|
169
164
|
try {
|
|
170
165
|
for (let t of tracks) {
|
|
166
|
+
// RIPRISTINATO: Il tuo XML originale esatto con <trackIDList> e ${evento} dinamico
|
|
171
167
|
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>`;
|
|
172
|
-
|
|
168
|
+
|
|
169
|
+
const resSearch = await camAuth.request({
|
|
173
170
|
method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
|
|
174
171
|
});
|
|
175
172
|
|
|
@@ -178,7 +175,7 @@ module.exports = function(RED) {
|
|
|
178
175
|
|
|
179
176
|
if (uriMatch) {
|
|
180
177
|
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
181
|
-
const resDown = await
|
|
178
|
+
const resDown = await camAuth.request({
|
|
182
179
|
method: 'GET',
|
|
183
180
|
url: `${baseUrl}/download`,
|
|
184
181
|
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
@@ -186,12 +183,14 @@ module.exports = function(RED) {
|
|
|
186
183
|
});
|
|
187
184
|
|
|
188
185
|
let buffer = Buffer.from(resDown.data);
|
|
189
|
-
if (t.
|
|
186
|
+
if (t.id === trackFoto.toString()) {
|
|
187
|
+
// SALVA FOTO IN LOCALE
|
|
190
188
|
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
191
189
|
fs.writeFileSync(fullImgPath, buffer);
|
|
192
190
|
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
193
191
|
fileDaCancellare.push(fullImgPath);
|
|
194
192
|
} else {
|
|
193
|
+
// SALVA VIDEO IN LOCALE
|
|
195
194
|
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
196
195
|
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
197
196
|
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
@@ -224,9 +223,9 @@ module.exports = function(RED) {
|
|
|
224
223
|
}
|
|
225
224
|
|
|
226
225
|
} catch (e) {
|
|
227
|
-
node.error(`Errore Download
|
|
226
|
+
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
228
227
|
}
|
|
229
|
-
|
|
228
|
+
updateNodeStatus();
|
|
230
229
|
}
|
|
231
230
|
|
|
232
231
|
// --- ALERT STREAM ---
|
|
@@ -234,33 +233,22 @@ module.exports = function(RED) {
|
|
|
234
233
|
if (isClosing) return;
|
|
235
234
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
236
235
|
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
237
|
-
|
|
238
236
|
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
239
237
|
.then(response => {
|
|
240
238
|
streamRequest = response;
|
|
241
239
|
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
242
240
|
updateNodeStatus();
|
|
243
|
-
|
|
244
241
|
response.data.on('data', (chunk) => {
|
|
245
242
|
const data = chunk.toString().toLowerCase();
|
|
246
|
-
|
|
247
243
|
if (data.includes("active")) {
|
|
248
244
|
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
249
245
|
if (chMatch) {
|
|
250
|
-
let channelID = chMatch[1];
|
|
251
246
|
let ev = "Unknown";
|
|
247
|
+
for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
|
|
252
248
|
|
|
253
|
-
|
|
254
|
-
if (data.includes(e.toLowerCase())) {
|
|
255
|
-
ev = e;
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
249
|
+
if (ev === "Unknown") return; // Ignora heartbeat o altri eventi non mappati
|
|
259
250
|
|
|
260
|
-
|
|
261
|
-
if (ev === "Unknown") return;
|
|
262
|
-
|
|
263
|
-
downloadMedia(ev, channelID);
|
|
251
|
+
downloadMedia(ev, chMatch[1]);
|
|
264
252
|
}
|
|
265
253
|
}
|
|
266
254
|
});
|
|
@@ -277,7 +265,6 @@ module.exports = function(RED) {
|
|
|
277
265
|
|
|
278
266
|
startAlertStream();
|
|
279
267
|
setTimeout(checkCameras, 2000);
|
|
280
|
-
|
|
281
268
|
node.on('close', (done) => {
|
|
282
269
|
isClosing = true;
|
|
283
270
|
clearInterval(heartbeatInterval);
|