node-red-contrib-hik-media-buffer 1.1.115 → 1.1.116
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 +19 -19
- package/hik-snapshot.js +72 -286
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -35,27 +35,28 @@ module.exports = function(RED) {
|
|
|
35
35
|
|
|
36
36
|
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
37
37
|
|
|
38
|
-
// --- PRENDE IL NOME DELLA TELECAMERA DALL'NVR ---
|
|
38
|
+
// --- PRENDE IL NOME REALE DELLA TELECAMERA DALL'NVR ---
|
|
39
39
|
async function getCameraNameFromNVR(channelID) {
|
|
40
40
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
41
41
|
try {
|
|
42
42
|
const res = await nvrAuth.request({
|
|
43
43
|
method: 'GET',
|
|
44
|
-
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}
|
|
44
|
+
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}`,
|
|
45
45
|
timeout: 5000,
|
|
46
46
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
const data = res.data.toString();
|
|
50
|
-
|
|
50
|
+
// Cerchiamo il tag channelName che contiene il nome impostato sull'NVR
|
|
51
|
+
const match = data.match(/<channelName>([^<]+)<\/channelName>/i);
|
|
51
52
|
if (match && match[1]) return match[1].trim();
|
|
52
|
-
return `
|
|
53
|
+
return `Canale ${channelID}`;
|
|
53
54
|
} catch (e) {
|
|
54
|
-
return `
|
|
55
|
+
return `Canale ${channelID}`;
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
// --- CONTROLLO STATUS CAMERE INTERROGANDO
|
|
59
|
+
// --- CONTROLLO STATUS CAMERE (ONLINE/OFFLINE) INTERROGANDO L'NVR ---
|
|
59
60
|
async function checkCameras() {
|
|
60
61
|
if (isClosing || !nvrOnline) return;
|
|
61
62
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
@@ -72,25 +73,29 @@ module.exports = function(RED) {
|
|
|
72
73
|
|
|
73
74
|
for (let block of channelBlocks) {
|
|
74
75
|
const idMatch = block.match(/<id>(\d+)<\/id>/i);
|
|
75
|
-
|
|
76
|
+
// Hikvision usa il tag <online> true/false per lo stato della telecamera IP accoppiata
|
|
77
|
+
const onlineMatch = block.match(/<online>([^<]+)<\/online>/i) || block.match(/<onLine>([^<]+)<\/onLine>/i);
|
|
76
78
|
|
|
77
79
|
if (idMatch) {
|
|
78
80
|
const ch = idMatch[1];
|
|
79
|
-
|
|
81
|
+
// Se il tag dice true è online, altrimenti se dice false (o manca) è offline
|
|
82
|
+
const isOnline = onlineMatch ? onlineMatch[1].trim().toLowerCase() === "true" : true;
|
|
80
83
|
|
|
81
84
|
if (isOnline && statoCamera[ch] === false) {
|
|
82
85
|
const nomeOnline = await getCameraNameFromNVR(ch);
|
|
83
86
|
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: node.host, channel: ch, msg: "Camera ripristinata" } });
|
|
84
87
|
statoCamera[ch] = true;
|
|
85
88
|
} else if (!isOnline && statoCamera[ch] !== false) {
|
|
86
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `
|
|
89
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera ${ch}`, ip_telecamera: node.host, channel: ch, msg: "Camera non raggiungibile" } });
|
|
87
90
|
statoCamera[ch] = false;
|
|
88
91
|
} else if (statoCamera[ch] === undefined) {
|
|
89
92
|
statoCamera[ch] = isOnline;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
|
-
} catch (e) {
|
|
96
|
+
} catch (e) {
|
|
97
|
+
node.error(`Errore durante il check delle camere: ${e.message}`);
|
|
98
|
+
}
|
|
94
99
|
updateNodeStatus();
|
|
95
100
|
}
|
|
96
101
|
|
|
@@ -118,13 +123,12 @@ module.exports = function(RED) {
|
|
|
118
123
|
const referenceTime = new Date();
|
|
119
124
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
120
125
|
|
|
121
|
-
// Calcolo dinamico del Track ID per il Video (canale * 100 + 1)
|
|
122
126
|
const videoTrackID = (parseInt(channelID) * 100) + 1;
|
|
123
127
|
|
|
128
|
+
// Recupera il nome reale prima di inviare l'allarme
|
|
124
129
|
const nomeCamera = await getCameraNameFromNVR(channelID);
|
|
125
130
|
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
126
131
|
|
|
127
|
-
// Finestra temporale per la ricerca foto JSON
|
|
128
132
|
const startSearchStr = referenceTime.toISOString().split('T')[0] + "T00:00:00 01:00";
|
|
129
133
|
const endSearchStr = referenceTime.toISOString().split('T')[0] + "T23:59:59 01:00";
|
|
130
134
|
|
|
@@ -144,7 +148,7 @@ module.exports = function(RED) {
|
|
|
144
148
|
let fileDaCancellare = [];
|
|
145
149
|
|
|
146
150
|
try {
|
|
147
|
-
// 1. SCARICAMENTO IMMAGINE
|
|
151
|
+
// 1. SCARICAMENTO IMMAGINE
|
|
148
152
|
const payloadFoto = {
|
|
149
153
|
"EventSearchDescription": {
|
|
150
154
|
"searchID": "C5AFEE35-B1E0-4C01-83F8-47FD77892E4A",
|
|
@@ -169,7 +173,6 @@ module.exports = function(RED) {
|
|
|
169
173
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
170
174
|
});
|
|
171
175
|
|
|
172
|
-
// CORRETTO: Adesso cerca dentro Targets e prende il pictureUrl grezzo come su Postman
|
|
173
176
|
if (resFotoSearch.data?.EventSearchResult?.Targets?.[0]?.pictureUrl) {
|
|
174
177
|
const urlFotoGrezzo = resFotoSearch.data.EventSearchResult.Targets[0].pictureUrl;
|
|
175
178
|
|
|
@@ -186,7 +189,7 @@ module.exports = function(RED) {
|
|
|
186
189
|
fileDaCancellare.push(fullImgPath);
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
// 2. SCARICAMENTO VIDEO
|
|
192
|
+
// 2. SCARICAMENTO VIDEO
|
|
190
193
|
const startVideoSearch = referenceTime.toISOString().split('T')[0] + "T00:00:00Z";
|
|
191
194
|
const endVideoSearch = referenceTime.toISOString().split('T')[0] + "T23:59:59Z";
|
|
192
195
|
|
|
@@ -254,7 +257,6 @@ module.exports = function(RED) {
|
|
|
254
257
|
});
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
// Invio dei media elaborati
|
|
258
260
|
if (output.foto_base64 || output.video_base64) {
|
|
259
261
|
node.send({ payload: output });
|
|
260
262
|
|
|
@@ -262,9 +264,7 @@ module.exports = function(RED) {
|
|
|
262
264
|
for (let file of fileDaCancellare) {
|
|
263
265
|
try {
|
|
264
266
|
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
265
|
-
} catch (err) {
|
|
266
|
-
node.error(`Errore pulizia file temporaneo ${file}: ${err.message}`);
|
|
267
|
-
}
|
|
267
|
+
} catch (err) {}
|
|
268
268
|
}
|
|
269
269
|
}, 120000);
|
|
270
270
|
}
|
package/hik-snapshot.js
CHANGED
|
@@ -1,320 +1,106 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
-
const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
|
|
3
2
|
const https = require('https');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const { exec } = require('child_process');
|
|
3
|
+
const mhocDigestSnapshot = require('@mhoc/axios-digest-auth');
|
|
4
|
+
const DigestAuthClass = mhocDigestSnapshot.default;
|
|
8
5
|
|
|
9
6
|
module.exports = function(RED) {
|
|
10
|
-
function
|
|
7
|
+
function HikSnapshotNode(config) {
|
|
11
8
|
RED.nodes.createNode(this, config);
|
|
12
9
|
const node = this;
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
|
|
11
|
+
// Recuperiamo il nome del nodo dalla configurazione (se vuoto usa l'host)
|
|
12
|
+
node.nodeName = config.name || `NVR_${config.host}`;
|
|
13
|
+
|
|
14
|
+
node.protocol = config.protocol || "http";
|
|
15
15
|
node.host = config.host;
|
|
16
16
|
node.port = config.port || "80";
|
|
17
|
-
node.protocol = config.protocol || "http";
|
|
18
17
|
node.user = config.user;
|
|
19
18
|
node.pass = config.pass;
|
|
20
|
-
|
|
21
|
-
let streamRequest = null;
|
|
22
|
-
let isClosing = false;
|
|
23
|
-
let lastTriggerTime = {};
|
|
24
|
-
let nvrOnline = true;
|
|
25
|
-
let statoCamera = {};
|
|
19
|
+
node.maxChannels = parseInt(config.channel) || 1;
|
|
26
20
|
|
|
27
21
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
28
|
-
const EventList = ["FieldDetection", "LineDetection"];
|
|
29
|
-
|
|
30
|
-
// CARTELLA TEMPORANEA
|
|
31
|
-
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
32
|
-
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
33
|
-
|
|
34
|
-
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
35
|
-
|
|
36
|
-
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
37
|
-
|
|
38
|
-
// --- PRENDE IL NOME REALE DELLA TELECAMERA DALL'NVR ---
|
|
39
|
-
async function getCameraNameFromNVR(channelID) {
|
|
40
|
-
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
41
|
-
try {
|
|
42
|
-
const res = await nvrAuth.request({
|
|
43
|
-
method: 'GET',
|
|
44
|
-
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}`,
|
|
45
|
-
timeout: 5000,
|
|
46
|
-
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const data = res.data.toString();
|
|
50
|
-
// Cerchiamo il tag channelName che contiene il nome impostato sull'NVR
|
|
51
|
-
const match = data.match(/<channelName>([^<]+)<\/channelName>/i);
|
|
52
|
-
if (match && match[1]) return match[1].trim();
|
|
53
|
-
return `Canale ${channelID}`;
|
|
54
|
-
} catch (e) {
|
|
55
|
-
return `Canale ${channelID}`;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// --- CONTROLLO STATUS CAMERE (ONLINE/OFFLINE) INTERROGANDO L'NVR ---
|
|
60
|
-
async function checkCameras() {
|
|
61
|
-
if (isClosing || !nvrOnline) return;
|
|
62
|
-
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
63
|
-
try {
|
|
64
|
-
const res = await nvrAuth.request({
|
|
65
|
-
method: 'GET',
|
|
66
|
-
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels`,
|
|
67
|
-
timeout: 8000,
|
|
68
|
-
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const xml = res.data.toString();
|
|
72
|
-
const channelBlocks = xml.match(/<VideoInputChannel>[\s\S]*?<\/VideoInputChannel>/g) || [];
|
|
73
|
-
|
|
74
|
-
for (let block of channelBlocks) {
|
|
75
|
-
const idMatch = block.match(/<id>(\d+)<\/id>/i);
|
|
76
|
-
// Hikvision usa il tag <online> true/false per lo stato della telecamera IP accoppiata
|
|
77
|
-
const onlineMatch = block.match(/<online>([^<]+)<\/online>/i) || block.match(/<onLine>([^<]+)<\/onLine>/i);
|
|
78
|
-
|
|
79
|
-
if (idMatch) {
|
|
80
|
-
const ch = idMatch[1];
|
|
81
|
-
// Se il tag dice true è online, altrimenti se dice false (o manca) è offline
|
|
82
|
-
const isOnline = onlineMatch ? onlineMatch[1].trim().toLowerCase() === "true" : true;
|
|
83
|
-
|
|
84
|
-
if (isOnline && statoCamera[ch] === false) {
|
|
85
|
-
const nomeOnline = await getCameraNameFromNVR(ch);
|
|
86
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: node.host, channel: ch, msg: "Camera ripristinata" } });
|
|
87
|
-
statoCamera[ch] = true;
|
|
88
|
-
} else if (!isOnline && statoCamera[ch] !== false) {
|
|
89
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera ${ch}`, ip_telecamera: node.host, channel: ch, msg: "Camera non raggiungibile" } });
|
|
90
|
-
statoCamera[ch] = false;
|
|
91
|
-
} else if (statoCamera[ch] === undefined) {
|
|
92
|
-
statoCamera[ch] = isOnline;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} catch (e) {
|
|
97
|
-
node.error(`Errore durante il check delle camere: ${e.message}`);
|
|
98
|
-
}
|
|
99
|
-
updateNodeStatus();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function updateNodeStatus() {
|
|
103
|
-
const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
|
|
104
|
-
if (!nvrOnline) {
|
|
105
|
-
node.status({fill:"red", shape:"ring", text:"NVR Offline"});
|
|
106
|
-
} else if (offlineCams > 0) {
|
|
107
|
-
node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
|
|
108
|
-
} else {
|
|
109
|
-
node.status({fill:"green", shape:"ring", text:"In ascolto"});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
22
|
|
|
113
|
-
|
|
23
|
+
node.on('input', async function(msg) {
|
|
24
|
+
if (msg.payload !== true) return;
|
|
114
25
|
|
|
115
|
-
|
|
116
|
-
async function downloadMedia(evento, channelID) {
|
|
117
|
-
const nowTime = Date.now();
|
|
118
|
-
if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
|
|
119
|
-
if (nowTime - lastTriggerTime[channelID] < 5000) return;
|
|
120
|
-
lastTriggerTime[channelID] = nowTime;
|
|
121
|
-
|
|
122
|
-
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
123
|
-
const referenceTime = new Date();
|
|
124
|
-
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
125
|
-
|
|
126
|
-
const videoTrackID = (parseInt(channelID) * 100) + 1;
|
|
26
|
+
node.status({fill: "blue", shape: "dot", text: "Verifica canali..."});
|
|
127
27
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
28
|
+
const digest = new DigestAuthClass({
|
|
29
|
+
username: node.user,
|
|
30
|
+
password: node.pass
|
|
31
|
+
});
|
|
131
32
|
|
|
132
|
-
const
|
|
133
|
-
const
|
|
33
|
+
const data = new Date();
|
|
34
|
+
const year = data.getFullYear();
|
|
35
|
+
const month = data.getMonth() + 1;
|
|
36
|
+
const day = data.getDate();
|
|
134
37
|
|
|
135
|
-
let
|
|
136
|
-
tipo_messaggio: "evento",
|
|
137
|
-
nome_cliente: node.name,
|
|
138
|
-
nome_telecamera: nomeCamera,
|
|
139
|
-
ip_telecamera: node.host,
|
|
140
|
-
tipo_evento: evento,
|
|
141
|
-
timestamp_epoch: timestamp,
|
|
142
|
-
stato_telecamera: "ONLINE",
|
|
143
|
-
channel: channelID.toString(),
|
|
144
|
-
foto_base64: null,
|
|
145
|
-
video_base64: null
|
|
146
|
-
};
|
|
38
|
+
let snapshotResults = [];
|
|
147
39
|
|
|
148
|
-
let
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"type": "all",
|
|
162
|
-
"channels": [parseInt(channelID)],
|
|
163
|
-
"eventType": "behavior",
|
|
164
|
-
"behavior": { "behaviorEventType": "linedetection" }
|
|
165
|
-
}
|
|
40
|
+
for (let i = 1; i <= node.maxChannels; i++) {
|
|
41
|
+
const chanId = i + "01";
|
|
42
|
+
const snapUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/Streaming/channels/${chanId}/picture`;
|
|
43
|
+
const recordUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/record/tracks/${chanId}/dailyDistribution`;
|
|
44
|
+
|
|
45
|
+
const recordXml = `<?xml version="1.0" encoding="utf-8"?><trackDailyParam><year>${year}</year><monthOfYear>${month}</monthOfYear><dayOfMonth>${day}</dayOfMonth></trackDailyParam>`;
|
|
46
|
+
|
|
47
|
+
let resCanale = {
|
|
48
|
+
name: node.nodeName, // <--- INIEZIONE RIGIDA DEL NOME DEL CONDOMINIO
|
|
49
|
+
channel: i,
|
|
50
|
+
photo: null,
|
|
51
|
+
snapOk: false,
|
|
52
|
+
isRecording: false
|
|
166
53
|
};
|
|
167
54
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
data: payloadFoto,
|
|
172
|
-
headers: { "Content-Type": "application/json" },
|
|
173
|
-
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
if (resFotoSearch.data?.EventSearchResult?.Targets?.[0]?.pictureUrl) {
|
|
177
|
-
const urlFotoGrezzo = resFotoSearch.data.EventSearchResult.Targets[0].pictureUrl;
|
|
178
|
-
|
|
179
|
-
const resDownFoto = await nvrAuth.request({
|
|
55
|
+
// 1. SNAPSHOT
|
|
56
|
+
try {
|
|
57
|
+
const responseSnap = await digest.request({
|
|
180
58
|
method: 'GET',
|
|
181
|
-
url:
|
|
59
|
+
url: snapUrl,
|
|
182
60
|
responseType: 'arraybuffer',
|
|
183
|
-
httpsAgent: node.protocol ===
|
|
61
|
+
httpsAgent: node.protocol === 'https' ? httpsAgent : undefined,
|
|
62
|
+
timeout: 5000
|
|
184
63
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
fileDaCancellare.push(fullImgPath);
|
|
64
|
+
resCanale.photo = responseSnap.data;
|
|
65
|
+
resCanale.snapOk = true;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
resCanale.snapError = err.message;
|
|
190
68
|
}
|
|
191
69
|
|
|
192
|
-
// 2.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<timeSpan>
|
|
202
|
-
<startTime>${startVideoSearch}</startTime>
|
|
203
|
-
<endTime>${endVideoSearch}</endTime>
|
|
204
|
-
</timeSpan>
|
|
205
|
-
</timeSpanList>
|
|
206
|
-
<maxResults>100</maxResults>
|
|
207
|
-
<searchResultPostion>0</searchResultPostion>
|
|
208
|
-
<metadataList><metadataDescriptor>//recordType.meta.std-cgi.com</metadataDescriptor></metadataList>
|
|
209
|
-
</CMSearchDescription>`;
|
|
210
|
-
|
|
211
|
-
const resVideoSearch = await nvrAuth.request({
|
|
212
|
-
method: 'POST',
|
|
213
|
-
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/search`,
|
|
214
|
-
data: payloadVideoSearch,
|
|
215
|
-
headers: { "Content-Type": "application/xml" },
|
|
216
|
-
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
const xmlString = resVideoSearch.data.toString();
|
|
220
|
-
const uriMatch = xmlString.match(/<playbackURI>([\s\S]*?)<\/playbackURI>/i);
|
|
221
|
-
|
|
222
|
-
if (uriMatch && uriMatch[1]) {
|
|
223
|
-
const playbackURIGrezzo = uriMatch[1].trim();
|
|
224
|
-
|
|
225
|
-
const payloadDownload = `<?xml version="1.0" encoding="UTF-8"?>
|
|
226
|
-
<downloadRequest>
|
|
227
|
-
<playbackURI>${playbackURIGrezzo}</playbackURI>
|
|
228
|
-
</downloadRequest>`;
|
|
229
|
-
|
|
230
|
-
const resDownVideo = await nvrAuth.request({
|
|
231
|
-
method: 'GET',
|
|
232
|
-
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/download`,
|
|
233
|
-
data: payloadDownload,
|
|
234
|
-
headers: { "Content-Type": "application/xml" },
|
|
235
|
-
responseType: 'arraybuffer',
|
|
236
|
-
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
let bufferVideo = Buffer.from(resDownVideo.data);
|
|
240
|
-
if (bufferVideo.slice(0, 4).toString() === 'IMKH') bufferVideo = bufferVideo.slice(40);
|
|
241
|
-
|
|
242
|
-
const rawPath = path.join(baseStorage, `raw_${timestamp}_ch${channelID}.mp4`);
|
|
243
|
-
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
244
|
-
fs.writeFileSync(rawPath, bufferVideo);
|
|
245
|
-
|
|
246
|
-
await new Promise((resolve) => {
|
|
247
|
-
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
248
|
-
if (!err && fs.existsSync(fixedPath)) {
|
|
249
|
-
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
250
|
-
fileDaCancellare.push(fixedPath);
|
|
251
|
-
} else {
|
|
252
|
-
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
253
|
-
}
|
|
254
|
-
fileDaCancellare.push(rawPath);
|
|
255
|
-
resolve();
|
|
256
|
-
});
|
|
70
|
+
// 2. RECORDING
|
|
71
|
+
try {
|
|
72
|
+
const responseRec = await digest.request({
|
|
73
|
+
method: 'POST',
|
|
74
|
+
url: recordUrl,
|
|
75
|
+
data: recordXml,
|
|
76
|
+
headers: { 'Content-Type': 'application/xml' },
|
|
77
|
+
httpsAgent: node.protocol === 'https' ? httpsAgent : undefined,
|
|
78
|
+
timeout: 5000
|
|
257
79
|
});
|
|
80
|
+
const xmlOutput = responseRec.data.toString();
|
|
81
|
+
const regex = new RegExp(`<id>${day}</id>[^]*?<record>true</record>`);
|
|
82
|
+
resCanale.isRecording = regex.test(xmlOutput);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
resCanale.recError = err.message;
|
|
258
85
|
}
|
|
259
86
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
setTimeout(() => {
|
|
264
|
-
for (let file of fileDaCancellare) {
|
|
265
|
-
try {
|
|
266
|
-
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
267
|
-
} catch (err) {}
|
|
268
|
-
}
|
|
269
|
-
}, 120000);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
} catch (e) {
|
|
273
|
-
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
87
|
+
snapshotResults.push(resCanale);
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
274
89
|
}
|
|
275
|
-
updateNodeStatus();
|
|
276
|
-
}
|
|
277
90
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (isClosing) return;
|
|
281
|
-
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
282
|
-
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
283
|
-
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
284
|
-
.then(response => {
|
|
285
|
-
streamRequest = response;
|
|
286
|
-
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
287
|
-
updateNodeStatus();
|
|
288
|
-
response.data.on('data', (chunk) => {
|
|
289
|
-
const data = chunk.toString().toLowerCase();
|
|
290
|
-
if (data.includes("active")) {
|
|
291
|
-
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
292
|
-
if (chMatch) {
|
|
293
|
-
let ev = "Unknown";
|
|
294
|
-
for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
|
|
295
|
-
downloadMedia(ev, chMatch[1]);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
response.data.on('error', () => handleNvrError());
|
|
300
|
-
response.data.on('end', () => !isClosing && setTimeout(startAlertStream, 5000));
|
|
301
|
-
}).catch(() => handleNvrError());
|
|
302
|
-
}
|
|
91
|
+
msg.payload = snapshotResults;
|
|
92
|
+
node.send(msg);
|
|
303
93
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
isClosing = true;
|
|
314
|
-
clearInterval(heartbeatInterval);
|
|
315
|
-
if (streamRequest) streamRequest.data.destroy();
|
|
316
|
-
done();
|
|
94
|
+
// Conteggi per lo stato del nodo
|
|
95
|
+
const snapCount = snapshotResults.filter(v => v.snapOk).length;
|
|
96
|
+
const recCount = snapshotResults.filter(v => v.isRecording).length;
|
|
97
|
+
|
|
98
|
+
node.status({
|
|
99
|
+
fill: (snapCount === node.maxChannels && recCount === node.maxChannels) ? "green" : "yellow",
|
|
100
|
+
shape: "dot",
|
|
101
|
+
text: `Snap: ${snapCount}/${node.maxChannels} | Rec: ${recCount}/${node.maxChannels}`
|
|
102
|
+
});
|
|
317
103
|
});
|
|
318
104
|
}
|
|
319
|
-
RED.nodes.registerType("hik-
|
|
105
|
+
RED.nodes.registerType("hik-snapshot", HikSnapshotNode);
|
|
320
106
|
};
|