node-red-contrib-hik-media-buffer 1.1.14 → 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 +64 -60
  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,59 +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 l'overlay del nome del canale specifico
52
44
  const res = await camAuth.request({
53
45
  method: 'GET',
54
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
- // Il manuale specifica che il nome è nel tag <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
- // Fallback: se non risponde, usiamo l'IP
72
- return `Camera_${cam.ip}`;
73
- }
52
+ return match && match[1] ? match[1].trim() : `Canale_${cam.channel}`;
53
+ } catch (e) { return `Camera_${cam.ip}`; }
74
54
  }
75
55
 
56
+ // --- CONTROLLO STATUS CAMERE ---
76
57
  async function checkCameras() {
77
58
  if (isClosing) return;
78
59
  for (let cam of node.cameras) {
79
60
  const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
80
61
  try {
81
62
  await camAuth.request({
82
- method: 'GET',
83
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
84
- timeout: 5000,
63
+ method: 'GET', url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`, timeout: 5000,
85
64
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
86
65
  });
87
66
  if (statoCamera[cam.ip] === false) {
88
67
  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" } });
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" } });
90
69
  statoCamera[cam.ip] = true;
91
- } else if (statoCamera[cam.ip] === undefined) {
92
- statoCamera[cam.ip] = true;
93
- }
70
+ } else if (statoCamera[cam.ip] === undefined) { statoCamera[cam.ip] = true; }
94
71
  } catch (e) {
95
72
  if (statoCamera[cam.ip] !== false) {
96
73
  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" } });
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" } });
98
75
  statoCamera[cam.ip] = false;
99
76
  }
100
77
  }
@@ -104,17 +81,14 @@ module.exports = function(RED) {
104
81
 
105
82
  function updateNodeStatus() {
106
83
  const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
107
- if (!nvrOnline) {
108
- node.status({fill:"red", shape:"ring", text:"NVR Offline"});
109
- } else if (offlineCams > 0) {
110
- node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
111
- } else {
112
- node.status({fill:"green", shape:"ring", text:"In ascolto"});
113
- }
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"}); }
114
87
  }
115
88
 
116
89
  const heartbeatInterval = setInterval(checkCameras, 30000);
117
90
 
91
+ // --- DOWNLOAD, CONVERSIONE E TIMEOUT DI ELIMINAZIONE A 2 MINUTI ---
118
92
  async function downloadMedia(evento, channelID) {
119
93
  const camera = node.cameras.find(c => c.channel == channelID);
120
94
  const nomeCamera = await getCameraName(camera);
@@ -134,7 +108,18 @@ module.exports = function(RED) {
134
108
  await new Promise(resolve => setTimeout(resolve, 6000));
135
109
 
136
110
  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 };
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
+ };
138
123
 
139
124
  try {
140
125
  const tracks = [{ id: "201" }, { id: "203" }];
@@ -151,46 +136,65 @@ module.exports = function(RED) {
151
136
  if (uriMatch) {
152
137
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
153
138
  const resDown = await camAuth.request({
154
- method: 'GET',
155
- url: `${baseUrl}/download`,
139
+ method: 'GET', url: `${baseUrl}/download`,
156
140
  data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
157
141
  responseType: 'arraybuffer'
158
142
  });
159
143
 
160
144
  let buffer = Buffer.from(resDown.data);
145
+
161
146
  if (t.id === "203") {
162
- // --- SALVA FOTO ---
163
- const fullImgPath = path.join(imgDir, `img_${timestamp}.jpg`);
164
- fs.writeFileSync(fullImgPath, buffer);
165
- output.imageBuffer = buffer;
166
- 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
+
167
159
  } else {
168
- // --- SALVA VIDEO ---
160
+ // Scrittura video temporaneo
169
161
  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`);
162
+ const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
163
+ const fixedPath = path.join(baseStorage, `fixed_${timestamp}.mp4`);
172
164
  fs.writeFileSync(rawPath, buffer);
173
165
 
174
166
  await new Promise((resolve) => {
175
167
  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; }
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
+
180
180
  resolve();
181
181
  });
182
182
  });
183
183
  }
184
184
  }
185
185
  }
186
- 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
+ }
187
191
  } catch (e) {
188
192
  node.error(`Errore Download Cam ${channelID}: ${e.message}`);
189
193
  }
190
194
  updateNodeStatus();
191
195
  }
192
196
 
193
- // --- ALERT STREAM (TUTTO COME PRIMA) ---
197
+ // --- ALERT STREAM ---
194
198
  function startAlertStream() {
195
199
  if (isClosing) return;
196
200
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
@@ -198,7 +202,7 @@ module.exports = function(RED) {
198
202
  nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
199
203
  .then(response => {
200
204
  streamRequest = response;
201
- 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; }
202
206
  updateNodeStatus();
203
207
  response.data.on('data', (chunk) => {
204
208
  const data = chunk.toString().toLowerCase();
@@ -217,7 +221,7 @@ module.exports = function(RED) {
217
221
  }
218
222
 
219
223
  function handleNvrError() {
220
- 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; }
221
225
  updateNodeStatus();
222
226
  if (!isClosing) setTimeout(startAlertStream, 10000);
223
227
  }
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.15",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",