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