node-red-contrib-hik-media-buffer 1.1.13 → 1.1.15

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 +65 -72
  2. package/package.json +1 -1
@@ -29,12 +29,9 @@ module.exports = function(RED) {
29
29
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
30
30
  const EventList = ["FieldDetection", "LineDetection"];
31
31
 
32
- // --- QUESTE RIGHE SERVONO PER IL SITO ---
33
- const baseStorage = `C:\\Users\\APerucca\\Documents\\progetto-docker\\storage\\${node.name}`;
34
- const imgDir = path.join(baseStorage, "allarmi");
35
- const vidDir = path.join(baseStorage, "video");
36
- if (!fs.existsSync(imgDir)) fs.mkdirSync(imgDir, { recursive: true });
37
- if (!fs.existsSync(vidDir)) fs.mkdirSync(vidDir, { recursive: true });
32
+ // BASE STORAGE TEMPORANEA
33
+ const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
34
+ if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
38
35
 
39
36
  node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
40
37
 
@@ -42,70 +39,39 @@ module.exports = function(RED) {
42
39
 
43
40
  // --- PRENDE IL NOME DELLA TELECAMERA ---
44
41
  async function getCameraName(cam) {
45
- const camAuth = new AxiosDigestAuth({
46
- username: node.user,
47
- password: node.camPass
48
- });
49
-
42
+ const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
50
43
  try {
51
- // Interroghiamo il CANALE specifico (es. /channels/2)
52
44
  const res = await camAuth.request({
53
45
  method: 'GET',
54
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`,
46
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
55
47
  timeout: 5000,
56
48
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
57
49
  });
58
-
59
50
  const data = res.data.toString();
60
-
61
- // In questo XML il tag è solitamente <name>
62
51
  const match = data.match(/<name>([^<]+)<\/name>/i);
63
-
64
- if (match && match[1]) {
65
- return match[1].trim(); // Restituirà "Ufficio"
66
- } else {
67
- return `Canale_${cam.channel}`;
68
- }
69
-
70
- } catch (e) {
71
- // Se l'URL sopra fallisce (alcuni modelli vecchi), proviamo questo URL alternativo
72
- try {
73
- const resAlt = await camAuth.request({
74
- method: 'GET',
75
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels/${cam.channel}`,
76
- timeout: 3000
77
- });
78
- const dataAlt = resAlt.data.toString();
79
- const matchAlt = dataAlt.match(/<name>([^<]+)<\/name>/i);
80
- return matchAlt ? matchAlt[1].trim() : `Cam_${cam.channel}`;
81
- } catch (err) {
82
- return `Camera_${cam.ip}`;
83
- }
84
- }
52
+ return match && match[1] ? match[1].trim() : `Canale_${cam.channel}`;
53
+ } catch (e) { return `Camera_${cam.ip}`; }
85
54
  }
86
55
 
