node-red-contrib-hik-media-buffer 1.1.67 → 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 +83 -44
- 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,82 @@ 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
|
+
let payloadLocale = {
|
|
160
169
|
"EventSearchDescription": {
|
|
161
170
|
"searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
|
|
162
171
|
"searchResultPosition": 0,
|
|
163
|
-
"maxResults":
|
|
164
|
-
"timeSpanList": [{
|
|
165
|
-
"startTime": startTimeJson,
|
|
166
|
-
"endTime": endTimeJson
|
|
167
|
-
}],
|
|
172
|
+
"maxResults": 10,
|
|
173
|
+
"timeSpanList": [{"startTime": startLocale, "endTime": endLocale}],
|
|
168
174
|
"type": "all",
|
|
169
175
|
"channels": [parsedChannel],
|
|
170
176
|
"eventType": "behavior",
|
|
171
|
-
"behavior": {
|
|
172
|
-
"behaviorEventType": evMinuscolo
|
|
173
|
-
}
|
|
177
|
+
"behavior": {"behaviorEventType": evMinuscolo}
|
|
174
178
|
}
|
|
175
179
|
};
|
|
176
180
|
|
|
177
|
-
node.warn(`[warn] [hik-media-buffer:test] Invio query JSON a eventRecordSearch per canale ${parsedChannel}...`);
|
|
178
|
-
|
|
179
181
|
try {
|
|
180
|
-
|
|
182
|
+
let resLocale = await camAuth.request({
|
|
181
183
|
method: 'POST',
|
|
182
184
|
url: `${baseUrl}/eventRecordSearch?format=json`,
|
|
183
|
-
data:
|
|
184
|
-
headers: { "Content-Type": "application/json" }
|
|
185
|
+
data: payloadLocale,
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
timeout: 5000
|
|
185
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
|
+
}
|
|
186
194
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
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)));
|
|
190
199
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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 {
|
|
196
236
|
const resDownVideo = await camAuth.request({
|
|
197
237
|
method: 'GET',
|
|
198
238
|
url: `${baseUrl}/download`,
|
|
@@ -210,38 +250,37 @@ module.exports = function(RED) {
|
|
|
210
250
|
fs.writeFileSync(rawPath, videoBuffer);
|
|
211
251
|
fileDaCancellare.push(rawPath);
|
|
212
252
|
|
|
213
|
-
node.warn(`[warn] [hik-media-buffer:test] Esecuzione FFMPEG
|
|
253
|
+
node.warn(`[warn] [hik-media-buffer:test] Esecuzione FFMPEG (Estrazione JPG + FastStart MP4)...`);
|
|
214
254
|
await new Promise((resolve) => {
|
|
215
|
-
// FFMPEG estrae il primo secondo in JPG e indicizza il video in FastStart in un colpo solo
|
|
216
255
|
exec(`ffmpeg -y -i "${rawPath}" -ss 00:00:01 -vframes 1 "${extractedImgPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
217
256
|
if (!err) {
|
|
218
257
|
if (fs.existsSync(extractedImgPath)) {
|
|
219
258
|
output.foto_base64 = fs.readFileSync(extractedImgPath, { encoding: 'base64' });
|
|
220
259
|
fileDaCancellare.push(extractedImgPath);
|
|
221
|
-
node.warn(`[warn] [hik-media-buffer:test]
|
|
260
|
+
node.warn(`[warn] [hik-media-buffer:test] Snapshot FOTO estratto con successo.`);
|
|
222
261
|
}
|
|
223
262
|
if (fs.existsSync(fixedPath)) {
|
|
224
263
|
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
225
264
|
fileDaCancellare.push(fixedPath);
|
|
226
|
-
node.warn(`[warn] [hik-media-buffer:test] Video
|
|
265
|
+
node.warn(`[warn] [hik-media-buffer:test] Video MP4 indicizzato con successo.`);
|
|
227
266
|
}
|
|
228
267
|
} else {
|
|
229
|
-
node.warn(`[warn] [hik-media-buffer:test] Errore FFMPEG,
|
|
268
|
+
node.warn(`[warn] [hik-media-buffer:test] Errore FFMPEG, uso file grezzo: ${err.message}`);
|
|
230
269
|
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
231
270
|
}
|
|
232
271
|
resolve();
|
|
233
272
|
});
|
|
234
273
|
});
|
|
235
|
-
}
|
|
236
|
-
node.
|
|
274
|
+
} catch (downErr) {
|
|
275
|
+
node.error(`[error] [hik-media-buffer:test] Errore durante il download fisico del file: ${downErr.message}`);
|
|
237
276
|
}
|
|
238
|
-
}
|
|
239
|
-
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.`);
|
|
240
279
|
}
|
|
241
280
|
|
|
242
|
-
// --- SPEDIZIONE VERSO PYTHON ---
|
|
281
|
+
// --- SPEDIZIONE PAYLOAD VERSO PYTHON ---
|
|
243
282
|
if (output.foto_base64 || output.video_base64) {
|
|
244
|
-
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!`);
|
|
245
284
|
node.send({ payload: output });
|
|
246
285
|
|
|
247
286
|
setTimeout(() => {
|
|
@@ -250,7 +289,7 @@ module.exports = function(RED) {
|
|
|
250
289
|
}
|
|
251
290
|
}, 120000);
|
|
252
291
|
} else {
|
|
253
|
-
node.warn(`[warn] [hik-media-buffer:test] Nessun
|
|
292
|
+
node.warn(`[warn] [hik-media-buffer:test] Nessun pacchetto inviato a Node-RED.`);
|
|
254
293
|
}
|
|
255
294
|
updateNodeStatus();
|
|
256
295
|
}
|