node-red-contrib-hik-media-buffer 1.0.19 → 1.1.1

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 +74 -51
  2. package/package.json +1 -1
@@ -1,5 +1,4 @@
1
- const axios = require('axios');
2
- const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
1
+ const urllib = require('urllib');
3
2
  const https = require('https');
4
3
  const fs = require('fs');
5
4
  const path = require('path');
@@ -11,57 +10,83 @@ module.exports = function(RED) {
11
10
  RED.nodes.createNode(this, config);
12
11
  const node = this;
13
12
 
14
- // --- ASSEGNAZIONE PROPRIETÀ AL NODO ---
13
+ node.name = config.name;
15
14
  node.host = config.host;
16
15
  node.port = config.port || "80";
17
16
  node.protocol = config.protocol || "http";
18
17
  node.user = config.user;
19
18
  node.pass = config.pass;
20
- node.camPass = config.camPass || config.pass; // Usa pass dell'NVR se quella cam specifica non c'è
19
+ node.camPass = config.camPass || config.pass;
21
20
  node.cameras = config.cameras || [];
22
- // --------------------------------------
23
21
 
24
22
  let streamRequest = null;
25
23
  let isClosing = false;
26
24
  let lastTriggerTime = 0;
27
-
28
- // Stati di connessione
29
25
  let nvrOnline = true;
30
- let statoCamera = {}; // Memorizza lo stato { "IP": true/false }
26
+ let statoCamera = {};
31
27
 
32
- const httpsAgent = new https.Agent({ rejectUnauthorized: false });
33
28
  const tempDir = os.tmpdir();
34
29
  const EventList = ["FieldDetection", "LineDetection"];
35
30
 
36
31
  node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
37
32
 
38
33
  function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
39
-
40
- // --- CONTROLLO SE LE TELECAMERE SONO ONLINE ---
34
+
35
+ // --- HELPER AGGIORNATO: Solo Urllib con Digest ---
36
+ async function hikRequest(options) {
37
+ const { method, url, data, responseType, user, pass, headers = {} } = options;
38
+
39
+ const res = await urllib.request(url, {
40
+ method: method,
41
+ digestAuth: `${user}:${pass}`,
42
+ content: data,
43
+ headers: headers,
44
+ dataType: responseType === 'arraybuffer' ? 'buffer' : 'text',
45
+ timeout: 15000,
46
+ rejectUnauthorized: false
47
+ });
48
+
49
+ // Urllib restituisce i dati in .data
50
+ return res;
51
+ }
52
+
53
+ async function getCameraName(cam) {
54
+ try {
55
+ const res = await hikRequest({
56
+ method: 'GET',
57
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`,
58
+ user: node.user,
59
+ pass: node.camPass
60
+ });
61
+ const match = res.data.toString().match(/<name>([^<]+)<\/name>/);
62
+ return match ? match[1] : `Cam_${cam.channel}`;
63
+ } catch (e) {
64
+ return `Camera_${cam.ip}`;
65
+ }
66
+ }
67
+
41
68
  async function checkCameras() {
42
69
  if (isClosing) return;
43
70
  for (let cam of node.cameras) {
44
- const camAuth = new AxiosDigestAuth({
45
- username: node.user,
46
- password: node.camPass
47
- });
48
71
  try {
49
- await camAuth.request({
72
+ await hikRequest({
50
73
  method: 'GET',
51
74
  url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
52
- timeout: 5000,
53
- httpsAgent: node.protocol === "https" ? httpsAgent : undefined
75
+ user: node.user,
76
+ pass: node.camPass
54
77
  });
55
78
 
56
79
  if (statoCamera[cam.ip] === false) {
57
- node.send({ payload: { status: "online", ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
80
+ const nomeOnline = await getCameraName(cam);
81
+ node.send({ payload: { status: "online", nomeCliente: node.name, nome_telecamera: nomeOnline, ip: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
58
82
  statoCamera[cam.ip] = true;
59
83
  } else if (statoCamera[cam.ip] === undefined) {
60
84
  statoCamera[cam.ip] = true;
61
85
  }
62
86
  } catch (e) {
63
87
  if (statoCamera[cam.ip] !== false) {
64
- node.send({ payload: { status: "offline", ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
88
+ const nomeOffline = await getCameraName(cam);
89
+ node.send({ payload: { status: "offline", nomeCliente: node.name, nome_telecamera: nomeOffline, ip: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
65
90
  statoCamera[cam.ip] = false;
66
91
  }
67
92
  }
@@ -82,7 +107,6 @@ module.exports = function(RED) {
82
107
 
83
108
  const heartbeatInterval = setInterval(checkCameras, 30000);
84
109
 
85
- // --- FUNZIONE DOWNLOAD ---
86
110
  async function downloadMedia(evento, channelID) {
87
111
  const camera = node.cameras.find(c => c.channel == channelID);
88
112
  if (!camera) return;
@@ -91,10 +115,7 @@ module.exports = function(RED) {
91
115
  if (nowTime - lastTriggerTime < 10000) return;
92
116
  lastTriggerTime = nowTime;
93
117
 
94
- const camAuth = new AxiosDigestAuth({
95
- username: node.user,
96
- password: node.camPass
97
- });
118
+ const nomeCamera = await getCameraName(camera);
98
119
  const referenceTime = new Date();
99
120
  const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
100
121
  const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
@@ -103,9 +124,10 @@ module.exports = function(RED) {
103
124
  await new Promise(resolve => setTimeout(resolve, 6000));
104
125
 
105
126
  const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
106
- let output = { ip: camera.ip, channel: channelID, event: evento, videoPath: null, imageBuffer: null };
127
+ let output = { ip: camera.ip, nomeCliente: node.name, nome_telecamera: nomeCamera, channel: channelID, event: evento, videoPath: null, imageBuffer: null };
107
128
 
108
129
  try {
130
+ // RIPRISTINATA LA TUA LOGICA DEI TRACKS
109
131
  const tracks = [{ name: "termicoV", id: "201" }, { name: "termico", id: "203" }];
110
132
  for (let t of tracks) {
111
133
  const searchXml = `<?xml version="1.0" encoding="utf-8"?>
@@ -114,26 +136,30 @@ module.exports = function(RED) {
114
136
  <trackIDList><trackID>${t.id}</trackID></trackIDList>
115
137
  <timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList>
116
138
  <maxResults>100</maxResults>
117
- <searchResultPostion>0</searchResultPostion>
118
139
  <metadataList><metadataDescriptor>//recordType.meta.std-cgi.com/${evento}</metadataDescriptor></metadataList>
119
140
  </CMSearchDescription>`;
120
-
121
- const resSearch = await camAuth.request({
141
+
142
+ const resSearch = await hikRequest({
122
143
  method: 'POST',
123
- url: `${baseUrl}/search`,
124
- data: searchXml,
125
- headers: { "Content-Type": "application/xml" }
144
+ url: `${baseUrl}/search`,
145
+ data: searchXml,
146
+ headers: { "Content-Type": "application/xml" },
147
+ user: node.user,
148
+ pass: node.camPass
126
149
  });
127
150
 
128
- let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
151
+ let xml = resSearch.data.toString().replace(/<(\/?)\w+:/g, "<$1");
129
152
  const uriMatch = xml.match(/<playbackURI>([^<]+)</);
130
153
 
131
154
  if (uriMatch) {
132
155
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
133
- const resDown = await camAuth.request({
134
- method: 'GET', url: `${baseUrl}/download`,
135
- data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
136
- responseType: 'arraybuffer'
156
+ const resDown = await hikRequest({
157
+ method: 'GET',
158
+ url: `${baseUrl}/download`,
159
+ data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
160
+ responseType: 'arraybuffer',
161
+ user: node.user,
162
+ pass: node.camPass
137
163
  });
138
164
 
139
165
  let buffer = Buffer.from(resDown.data);
@@ -144,6 +170,7 @@ module.exports = function(RED) {
144
170
  const rawPath = path.join(tempDir, `raw_${Date.now()}.mp4`);
145
171
  const fixedPath = path.join(tempDir, `hik_v_${channelID}_${Date.now()}.mp4`);
146
172
  fs.writeFileSync(rawPath, buffer);
173
+
147
174
  await new Promise((resolve) => {
148
175
  exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
149
176
  if (!err) {
@@ -164,27 +191,24 @@ module.exports = function(RED) {
164
191
  updateNodeStatus();
165
192
  }
166
193
 
167
- // --- GESTIONE NVR ALERT STREAM ---
168
194
  function startAlertStream() {
169
195
  if (isClosing) return;
170
- const nvrAuth = new AxiosDigestAuth({
171
- username: node.user,
172
- password: node.pass
173
- });
174
196
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
175
197
 
176
- nvrAuth.request({
177
- method: 'GET', url: url, responseType: 'stream',
178
- httpsAgent: node.protocol === "https" ? httpsAgent : undefined
179
- }).then(response => {
180
- streamRequest = response;
198
+ urllib.request(url, {
199
+ method: 'GET',
200
+ digestAuth: `${node.user}:${node.pass}`,
201
+ streaming: true,
202
+ timeout: 60000,
203
+ rejectUnauthorized: false
204
+ }).then(res => {
181
205
  if (!nvrOnline) {
182
206
  node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } });
183
207
  nvrOnline = true;
184
208
  }
185
209
  updateNodeStatus();
186
210
 
187
- response.data.on('data', (chunk) => {
211
+ res.res.on('data', (chunk) => {
188
212
  const data = chunk.toString().toLowerCase();
189
213
  if (data.includes("active")) {
190
214
  const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
@@ -196,8 +220,8 @@ module.exports = function(RED) {
196
220
  }
197
221
  });
198
222
 
199
- response.data.on('error', () => handleNvrError());
200
- response.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
223
+ res.res.on('error', () => handleNvrError());
224
+ res.res.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
201
225
  }).catch(() => handleNvrError());
202
226
  }
203
227
 
@@ -216,7 +240,6 @@ module.exports = function(RED) {
216
240
  node.on('close', (done) => {
217
241
  isClosing = true;
218
242
  clearInterval(heartbeatInterval);
219
- if (streamRequest) streamRequest.data.destroy();
220
243
  done();
221
244
  });
222
245
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.0.19",
3
+ "version": "1.1.1",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",