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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +83 -44
  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,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
- // --- 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
+ let payloadLocale = {
160
169
  "EventSearchDescription": {
161
170
  "searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
162
171
  "searchResultPosition": 0,
163
- "maxResults": 30,
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
- const resSearchVideo = await camAuth.request({
182
+ let resLocale = await camAuth.request({
181
183
  method: 'POST',
182
184
  url: `${baseUrl}/eventRecordSearch?format=json`,
183
- data: searchJsonPayload,
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
- 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);
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
- 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
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 simultanea (Estrazione frame JPG + conversione video MP4)...`);
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] Foto estratta dal video correttamente.`);
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 ottimizzato convertito correttamente.`);
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, invio video originale grezzo: ${err.message}`);
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
- } else {
236
- node.warn(`[warn] [hik-media-buffer:test] Nessun file video trovato nel database JSON dell'NVR.`);
274
+ } catch (downErr) {
275
+ node.error(`[error] [hik-media-buffer:test] Errore durante il download fisico del file: ${downErr.message}`);
237
276
  }
238
- } catch (videoErr) {
239
- node.error(`[error] [hik-media-buffer:test] Errore critico durante la ricerca JSON: ${videoErr.message}`);
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 file multimediale generato.`);
292
+ node.warn(`[warn] [hik-media-buffer:test] Nessun pacchetto inviato a Node-RED.`);
254
293
  }
255
294
  updateNodeStatus();
256
295
  }
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.68",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",