56
+ // --- CONTROLLO STATUS CAMERE ---
87
57
  async function checkCameras() {
88
58
  if (isClosing) return;
89
59
  for (let cam of node.cameras) {
90
60
  const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
91
61
  try {
92
62
  await camAuth.request({
93
- method: 'GET',
94
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
95
- timeout: 5000,
63
+ method: 'GET', url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`, timeout: 5000,
96
64
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
97
65
  });
98
66
  if (statoCamera[cam.ip] === false) {
99
67
  const nomeOnline = await getCameraName(cam);
100
- node.send({ payload: { status: "online", nomeCliente: node.name, nome_telecamera: nomeOnline, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
101
- statoCamera[cam.ip] = true;
102
- } else if (statoCamera[cam.ip] === undefined) {
68
+ node.send({ payload: { tipo_messaggio: "status", status: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
103
69
  statoCamera[cam.ip] = true;
104
- }
70
+ } else if (statoCamera[cam.ip] === undefined) { statoCamera[cam.ip] = true; }
105
71
  } catch (e) {
106
72
  if (statoCamera[cam.ip] !== false) {
107
73
  const nomeOffline = await getCameraName(cam);
108
- node.send({ payload: { status: "offline", nomeCliente: node.name, nome_telecamera: nomeOffline, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
74
+ node.send({ payload: { tipo_messaggio: "status", status: "offline", nome_cliente: node.name, nome_telecamera: nomeOffline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
109
75
  statoCamera[cam.ip] = false;
110
76
  }
111
77
  }
@@ -115,17 +81,14 @@ module.exports = function(RED) {
115
81
 
116
82
  function updateNodeStatus() {
117
83
  const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
118
- if (!nvrOnline) {
119
- node.status({fill:"red", shape:"ring", text:"NVR Offline"});
120
- } else if (offlineCams > 0) {
121
- node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
122
- } else {
123
- node.status({fill:"green", shape:"ring", text:"In ascolto"});
124
- }
84
+ if (!nvrOnline) { node.status({fill:"red", shape:"ring", text:"NVR Offline"}); }
85
+ else if (offlineCams > 0) { node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`}); }
86
+ else { node.status({fill:"green", shape:"ring", text:"In ascolto"}); }
125
87
  }
126
88
 
127
89
  const heartbeatInterval = setInterval(checkCameras, 30000);
128
90
 
91
+ // --- DOWNLOAD, CONVERSIONE E TIMEOUT DI ELIMINAZIONE A 2 MINUTI ---
129
92
  async function downloadMedia(evento, channelID) {
130
93
  const camera = node.cameras.find(c => c.channel == channelID);
131
94
  const nomeCamera = await getCameraName(camera);
@@ -145,7 +108,18 @@ module.exports = function(RED) {
145
108
  await new Promise(resolve => setTimeout(resolve, 6000));
146
109
 
147
110
  const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
148
- let output = { ip: camera.ip, nomeCliente: node.name, nome_telecamera: nomeCamera, channel: channelID, event: evento, videoPath: null, imageBuffer: null, imagePath: null };
111
+
112
+ let output = {
113
+ tipo_messaggio: "evento",
114
+ nome_cliente: node.name,
115
+ nome_telecamera: nomeCamera,
116
+ ip_telecamera: camera.ip,
117
+ tipo_evento: evento,
118
+ timestamp_epoch: timestamp,
119
+ stato_telecamera: "ONLINE",
120
+ foto_base64: null,
121
+ video_base64: null
122
+ };
149
123
 
150
124
  try {
151
125
  const tracks = [{ id: "201" }, { id: "203" }];
@@ -162,46 +136,65 @@ module.exports = function(RED) {
162
136
  if (uriMatch) {
163
137
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
164
138
  const resDown = await camAuth.request({
165
- method: 'GET',
166
- url: `${baseUrl}/download`,
139
+ method: 'GET', url: `${baseUrl}/download`,
167
140
  data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
168
141
  responseType: 'arraybuffer'
169
142
  });
170
143
 
171
144
  let buffer = Buffer.from(resDown.data);
145
+
172
146
  if (t.id === "203") {
173
- // --- SALVA FOTO ---
174
- const fullImgPath = path.join(imgDir, `img_${timestamp}.jpg`);
175
- fs.writeFileSync(fullImgPath, buffer);
176
- output.imageBuffer = buffer;
177
- output.imagePath = fullImgPath;
147
+ // Scrittura immagine temporanea
148
+ const tempImgPath = path.join(baseStorage, `temp_${timestamp}.jpg`);
149
+ fs.writeFileSync(tempImgPath, buffer);
150
+
151
+ // Conversione immediata in stringa di testo Base64
152
+ output.foto_base64 = fs.readFileSync(tempImgPath, { encoding: 'base64' });
153
+
154
+ // MODIFICATO: Rimozione file posticipata a 2 minuti (120000 ms)
155
+ setTimeout(() => {
156
+ try { if (fs.existsSync(tempImgPath)) fs.unlinkSync(tempImgPath); } catch(e){}
157
+ }, 120000);
158
+
178
159
  } else {
179
- // --- SALVA VIDEO ---
160
+ // Scrittura video temporaneo
180
161
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
181
- const rawPath = path.join(vidDir, `raw_${timestamp}.mp4`);
182
- const fixedPath = path.join(vidDir, `hik_v_${channelID}_${timestamp}.mp4`);
162
+ const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
163
+ const fixedPath = path.join(baseStorage, `fixed_${timestamp}.mp4`);
183
164
  fs.writeFileSync(rawPath, buffer);
184
165
 
185
166
  await new Promise((resolve) => {
186
167
  exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
187
- if (!err) {
188
- output.videoPath = fixedPath;
189
- try { fs.unlinkSync(rawPath); } catch(e) {}
190
- } else { output.videoPath = rawPath; }
168
+ if (!err && fs.existsSync(fixedPath)) {
169
+ output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
170
+ } else {
171
+ output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
172
+ }
173
+
174
+ // MODIFICATO: Rimozione file video posticipata a 2 minuti (120000 ms)
175
+ setTimeout(() => {
176
+ try { if (fs.existsSync(rawPath)) fs.unlinkSync(rawPath); } catch(e){}
177
+ try { if (fs.existsSync(fixedPath)) fs.unlinkSync(fixedPath); } catch(e){}
178
+ }, 120000);
179
+
191
180
  resolve();
192
181
  });
193
182
  });
194
183
  }
195
184
  }
196
185
  }
197
- if (output.imagePath || output.videoPath) node.send({ payload: output });
186
+
187
+ // Spedisce il payload al flusso di Node-RED
188
+ if (output.foto_base64 || output.video_base64) {
189
+ node.send({ payload: output });
190
+ }
198
191
  } catch (e) {
199
192
  node.error(`Errore Download Cam ${channelID}: ${e.message}`);
200
193
  }
201
194
  updateNodeStatus();
202
195
  }
203
196
 
204
- // --- ALERT STREAM (TUTTO COME PRIMA) ---
197
+ // --- ALERT STREAM ---
205
198
  function startAlertStream() {
206
199
  if (isClosing) return;
207
200
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
@@ -209,7 +202,7 @@ module.exports = function(RED) {
209
202
  nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
210
203
  .then(response => {
211
204
  streamRequest = response;
212
- if (!nvrOnline) { node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } }); nvrOnline = true; }
205
+ if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "online", ip_telecamera: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
213
206
  updateNodeStatus();
214
207
  response.data.on('data', (chunk) => {
215
208
  const data = chunk.toString().toLowerCase();
@@ -228,7 +221,7 @@ module.exports = function(RED) {
228
221
  }
229
222
 
230
223
  function handleNvrError() {
231
- if (nvrOnline) { node.send({ payload: { status: "offline", ip: node.host, msg: "NVR Offline" } }); nvrOnline = false; }
224
+ if (nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "offline", ip_telecamera: node.host, msg: "NVR Offline", nome_cliente: node.name } }); nvrOnline = false; }
232
225
  updateNodeStatus();
233
226
  if (!isClosing) setTimeout(startAlertStream, 10000);
234
227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",