node-red-contrib-hik-media-buffer 1.1.6 → 1.1.7

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 +42 -48
  2. package/package.json +1 -1
@@ -1,6 +1,9 @@
1
- const axiosDigest = require('axios-digest');
1
+ const axios = require('axios');
2
+ const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
3
+ const https = require('https');
2
4
  const fs = require('fs');
3
5
  const path = require('path');
6
+ const os = require('os');
4
7
  const { exec } = require('child_process');
5
8
 
6
9
  module.exports = function(RED) {
@@ -8,7 +11,7 @@ module.exports = function(RED) {
8
11
  RED.nodes.createNode(this, config);
9
12
  const node = this;
10
13
 
11
- node.name = config.name || "TEST";
14
+ node.name = config.name || "TEST"; // Nome cliente usato per le cartelle
12
15
  node.host = config.host;
13
16
  node.port = config.port || "80";
14
17
  node.protocol = config.protocol || "http";
@@ -17,61 +20,48 @@ module.exports = function(RED) {
17
20
  node.camPass = config.camPass || config.pass;
18
21
  node.cameras = config.cameras || [];
19
22
 
23
+ let streamRequest = null;
20
24
  let isClosing = false;
21
25
  let lastTriggerTime = 0;
22
26
  let nvrOnline = true;
23
27
  let statoCamera = {};
24
28
 
29
+ const httpsAgent = new https.Agent({ rejectUnauthorized: false });
25
30
  const EventList = ["FieldDetection", "LineDetection"];
26
31
 
27
- // --- DEFINIZIONE PERCORSI STORAGE ---
32
+ // --- CONFIGURAZIONE PERCORSI STORAGE ---
28
33
  const baseStorage = `C:\\Users\\APerucca\\Documents\\progetto-docker\\storage\\${node.name}`;
29
34
  const imgDir = path.join(baseStorage, "allarmi");
30
35
  const vidDir = path.join(baseStorage, "video");
31
36
 
37
+ // Crea le cartelle se non esistono
32
38
  if (!fs.existsSync(imgDir)) fs.mkdirSync(imgDir, { recursive: true });
33
39
  if (!fs.existsSync(vidDir)) fs.mkdirSync(vidDir, { recursive: true });
34
40
 
35
- // Client Digest per NVR e Camere
36
- const nvrAuth = axiosDigest(node.user, node.pass);
37
-
38
41
  node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
39
42
 
40
43
  function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
41
44
 
42
- async function getCameraName(cam) {
43
- try {
44
- const res = await nvrAuth.request({
45
- method: 'GET',
46
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`
47
- });
48
- const match = res.data.toString().match(/<name>([^<]+)<\/name>/);
49
- return match ? match[1] : `Cam_${cam.channel}`;
50
- } catch (e) {
51
- return `Camera_${cam.ip}`;
52
- }
53
- }
54
-
55
45
  async function checkCameras() {
56
46
  if (isClosing) return;
57
47
  for (let cam of node.cameras) {
48
+ const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
58
49
  try {
59
- await nvrAuth.request({
50
+ await camAuth.request({
60
51
  method: 'GET',
61
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`
52
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
53
+ timeout: 5000,
54
+ httpsAgent: node.protocol === "https" ? httpsAgent : undefined
62
55
  });
63
-
64
56
  if (statoCamera[cam.ip] === false) {
65
- const nomeOnline = await getCameraName(cam);
66
- node.send({ payload: { status: "online", nomeCliente: node.name, nome_telecamera: nomeOnline, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
57
+ node.send({ payload: { status: "online", nomeCliente: node.name, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
67
58
  statoCamera[cam.ip] = true;
68
59
  } else if (statoCamera[cam.ip] === undefined) {
69
60
  statoCamera[cam.ip] = true;
70
61
  }
71
62
  } catch (e) {
72
63
  if (statoCamera[cam.ip] !== false) {
73
- const nomeOffline = await getCameraName(cam);
74
- node.send({ payload: { status: "offline", nomeCliente: node.name, nome_telecamera: nomeOffline, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
64
+ node.send({ payload: { status: "offline", nomeCliente: node.name, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
75
65
  statoCamera[cam.ip] = false;
76
66
  }
77
67
  }
@@ -100,51 +90,50 @@ module.exports = function(RED) {
100
90
  if (nowTime - lastTriggerTime < 10000) return;
101
91
  lastTriggerTime = nowTime;
102
92
 
103
- const timestamp = Math.floor(nowTime / 1000);
104
- const nomeCamera = await getCameraName(camera);
93
+ const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
105
94
  const referenceTime = new Date();
95
+ const timestamp = Math.floor(referenceTime.getTime() / 1000);
106
96
  const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
107
97
  const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
108
98
 
109
- const camAuth = axiosDigest(node.user, node.camPass);
110
- const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
111
-
112
99
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
113
100
  await new Promise(resolve => setTimeout(resolve, 6000));
114
101
 
115
- let output = { ip: camera.ip, nomeCliente: node.name, nome_telecamera: nomeCamera, channel: channelID, event: evento, videoPath: null, imagePath: null };
102
+ const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
103
+ let output = { ip: camera.ip, nomeCliente: node.name, channel: channelID, event: evento, videoPath: null, imageBuffer: null, imagePath: null };
116
104
 
117
105
  try {
118
106
  const tracks = [{ id: "201" }, { id: "203" }];
119
107
  for (let t of tracks) {
120
108
  const 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><maxResults>100</maxResults><metadataList><metadataDescriptor>//recordType.meta.std-cgi.com/${evento}</metadataDescriptor></metadataList></CMSearchDescription>`;
121
-
122
- const resSearch = await camAuth.request({
123
- method: 'POST', url: `${baseUrl}/search`, data: searchXml,
124
- headers: { "Content-Type": "application/xml" }
109
+
110
+ const resSearch = await camAuth.request({
111
+ method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
125
112
  });
126
113
 
127
- let xml = resSearch.data.toString().replace(/<(\/?)\w+:/g, "<$1");
114
+ let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
128
115
  const uriMatch = xml.match(/<playbackURI>([^<]+)</);
129
116
 
130
117
  if (uriMatch) {
131
118
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
132
- const resDown = await camAuth.request({
133
- method: 'POST', // POST risolve l'errore "Invalid XML"
134
- url: `${baseUrl}/download`,
135
- data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
119
+ const resDown = await camAuth.request({
120
+ method: 'GET',
121
+ url: `${baseUrl}/download`,
122
+ data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
136
123
  responseType: 'arraybuffer',
137
124
  headers: { "Content-Type": "application/xml" }
138
125
  });
139
126
 
140
127
  let buffer = Buffer.from(resDown.data);
128
+
141
129
  if (t.id === "203") {
142
- // FOTO
130
+ // --- SALVATAGGIO FOTO ---
143
131
  const fullImgPath = path.join(imgDir, `img_${timestamp}.jpg`);
144
132
  fs.writeFileSync(fullImgPath, buffer);
133
+ output.imageBuffer = buffer; // Mantengo il buffer per compatibilità
145
134
  output.imagePath = fullImgPath;
146
135
  } else {
147
- // VIDEO
136
+ // --- SALVATAGGIO VIDEO ---
148
137
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
149
138
  const rawPath = path.join(vidDir, `raw_${timestamp}.mp4`);
150
139
  const fixedPath = path.join(vidDir, `hik_v_${channelID}_${timestamp}.mp4`);
@@ -172,17 +161,21 @@ module.exports = function(RED) {
172
161
 
173
162
  function startAlertStream() {
174
163
  if (isClosing) return;
164
+ const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
175
165
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
176
166
 
177
- nvrAuth.request({ method: 'GET', url: url, responseType: 'stream' })
178
- .then(res => {
167
+ nvrAuth.request({
168
+ method: 'GET', url: url, responseType: 'stream',
169
+ httpsAgent: node.protocol === "https" ? httpsAgent : undefined
170
+ }).then(response => {
171
+ streamRequest = response;
179
172
  if (!nvrOnline) {
180
173
  node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } });
181
174
  nvrOnline = true;
182
175
  }
183
176
  updateNodeStatus();
184
177
 
185
- res.data.on('data', (chunk) => {
178
+ response.data.on('data', (chunk) => {
186
179
  const data = chunk.toString().toLowerCase();
187
180
  if (data.includes("active")) {
188
181
  const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
@@ -194,8 +187,8 @@ module.exports = function(RED) {
194
187
  }
195
188
  });
196
189
 
197
- res.data.on('error', () => handleNvrError());
198
- res.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
190
+ response.data.on('error', () => handleNvrError());
191
+ response.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
199
192
  }).catch(() => handleNvrError());
200
193
  }
201
194
 
@@ -214,6 +207,7 @@ module.exports = function(RED) {
214
207
  node.on('close', (done) => {
215
208
  isClosing = true;
216
209
  clearInterval(heartbeatInterval);
210
+ if (streamRequest) streamRequest.data.destroy();
217
211
  done();
218
212
  });
219
213
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",