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

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.
@@ -0,0 +1,79 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('hik-download', {
3
+ category: 'Hikvision',
4
+ color: '#f77f00',
5
+ defaults: {
6
+ name: { value: "" },
7
+ host: { value: "" },
8
+ channels: { value: "1" }, // Rinominato in 'channels'
9
+ protocol: { value: "http" },
10
+ port: { value: "80" },
11
+ user: { value: "admin" },
12
+ pass: { value: "" },
13
+ startTime: { value: "" },
14
+ endTime: { value: "" }
15
+ },
16
+ inputs: 1,
17
+ outputs: 1,
18
+ icon: "font-awesome/fa-video-camera",
19
+ label: function() { return this.name || "Hik Download Video"; }
20
+ });
21
+ </script>
22
+
23
+ <script type="text/html" data-template-name="hik-download">
24
+ <div class="form-row">
25
+ <label for="node-input-name"><i class="fa fa-tag"></i> Nome</label>
26
+ <input type="text" id="node-input-name">
27
+ </div>
28
+
29
+ <hr>
30
+ <h4>Configurazione NVR</h4>
31
+ <div class="form-row">
32
+ <label for="node-input-host"><i class="fa fa-globe"></i> IP NVR</label>
33
+ <input type="text" id="node-input-host" placeholder="Es: 192.168.1.100">
34
+ </div>
35
+
36
+ <div class="form-row">
37
+ <label for="node-input-channels"><i class="fa fa-list-ol"></i> Canali</label>
38
+ <input type="text" id="node-input-channels" placeholder="Es: 1 o 1,3,5 o 1-4">
39
+ </div>
40
+
41
+ <div class="form-row">
42
+ <label for="node-input-user"><i class="fa fa-user"></i> Utente</label>
43
+ <input type="text" id="node-input-user">
44
+ </div>
45
+
46
+ <div class="form-row">
47
+ <label for="node-input-pass"><i class="fa fa-lock"></i> Password NVR</label>
48
+ <input type="password" id="node-input-pass">
49
+ </div>
50
+
51
+ <div class="form-row">
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">
54
+ </div>
55
+
56
+ <div class="form-row">
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">
59
+ </div>
60
+
61
+ <div class="form-row">
62
+ <label for="node-input-protocol"><i class="fa fa-shield"></i> Protocollo</label>
63
+ <select id="node-input-protocol" style="width: 70%">
64
+ <option value="http">HTTP</option>
65
+ <option value="https">HTTPS</option>
66
+ </select>
67
+ </div>
68
+
69
+ <div class="form-row">
70
+ <label for="node-input-port"><i class="fa fa-plug"></i> Porta</label>
71
+ <select id="node-input-port" style="width: 70%">
72
+ <option value="80">80</option>
73
+ <option value="443">443</option>
74
+ <option value="85">85</option>
75
+ <option value="8000">8000</option>
76
+ <option value="8800">8800</option>
77
+ </select>
78
+ </div>
79
+ </script>
@@ -0,0 +1,133 @@
1
+ const axios = require('axios');
2
+ const https = require('https');
3
+ const mhocDigest = require('@mhoc/axios-digest-auth');
4
+ const DigestAuthClass = mhocDigest.default;
5
+
6
+ module.exports = function(RED) {
7
+ function HikDownloadNode(config) {
8
+ RED.nodes.createNode(this, config);
9
+ const node = this;
10
+
11
+ node.protocol = config.protocol || "http";
12
+ node.host = config.host;
13
+ node.port = config.port || "80";
14
+ node.user = config.user;
15
+ node.pass = config.pass;
16
+ node.channels = config.channels || "1"; // Ora accetta stringhe (es: "1,2,3" o "1-4")
17
+ node.startTime = config.startTime || "";
18
+ node.endTime = config.endTime || "";
19
+
20
+ const httpsAgent = new https.Agent({ rejectUnauthorized: false });
21
+
22
+ // 🌟 FUNZIONE DI SUPPORTO: Decifra la stringa dei canali (es: "1,3,5" o "1-3" -> [1, 2, 3, 5])
23
+ function parseChannels(channelsStr) {
24
+ let channels = [];
25
+ if (!channelsStr) return [1];
26
+
27
+ let parts = channelsStr.toString().split(',');
28
+ parts.forEach(part => {
29
+ part = part.trim();
30
+ if (part.includes('-')) {
31
+ let range = part.split('-');
32
+ let start = parseInt(range[0]);
33
+ let end = parseInt(range[1]);
34
+ if (!isNaN(start) && !isNaN(end)) {
35
+ for (let i = start; i <= end; i++) {
36
+ channels.push(i);
37
+ }
38
+ }
39
+ } else {
40
+ let ch = parseInt(part);
41
+ if (!isNaN(ch)) {
42
+ channels.push(ch);
43
+ }
44
+ }
45
+ });
46
+ // Rimuove i duplicati e ordina i canali
47
+ return [...new Set(channels)].sort((a, b) => a - b);
48
+ }
49
+
50
+ node.on('input', async function(msg) {
51
+ if (msg.payload !== true) return;
52
+
53
+ node.status({ fill: "blue", shape: "dot", text: "Verifica parametri..." });
54
+
55
+ const host = msg.nvr_host || node.host;
56
+ const port = msg.nvr_port || node.port;
57
+ const user = msg.nvr_user || node.user;
58
+ const pass = msg.nvr_pass || node.pass;
59
+ const protocol = msg.nvr_protocol || node.protocol;
60
+ const startTimeRaw = msg.startTime || node.startTime;
61
+ 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);
66
+
67
+ if (!startTimeRaw || !endTimeRaw) {
68
+ node.status({ fill: "red", shape: "ring", text: "Date mancanti!" });
69
+ node.error("startTime o endTime non configurati.");
70
+ return;
71
+ }
72
+
73
+ const pulisciData = (dataStr) => {
74
+ return dataStr
75
+ .replace(/[-:]/g, '')
76
+ .split('.')[0]
77
+ .replace('Z', '');
78
+ };
79
+
80
+ const startTime = pulisciData(startTimeRaw);
81
+ const endTime = pulisciData(endTimeRaw);
82
+ const digest = new DigestAuthClass({ username: user, password: pass });
83
+
84
+ // 🌟 AVVIAMO IL CICLO DI DOWNLOAD PER OGNI CANALE SELEZIONATO
85
+ for (let index = 0; index < targetChannels.length; index++) {
86
+ const ch = targetChannels[index];
87
+ const trackId = ch + "01";
88
+
89
+ node.status({
90
+ fill: "blue",
91
+ shape: "dot",
92
+ text: `Download Ch ${ch} (${index + 1}/${targetChannels.length})...`
93
+ });
94
+
95
+ const playbackURI = `rtsp://${host}/Streaming/tracks/${trackId}/?starttime=${startTime}&endtime=${endTime}`;
96
+ const downloadUrl = `${protocol}://${host}:${port}/ISAPI/ContentMgmt/download?playbackURI=${encodeURIComponent(playbackURI)}`;
97
+
98
+ try {
99
+ const response = await digest.request({
100
+ method: 'GET',
101
+ url: downloadUrl,
102
+ responseType: 'arraybuffer',
103
+ httpsAgent: protocol === 'https' ? httpsAgent : undefined,
104
+ timeout: 90000
105
+ });
106
+
107
+ if (response.status === 200 || response.status === 206) {
108
+ // Creiamo un nuovo msg per non sovrapporre i flussi se sono multipli
109
+ let outMsg = RED.util.cloneMessage(msg);
110
+ outMsg.payload = Buffer.from(response.data);
111
+ outMsg.channel = ch;
112
+ outMsg.filename = `${msg.nvr_name || 'NVR'}_Cam${ch}_${startTime}.mp4`;
113
+
114
+ node.send(outMsg);
115
+ } else {
116
+ node.error(`Errore NVR Ch ${ch} Status: ${response.status}`);
117
+ }
118
+
119
+ } catch (err) {
120
+ node.error(`Errore download video Cam ${ch}: ${err.message}`);
121
+ }
122
+
123
+ // Piccolo delay di sicurezza tra un download e l'altro per non stressare la CPU dell'NVR
124
+ if (targetChannels.length > 1 && index < targetChannels.length - 1) {
125
+ await new Promise(resolve => setTimeout(resolve, 1000));
126
+ }
127
+ }
128
+
129
+ node.status({ fill: "green", shape: "dot", text: "Tutti i download completati!" });
130
+ });
131
+ }
132
+ RED.nodes.registerType("hik-download", HikDownloadNode);
133
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",
@@ -16,7 +16,8 @@
16
16
  "node-red": {
17
17
  "nodes": {
18
18
  "hik-media-buffer": "hik-media-buffer.js",
19
- "hik-snapshot": "hik-snapshot.js"
19
+ "hik-snapshot": "hik-snapshot.js",
20
+ "hik-download": "hik-download.js"
20
21
  }
21
22
  },
22
23
  "dependencies": {