node-red-contrib-hik-media-buffer 1.1.15 → 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 +68 -35
  2. package/package.json +1 -1
@@ -29,7 +29,7 @@ module.exports = function(RED) {
29
29
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
30
30
  const EventList = ["FieldDetection", "LineDetection"];
31
31
 
32
- // BASE STORAGE TEMPORANEA
32
+ // --- CARTELLA TEMPORANEA SUL PC DEL CLIENTE ---
33
33
  const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
34
34
  if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
35
35
 
@@ -39,7 +39,11 @@ module.exports = function(RED) {
39
39
 
40
40
  // --- PRENDE IL NOME DELLA TELECAMERA ---
41
41
  async function getCameraName(cam) {
42
- const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
42
+ const camAuth = new AxiosDigestAuth({
43
+ username: node.user,
44
+ password: node.camPass
45
+ });
46
+
43
47
  try {
44
48
  const res = await camAuth.request({
45
49
  method: 'GET',
@@ -47,10 +51,19 @@ module.exports = function(RED) {
47
51
  timeout: 5000,
48
52
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
49
53
  });
54
+
50
55
  const data = res.data.toString();
51
56
  const match = data.match(/<name>([^<]+)<\/name>/i);
52
- return match && match[1] ? match[1].trim() : `Canale_${cam.channel}`;
53
- } catch (e) { return `Camera_${cam.ip}`; }
57
+
58
+ if (match && match[1]) {
59
+ return match[1].trim();
60
+ } else {
61
+ return `Canale_${cam.channel}`;
62
+ }
63
+
64
+ } catch (e) {
65
+ return `Camera_${cam.ip}`;
66
+ }
54
67
  }
55
68
 
56
69
  // --- CONTROLLO STATUS CAMERE ---
@@ -60,14 +73,18 @@ module.exports = function(RED) {
60
73
  const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
61
74
  try {
62
75
  await camAuth.request({
63
- method: 'GET', url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`, timeout: 5000,
76
+ method: 'GET',
77
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
78
+ timeout: 5000,
64
79
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
65
80
  });
66
81
  if (statoCamera[cam.ip] === false) {
67
82
  const nomeOnline = await getCameraName(cam);
68
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" } });
69
84
  statoCamera[cam.ip] = true;
70
- } else if (statoCamera[cam.ip] === undefined) { statoCamera[cam.ip] = true; }
85
+ } else if (statoCamera[cam.ip] === undefined) {
86
+ statoCamera[cam.ip] = true;
87
+ }
71
88
  } catch (e) {
72
89
  if (statoCamera[cam.ip] !== false) {
73
90
  const nomeOffline = await getCameraName(cam);
@@ -81,14 +98,18 @@ module.exports = function(RED) {
81
98
 
82
99
  function updateNodeStatus() {
83
100
  const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
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"}); }
101
+ if (!nvrOnline) {
102
+ node.status({fill:"red", shape:"ring", text:"NVR Offline"});
103
+ } else if (offlineCams > 0) {
104
+ node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
105
+ } else {
106
+ node.status({fill:"green", shape:"ring", text:"In ascolto"});
107
+ }
87
108
  }
88
109
 
89
110
  const heartbeatInterval = setInterval(checkCameras, 30000);
90
111
 
91
- // --- DOWNLOAD, CONVERSIONE E TIMEOUT DI ELIMINAZIONE A 2 MINUTI ---
112
+ // --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
92
113
  async function downloadMedia(evento, channelID) {
93
114
  const camera = node.cameras.find(c => c.channel == channelID);
94
115
  const nomeCamera = await getCameraName(camera);
@@ -109,6 +130,7 @@ module.exports = function(RED) {
109
130
 
110
131
  const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
111
132
 
133
+ // Prepariamo la struttura del payload per Python
112
134
  let output = {
113
135
  tipo_messaggio: "evento",
114
136
  nome_cliente: node.name,
@@ -117,10 +139,14 @@ module.exports = function(RED) {
117
139
  tipo_evento: evento,
118
140
  timestamp_epoch: timestamp,
119
141
  stato_telecamera: "ONLINE",
120
- foto_base64: null,
121
- video_base64: null
142
+ channel: channelID.toString(),
143
+ foto_base64: null,
144
+ video_base64: null
122
145
  };
123
146
 
147
+ // Teniamo traccia dei percorsi per poterli cancellare tra 2 minuti
148
+ let fileDaCancellare = [];
149
+
124
150
  try {
125
151
  const tracks = [{ id: "201" }, { id: "203" }];
126
152
  for (let t of tracks) {
@@ -136,58 +162,65 @@ module.exports = function(RED) {
136
162
  if (uriMatch) {
137
163
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
138
164
  const resDown = await camAuth.request({
139
- method: 'GET', url: `${baseUrl}/download`,
165
+ method: 'GET',
166
+ url: `${baseUrl}/download`,
140
167
  data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
141
168
  responseType: 'arraybuffer'
142
169
  });
143
170
 
144
171
  let buffer = Buffer.from(resDown.data);
145
-
146
172
  if (t.id === "203") {
147
- // Scrittura immagine temporanea
148
- const tempImgPath = path.join(baseStorage, `temp_${timestamp}.jpg`);
149
- fs.writeFileSync(tempImgPath, buffer);
173
+ // --- SALVA FOTO IN LOCALE COME PRIMA ---
174
+ const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
175
+ fs.writeFileSync(fullImgPath, buffer);
150
176
 
151
- // Conversione immediata in stringa di testo Base64
152
- output.foto_base64 = fs.readFileSync(tempImgPath, { encoding: 'base64' });
177
+ // La convertiamo subito in testo per il payload
178
+ output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
153
179
 
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
-
180
+ // Registriamo il file per la distruzione futura
181
+ fileDaCancellare.push(fullImgPath);
159
182
  } else {
160
- // Scrittura video temporaneo
183
+ // --- SALVA VIDEO IN LOCALE COME PRIMA ---
161
184
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
162
185
  const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
163
- const fixedPath = path.join(baseStorage, `fixed_${timestamp}.mp4`);
186
+ const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
164
187
  fs.writeFileSync(rawPath, buffer);
165
188
 
189
+ // Eseguiamo ffmpeg localmente sul PC del cliente
166
190
  await new Promise((resolve) => {
167
191
  exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
168
192
  if (!err && fs.existsSync(fixedPath)) {
169
193
  output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
194
+ fileDaCancellare.push(fixedPath);
170
195
  } else {
171
196
  output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
172
197
  }
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
-
198
+ fileDaCancellare.push(rawPath);
180
199
  resolve();
181
200
  });
182
201
  });
183
202
  }
184
203
  }
185
204
  }
186
-
187
- // Spedisce il payload al flusso di Node-RED
205
+
206
+ // Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
188
207
  if (output.foto_base64 || output.video_base64) {
189
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
190
222
  }
223
+
191
224
  } catch (e) {
192
225
  node.error(`Errore Download Cam ${channelID}: ${e.message}`);
193
226
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.15",
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",