node-red-contrib-hik-media-buffer 1.2.4 → 1.2.5

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-download.html CHANGED
@@ -1,11 +1,11 @@
1
1
  <script type="text/javascript">
2
2
  RED.nodes.registerType('hik-download', {
3
3
  category: 'Hikvision',
4
- color: '#f77f00',
4
+ color: '#00b4d8',
5
5
  defaults: {
6
6
  name: { value: "" },
7
7
  host: { value: "" },
8
- channels: { value: "1" }, // Rinominato in 'channels'
8
+ channels: { value: "1" },
9
9
  protocol: { value: "http" },
10
10
  port: { value: "80" },
11
11
  user: { value: "admin" },
@@ -50,12 +50,12 @@
50
50
 
51
51
  <div class="form-row">
52
52
  <label for="node-input-startTime"><i class="fa fa-calendar"></i> Data Inizio</label>
53
- <input type="text" id="node-input-startTime" placeholder="Es: 20260703T134125">
53
+ <input type="text" id="node-input-startTime" placeholder="Formato: AAAA-MM-GG ORE:MIN:SEC (Es: 2026-07-14 10:00:00)">
54
54
  </div>
55
55
 
56
56
  <div class="form-row">
57
57
  <label for="node-input-endTime"><i class="fa fa-calendar"></i> Data Fine</label>
58
- <input type="text" id="node-input-endTime" placeholder="Es: 20260703T134135">
58
+ <input type="text" id="node-input-endTime" placeholder="Formato: AAAA-MM-GG ORE:MIN:SEC (Es: 2026-07-14 10:05:00)">
59
59
  </div>
60
60
 
61
61
  <div class="form-row">
package/hik-download.js CHANGED
@@ -1,5 +1,7 @@
1
1
  const axios = require('axios');
2
2
  const https = require('https');
3
+ const fs = require('fs');
4
+ const path = require('path');
3
5
  const mhocDigest = require('@mhoc/axios-digest-auth');
4
6
  const DigestAuthClass = mhocDigest.default;
5
7
 
@@ -13,17 +15,16 @@ module.exports = function(RED) {
13
15
  node.port = config.port || "80";
14
16
  node.user = config.user;
15
17
  node.pass = config.pass;
16
- node.channels = config.channels || "1"; // Ora accetta stringhe (es: "1,2,3" o "1-4")
18
+ node.channels = config.channels || "1";
17
19
  node.startTime = config.startTime || "";
18
20
  node.endTime = config.endTime || "";
19
21
 
20
22
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
21
23
 
22
- // 🌟 FUNZIONE DI SUPPORTO: Decifra la stringa dei canali (es: "1,3,5" o "1-3" -> [1, 2, 3, 5])
24
+ // Helper per decifrare i canali (es: "1,3" o "1-3")
23
25
  function parseChannels(channelsStr) {
24
26
  let channels = [];
25
27
  if (!channelsStr) return [1];
26
-
27
28
  let parts = channelsStr.toString().split(',');
28
29
  parts.forEach(part => {
29
30
  part = part.trim();
@@ -32,18 +33,13 @@ module.exports = function(RED) {
32
33
  let start = parseInt(range[0]);
33
34
  let end = parseInt(range[1]);
34
35
  if (!isNaN(start) && !isNaN(end)) {
35
- for (let i = start; i <= end; i++) {
36
- channels.push(i);
37
- }
36
+ for (let i = start; i <= end; i++) channels.push(i);
38
37
  }
39
38
  } else {
40
39
  let ch = parseInt(part);
41
- if (!isNaN(ch)) {
42
- channels.push(ch);
43
- }
40
+ if (!isNaN(ch)) channels.push(ch);
44
41
  }
45
42
  });
46
- // Rimuove i duplicati e ordina i canali
47
43
  return [...new Set(channels)].sort((a, b) => a - b);
48
44
  }
49
45
 
@@ -59,10 +55,7 @@ module.exports = function(RED) {
59
55
  const protocol = msg.nvr_protocol || node.protocol;
60
56
  const startTimeRaw = msg.startTime || node.startTime;
61
57
  const endTimeRaw = msg.endTime || node.endTime;
62
-
63
- // Decodifichiamo i canali (usa quelli del msg se presenti, altrimenti quelli del nodo)
64
- const targetChannelsStr = msg.channels || node.channels;
65
- const targetChannels = parseChannels(targetChannelsStr);
58
+ const targetChannels = parseChannels(msg.channels || node.channels);
66
59
 
67
60
  if (!startTimeRaw || !endTimeRaw) {
68
61
  node.status({ fill: "red", shape: "ring", text: "Date mancanti!" });
@@ -70,18 +63,36 @@ module.exports = function(RED) {
70
63
  return;
71
64
  }
72
65
 
73
- const pulisciData = (dataStr) => {
66
+ // 🌟 1. LOGICA DI PULIZIA DELLE DATE PER HIKVISION (es. "2026-07-14 10:00:00" -> "20260714T100000")
67
+ const pulisciDataNVR = (dataStr) => {
74
68
  return dataStr
75
- .replace(/[-:]/g, '')
76
- .split('.')[0]
77
- .replace('Z', '');
69
+ .replace(/[-:\s]/g, '') // Rimuove trattini, due punti e spazi
70
+ .split('.')[0] // Rimuove eventuali millisecondi
71
+ .replace('Z', ''); // Rimuove la Z se presente
78
72
  };
79
73
 
80
- const startTime = pulisciData(startTimeRaw);
81
- const endTime = pulisciData(endTimeRaw);
74
+ const startTimeNVR = pulisciDataNVR(startTimeRaw);
75
+ const endTimeNVR = pulisciDataNVR(endTimeRaw);
76
+
77
+ // 🌟 2. CREAZIONE CARTELLA DI DESTINAZIONE (C:\download\YYYY-MM-DD)
78
+ // Estraiamo la data del giorno dallo startTime (i primi 10 caratteri, es: "2026-07-14")
79
+ const dataGiorno = startTimeRaw.substring(0, 10).replace(/\//g, '-');
80
+ const baseDir = "C:\\download";
81
+ const targetDir = path.join(baseDir, dataGiorno);
82
+
83
+ try {
84
+ if (!fs.existsSync(targetDir)) {
85
+ fs.mkdirSync(targetDir, { recursive: true });
86
+ }
87
+ } catch (err) {
88
+ node.status({ fill: "red", shape: "dot", text: "Errore cartella" });
89
+ node.error(`Impossibile creare la cartella ${targetDir}: ${err.message}`);
90
+ return;
91
+ }
92
+
82
93
  const digest = new DigestAuthClass({ username: user, password: pass });
83
94
 
84
- // 🌟 AVVIAMO IL CICLO DI DOWNLOAD PER OGNI CANALE SELEZIONATO
95
+ // 🌟 3. CICLO DI DOWNLOAD E SALVATAGGIO SU DISCO
85
96
  for (let index = 0; index < targetChannels.length; index++) {
86
97
  const ch = targetChannels[index];
87
98
  const trackId = ch + "01";
@@ -92,7 +103,7 @@ module.exports = function(RED) {
92
103
  text: `Download Ch ${ch} (${index + 1}/${targetChannels.length})...`
93
104
  });
94
105
 
95
- const playbackURI = `rtsp://${host}/Streaming/tracks/${trackId}/?starttime=${startTime}&endtime=${endTime}`;
106
+ const playbackURI = `rtsp://${host}/Streaming/tracks/${trackId}/?starttime=${startTimeNVR}&endtime=${endTimeNVR}`;
96
107
  const downloadUrl = `${protocol}://${host}:${port}/ISAPI/ContentMgmt/download?playbackURI=${encodeURIComponent(playbackURI)}`;
97
108
 
98
109
  try {
@@ -101,16 +112,27 @@ module.exports = function(RED) {
101
112
  url: downloadUrl,
102
113
  responseType: 'arraybuffer',
103
114
  httpsAgent: protocol === 'https' ? httpsAgent : undefined,
104
- timeout: 90000
115
+ timeout: 120000 // 2 minuti di timeout per i video corposi
105
116
  });
106
117
 
107
118
  if (response.status === 200 || response.status === 206) {
108
- // Creiamo un nuovo msg per non sovrapporre i flussi se sono multipli
119
+ const videoBuffer = Buffer.from(response.data);
120
+
121
+ // Prepariamo il nome del file ripulito (es: NVR_Cam1_100000.mp4)
122
+ const nomeCondominio = (msg.nvr_name || node.name || 'NVR').replace(/[^a-zA-Z0-9]/g, '_');
123
+ const oraInizio = startTimeNVR.split('T')[1] || startTimeNVR;
124
+ const finalFileName = `${nomeCondominio}_Cam${ch}_${oraInizio}.mp4`;
125
+ const finalFilePath = path.join(targetDir, finalFileName);
126
+
127
+ // Scrittura fisica del file su disco C:\download\data\file.mp4
128
+ fs.writeFileSync(finalFilePath, videoBuffer);
129
+ node.log(`File salvato con successo in: ${finalFilePath}`);
130
+
131
+ // Inviamo comunque il messaggio avanti nel flusso nel caso servisse ad altri nodi
109
132
  let outMsg = RED.util.cloneMessage(msg);
110
- outMsg.payload = Buffer.from(response.data);
133
+ outMsg.payload = finalFilePath; // Spedisce il percorso del file salvato come testo
111
134
  outMsg.channel = ch;
112
- outMsg.filename = `${msg.nvr_name || 'NVR'}_Cam${ch}_${startTime}.mp4`;
113
-
135
+ outMsg.filename = finalFileName;
114
136
  node.send(outMsg);
115
137
  } else {
116
138
  node.error(`Errore NVR Ch ${ch} Status: ${response.status}`);
@@ -120,13 +142,12 @@ module.exports = function(RED) {
120
142
  node.error(`Errore download video Cam ${ch}: ${err.message}`);
121
143
  }
122
144
 
123
- // Piccolo delay di sicurezza tra un download e l'altro per non stressare la CPU dell'NVR
124
145
  if (targetChannels.length > 1 && index < targetChannels.length - 1) {
125
146
  await new Promise(resolve => setTimeout(resolve, 1000));
126
147
  }
127
148
  }
128
149
 
129
- node.status({ fill: "green", shape: "dot", text: "Tutti i download completati!" });
150
+ node.status({ fill: "green", shape: "dot", text: `Salvati in C:\\download\\${dataGiorno}` });
130
151
  });
131
152
  }
132
153
  RED.nodes.registerType("hik-download", HikDownloadNode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",