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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +59 -89
  2. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
- const got = require('got'); // Assicurati di aver installato got@11 nel container!
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 activeStream = null;
20
+ let streamRequest = null;
20
21
  let isClosing = false;
21
22
  let lastTriggerTime = {};
22
23
  let nvrOnline = true;
23
- let statoCamera = {}; // Indicizzato per ChannelID: true = online, false = offline
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
- // Client base di GOT configurato con il Digest nativo per l'NVR
36
- const nvrClient = got.extend({
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 DIRETTAMENTE DALL'NVR ---
37
+ // --- PRENDE IL NOME DELLA TELECAMERA DALL'NVR ---
48
38
  async function getCameraName(channelID) {
49
39
  try {
50
- const res = await nvrClient.get(`System/Video/inputs/channels/${channelID}/overlays/channelNameOverlay`, { timeout: 5000 });
51
- const data = res.body.toString();
52
- const match = data.match(/<name>([^<]+)<\/name>/i);
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
- // Interroghiamo lo stato di tutti i canali IP proxy sull'NVR
65
- const res = await nvrClient.get('ContentMgmt/InputProxy/channels/status', { timeout: 8000 });
66
- const xml = res.body.toString();
67
-
68
- // Estraiamo tutti i blocchi <InputProxyChannelStatus> presenti nell'XML dell'NVR
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
- // La telecamera si è scollegata! (Ignoriamo il primo avvio se è già scollegata per non intasare di alert)
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
- node.error(`[DEBUG STATUS] Errore lettura canali da NVR: ${e.message}`);
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 AUTOMATICO DA EVENTO NVR ---
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
- nome_cliente: node.name,
132
- nome_telecamera: nomeCamera,
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
- let searchXml = ``;
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
- const resSearch = await nvrClient.post('ContentMgmt/search', {
158
- body: searchXml,
159
- headers: { "Content-Type": "application/xml" }
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.body.replace(/<(\/?)\w+:/g, "<$1");
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(/&amp;/g, '&');
167
154
 
168
- const resDown = await nvrClient.post('ContentMgmt/download', {
169
- body: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
170
- responseType: 'buffer',
171
- headers: { "Content-Type": "application/xml" }
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.body;
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
- const downloadStream = got.stream(streamUrl, {
225
- username: node.user,
226
- password: node.pass,
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
- downloadStream.on('data', (chunk) => {
242
- const data = chunk.toString().toLowerCase();
243
- if (data.includes("active")) {
244
- const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
245
- if (chMatch) {
246
- let ev = "Unknown";
247
- for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
248
- downloadMedia(ev, chMatch[1]);
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
- downloadStream.on('error', (err) => {
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 (activeStream) {
278
- try { activeStream.destroy(); } catch(e){}
279
- }
249
+ if (streamRequest) streamRequest.data.destroy();
280
250
  done();
281
251
  });
282
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.82",
3
+ "version": "1.1.84",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",