node-red-contrib-hik-media-buffer 1.1.70 → 1.1.72
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 +136 -213
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -5,6 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const { exec } = require('child_process');
|
|
8
|
+
const { channel } = require('diagnostics_channel');
|
|
8
9
|
|
|
9
10
|
module.exports = function(RED) {
|
|
10
11
|
function HikMediaBufferNode(config) {
|
|
@@ -12,88 +13,91 @@ module.exports = function(RED) {
|
|
|
12
13
|
const node = this;
|
|
13
14
|
|
|
14
15
|
node.name = config.name || "TEST";
|
|
15
|
-
node.host = config.host;
|
|
16
|
+
node.host = config.host;
|
|
16
17
|
node.port = config.port || "80";
|
|
17
18
|
node.protocol = config.protocol || "http";
|
|
18
19
|
node.user = config.user;
|
|
19
20
|
node.pass = config.pass;
|
|
21
|
+
node.camPass = config.camPass || config.pass;
|
|
22
|
+
node.cameras = config.cameras || [];
|
|
20
23
|
|
|
21
24
|
let streamRequest = null;
|
|
22
25
|
let isClosing = false;
|
|
23
26
|
let lastTriggerTime = {};
|
|
24
27
|
let nvrOnline = true;
|
|
25
|
-
let
|
|
28
|
+
let statoCamera = {};
|
|
26
29
|
|
|
27
30
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
28
31
|
const EventList = ["FieldDetection", "LineDetection"];
|
|
29
32
|
|
|
33
|
+
// CARTELLA TEMPORANEA
|
|
30
34
|
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
31
35
|
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
32
36
|
|
|
33
37
|
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
40
|
+
|
|
41
|
+
// --- PRENDE IL NOME DELLA TELECAMERA ---
|
|
42
|
+
async function getCameraName(cam) {
|
|
43
|
+
const camAuth = new AxiosDigestAuth({
|
|
44
|
+
username: node.user,
|
|
45
|
+
password: node.camPass
|
|
46
|
+
});
|
|
47
|
+
|
|
37
48
|
try {
|
|
38
|
-
const res = await
|
|
49
|
+
const res = await camAuth.request({
|
|
39
50
|
method: 'GET',
|
|
40
|
-
url: `${node.protocol}://${
|
|
51
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
|
|
41
52
|
timeout: 5000,
|
|
42
53
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
43
54
|
});
|
|
55
|
+
|
|
44
56
|
const data = res.data.toString();
|
|
45
57
|
const match = data.match(/<name>([^<]+)<\/name>/i);
|
|
46
|
-
|
|
58
|
+
|
|
59
|
+
if (match && match[1]) {
|
|
60
|
+
return match[1].trim();
|
|
61
|
+
} else {
|
|
62
|
+
return `Canale_${cam.channel}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
} catch (e) {
|
|
48
|
-
return `
|
|
66
|
+
return `Camera_${cam.channel}`;
|
|
49
67
|
}
|
|
50
68
|
}
|
|
51
69
|
|
|
70
|
+
// --- CONTROLLO STATUS CAMERE ---
|
|
52
71
|
async function checkCameras() {
|
|
53
72
|
if (isClosing) return;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const statusStr = statusMatch[1].toLowerCase();
|
|
75
|
-
const isOnline = statusStr.includes("online") || statusStr.includes("recording");
|
|
76
|
-
|
|
77
|
-
if (isOnline && statoCanale[ch] === false) {
|
|
78
|
-
const nomeOnline = await getCameraName(ch);
|
|
79
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_nvr: node.host, channel: ch.toString(), msg: "Camera ripristinata su NVR" } });
|
|
80
|
-
statoCanale[ch] = true;
|
|
81
|
-
} else if (!isOnline && statoCanale[ch] !== false && statoCanale[ch] !== undefined) {
|
|
82
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Canale_${ch}`, ip_nvr: node.host, channel: ch.toString(), msg: "Camera scollegata o offline su NVR" } });
|
|
83
|
-
statoCanale[ch] = false;
|
|
84
|
-
} else if (statoCanale[ch] === undefined) {
|
|
85
|
-
statoCanale[ch] = isOnline;
|
|
86
|
-
}
|
|
73
|
+
for (let cam of node.cameras) {
|
|
74
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
75
|
+
try {
|
|
76
|
+
await camAuth.request({
|
|
77
|
+
method: 'GET',
|
|
78
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
|
|
79
|
+
timeout: 5000,
|
|
80
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
81
|
+
});
|
|
82
|
+
if (statoCamera[cam.ip] === false) {
|
|
83
|
+
const nomeOnline = await getCameraName(cam);
|
|
84
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
|
|
85
|
+
statoCamera[cam.ip] = true;
|
|
86
|
+
} else if (statoCamera[cam.ip] === undefined) {
|
|
87
|
+
statoCamera[cam.ip] = true;
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (statoCamera[cam.ip] !== false) {
|
|
91
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera_${cam.channel}`, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
|
|
92
|
+
statoCamera[cam.ip] = false;
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
|
-
} catch (e) {
|
|
90
|
-
nvrOnline = false;
|
|
91
95
|
}
|
|
92
96
|
updateNodeStatus();
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
function updateNodeStatus() {
|
|
96
|
-
const offlineCams = Object.values(
|
|
100
|
+
const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
|
|
97
101
|
if (!nvrOnline) {
|
|
98
102
|
node.status({fill:"red", shape:"ring", text:"NVR Offline"});
|
|
99
103
|
} else if (offlineCams > 0) {
|
|
@@ -105,58 +109,38 @@ module.exports = function(RED) {
|
|
|
105
109
|
|
|
106
110
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
107
111
|
|
|
108
|
-
// ---
|
|
109
|
-
// --- STRATEGIA DI DOWNLOAD MULTIMEDIALE 100% JSON + FFMPEG EXTRACTION ---
|
|
110
|
-
// --- STRATEGIA DI DOWNLOAD MULTIMEDIALE CON DOPPIO FALLBACK ORARIO (LOCALE / UTC) ---
|
|
112
|
+
// --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
|
|
111
113
|
async function downloadMedia(evento, channelID) {
|
|
112
|
-
node.
|
|
114
|
+
const camera = node.cameras.find(c => c.channel == channelID);
|
|
115
|
+
if (!camera) return;
|
|
116
|
+
|
|
117
|
+
const nomeCamera = await getCameraName(camera);
|
|
113
118
|
|
|
114
119
|
const nowTime = Date.now();
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
if(!lastTriggerTime[camera.ip]){
|
|
121
|
+
lastTriggerTime[camera.ip] = 0;
|
|
122
|
+
}
|
|
123
|
+
if (nowTime - lastTriggerTime[camera.ip] < 5000) return;
|
|
124
|
+
lastTriggerTime[camera.ip] = nowTime;
|
|
118
125
|
|
|
119
|
-
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.
|
|
126
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
120
127
|
const referenceTime = new Date();
|
|
121
128
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const formatLocaleDate = (d) => {
|
|
125
|
-
const pad = (num) => String(num).padStart(2, '0');
|
|
126
|
-
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
// FUNZIONE 2: FORMATTO IN ORA UTC (Es. YYYY-MM-DDTHH:mm:ssZ)
|
|
130
|
-
const formatUtcDate = (d) => {
|
|
131
|
-
const pad = (num) => String(num).padStart(2, '0');
|
|
132
|
-
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth()+1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`;
|
|
133
|
-
};
|
|
129
|
+
const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
|
|
130
|
+
const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
|
|
134
131
|
|
|
135
132
|
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
136
|
-
|
|
133
|
+
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
137
134
|
|
|
138
|
-
|
|
139
|
-
node.warn(`[warn] [hik-media-buffer:test] Attesa scrittura indice Hard Disk (8s)...`);
|
|
140
|
-
await new Promise(resolve => setTimeout(resolve, 8000));
|
|
135
|
+
const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
const evMinuscolo = evento.toLowerCase();
|
|
144
|
-
|
|
145
|
-
// CREIAMO LE FINESTRE TEMPORALI: Dal passato fino a ORA (Senza sforare nel futuro!)
|
|
146
|
-
const oraLocaleRichiesta = new Date(); // Questo è il momento esatto del termine degli 8s
|
|
147
|
-
const startLocale = formatLocaleDate(new Date(oraLocaleRichiesta.getTime() - (90 * 1000))); // 90 secondi fa
|
|
148
|
-
const endLocale = formatLocaleDate(oraLocaleRichiesta); // Esattamente ADESSO
|
|
149
|
-
|
|
150
|
-
const oraUtcRichiesta = new Date();
|
|
151
|
-
const startUtc = formatUtcDate(new Date(oraUtcRichiesta.getTime() - (90 * 1000))); // 90 secondi fa
|
|
152
|
-
const endUtc = formatUtcDate(oraUtcRichiesta); // Esattamente ADESSO
|
|
153
|
-
|
|
137
|
+
// struttura del payload per Python
|
|
154
138
|
let output = {
|
|
155
139
|
tipo_messaggio: "evento",
|
|
156
140
|
nome_cliente: node.name,
|
|
157
141
|
nome_telecamera: nomeCamera,
|
|
158
|
-
ip_telecamera:
|
|
159
|
-
tipo_evento:
|
|
142
|
+
ip_telecamera: camera.ip,
|
|
143
|
+
tipo_evento: evento,
|
|
160
144
|
timestamp_epoch: timestamp,
|
|
161
145
|
stato_telecamera: "ONLINE",
|
|
162
146
|
channel: channelID.toString(),
|
|
@@ -164,143 +148,87 @@ module.exports = function(RED) {
|
|
|
164
148
|
video_base64: null
|
|
165
149
|
};
|
|
166
150
|
|
|
167
|
-
const parsedChannel = parseInt(channelID, 10);
|
|
168
|
-
let fileDaCancellare = [];
|
|
169
|
-
let videoUriMatch = null;
|
|
170
|
-
|
|
171
|
-
// --- TENTATIVO 1: RICERCA IN ORA LOCALE (La più probabile per i dischi NVR) ---
|
|
172
|
-
const startLocale = formatLocaleDate(new Date(referenceTime.getTime() - (120 * 1000)));
|
|
173
|
-
const endLocale = formatLocaleDate(new Date(referenceTime.getTime() + (30 * 1000)));
|
|
174
|
-
|
|
175
|
-
node.warn(`[warn] [hik-media-buffer:test] [TENTATIVO 1] Ricerca video in Ora Locale: ${startLocale} -> ${endLocale}`);
|
|
176
151
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
let payloadLocale = {
|
|
180
|
-
"EventSearchDescription": {
|
|
181
|
-
"searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
|
|
182
|
-
"searchResultPosition": 0,
|
|
183
|
-
"maxResults": 10,
|
|
184
|
-
"timeSpanList": [{"startTime": startLocale, "endTime": endLocale}],
|
|
185
|
-
"type": "all",
|
|
186
|
-
"channels": [trackVideoId],
|
|
187
|
-
"eventType": "behavior",
|
|
188
|
-
"behavior": {"behaviorEventType": evMinuscolo}
|
|
189
|
-
}
|
|
190
|
-
};
|
|
152
|
+
let fileDaCancellare = [];
|
|
191
153
|
|
|
192
154
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
155
|
+
const trackV = (channelID*100+1).toString();
|
|
156
|
+
const trackI = (channelID*100+3).toString();
|
|
157
|
+
const tracks = [{ id: trackV }, { id: trackI }];
|
|
158
|
+
for (let t of tracks) {
|
|
159
|
+
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>`;
|
|
160
|
+
|
|
161
|
+
const resSearch = await camAuth.request({
|
|
162
|
+
method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
166
|
+
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
167
|
+
|
|
168
|
+
if (uriMatch) {
|
|
169
|
+
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
170
|
+
const resDown = await camAuth.request({
|
|
171
|
+
method: 'GET',
|
|
172
|
+
url: `${baseUrl}/download`,
|
|
173
|
+
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
174
|
+
responseType: 'arraybuffer'
|
|
175
|
+
});
|
|
205
176
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
177
|
+
let buffer = Buffer.from(resDown.data);
|
|
178
|
+
if (t.id === "203") {
|
|
179
|
+
// SALVA FOTO IN LOCALE
|
|
180
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
181
|
+
fs.writeFileSync(fullImgPath, buffer);
|
|
182
|
+
|
|
183
|
+
// La convertiamo subito in testo per il payload
|
|
184
|
+
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
185
|
+
|
|
186
|
+
// Registriamo il file per la distruzione futura
|
|
187
|
+
fileDaCancellare.push(fullImgPath);
|
|
188
|
+
} else {
|
|
189
|
+
// SALVA VIDEO IN LOCALE-
|
|
190
|
+
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
191
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
192
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
193
|
+
fs.writeFileSync(rawPath, buffer);
|
|
194
|
+
|
|
195
|
+
// Eseguiamo ffmpeg localmente
|
|
196
|
+
await new Promise((resolve) => {
|
|
197
|
+
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
198
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
199
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
200
|
+
fileDaCancellare.push(fixedPath);
|
|
201
|
+
} else {
|
|
202
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
203
|
+
}
|
|
204
|
+
fileDaCancellare.push(rawPath);
|
|
205
|
+
resolve();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
223
209
|
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
let resUtc = await camAuth.request({
|
|
228
|
-
method: 'POST',
|
|
229
|
-
url: `${baseUrl}/eventRecordSearch?format=json`,
|
|
230
|
-
data: payloadUtc,
|
|
231
|
-
headers: { "Content-Type": "application/json" },
|
|
232
|
-
timeout: 5000
|
|
233
|
-
});
|
|
234
|
-
let dataStr = JSON.stringify(resUtc.data);
|
|
235
|
-
videoUriMatch = dataStr.match(/rtsp:\/\/[^"]+/i) || dataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
|
|
236
|
-
} catch (e) {
|
|
237
|
-
node.error(`[error] [hik-media-buffer:test] Errore critico anche nel Fallback UTC: ${e.message}`);
|
|
238
210
|
}
|
|
239
|
-
}
|
|
240
211
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
node.warn(`[warn] [hik-media-buffer:test] [FOUND] Trovato playbackURI video: ${rawVideoUri}`);
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
const resDownVideo = await camAuth.request({
|
|
248
|
-
method: 'GET',
|
|
249
|
-
url: `${baseUrl}/download`,
|
|
250
|
-
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawVideoUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
251
|
-
responseType: 'arraybuffer'
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
let videoBuffer = Buffer.from(resDownVideo.data);
|
|
255
|
-
if (videoBuffer.slice(0, 4).toString() === 'IMKH') videoBuffer = videoBuffer.slice(40);
|
|
256
|
-
|
|
257
|
-
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
258
|
-
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
259
|
-
const extractedImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
212
|
+
// Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
|
|
213
|
+
if (output.foto_base64 || output.video_base64) {
|
|
214
|
+
node.send({ payload: output });
|
|
260
215
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (!err) {
|
|
268
|
-
if (fs.existsSync(extractedImgPath)) {
|
|
269
|
-
output.foto_base64 = fs.readFileSync(extractedImgPath, { encoding: 'base64' });
|
|
270
|
-
fileDaCancellare.push(extractedImgPath);
|
|
271
|
-
node.warn(`[warn] [hik-media-buffer:test] Snapshot FOTO estratto con successo.`);
|
|
272
|
-
}
|
|
273
|
-
if (fs.existsSync(fixedPath)) {
|
|
274
|
-
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
275
|
-
fileDaCancellare.push(fixedPath);
|
|
276
|
-
node.warn(`[warn] [hik-media-buffer:test] Video MP4 indicizzato con successo.`);
|
|
216
|
+
// TIMER A 2 MINUTI PER LA PULIZIA DEL DISCO
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
for (let file of fileDaCancellare) {
|
|
219
|
+
try {
|
|
220
|
+
if (fs.existsSync(file)) {
|
|
221
|
+
fs.unlinkSync(file);
|
|
277
222
|
}
|
|
278
|
-
}
|
|
279
|
-
node.
|
|
280
|
-
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
223
|
+
} catch (err) {
|
|
224
|
+
node.error(`Errore durante la pulizia del file temporaneo ${file}: ${err.message}`);
|
|
281
225
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
});
|
|
285
|
-
} catch (downErr) {
|
|
286
|
-
node.error(`[error] [hik-media-buffer:test] Errore durante il download fisico del file: ${downErr.message}`);
|
|
226
|
+
}
|
|
227
|
+
}, 120000);
|
|
287
228
|
}
|
|
288
|
-
} else {
|
|
289
|
-
node.warn(`[warn] [hik-media-buffer:test] [NOT FOUND] Il video non è stato trovato in nessuna delle due finestre temporali (Locale/UTC). Verificare se l'NVR sta effettivamente registrando l'evento.`);
|
|
290
|
-
}
|
|
291
229
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
node.warn(`[warn] [hik-media-buffer:test] Spedizione payload multimediale completata con successo!`);
|
|
295
|
-
node.send({ payload: output });
|
|
296
|
-
|
|
297
|
-
setTimeout(() => {
|
|
298
|
-
for (let file of fileDaCancellare) {
|
|
299
|
-
try { if (fs.existsSync(file)) fs.unlinkSync(file); } catch (err) {}
|
|
300
|
-
}
|
|
301
|
-
}, 120000);
|
|
302
|
-
} else {
|
|
303
|
-
node.warn(`[warn] [hik-media-buffer:test] Nessun pacchetto inviato a Node-RED.`);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
304
232
|
}
|
|
305
233
|
updateNodeStatus();
|
|
306
234
|
}
|
|
@@ -310,22 +238,18 @@ module.exports = function(RED) {
|
|
|
310
238
|
if (isClosing) return;
|
|
311
239
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
312
240
|
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
313
|
-
|
|
314
241
|
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
315
242
|
.then(response => {
|
|
316
243
|
streamRequest = response;
|
|
317
244
|
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
318
245
|
updateNodeStatus();
|
|
319
|
-
|
|
320
246
|
response.data.on('data', (chunk) => {
|
|
321
|
-
if (isClosing) return;
|
|
322
247
|
const data = chunk.toString().toLowerCase();
|
|
323
248
|
if (data.includes("active")) {
|
|
324
249
|
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
325
250
|
if (chMatch) {
|
|
326
251
|
let ev = "Unknown";
|
|
327
252
|
for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
|
|
328
|
-
if (ev === "Unknown") return;
|
|
329
253
|
downloadMedia(ev, chMatch[1]);
|
|
330
254
|
}
|
|
331
255
|
}
|
|
@@ -343,11 +267,10 @@ module.exports = function(RED) {
|
|
|
343
267
|
|
|
344
268
|
startAlertStream();
|
|
345
269
|
setTimeout(checkCameras, 2000);
|
|
346
|
-
|
|
347
270
|
node.on('close', (done) => {
|
|
348
271
|
isClosing = true;
|
|
349
272
|
clearInterval(heartbeatInterval);
|
|
350
|
-
if (streamRequest
|
|
273
|
+
if (streamRequest) streamRequest.data.destroy();
|
|
351
274
|
done();
|
|
352
275
|
});
|
|
353
276
|
}
|