node-red-contrib-hik-media-buffer 1.1.66 → 1.1.68
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 +85 -42
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -106,6 +106,8 @@ module.exports = function(RED) {
|
|
|
106
106
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
107
107
|
|
|
108
108
|
// --- STRATEGIA DI DOWNLOAD MULTIMEDIALE 100% JSON + FFMPEG EXTRACTION ---
|
|
109
|
+
// --- STRATEGIA DI DOWNLOAD MULTIMEDIALE 100% JSON + FFMPEG EXTRACTION ---
|
|
110
|
+
// --- STRATEGIA DI DOWNLOAD MULTIMEDIALE CON DOPPIO FALLBACK ORARIO (LOCALE / UTC) ---
|
|
109
111
|
async function downloadMedia(evento, channelID) {
|
|
110
112
|
node.warn(`[warn] [hik-media-buffer:test] [TRIGGER] Ricevuto evento. Evento: ${evento}, Canale originario: ${channelID}`);
|
|
111
113
|
|
|
@@ -118,19 +120,24 @@ module.exports = function(RED) {
|
|
|
118
120
|
const referenceTime = new Date();
|
|
119
121
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
120
122
|
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
+
// FUNZIONE 1: FORMATTO IN ORA LOCALE (Es. 2026-06-09T14:30:00)
|
|
124
|
+
const formatLocaleDate = (d) => {
|
|
123
125
|
const pad = (num) => String(num).padStart(2, '0');
|
|
124
|
-
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}
|
|
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. 2026-06-09T12:30:00Z)
|
|
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`;
|
|
125
133
|
};
|
|
126
|
-
const startTimeJson = formatJsonDate(new Date(referenceTime.getTime() - (15 * 1000)));
|
|
127
|
-
const endTimeJson = formatJsonDate(new Date(referenceTime.getTime() + (15 * 1000)));
|
|
128
134
|
|
|
129
135
|
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
130
136
|
const nomeCamera = await getCameraName(channelID);
|
|
131
137
|
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
// Aspettiamo 8 secondi per dare tempo all'Hard Disk di scrivere l'indice
|
|
139
|
+
node.warn(`[warn] [hik-media-buffer:test] Attesa scrittura indice Hard Disk (8s)...`);
|
|
140
|
+
await new Promise(resolve => setTimeout(resolve, 8000));
|
|
134
141
|
|
|
135
142
|
const baseUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt`;
|
|
136
143
|
const evMinuscolo = evento.toLowerCase();
|
|
@@ -150,45 +157,82 @@ module.exports = function(RED) {
|
|
|
150
157
|
|
|
151
158
|
const parsedChannel = parseInt(channelID, 10);
|
|
152
159
|
let fileDaCancellare = [];
|
|
160
|
+
let videoUriMatch = null;
|
|
153
161
|
|
|
154
|
-
// ---
|
|
155
|
-
const
|
|
162
|
+
// --- TENTATIVO 1: RICERCA IN ORA LOCALE (La più probabile per i dischi NVR) ---
|
|
163
|
+
const startLocale = formatLocaleDate(new Date(referenceTime.getTime() - (120 * 1000)));
|
|
164
|
+
const endLocale = formatLocaleDate(new Date(referenceTime.getTime() + (30 * 1000)));
|
|
165
|
+
|
|
166
|
+
node.warn(`[warn] [hik-media-buffer:test] [TENTATIVO 1] Ricerca video in Ora Locale: ${startLocale} -> ${endLocale}`);
|
|
167
|
+
|
|
168
|
+
let payloadLocale = {
|
|
156
169
|
"EventSearchDescription": {
|
|
157
170
|
"searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
|
|
158
171
|
"searchResultPosition": 0,
|
|
159
|
-
"maxResults":
|
|
160
|
-
"timeSpanList": [{
|
|
161
|
-
"startTime": startTimeJson,
|
|
162
|
-
"endTime": endTimeJson
|
|
163
|
-
}],
|
|
172
|
+
"maxResults": 10,
|
|
173
|
+
"timeSpanList": [{"startTime": startLocale, "endTime": endLocale}],
|
|
164
174
|
"type": "all",
|
|
165
175
|
"channels": [parsedChannel],
|
|
166
176
|
"eventType": "behavior",
|
|
167
|
-
"behavior": {
|
|
168
|
-
"behaviorEventType": evMinuscolo
|
|
169
|
-
}
|
|
177
|
+
"behavior": {"behaviorEventType": evMinuscolo}
|
|
170
178
|
}
|
|
171
179
|
};
|
|
172
180
|
|
|
173
|
-
node.warn(`[warn] [hik-media-buffer:test] Invio query JSON a eventRecordSearch per canale ${parsedChannel}...`);
|
|
174
|
-
|
|
175
181
|
try {
|
|
176
|
-
|
|
182
|
+
let resLocale = await camAuth.request({
|
|
177
183
|
method: 'POST',
|
|
178
184
|
url: `${baseUrl}/eventRecordSearch?format=json`,
|
|
179
|
-
data:
|
|
180
|
-
headers: { "Content-Type": "application/json" }
|
|
185
|
+
data: payloadLocale,
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
timeout: 5000
|
|
181
188
|
});
|
|
189
|
+
let dataStr = JSON.stringify(resLocale.data);
|
|
190
|
+
videoUriMatch = dataStr.match(/rtsp:\/\/[^"]+/i) || dataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
node.warn(`[warn] [hik-media-buffer:test] Tentativo 1 fallito o timeout: ${e.message}`);
|
|
193
|
+
}
|
|
182
194
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
195
|
+
// --- TENTATIVO 2: FALLBACK IN ORA UTC (Se il primo tentativo è andato a vuoto) ---
|
|
196
|
+
if (!videoUriMatch) {
|
|
197
|
+
const startUtc = formatUtcDate(new Date(referenceTime.getTime() - (120 * 1000)));
|
|
198
|
+
const endUtc = formatUtcDate(new Date(referenceTime.getTime() + (30 * 1000)));
|
|
186
199
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
200
|
+
node.warn(`[warn] [hik-media-buffer:test] [TENTATIVO 2] Video locale non trovato. Provo Fallback in Ora UTC: ${startUtc} -> ${endUtc}`);
|
|
201
|
+
|
|
202
|
+
let payloadUtc = {
|
|
203
|
+
"EventSearchDescription": {
|
|
204
|
+
"searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
|
|
205
|
+
"searchResultPosition": 0,
|
|
206
|
+
"maxResults": 10,
|
|
207
|
+
"timeSpanList": [{"startTime": startUtc, "endTime": endUtc}],
|
|
208
|
+
"type": "all",
|
|
209
|
+
"channels": [parsedChannel],
|
|
210
|
+
"eventType": "behavior",
|
|
211
|
+
"behavior": {"behaviorEventType": evMinuscolo}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
let resUtc = await camAuth.request({
|
|
217
|
+
method: 'POST',
|
|
218
|
+
url: `${baseUrl}/eventRecordSearch?format=json`,
|
|
219
|
+
data: payloadUtc,
|
|
220
|
+
headers: { "Content-Type": "application/json" },
|
|
221
|
+
timeout: 5000
|
|
222
|
+
});
|
|
223
|
+
let dataStr = JSON.stringify(resUtc.data);
|
|
224
|
+
videoUriMatch = dataStr.match(/rtsp:\/\/[^"]+/i) || dataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
node.error(`[error] [hik-media-buffer:test] Errore critico anche nel Fallback UTC: ${e.message}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- PROCESSO DI SCARICAMENTO SE UNA DELLE DUE QUERIES HA TROVATO IL VIDEO ---
|
|
231
|
+
if (videoUriMatch) {
|
|
232
|
+
let rawVideoUri = videoUriMatch[0].replace(/\\/g, '');
|
|
233
|
+
node.warn(`[warn] [hik-media-buffer:test] [FOUND] Trovato playbackURI video: ${rawVideoUri}`);
|
|
234
|
+
|
|
235
|
+
try {
|
|
192
236
|
const resDownVideo = await camAuth.request({
|
|
193
237
|
method: 'GET',
|
|
194
238
|
url: `${baseUrl}/download`,
|
|
@@ -206,38 +250,37 @@ module.exports = function(RED) {
|
|
|
206
250
|
fs.writeFileSync(rawPath, videoBuffer);
|
|
207
251
|
fileDaCancellare.push(rawPath);
|
|
208
252
|
|
|
209
|
-
node.warn(`[warn] [hik-media-buffer:test] Esecuzione FFMPEG
|
|
253
|
+
node.warn(`[warn] [hik-media-buffer:test] Esecuzione FFMPEG (Estrazione JPG + FastStart MP4)...`);
|
|
210
254
|
await new Promise((resolve) => {
|
|
211
|
-
// FFMPEG estrae il primo secondo in JPG e indicizza il video in FastStart in un colpo solo
|
|
212
255
|
exec(`ffmpeg -y -i "${rawPath}" -ss 00:00:01 -vframes 1 "${extractedImgPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
213
256
|
if (!err) {
|
|
214
257
|
if (fs.existsSync(extractedImgPath)) {
|
|
215
258
|
output.foto_base64 = fs.readFileSync(extractedImgPath, { encoding: 'base64' });
|
|
216
259
|
fileDaCancellare.push(extractedImgPath);
|
|
217
|
-
node.warn(`[warn] [hik-media-buffer:test]
|
|
260
|
+
node.warn(`[warn] [hik-media-buffer:test] Snapshot FOTO estratto con successo.`);
|
|
218
261
|
}
|
|
219
262
|
if (fs.existsSync(fixedPath)) {
|
|
220
263
|
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
221
264
|
fileDaCancellare.push(fixedPath);
|
|
222
|
-
node.warn(`[warn] [hik-media-buffer:test] Video
|
|
265
|
+
node.warn(`[warn] [hik-media-buffer:test] Video MP4 indicizzato con successo.`);
|
|
223
266
|
}
|
|
224
267
|
} else {
|
|
225
|
-
node.warn(`[warn] [hik-media-buffer:test] Errore FFMPEG,
|
|
268
|
+
node.warn(`[warn] [hik-media-buffer:test] Errore FFMPEG, uso file grezzo: ${err.message}`);
|
|
226
269
|
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
227
270
|
}
|
|
228
271
|
resolve();
|
|
229
272
|
});
|
|
230
273
|
});
|
|
231
|
-
}
|
|
232
|
-
node.
|
|
274
|
+
} catch (downErr) {
|
|
275
|
+
node.error(`[error] [hik-media-buffer:test] Errore durante il download fisico del file: ${downErr.message}`);
|
|
233
276
|
}
|
|
234
|
-
}
|
|
235
|
-
node.
|
|
277
|
+
} else {
|
|
278
|
+
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.`);
|
|
236
279
|
}
|
|
237
280
|
|
|
238
|
-
// --- SPEDIZIONE VERSO PYTHON ---
|
|
281
|
+
// --- SPEDIZIONE PAYLOAD VERSO PYTHON ---
|
|
239
282
|
if (output.foto_base64 || output.video_base64) {
|
|
240
|
-
node.warn(`[warn] [hik-media-buffer:test] Spedizione payload multimediale completata!`);
|
|
283
|
+
node.warn(`[warn] [hik-media-buffer:test] Spedizione payload multimediale completata con successo!`);
|
|
241
284
|
node.send({ payload: output });
|
|
242
285
|
|
|
243
286
|
setTimeout(() => {
|
|
@@ -246,7 +289,7 @@ module.exports = function(RED) {
|
|
|
246
289
|
}
|
|
247
290
|
}, 120000);
|
|
248
291
|
} else {
|
|
249
|
-
node.warn(`[warn] [hik-media-buffer:test] Nessun
|
|
292
|
+
node.warn(`[warn] [hik-media-buffer:test] Nessun pacchetto inviato a Node-RED.`);
|
|
250
293
|
}
|
|
251
294
|
updateNodeStatus();
|
|
252
295
|
}
|