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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +85 -42
  2. package/package.json +1 -1
@@ -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
- // Orario formattato per il database JSON dell'NVR (Ora locale con indicazione fuso es. 02:00)
122
- const formatJsonDate = (d) => {
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())} 02:00`;
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
- node.warn(`[warn] [hik-media-buffer:test] Attesa scrittura file su Hard Disk (6s)...`);
133
- await new Promise(resolve => setTimeout(resolve, 6000));
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
- // --- STRUTTURA JSON DALL'ISPEZIONA NVR NATIVO ---
155
- 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 = {
156
169
  "EventSearchDescription": {
157
170
  "searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
158
171
  "searchResultPosition": 0,
159
- "maxResults": 30,
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
- const resSearchVideo = await camAuth.request({
182
+ let resLocale = await camAuth.request({
177
183
  method: 'POST',
178
184
  url: `${baseUrl}/eventRecordSearch?format=json`,
179
- data: searchJsonPayload,
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
- const resDataStr = JSON.stringify(resSearchVideo.data);
184
- // Estrae l'RTSP playbackURI o l'endpoint di download dal JSON di risposta
185
- 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)));
186
199
 
187
- if (videoUriMatch) {
188
- let rawVideoUri = videoUriMatch[0].replace(/\\/g, '');
189
- node.warn(`[warn] [hik-media-buffer:test] Trovato playbackURI video nel JSON: ${rawVideoUri}`);
190
-
191
- // 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 {
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 simultanea (Estrazione frame JPG + conversione video MP4)...`);
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] Foto estratta dal video correttamente.`);
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 ottimizzato convertito correttamente.`);
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, invio video originale grezzo: ${err.message}`);
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
- } else {
232
- 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}`);
233
276
  }
234
- } catch (videoErr) {
235
- 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.`);
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 file multimediale generato.`);
292
+ node.warn(`[warn] [hik-media-buffer:test] Nessun pacchetto inviato a Node-RED.`);
250
293
  }
251
294
  updateNodeStatus();
252
295
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.66",
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",