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.
- package/hik-media-buffer.js +37 -74
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
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 = [{
|
|
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
|
|
122
|
+
const resSearch = await camAuth.request({
|
|
142
123
|
method: 'POST', url: `${baseUrl}/search`, data: searchXml,
|
|
143
|
-
headers: { "Content-Type": "application/xml" }
|
|
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(/&/g, '&');
|
|
151
|
-
const resDown = await
|
|
152
|
-
method: '
|
|
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, '&')}</playbackURI></downloadRequest>`,
|
|
155
136
|
responseType: 'arraybuffer',
|
|
156
|
-
|
|
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
|
-
//
|
|
164
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
210
|
-
|
|
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.
|
|
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.
|
|
235
|
-
res.
|
|
197
|
+
res.data.on('error', () => handleNvrError());
|
|
198
|
+
res.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
|
|
236
199
|
}).catch(() => handleNvrError());
|
|
237
200
|
}
|
|
238
201
|
|