node-red-contrib-hik-media-buffer 1.1.3 → 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 -74
  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,31 +24,26 @@ module.exports = function(RED) {
38
24
 
39
25
  const EventList = ["FieldDetection", "LineDetection"];
40
26
 
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");
31
+
32
+ if (!fs.existsSync(imgDir)) fs.mkdirSync(imgDir, { recursive: true });
33
+ if (!fs.existsSync(vidDir)) fs.mkdirSync(vidDir, { recursive: true });
34
+
35
+ // Client Digest per NVR e Camere
36
+ const nvrAuth = new axiosDigestAuth.AxiosDigestAuth(node.user, node.pass);
37
+
41
38
  node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
42
39
 
43
40
  function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
44
41
 
45
- async function hikRequest(options) {
46
- const { method, url, data, responseType, user, pass, headers = {} } = options;
47
- const res = await urllib.request(url, {
48
- method: method,
49
- digestAuth: `${user}:${pass}`,
50
- content: data,
51
- headers: headers,
52
- dataType: responseType === 'arraybuffer' ? 'buffer' : 'text',
53
- timeout: 15000,
54
- rejectUnauthorized: false
55
- });
56
- return res;
57
- }
58
-
59
42
  async function getCameraName(cam) {
60
43
  try {
61
- const res = await hikRequest({
44
+ const res = await nvrAuth.request({
62
45
  method: 'GET',
63
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`,
64
- user: node.user,
65
- pass: node.camPass
46
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}`
66
47
  });
67
48
  const match = res.data.toString().match(/<name>([^<]+)<\/name>/);
68
49
  return match ? match[1] : `Cam_${cam.channel}`;
@@ -75,11 +56,9 @@ module.exports = function(RED) {
75
56
  if (isClosing) return;
76
57
  for (let cam of node.cameras) {
77
58
  try {
78
- await hikRequest({
59
+ await nvrAuth.request({
79
60
  method: 'GET',
80
- url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
81
- user: node.user,
82
- pass: node.camPass
61
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`
83
62
  });
84
63
 
85
64
  if (statoCamera[cam.ip] === false) {
@@ -121,26 +100,28 @@ module.exports = function(RED) {
121
100
  if (nowTime - lastTriggerTime < 10000) return;
122
101
  lastTriggerTime = nowTime;
123
102
 
103
+ const timestamp = Math.floor(nowTime / 1000);
124
104
  const nomeCamera = await getCameraName(camera);
125
105
  const referenceTime = new Date();
126
- const timestamp = Math.floor(referenceTime.getTime() / 1000);
127
106
  const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
128
107
  const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
129
108
 
109
+ const camAuth = new axiosDigestAuth.AxiosDigestAuth(node.user, node.camPass);
110
+ const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
111
+
130
112
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
131
113
  await new Promise(resolve => setTimeout(resolve, 6000));
132
114
 
133
- const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
134
- 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 };
135
116
 
136
117
  try {
137
- const tracks = [{ name: "termicoV", id: "201" }, { name: "termico", id: "203" }];
118
+ const tracks = [{ id: "201" }, { id: "203" }];
138
119
  for (let t of tracks) {
139
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>`;
140
121
 
141
- const resSearch = await hikRequest({
122
+ const resSearch = await camAuth.request({
142
123
  method: 'POST', url: `${baseUrl}/search`, data: searchXml,
143
- headers: { "Content-Type": "application/xml" }, user: node.user, pass: node.camPass
124
+ headers: { "Content-Type": "application/xml" }
144
125
  });
145
126
 
146
127
  let xml = resSearch.data.toString().replace(/<(\/?)\w+:/g, "<$1");
@@ -148,34 +129,25 @@ module.exports = function(RED) {
148
129
 
149
130
  if (uriMatch) {
150
131
  const rawUri = uriMatch[1].replace(/&amp;/g, '&');
151
- const resDown = await hikRequest({
152
- method: 'GET',
132
+ const resDown = await camAuth.request({
133
+ method: 'POST', // POST risolve l'errore "Invalid XML"
153
134
  url: `${baseUrl}/download`,
154
135
  data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
155
136
  responseType: 'arraybuffer',
156
- user: node.user,
157
- pass: node.camPass
137
+ headers: { "Content-Type": "application/xml" }
158
138
  });
159
139
 
160
140
  let buffer = Buffer.from(resDown.data);
161
-
162
141
  if (t.id === "203") {
163
- // --- LOGICA FOTO (IDENTICA ALLA TUA) ---
164
- output.imageBuffer = buffer;
165
-
166
- // AGGIUNTA: Salviamo fisicamente il file per il sito
167
- const imgFileName = `img_${timestamp}.jpg`;
168
- const fullImgPath = path.join(imgDir, imgFileName);
169
- fs.writeFileSync(fullImgPath, buffer);
142
+ // FOTO
143
+ const fullImgPath = path.join(imgDir, `img_${timestamp}.jpg`);
144
+ fs.writeFileSync(fullImgPath, buffer);
170
145
  output.imagePath = fullImgPath;
171
-
172
146
  } else {
173
- // --- LOGICA VIDEO (IDENTICA ALLA TUA) ---
147
+ // VIDEO
174
148
  if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
175
-
176
149
  const rawPath = path.join(vidDir, `raw_${timestamp}.mp4`);
177
150
  const fixedPath = path.join(vidDir, `hik_v_${channelID}_${timestamp}.mp4`);
178
-
179
151
  fs.writeFileSync(rawPath, buffer);
180
152
 
181
153
  await new Promise((resolve) => {
@@ -183,13 +155,10 @@ module.exports = function(RED) {
183
155
  if (!err) {
184
156
  output.videoPath = fixedPath;
185
157
  try { fs.unlinkSync(rawPath); } catch(e) {}
186
- } else {
187
- output.videoPath = rawPath;
188
- }
158
+ } else { output.videoPath = rawPath; }
189
159
  resolve();
190
160
  });
191
161
  });
192
-
193
162
  setTimeout(() => { if (output.videoPath && fs.existsSync(output.videoPath)) fs.unlinkSync(output.videoPath); }, 180000);
194
163
  }
195
164
  }
@@ -201,25 +170,19 @@ module.exports = function(RED) {
201
170
  updateNodeStatus();
202
171
  }
203
172
 
204
- // --- GESTIONE NVR ALERT STREAM ---
205
173
  function startAlertStream() {
206
174
  if (isClosing) return;
207
175
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
208
176
 
209
- urllib.request(url, {
210
- method: 'GET',
211
- digestAuth: `${node.user}:${node.pass}`,
212
- streaming: true,
213
- timeout: 60000,
214
- rejectUnauthorized: false
215
- }).then(res => {
177
+ nvrAuth.request({ method: 'GET', url: url, responseType: 'stream' })
178
+ .then(res => {
216
179
  if (!nvrOnline) {
217
180
  node.send({ payload: { status: "online", ip: node.host, msg: "NVR Online" } });
218
181
  nvrOnline = true;
219
182
  }
220
183
  updateNodeStatus();
221
184
 
222
- res.res.on('data', (chunk) => {
185
+ res.data.on('data', (chunk) => {
223
186
  const data = chunk.toString().toLowerCase();
224
187
  if (data.includes("active")) {
225
188
  const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
@@ -231,8 +194,8 @@ module.exports = function(RED) {
231
194
  }
232
195
  });
233
196
 
234
- res.res.on('error', () => handleNvrError());
235
- res.res.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
197
+ res.data.on('error', () => handleNvrError());
198
+ res.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
236
199
  }).catch(() => handleNvrError());
237
200
  }
238
201
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.3",
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",