node-red-contrib-hik-media-buffer 1.1.14 → 1.1.15
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 +64 -60
- 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
|
+
// BASE STORAGE TEMPORANEA
|
|
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
|
|
|
@@ -42,59 +39,39 @@ module.exports = function(RED) {
|
|
|
42
39
|
|
|
43
40
|
// --- PRENDE IL NOME DELLA TELECAMERA ---
|
|
44
41
|
async function getCameraName(cam) {
|
|
45
|
-
const camAuth = new AxiosDigestAuth({
|
|
46
|
-
username: node.user,
|
|
47
|
-
password: node.camPass
|
|
48
|
-
});
|
|
49
|
-
|
|
42
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
50
43
|
try {
|
|
51
|
-
// Interroghiamo l'overlay del nome del canale specifico
|
|
52
44
|
const res = await camAuth.request({
|
|
53
45
|
method: 'GET',
|
|
54
46
|
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
|
|
55
47
|
timeout: 5000,
|
|
56
48
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
57
49
|
});
|
|
58
|
-
|
|
59
50
|
const data = res.data.toString();
|
|
60
|
-
|
|
61
|
-
// Il manuale specifica che il nome è nel tag <name>
|
|
62
51
|
const match = data.match(/<name>([^<]+)<\/name>/i);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return match[1].trim(); // Restituirà "Ufficio"
|
|
66
|
-
} else {
|
|
67
|
-
return `Canale_${cam.channel}`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
} catch (e) {
|
|
71
|
-
// Fallback: se non risponde, usiamo l'IP
|
|
72
|
-
return `Camera_${cam.ip}`;
|
|
73
|
-
}
|
|
52
|
+
return match && match[1] ? match[1].trim() : `Canale_${cam.channel}`;
|
|
53
|
+
} catch (e) { return `Camera_${cam.ip}`; }
|
|
74
54
|
}
|
|
75
55
|
|
|
56
|
+
// --- CONTROLLO STATUS CAMERE ---
|
|
76
57
|
async function checkCameras() {
|
|
77
58
|
if (isClosing) return;
|
|
78
59
|
for (let cam of node.cameras) {
|
|
79
60
|
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
80
61
|
try {
|
|
81
62
|
await camAuth.request({
|
|
82
|
-
method: 'GET',
|
|
83
|
-
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
|
|
84
|
-
timeout: 5000,
|
|
63
|
+
method: 'GET', url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`, timeout: 5000,
|
|
85
64
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
86
65
|
});
|
|
87
66
|
if (statoCamera[cam.ip] === false) {
|
|
88
67
|
const nomeOnline = await getCameraName(cam);
|
|
89
|
-
node.send({ payload: { status: "online",
|
|
68
|
+
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
69
|
statoCamera[cam.ip] = true;
|
|
91
|
-
} else if (statoCamera[cam.ip] === undefined) {
|
|
92
|
-
statoCamera[cam.ip] = true;
|
|
93
|
-
}
|
|
70
|
+
} else if (statoCamera[cam.ip] === undefined) { statoCamera[cam.ip] = true; }
|
|
94
71
|
} catch (e) {
|
|
95
72
|
if (statoCamera[cam.ip] !== false) {
|
|
96
73
|
const nomeOffline = await getCameraName(cam);
|
|
97
|
-
node.send({ payload: { status: "offline",
|
|
74
|
+
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
75
|
statoCamera[cam.ip] = false;
|
|
99
76
|
}
|
|
100
77
|
}
|
|
@@ -104,17 +81,14 @@ module.exports = function(RED) {
|
|
|
104
81
|
|
|
105
82
|
function updateNodeStatus() {
|
|
106
83
|
const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
|
|
107
|
-
if (!nvrOnline) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
|
|
111
|
-
} else {
|
|
112
|
-
node.status({fill:"green", shape:"ring", text:"In ascolto"});
|
|
113
|
-
}
|
|
84
|
+
if (!nvrOnline) { node.status({fill:"red", shape:"ring", text:"NVR Offline"}); }
|
|
85
|
+
else if (offlineCams > 0) { node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`}); }
|
|
86
|
+
else { node.status({fill:"green", shape:"ring", text:"In ascolto"}); }
|
|
114
87
|
}
|
|
115
88
|
|
|
116
89
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
117
90
|
|
|
91
|
+
// --- DOWNLOAD, CONVERSIONE E TIMEOUT DI ELIMINAZIONE A 2 MINUTI ---
|
|
118
92
|
async function downloadMedia(evento, channelID) {
|
|
119
93
|
const camera = node.cameras.find(c => c.channel == channelID);
|
|
120
94
|
const nomeCamera = await getCameraName(camera);
|
|
@@ -134,7 +108,18 @@ module.exports = function(RED) {
|
|
|
134
108
|
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
135
109
|
|
|
136
110
|
const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
|
|
137
|
-
|
|
111
|
+
|
|
112
|
+
let output = {
|
|
113
|
+
tipo_messaggio: "evento",
|
|
114
|
+
nome_cliente: node.name,
|
|
115
|
+
nome_telecamera: nomeCamera,
|
|
116
|
+
ip_telecamera: camera.ip,
|
|
117
|
+
tipo_evento: evento,
|
|
118
|
+
timestamp_epoch: timestamp,
|
|
119
|
+
stato_telecamera: "ONLINE",
|
|
120
|
+
foto_base64: null,
|
|
121
|
+
video_base64: null
|
|
122
|
+
};
|
|
138
123
|
|
|
139
124
|
try {
|
|
140
125
|
const tracks = [{ id: "201" }, { id: "203" }];
|
|
@@ -151,46 +136,65 @@ module.exports = function(RED) {
|
|
|
151
136
|
if (uriMatch) {
|
|
152
137
|
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
153
138
|
const resDown = await camAuth.request({
|
|
154
|
-
method: 'GET',
|
|
155
|
-
url: `${baseUrl}/download`,
|
|
139
|
+
method: 'GET', url: `${baseUrl}/download`,
|
|
156
140
|
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
157
141
|
responseType: 'arraybuffer'
|
|
158
142
|
});
|
|
159
143
|
|
|
160
144
|
let buffer = Buffer.from(resDown.data);
|
|
145
|
+
|
|
161
146
|
if (t.id === "203") {
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
fs.writeFileSync(
|
|
165
|
-
|
|
166
|
-
|
|
147
|
+
// Scrittura immagine temporanea
|
|
148
|
+
const tempImgPath = path.join(baseStorage, `temp_${timestamp}.jpg`);
|
|
149
|
+
fs.writeFileSync(tempImgPath, buffer);
|
|
150
|
+
|
|
151
|
+
// Conversione immediata in stringa di testo Base64
|
|
152
|
+
output.foto_base64 = fs.readFileSync(tempImgPath, { encoding: 'base64' });
|
|
153
|
+
|
|
154
|
+
// MODIFICATO: Rimozione file posticipata a 2 minuti (120000 ms)
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
try { if (fs.existsSync(tempImgPath)) fs.unlinkSync(tempImgPath); } catch(e){}
|
|
157
|
+
}, 120000);
|
|
158
|
+
|
|
167
159
|
} else {
|
|
168
|
-
//
|
|
160
|
+
// Scrittura video temporaneo
|
|
169
161
|
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
170
|
-
const rawPath = path.join(
|
|
171
|
-
const fixedPath = path.join(
|
|
162
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
163
|
+
const fixedPath = path.join(baseStorage, `fixed_${timestamp}.mp4`);
|
|
172
164
|
fs.writeFileSync(rawPath, buffer);
|
|
173
165
|
|
|
174
166
|
await new Promise((resolve) => {
|
|
175
167
|
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
176
|
-
if (!err) {
|
|
177
|
-
output.
|
|
178
|
-
|
|
179
|
-
|
|
168
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
169
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
170
|
+
} else {
|
|
171
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
172
|
+
}
|
|
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
|
+
|
|
180
180
|
resolve();
|
|
181
181
|
});
|
|
182
182
|
});
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
-
|
|
186
|
+
|
|
187
|
+
// Spedisce il payload al flusso di Node-RED
|
|
188
|
+
if (output.foto_base64 || output.video_base64) {
|
|
189
|
+
node.send({ payload: output });
|
|
190
|
+
}
|
|
187
191
|
} catch (e) {
|
|
188
192
|
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
189
193
|
}
|
|
190
194
|
updateNodeStatus();
|
|
191
195
|
}
|
|
192
196
|
|
|
193
|
-
// --- ALERT STREAM
|
|
197
|
+
// --- ALERT STREAM ---
|
|
194
198
|
function startAlertStream() {
|
|
195
199
|
if (isClosing) return;
|
|
196
200
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
@@ -198,7 +202,7 @@ module.exports = function(RED) {
|
|
|
198
202
|
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
199
203
|
.then(response => {
|
|
200
204
|
streamRequest = response;
|
|
201
|
-
if (!nvrOnline) { node.send({ payload: { status: "online",
|
|
205
|
+
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "online", ip_telecamera: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
202
206
|
updateNodeStatus();
|
|
203
207
|
response.data.on('data', (chunk) => {
|
|
204
208
|
const data = chunk.toString().toLowerCase();
|
|
@@ -217,7 +221,7 @@ module.exports = function(RED) {
|
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
function handleNvrError() {
|
|
220
|
-
if (nvrOnline) { node.send({ payload: { status: "offline",
|
|
224
|
+
if (nvrOnline) { node.send({ payload: { tipo_messaggio: "status", status: "offline", ip_telecamera: node.host, msg: "NVR Offline", nome_cliente: node.name } }); nvrOnline = false; }
|
|
221
225
|
updateNodeStatus();
|
|
222
226
|
if (!isClosing) setTimeout(startAlertStream, 10000);
|
|
223
227
|
}
|