node-red-contrib-hik-media-buffer 1.1.4 → 1.1.5

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 +37 -83
  2. package/package.json +1 -1
@@ -1,8 +1,6 @@
1
- const urllib = require('urllib');
2
- const https = require('https');
1
+ const axiosDigestAuth = require('axios-digest-auth');
3
2
  const fs = require('fs');
4
3
  const path = require('path');
5
- const os = require('os');
6
4
  const { exec } = require('child_process');
7
5
 
8
6
  module.exports = function(RED) {
@@ -19,18 +17,6 @@ module.exports = function(RED) {
19
17
  node.camPass = config.camPass || config.pass;
20
18
  node.cameras = config.cameras || [];
21
19
 
22
- // --- DEFINIZIONE PERCORSI PROGETTO ---
23
- // Puntiamo direttamente alla cartella del tuo progetto Docker
24
- const baseStorage = `C:\\Users\\APerucca\\Documents\\progetto-docker\\storage\\${node.name}`;
25
- const imgDir = path.join(baseStorage, "allarmi");
26
- const vidDir = path.join(baseStorage, "video");
27
-
28
- // Creazione cartelle se non esistono
29
- [imgDir, vidDir].forEach(dir => {
30
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
31
- });
32
-
33
- let streamRequest = null;
34
20
  let isClosing = false;
35
21
  let lastTriggerTime = 0;
36
22
  let nvrOnline = true;
@@ -38,40 +24,26 @@ module.exports = function(RED) {
38
24
 
39
25
  const EventList = ["FieldDetection", "LineDetection"];
40
26
 
41
- node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
27
+ // --- DEFINIZIONE PERCORSI STORAGE ---
28
+ const baseStorage = `C:\\Users\\APerucca\\Documents\\progetto-docker\\storage\\${node.name}`;
29
+ const imgDir = path.join(baseStorage, "allarmi");
30
+ const vidDir = path.join(baseStorage, "video");
42
31
 
43
- function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
32
+ if (!fs.existsSync(imgDir)) fs.mkdirSync(imgDir, { recursive: true });
33
+ if (!fs.existsSync(vidDir)) fs.mkdirSync(vidDir, { recursive: true });
44
34
 
45
- async function hikRequest(options) {
46
- const { method, url, data, responseType, user, pass, headers = {} } = options;
47
-
48
- // Assicuriamoci che data sia una stringa se stiamo mandando XML
49
- const body = (typeof data === 'object') ? JSON.stringify(data) : data;
35
+ // Client Digest per NVR e Camere
36
+ const nvrAuth = new axiosDigestAuth.AxiosDigestAuth(node.user, node.pass);
50
37
 
51
- const res = await urllib.request(url, {
52
- method: method,
53
- digestAuth: `${user}:${pass}`,
54
- content: body,
55
- headers: {
56
- 'Content-Type': 'application/xml', // Questo dice alla telecamera: "Leggi l'XML"
57
- 'Accept': '*/*',
58
- ...headers
59
- },
60
- dataType: responseType === 'arraybuffer' ? 'buffer' : 'text',
61
- timeout: 15000,
62
- rejectUnauthorized: false
63
- });
64
-
65
- return res;
66
- }
38
+ node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
39
+
40
+ function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
67
41
 
68
42
  async function getCameraName(cam) {
69
43
  try {
70
- const res = await hikRequest({
44
+ const res = await nvrAuth.request({
71
45
  method: 'GET',
72
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`,
73
- user: node.user,
74
- pass: node.camPass
46
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`
75
47
  });
76
48
  const match = res.data.toString().match(/<name>([^<]+)<\/name>/);
77
49
  return match ? match[1] : `Cam_${cam.channel}`;
@@ -84,11 +56,9 @@ module.exports = function(RED) {
84
56
  if (isClosing) return;
85
57
  for (let cam of node.cameras) {
86
58
  try {
87
- await hikRequest({
59
+ await nvrAuth.request({
88
60
  method: 'GET',
89
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
90
- user: node.user,
91
- pass: node.camPass
61
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`
92
62
  });
93
63
 
94
64
  if (statoCamera[cam.ip] === false) {
@@ -130,26 +100,28 @@ module.exports = function(RED) {
130
100
  if (nowTime - lastTriggerTime < 10000) return;
131
101
  lastTriggerTime = nowTime;
132
102
 
103
+ const timestamp = Math.floor(nowTime / 1000);
133
104
  const nomeCamera = await getCameraName(camera);
134
105
  const referenceTime = new Date();
135
- const timestamp = Math.floor(referenceTime.getTime() / 1000);
136
106
  const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
137
107
  const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
138
108
 
109
+ const camAuth = new axiosDigestAuth.AxiosDigestAuth(node.user, node.camPass);
110
+ const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
111
+
139
112
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
140
113
  await new Promise(resolve => setTimeout(resolve, 6000));
141
114
 
142
- const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
143
- let output = { ip: camera.ip, nomeCliente: node.name, nome_telecamera: nomeCamera, channel: channelID, event: evento, videoPath: null, imageBuffer: null, imagePath: null };
115
+ let output = { ip: camera.ip, nomeCliente: node.name, nome_telecamera: nomeCamera, channel: channelID, event: evento, videoPath: null, imagePath: null };
144
116
 
145
117
  try {
146
- const tracks = [{ name: "termicoV", id: "201" }, { name: "termico", id: "203" }];
118
+ const tracks = [{ id: "201" }, { id: "203" }];
147
119
  for (let t of tracks) {
148
120
  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>`;
149
121
 
150
- const resSearch = await hikRequest({
122
+ const resSearch = await camAuth.request({
151
123
  method: 'POST', url: `${baseUrl}/search`, data: searchXml,
152
- headers: { "Content-Type": "application/xml" }, user: node.user, pass: node.camPass
124
+ headers: { "Content-Type": "application/xml" }
153
125
  });
154
126
 
155
127
  let xml = resSearch.data.toString().replace(/<(\/?)\w+:/g, "<$1");
@@ -157,34 +129,25 @@ module.exports = function(RED) {
157
129
 
158
130
  if (uriMatch) {
159
131
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
160
- const resDown = await hikRequest({
161
- method: 'GET',
132
+ const resDown = await camAuth.request({
133
+ method: 'POST', // POST risolve l'errore "Invalid XML"
162
134
  url: `${baseUrl}/download`,
163
135
  data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
164
136
  responseType: 'arraybuffer',
165
- user: node.user,
166
- pass: node.camPass
137
+ headers: { "Content-Type": "application/xml" }
167
138
  });
168
139
 
169
140
  let buffer = Buffer.from(resDown.data);
170
-
171
141
  if (t.id === "203") {
172
- // --- LOGICA FOTO (IDENTICA ALLA TUA) ---
173
- output.imageBuffer = buffer;
174
-
175
- // AGGIUNTA: Salviamo fisicamente il file per il sito
176
- const imgFileName = `img_${timestamp}.jpg`;
177
- const fullImgPath = path.join(imgDir, imgFileName);
178
- fs.writeFileSync(fullImgPath, buffer);
142
+ // FOTO
143
+ const fullImgPath = path.join(imgDir, `img_${timestamp}.jpg`);
144
+ fs.writeFileSync(fullImgPath, buffer);
179
145
  output.imagePath = fullImgPath;
180
-
181
146
  } else {
182
- // --- LOGICA VIDEO (IDENTICA ALLA TUA) ---
147
+ // VIDEO
183
148
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
184
-
185
149
  const rawPath = path.join(vidDir, `raw_${timestamp}.mp4`);
186
150
  const fixedPath = path.join(vidDir, `hik_v_${channelID}_${timestamp}.mp4`);
187
-
188
151
  fs.writeFileSync(rawPath, buffer);
189
152
 
190
153
  await new Promise((resolve) => {
@@ -192,13 +155,10 @@ module.exports = function(RED) {
192
155
  if (!err) {
193
156
  output.videoPath = fixedPath;
194
157
  try { fs.unlinkSync(rawPath); } catch(e) {}
195
- } else {
196
- output.videoPath = rawPath;
197
- }
158
+ } else { output.videoPath = rawPath; }
198
159
  resolve();
199
160
  });
200
161
  });
201
-
202
162
  setTimeout(() => { if (output.videoPath && fs.existsSync(output.videoPath)) fs.unlinkSync(output.videoPath); }, 180000);
203
163
  }
204
164
  }
@@ -210,25 +170,19 @@ module.exports = function(RED) {
210
170
  updateNodeStatus();
211
171
  }
212
172
 
213
- // --- GESTIONE NVR ALERT STREAM ---
214
173
  function startAlertStream() {
215
174
  if (isClosing) return;
216
175
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
217
176
 
218
- urllib.request(url, {
219
- method: 'GET',
220
- digestAuth: `${node.user}:${node.pass}`,
221
- streaming: true,
222
- timeout: 60000,
223
- rejectUnauthorized: false
224
- }).then(res => {
177
+ nvrAuth.request({ method: 'GET', url: url, responseType: 'stream' })
178
+ .then(res => {
225
179
  if (!nvrOnline) {
226
180
  node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } });
227
181
  nvrOnline = true;
228
182
  }
229
183
  updateNodeStatus();
230
184
 
231
- res.res.on('data', (chunk) => {
185
+ res.data.on('data', (chunk) => {
232
186
  const data = chunk.toString().toLowerCase();
233
187
  if (data.includes("active")) {
234
188
  const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
@@ -240,8 +194,8 @@ module.exports = function(RED) {
240
194
  }
241
195
  });
242
196
 
243
- res.res.on('error', () => handleNvrError());
244
- res.res.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
197
+ res.data.on('error', () => handleNvrError());
198
+ res.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
245
199
  }).catch(() => handleNvrError());
246
200
  }
247
201
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",