node-red-contrib-hik-media-buffer 1.1.15 → 1.1.17
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 +70 -37
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -29,7 +29,7 @@ module.exports = function(RED) {
|
|
|
29
29
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
30
30
|
const EventList = ["FieldDetection", "LineDetection"];
|
|
31
31
|
|
|
32
|
-
//
|
|
32
|
+
// --- CARTELLA TEMPORANEA SUL PC DEL CLIENTE ---
|
|
33
33
|
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
34
34
|
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
35
35
|
|
|
@@ -39,7 +39,11 @@ module.exports = function(RED) {
|
|
|
39
39
|
|
|
40
40
|
// --- PRENDE IL NOME DELLA TELECAMERA ---
|
|
41
41
|
async function getCameraName(cam) {
|
|
42
|
-
const camAuth = new AxiosDigestAuth({
|
|
42
|
+
const camAuth = new AxiosDigestAuth({
|
|
43
|
+
username: node.user,
|
|
44
|
+
password: node.camPass
|
|
45
|
+
});
|
|
46
|
+
|
|
43
47
|
try {
|
|
44
48
|
const res = await camAuth.request({
|
|
45
49
|
method: 'GET',
|
|
@@ -47,10 +51,19 @@ module.exports = function(RED) {
|
|
|
47
51
|
timeout: 5000,
|
|
48
52
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
49
53
|
});
|
|
54
|
+
|
|
50
55
|
const data = res.data.toString();
|
|
51
56
|
const match = data.match(/<name>([^<]+)<\/name>/i);
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
|
|
58
|
+
if (match && match[1]) {
|
|
59
|
+
return match[1].trim();
|
|
60
|
+
} else {
|
|
61
|
+
return `Canale_${cam.channel}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return `Camera_${cam.ip}`;
|
|
66
|
+
}
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
// --- CONTROLLO STATUS CAMERE ---
|
|
@@ -60,18 +73,22 @@ module.exports = function(RED) {
|
|
|
60
73
|
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
61
74
|
try {
|
|
62
75
|
await camAuth.request({
|
|
63
|
-
method: 'GET',
|
|
76
|
+
method: 'GET',
|
|
77
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
|
|
78
|
+
timeout: 5000,
|
|
64
79
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
65
80
|
});
|
|
66
81
|
if (statoCamera[cam.ip] === false) {
|
|
67
82
|
const nomeOnline = await getCameraName(cam);
|
|
68
|
-
node.send({ payload: { tipo_messaggio: "status",
|
|
83
|
+
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" } });
|
|
84
|
+
statoCamera[cam.ip] = true;
|
|
85
|
+
} else if (statoCamera[cam.ip] === undefined) {
|
|
69
86
|
statoCamera[cam.ip] = true;
|
|
70
|
-
}
|
|
87
|
+
}
|
|
71
88
|
} catch (e) {
|
|
72
89
|
if (statoCamera[cam.ip] !== false) {
|
|
73
90
|
const nomeOffline = await getCameraName(cam);
|
|
74
|
-
node.send({ payload: { tipo_messaggio: "status",
|
|
91
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: nomeOffline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
|
|
75
92
|
statoCamera[cam.ip] = false;
|
|
76
93
|
}
|
|
77
94
|
}
|
|
@@ -81,14 +98,18 @@ module.exports = function(RED) {
|
|
|
81
98
|
|
|
82
99
|
function updateNodeStatus() {
|
|
83
100
|
const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
|
|
84
|
-
if (!nvrOnline) {
|
|
85
|
-
|
|
86
|
-
else
|
|
101
|
+
if (!nvrOnline) {
|
|
102
|
+
node.status({fill:"red", shape:"ring", text:"NVR Offline"});
|
|
103
|
+
} else if (offlineCams > 0) {
|
|
104
|
+
node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
|
|
105
|
+
} else {
|
|
106
|
+
node.status({fill:"green", shape:"ring", text:"In ascolto"});
|
|
107
|
+
}
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
90
111
|
|
|
91
|
-
// --- DOWNLOAD, CONVERSIONE E
|
|
112
|
+
// --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
|
|
92
113
|
async function downloadMedia(evento, channelID) {
|
|
93
114
|
const camera = node.cameras.find(c => c.channel == channelID);
|
|
94
115
|
const nomeCamera = await getCameraName(camera);
|
|
@@ -109,6 +130,7 @@ module.exports = function(RED) {
|
|
|
109
130
|
|
|
110
131
|
const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
|
|
111
132
|
|
|
133
|
+
// Prepariamo la struttura del payload per Python
|
|
112
134
|
let output = {
|
|
113
135
|
tipo_messaggio: "evento",
|
|
114
136
|
nome_cliente: node.name,
|
|
@@ -117,10 +139,14 @@ module.exports = function(RED) {
|
|
|
117
139
|
tipo_evento: evento,
|
|
118
140
|
timestamp_epoch: timestamp,
|
|
119
141
|
stato_telecamera: "ONLINE",
|
|
120
|
-
|
|
121
|
-
|
|
142
|
+
channel: channelID.toString(),
|
|
143
|
+
foto_base64: null,
|
|
144
|
+
video_base64: null
|
|
122
145
|
};
|
|
123
146
|
|
|
147
|
+
// Teniamo traccia dei percorsi per poterli cancellare tra 2 minuti
|
|
148
|
+
let fileDaCancellare = [];
|
|
149
|
+
|
|
124
150
|
try {
|
|
125
151
|
const tracks = [{ id: "201" }, { id: "203" }];
|
|
126
152
|
for (let t of tracks) {
|
|
@@ -136,58 +162,65 @@ module.exports = function(RED) {
|
|
|
136
162
|
if (uriMatch) {
|
|
137
163
|
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
138
164
|
const resDown = await camAuth.request({
|
|
139
|
-
method: 'GET',
|
|
165
|
+
method: 'GET',
|
|
166
|
+
url: `${baseUrl}/download`,
|
|
140
167
|
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
141
168
|
responseType: 'arraybuffer'
|
|
142
169
|
});
|
|
143
170
|
|
|
144
171
|
let buffer = Buffer.from(resDown.data);
|
|
145
|
-
|
|
146
172
|
if (t.id === "203") {
|
|
147
|
-
//
|
|
148
|
-
const
|
|
149
|
-
fs.writeFileSync(
|
|
173
|
+
// --- SALVA FOTO IN LOCALE COME PRIMA ---
|
|
174
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
175
|
+
fs.writeFileSync(fullImgPath, buffer);
|
|
150
176
|
|
|
151
|
-
//
|
|
152
|
-
output.foto_base64 = fs.readFileSync(
|
|
177
|
+
// La convertiamo subito in testo per il payload
|
|
178
|
+
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
153
179
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
try { if (fs.existsSync(tempImgPath)) fs.unlinkSync(tempImgPath); } catch(e){}
|
|
157
|
-
}, 120000);
|
|
158
|
-
|
|
180
|
+
// Registriamo il file per la distruzione futura
|
|
181
|
+
fileDaCancellare.push(fullImgPath);
|
|
159
182
|
} else {
|
|
160
|
-
//
|
|
183
|
+
// --- SALVA VIDEO IN LOCALE COME PRIMA ---
|
|
161
184
|
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
162
185
|
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
163
|
-
const fixedPath = path.join(baseStorage, `
|
|
186
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
164
187
|
fs.writeFileSync(rawPath, buffer);
|
|
165
188
|
|
|
189
|
+
// Eseguiamo ffmpeg localmente sul PC del cliente
|
|
166
190
|
await new Promise((resolve) => {
|
|
167
191
|
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
168
192
|
if (!err && fs.existsSync(fixedPath)) {
|
|
169
193
|
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
194
|
+
fileDaCancellare.push(fixedPath);
|
|
170
195
|
} else {
|
|
171
196
|
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
172
197
|
}
|
|
173
|
-
|
|
174
|
-
// MODIFICATO: Rimozione file video posticipata a 2 minuti (120000 ms)
|
|
175
|
-
setTimeout(() => {
|
|
176
|
-
try { if (fs.existsSync(rawPath)) fs.unlinkSync(rawPath); } catch(e){}
|
|
177
|
-
try { if (fs.existsSync(fixedPath)) fs.unlinkSync(fixedPath); } catch(e){}
|
|
178
|
-
}, 120000);
|
|
179
|
-
|
|
198
|
+
fileDaCancellare.push(rawPath);
|
|
180
199
|
resolve();
|
|
181
200
|
});
|
|
182
201
|
});
|
|
183
202
|
}
|
|
184
203
|
}
|
|
185
204
|
}
|
|
186
|
-
|
|
187
|
-
//
|
|
205
|
+
|
|
206
|
+
// Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
|
|
188
207
|
if (output.foto_base64 || output.video_base64) {
|
|
189
208
|
node.send({ payload: output });
|
|
209
|
+
|
|
210
|
+
// --- TIMER RIGIDO A 2 MINUTI PER LA PULIZIA DEL DISCO ---
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
for (let file of fileDaCancellare) {
|
|
213
|
+
try {
|
|
214
|
+
if (fs.existsSync(file)) {
|
|
215
|
+
fs.unlinkSync(file);
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
node.error(`Errore durante la pulizia del file temporaneo ${file}: ${err.message}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}, 120000); // 120.000 ms = 2 minuti esatti
|
|
190
222
|
}
|
|
223
|
+
|
|
191
224
|
} catch (e) {
|
|
192
225
|
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
193
226
|
}
|