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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +86 -45
  2. package/package.json +1 -1
@@ -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
- // FORMATTAZIONE DATA UTC PULITA PER IL JSON DELL'NVR
123
- const formatJsonDate = (d) => {
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
- // PORTIAMO L'ATTESA A 10 SECONDI PER L'HARD DISK
136
- node.warn(`[warn] [hik-media-buffer:test] Attesa scrittura completa su Hard Disk (10s)...`);
137
- await new Promise(resolve => setTimeout(resolve, 10000));
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
- // --- STRUTTURA JSON DALL'ISPEZIONA NVR NATIVO ---
159
- const searchJsonPayload = {
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": 30,
164
- "timeSpanList": [{
165
- "startTime": startTimeJson,
166
- "endTime": endTimeJson
167
- }],
174
+ "maxResults": 10,
175
+ "timeSpanList": [{"startTime": startLocale, "endTime": endLocale}],
168
176
  "type": "all",
169
- "channels": [parsedChannel],
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
- const resSearchVideo = await camAuth.request({
184
+ let resLocale = await camAuth.request({
181
185
  method: 'POST',
182
186
  url: `${baseUrl}/eventRecordSearch?format=json`,
183
- data: searchJsonPayload,
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
- const resDataStr = JSON.stringify(resSearchVideo.data);
188
- // Estrae l'RTSP playbackURI o l'endpoint di download dal JSON di risposta
189
- const videoUriMatch = resDataStr.match(/rtsp:\/\/[^"]+/i) || resDataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
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
- if (videoUriMatch) {
192
- let rawVideoUri = videoUriMatch[0].replace(/\\/g, '');
193
- node.warn(`[warn] [hik-media-buffer:test] Trovato playbackURI video nel JSON: ${rawVideoUri}`);
194
-
195
- // Scarichiamo il filmato dall'NVR
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 simultanea (Estrazione frame JPG + conversione video MP4)...`);
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] Foto estratta dal video correttamente.`);
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 ottimizzato convertito correttamente.`);
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, invio video originale grezzo: ${err.message}`);
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
- } else {
236
- node.warn(`[warn] [hik-media-buffer:test] Nessun file video trovato nel database JSON dell'NVR.`);
276
+ } catch (downErr) {
277
+ node.error(`[error] [hik-media-buffer:test] Errore durante il download fisico del file: ${downErr.message}`);
237
278
  }
238
- } catch (videoErr) {
239
- node.error(`[error] [hik-media-buffer:test] Errore critico durante la ricerca JSON: ${videoErr.message}`);
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 file multimediale generato.`);
294
+ node.warn(`[warn] [hik-media-buffer:test] Nessun pacchetto inviato a Node-RED.`);
254
295
  }
255
296
  updateNodeStatus();
256
297
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.67",
3
+ "version": "1.1.69",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",