node-red-contrib-hik-media-buffer 1.1.14 → 1.1.16

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 +66 -29
  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
+ // --- CARTELLA TEMPORANEA SUL PC DEL CLIENTE ---
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
 
@@ -48,7 +45,6 @@ module.exports = function(RED) {
48
45
  });
49
46
 
50
47
  try {
51
- // Interroghiamo l'overlay del nome del canale specifico
52
48
  const res = await camAuth.request({
53
49
  method: 'GET',
54
50
  url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
@@ -57,22 +53,20 @@ module.exports = function(RED) {
57
53
  });
58
54
 
59
55
  const data = res.data.toString();
60
-
61
- // Il manuale specifica che il nome è nel tag <name>
62
56
  const match = data.match(/<name>([^<]+)<\/name>/i);
63
57
 
64
58
  if (match && match[1]) {
65
- return match[1].trim(); // Restituirà "Ufficio"
59
+ return match[1].trim();
66
60
  } else {
67
61
  return `Canale_${cam.channel}`;
68
62
  }
69
63
 
70
64
  } catch (e) {
71
- // Fallback: se non risponde, usiamo l'IP
72
65
  return `Camera_${cam.ip}`;
73
66
  }
74
67
  }
75
68
 
69
+ // --- CONTROLLO STATUS CAMERE ---
76
70
  async function checkCameras() {
77
71
  if (isClosing) return;
78
72
  for (let cam of node.cameras) {
@@ -86,7 +80,7 @@ module.exports = function(RED) {
86
80
  });
87
81
  if (statoCamera[cam.ip] === false) {
88
82
  const nomeOnline = await getCameraName(cam);
89
- node.send({ payload: { status: "online", nomeCliente: node.name, nome_telecamera: nomeOnline, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
83
+ 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" } });
90
84
  statoCamera[cam.ip] = true;
91
85
  } else if (statoCamera[cam.ip] === undefined) {
92
86
  statoCamera[cam.ip] = true;
@@ -94,7 +88,7 @@ module.exports = function(RED) {
94
88
  } catch (e) {
95
89
  if (statoCamera[cam.ip] !== false) {
96
90
  const nomeOffline = await getCameraName(cam);
97
- node.send({ payload: { status: "offline", nomeCliente: node.name, nome_telecamera: nomeOffline, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
91
+ 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" } });
98
92
  statoCamera[cam.ip] = false;
99
93
  }
100
94
  }
@@ -115,6 +109,7 @@ module.exports = function(RED) {
115
109
 
116
110
  const heartbeatInterval = setInterval(checkCameras, 30000);
117
111
 
112
+ // --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
118
113
  async function downloadMedia(evento, channelID) {
119
114
  const camera = node.cameras.find(c => c.channel == channelID);
120
115
  const nomeCamera = await getCameraName(camera);
@@ -134,7 +129,23 @@ module.exports = function(RED) {
134
129
  await new Promise(resolve => setTimeout(resolve, 6000));
135
130
 
136
131
  const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
137
- let output = { ip: camera.ip, nomeCliente: node.name, nome_telecamera: nomeCamera, channel: channelID, event: evento, videoPath: null, imageBuffer: null, imagePath: null };
132
+
133
+ // Prepariamo la struttura del payload per Python
134
+ let output = {
135
+ tipo_messaggio: "evento",
136
+ nome_cliente: node.name,
137
+ nome_telecamera: nomeCamera,
138
+ ip_telecamera: camera.ip,
139
+ tipo_evento: evento,
140
+ timestamp_epoch: timestamp,
141
+ stato_telecamera: "ONLINE",
142
+ channel: channelID.toString(),
143
+ foto_base64: null,
144
+ video_base64: null
145
+ };
146
+
147
+ // Teniamo traccia dei percorsi per poterli cancellare tra 2 minuti
148
+ let fileDaCancellare = [];
138
149
 
139
150
  try {
140
151
  const tracks = [{ id: "201" }, { id: "203" }];
@@ -159,38 +170,64 @@ module.exports = function(RED) {
159
170
 
160
171
  let buffer = Buffer.from(resDown.data);
161
172
  if (t.id === "203") {
162
- // --- SALVA FOTO ---
163
- const fullImgPath = path.join(imgDir, `img_${timestamp}.jpg`);
173
+ // --- SALVA FOTO IN LOCALE COME PRIMA ---
174
+ const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
164
175
  fs.writeFileSync(fullImgPath, buffer);
165
- output.imageBuffer = buffer;
166
- output.imagePath = fullImgPath;
176
+
177
+ // La convertiamo subito in testo per il payload
178
+ output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
179
+
180
+ // Registriamo il file per la distruzione futura
181
+ fileDaCancellare.push(fullImgPath);
167
182
  } else {
168
- // --- SALVA VIDEO ---
183
+ // --- SALVA VIDEO IN LOCALE COME PRIMA ---
169
184
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
170
- const rawPath = path.join(vidDir, `raw_${timestamp}.mp4`);
171
- const fixedPath = path.join(vidDir, `hik_v_${channelID}_${timestamp}.mp4`);
185
+ const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
186
+ const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
172
187
  fs.writeFileSync(rawPath, buffer);
173
188
 
189
+ // Eseguiamo ffmpeg localmente sul PC del cliente
174
190
  await new Promise((resolve) => {
175
191
  exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
176
- if (!err) {
177
- output.videoPath = fixedPath;
178
- try { fs.unlinkSync(rawPath); } catch(e) {}
179
- } else { output.videoPath = rawPath; }
192
+ if (!err && fs.existsSync(fixedPath)) {
193
+ output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
194
+ fileDaCancellare.push(fixedPath);
195
+ } else {
196
+ output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
197
+ }
198
+ fileDaCancellare.push(rawPath);
180
199
  resolve();
181
200
  });
182
201
  });
183
202
  }
184
203
  }
185
204
  }
186
- if (output.imagePath || output.videoPath) node.send({ payload: output });
205
+
206
+ // Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
207
+ if (output.foto_base64 || output.video_base64) {
208
+ node.send({ payload: output });
209
+
210
+ // --- TIMER RIGIDO A 2 MINUTI PER LA PULIZIA DEL DISCO ---
211
+ setTimeout(() => {
212
+ for (let file of fileDaCancellare) {
213
+ try {
214
+ if (fs.existsSync(file)) {
215
+ fs.unlinkSync(file);
216
+ }
217
+ } catch (err) {
218
+ node.error(`Errore durante la pulizia del file temporaneo ${file}: ${err.message}`);
219
+ }
220
+ }
221
+ }, 120000); // 120.000 ms = 2 minuti esatti
222
+ }
223
+
187
224
  } catch (e) {
188
225
  node.error(`Errore Download Cam ${channelID}: ${e.message}`);
189
226
  }
190
227
  updateNodeStatus();
191
228
  }
192
229
 
193
- // --- ALERT STREAM (TUTTO COME PRIMA) ---
230
+ // --- ALERT STREAM ---
194
231
  function startAlertStream() {
195
232
  if (isClosing) return;
196
233
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
@@ -198,7 +235,7 @@ module.exports = function(RED) {
198
235
  nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
199
236
  .then(response => {
200
237
  streamRequest = response;
201
- if (!nvrOnline) { node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } }); nvrOnline = true; }
238
+ if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "online", ip_telecamera: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
202
239
  updateNodeStatus();
203
240
  response.data.on('data', (chunk) => {
204
241
  const data = chunk.toString().toLowerCase();
@@ -217,7 +254,7 @@ module.exports = function(RED) {
217
254
  }
218
255
 
219
256
  function handleNvrError() {
220
- if (nvrOnline) { node.send({ payload: { status: "offline", ip: node.host, msg: "NVR Offline" } }); nvrOnline = false; }
257
+ if (nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "offline", ip_telecamera: node.host, msg: "NVR Offline", nome_cliente: node.name } }); nvrOnline = false; }
221
258
  updateNodeStatus();
222
259
  if (!isClosing) setTimeout(startAlertStream, 10000);
223
260
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",