node-red-contrib-hik-media-buffer 1.1.14 → 1.1.16
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 +66 -29
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -29,12 +29,9 @@ module.exports = function(RED) {
|
|
|
29
29
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
30
30
|
const EventList = ["FieldDetection", "LineDetection"];
|
|
31
31
|
|
|
32
|
-
// ---
|
|
33
|
-
const baseStorage =
|
|
34
|
-
|
|
35
|
-
const vidDir = path.join(baseStorage, "video");
|
|
36
|
-
if (!fs.existsSync(imgDir)) fs.mkdirSync(imgDir, { recursive: true });
|
|
37
|
-
if (!fs.existsSync(vidDir)) fs.mkdirSync(vidDir, { recursive: true });
|
|
32
|
+
// --- CARTELLA TEMPORANEA SUL PC DEL CLIENTE ---
|
|
33
|
+
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
34
|
+
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
38
35
|
|
|
39
36
|
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
40
37
|
|
|
@@ -48,7 +45,6 @@ module.exports = function(RED) {
|
|
|
48
45
|
});
|
|
49
46
|
|
|
50
47
|
try {
|
|
51
|
-
// Interroghiamo l'overlay del nome del canale specifico
|
|
52
48
|
const res = await camAuth.request({
|
|
53
49
|
method: 'GET',
|
|
54
50
|
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
|
|
@@ -57,22 +53,20 @@ module.exports = function(RED) {
|
|
|
57
53
|
});
|
|
58
54
|
|
|
59
55
|
const data = res.data.toString();
|
|
60
|
-
|
|
61
|
-
// Il manuale specifica che il nome è nel tag <name>
|
|
62
56
|
const match = data.match(/<name>([^<]+)<\/name>/i);
|
|
63
57
|
|
|
64
58
|
if (match && match[1]) {
|
|
65
|
-
return match[1].trim();
|
|
59
|
+
return match[1].trim();
|
|
66
60
|
} else {
|
|
67
61
|
return `Canale_${cam.channel}`;
|
|
68
62
|
}
|
|
69
63
|
|
|
70
64
|
} catch (e) {
|
|
71
|
-
// Fallback: se non risponde, usiamo l'IP
|
|
72
65
|
return `Camera_${cam.ip}`;
|
|
73
66
|
}
|
|
74
67
|
}
|
|
75
68
|
|
|
69
|
+
// --- CONTROLLO STATUS CAMERE ---
|
|
76
70
|
async function checkCameras() {
|
|
77
71
|
if (isClosing) return;
|
|
78
72
|
for (let cam of node.cameras) {
|
|
@@ -86,7 +80,7 @@ module.exports = function(RED) {
|
|
|
86
80
|
});
|
|
87
81
|
if (statoCamera[cam.ip] === false) {
|
|
88
82
|
const nomeOnline = await getCameraName(cam);
|
|
89
|
-
node.send({ payload: { status: "online",
|
|
83
|
+
node.send({ payload: { tipo_messaggio: "status", status: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
|
|
90
84
|
statoCamera[cam.ip] = true;
|
|
91
85
|
} else if (statoCamera[cam.ip] === undefined) {
|
|
92
86
|
statoCamera[cam.ip] = true;
|
|
@@ -94,7 +88,7 @@ module.exports = function(RED) {
|
|
|
94
88
|
} catch (e) {
|
|
95
89
|
if (statoCamera[cam.ip] !== false) {
|
|
96
90
|
const nomeOffline = await getCameraName(cam);
|
|
97
|
-
node.send({ payload: { status: "offline",
|
|
91
|
+
node.send({ payload: { tipo_messaggio: "status", status: "offline", nome_cliente: node.name, nome_telecamera: nomeOffline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
|
|
98
92
|
statoCamera[cam.ip] = false;
|
|
99
93
|
}
|
|
100
94
|
}
|
|
@@ -115,6 +109,7 @@ module.exports = function(RED) {
|
|
|
115
109
|
|
|
116
110
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
117
111
|
|
|
112
|
+
// --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
|
|
118
113
|
async function downloadMedia(evento, channelID) {
|
|
119
114
|
const camera = node.cameras.find(c => c.channel == channelID);
|
|
120
115
|
const nomeCamera = await getCameraName(camera);
|
|
@@ -134,7 +129,23 @@ module.exports = function(RED) {
|
|
|
134
129
|
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
135
130
|
|
|
136
131
|
const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
|
|
137
|
-
|
|
132
|
+
|
|
133
|
+
// Prepariamo la struttura del payload per Python
|
|
134
|
+
let output = {
|
|
135
|
+
tipo_messaggio: "evento",
|
|
136
|
+
nome_cliente: node.name,
|
|
137
|
+
nome_telecamera: nomeCamera,
|
|
138
|
+
ip_telecamera: camera.ip,
|
|
139
|
+
tipo_evento: evento,
|
|
140
|
+
timestamp_epoch: timestamp,
|
|
141
|
+
stato_telecamera: "ONLINE",
|
|
142
|
+
channel: channelID.toString(),
|
|
143
|
+
foto_base64: null,
|
|
144
|
+
video_base64: null
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Teniamo traccia dei percorsi per poterli cancellare tra 2 minuti
|
|
148
|
+
let fileDaCancellare = [];
|
|
138
149
|
|
|
139
150
|
try {
|
|
140
151
|
const tracks = [{ id: "201" }, { id: "203" }];
|
|
@@ -159,38 +170,64 @@ module.exports = function(RED) {
|
|
|
159
170
|
|
|
160
171
|
let buffer = Buffer.from(resDown.data);
|
|
161
172
|
if (t.id === "203") {
|
|
162
|
-
// --- SALVA FOTO ---
|
|
163
|
-
const fullImgPath = path.join(
|
|
173
|
+
// --- SALVA FOTO IN LOCALE COME PRIMA ---
|
|
174
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
164
175
|
fs.writeFileSync(fullImgPath, buffer);
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
|
|
177
|
+
// La convertiamo subito in testo per il payload
|
|
178
|
+
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
179
|
+
|
|
180
|
+
// Registriamo il file per la distruzione futura
|
|
181
|
+
fileDaCancellare.push(fullImgPath);
|
|
167
182
|
} else {
|
|
168
|
-
// --- SALVA VIDEO ---
|
|
183
|
+
// --- SALVA VIDEO IN LOCALE COME PRIMA ---
|
|
169
184
|
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
170
|
-
const rawPath = path.join(
|
|
171
|
-
const fixedPath = path.join(
|
|
185
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
186
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
172
187
|
fs.writeFileSync(rawPath, buffer);
|
|
173
188
|
|
|
189
|
+
// Eseguiamo ffmpeg localmente sul PC del cliente
|
|
174
190
|
await new Promise((resolve) => {
|
|
175
191
|
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
176
|
-
if (!err) {
|
|
177
|
-
output.
|
|
178
|
-
|
|
179
|
-
} else {
|
|
192
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
193
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
194
|
+
fileDaCancellare.push(fixedPath);
|
|
195
|
+
} else {
|
|
196
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
197
|
+
}
|
|
198
|
+
fileDaCancellare.push(rawPath);
|
|
180
199
|
resolve();
|
|
181
200
|
});
|
|
182
201
|
});
|
|
183
202
|
}
|
|
184
203
|
}
|
|
185
204
|
}
|
|
186
|
-
|
|
205
|
+
|
|
206
|
+
// Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
|
|
207
|
+
if (output.foto_base64 || output.video_base64) {
|
|
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
|
|
222
|
+
}
|
|
223
|
+
|
|
187
224
|
} catch (e) {
|
|
188
225
|
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
189
226
|
}
|
|
190
227
|
updateNodeStatus();
|
|
191
228
|
}
|
|
192
229
|
|
|
193
|
-
// --- ALERT STREAM
|
|
230
|
+
// --- ALERT STREAM ---
|
|
194
231
|
function startAlertStream() {
|
|
195
232
|
if (isClosing) return;
|
|
196
233
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
@@ -198,7 +235,7 @@ module.exports = function(RED) {
|
|
|
198
235
|
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
199
236
|
.then(response => {
|
|
200
237
|
streamRequest = response;
|
|
201
|
-
if (!nvrOnline) { node.send({ payload: { status: "online",
|
|
238
|
+
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "online", ip_telecamera: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
202
239
|
updateNodeStatus();
|
|
203
240
|
response.data.on('data', (chunk) => {
|
|
204
241
|
const data = chunk.toString().toLowerCase();
|
|
@@ -217,7 +254,7 @@ module.exports = function(RED) {
|
|
|
217
254
|
}
|
|
218
255
|
|
|
219
256
|
function handleNvrError() {
|
|
220
|
-
if (nvrOnline) { node.send({ payload: { status: "offline",
|
|
257
|
+
if (nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "offline", ip_telecamera: node.host, msg: "NVR Offline", nome_cliente: node.name } }); nvrOnline = false; }
|
|
221
258
|
updateNodeStatus();
|
|
222
259
|
if (!isClosing) setTimeout(startAlertStream, 10000);
|
|
223
260
|
}
|