node-red-contrib-hik-media-buffer 1.2.4 → 1.2.6
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 +4 -4
- package/hik-download.js +73 -34
- package/package.json +1 -1
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: '#
|
|
4
|
+
color: '#00b4d8',
|
|
5
5
|
defaults: {
|
|
6
6
|
name: { value: "" },
|
|
7
7
|
host: { value: "" },
|
|
8
|
-
channels: { value: "
|
|
8
|
+
channels: { value: "" },
|
|
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="
|
|
53
|
+
<input type="datetime-local" id="node-input-startTime" step="1" style="width: 70%">
|
|
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="
|
|
58
|
+
<input type="datetime-local" id="node-input-endTime" step="1" style="width: 70%">
|
|
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,15 @@ 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";
|
|
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])
|
|
23
24
|
function parseChannels(channelsStr) {
|
|
24
25
|
let channels = [];
|
|
25
26
|
if (!channelsStr) return [1];
|
|
26
|
-
|
|
27
27
|
let parts = channelsStr.toString().split(',');
|
|
28
28
|
parts.forEach(part => {
|
|
29
29
|
part = part.trim();
|
|
@@ -32,18 +32,13 @@ module.exports = function(RED) {
|
|
|
32
32
|
let start = parseInt(range[0]);
|
|
33
33
|
let end = parseInt(range[1]);
|
|
34
34
|
if (!isNaN(start) && !isNaN(end)) {
|
|
35
|
-
for (let i = start; i <= end; i++)
|
|
36
|
-
channels.push(i);
|
|
37
|
-
}
|
|
35
|
+
for (let i = start; i <= end; i++) channels.push(i);
|
|
38
36
|
}
|
|
39
37
|
} else {
|
|
40
38
|
let ch = parseInt(part);
|
|
41
|
-
if (!isNaN(ch))
|
|
42
|
-
channels.push(ch);
|
|
43
|
-
}
|
|
39
|
+
if (!isNaN(ch)) channels.push(ch);
|
|
44
40
|
}
|
|
45
41
|
});
|
|
46
|
-
// Rimuove i duplicati e ordina i canali
|
|
47
42
|
return [...new Set(channels)].sort((a, b) => a - b);
|
|
48
43
|
}
|
|
49
44
|
|
|
@@ -59,10 +54,7 @@ module.exports = function(RED) {
|
|
|
59
54
|
const protocol = msg.nvr_protocol || node.protocol;
|
|
60
55
|
const startTimeRaw = msg.startTime || node.startTime;
|
|
61
56
|
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);
|
|
57
|
+
const targetChannels = parseChannels(msg.channels || node.channels);
|
|
66
58
|
|
|
67
59
|
if (!startTimeRaw || !endTimeRaw) {
|
|
68
60
|
node.status({ fill: "red", shape: "ring", text: "Date mancanti!" });
|
|
@@ -70,18 +62,39 @@ module.exports = function(RED) {
|
|
|
70
62
|
return;
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
// Pulizia date per l'NVR
|
|
66
|
+
const pulisciDataNVR = (dataStr) => {
|
|
67
|
+
let d = dataStr.replace(' ', 'T');
|
|
68
|
+
d = d.replace(/[-:]/g, '')
|
|
69
|
+
.split('.')[0]
|
|
70
|
+
.replace('Z', '');
|
|
71
|
+
|
|
72
|
+
if (d.includes('T') && d.split('T')[1].length === 4) {
|
|
73
|
+
d += '00';
|
|
74
|
+
}
|
|
75
|
+
return d;
|
|
78
76
|
};
|
|
79
77
|
|
|
80
|
-
const
|
|
81
|
-
const
|
|
78
|
+
const startTimeNVR = pulisciDataNVR(startTimeRaw);
|
|
79
|
+
const endTimeNVR = pulisciDataNVR(endTimeRaw);
|
|
80
|
+
|
|
81
|
+
// Creazione cartella C:\download\YYYY-MM-DD
|
|
82
|
+
const dataGiorno = startTimeRaw.substring(0, 10).replace(/\//g, '-');
|
|
83
|
+
const baseDir = "C:\\download";
|
|
84
|
+
const targetDir = path.join(baseDir, dataGiorno);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
if (!fs.existsSync(targetDir)) {
|
|
88
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
node.status({ fill: "red", shape: "dot", text: "Errore cartella" });
|
|
92
|
+
node.error(`Impossibile creare la cartella ${targetDir}: ${err.message}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
const digest = new DigestAuthClass({ username: user, password: pass });
|
|
83
97
|
|
|
84
|
-
// 🌟 AVVIAMO IL CICLO DI DOWNLOAD PER OGNI CANALE SELEZIONATO
|
|
85
98
|
for (let index = 0; index < targetChannels.length; index++) {
|
|
86
99
|
const ch = targetChannels[index];
|
|
87
100
|
const trackId = ch + "01";
|
|
@@ -92,25 +105,52 @@ module.exports = function(RED) {
|
|
|
92
105
|
text: `Download Ch ${ch} (${index + 1}/${targetChannels.length})...`
|
|
93
106
|
});
|
|
94
107
|
|
|
95
|
-
|
|
96
|
-
const
|
|
108
|
+
// 🌟 1. GENERIAMO L'URI DI PLAYBACK ESATTAMENTE COME RICHIESTO
|
|
109
|
+
const playbackURIGrezzo = `rtsp://${host}:${port}/Streaming/tracks/${trackId}/?starttime=${startTimeNVR}&endtime=${endTimeNVR}`;
|
|
110
|
+
const playbackURIXml = playbackURIGrezzo.replace(/&/g, '&');
|
|
111
|
+
|
|
112
|
+
const payloadDownload = `<?xml version="1.0" encoding="UTF-8"?>
|
|
113
|
+
<downloadRequest>
|
|
114
|
+
<playbackURI>${playbackURIXml}</playbackURI>
|
|
115
|
+
</downloadRequest>`;
|
|
97
116
|
|
|
98
117
|
try {
|
|
118
|
+
// 🌟 2. GET DI DOWNLOAD CON PAYLOAD XML E RESPONSE 'STREAM'
|
|
99
119
|
const response = await digest.request({
|
|
100
120
|
method: 'GET',
|
|
101
|
-
url:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
url: `${protocol}://${host}:${port}/ISAPI/ContentMgmt/download`,
|
|
122
|
+
data: payloadDownload,
|
|
123
|
+
headers: { "Content-Type": "application/xml" },
|
|
124
|
+
responseType: 'stream',
|
|
125
|
+
insecureHTTPParser: true,
|
|
126
|
+
httpsAgent: protocol === "https" ? httpsAgent : undefined,
|
|
127
|
+
timeout: 120000
|
|
105
128
|
});
|
|
106
129
|
|
|
107
130
|
if (response.status === 200 || response.status === 206) {
|
|
108
|
-
|
|
131
|
+
const nomeCondominio = (msg.nvr_name || node.name || 'NVR').replace(/[^a-zA-Z0-9]/g, '_');
|
|
132
|
+
const oraInizio = startTimeNVR.split('T')[1] || startTimeNVR;
|
|
133
|
+
const finalFileName = `${nomeCondominio}_Cam${ch}_${oraInizio}.mp4`;
|
|
134
|
+
const finalFilePath = path.join(targetDir, finalFileName);
|
|
135
|
+
|
|
136
|
+
// 🌟 3. SALVATAGGIO TRAMITE STREAM (Ottimizzato per file grandi)
|
|
137
|
+
const writer = fs.createWriteStream(finalFilePath);
|
|
138
|
+
response.data.pipe(writer);
|
|
139
|
+
|
|
140
|
+
await new Promise((resolve, reject) => {
|
|
141
|
+
writer.on('finish', resolve);
|
|
142
|
+
writer.on('error', (err) => {
|
|
143
|
+
writer.close();
|
|
144
|
+
reject(err);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
node.log(`File salvato in: ${finalFilePath}`);
|
|
149
|
+
|
|
109
150
|
let outMsg = RED.util.cloneMessage(msg);
|
|
110
|
-
outMsg.payload =
|
|
151
|
+
outMsg.payload = finalFilePath;
|
|
111
152
|
outMsg.channel = ch;
|
|
112
|
-
outMsg.filename =
|
|
113
|
-
|
|
153
|
+
outMsg.filename = finalFileName;
|
|
114
154
|
node.send(outMsg);
|
|
115
155
|
} else {
|
|
116
156
|
node.error(`Errore NVR Ch ${ch} Status: ${response.status}`);
|
|
@@ -120,13 +160,12 @@ module.exports = function(RED) {
|
|
|
120
160
|
node.error(`Errore download video Cam ${ch}: ${err.message}`);
|
|
121
161
|
}
|
|
122
162
|
|
|
123
|
-
// Piccolo delay di sicurezza tra un download e l'altro per non stressare la CPU dell'NVR
|
|
124
163
|
if (targetChannels.length > 1 && index < targetChannels.length - 1) {
|
|
125
164
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
126
165
|
}
|
|
127
166
|
}
|
|
128
167
|
|
|
129
|
-
node.status({ fill: "green", shape: "dot", text:
|
|
168
|
+
node.status({ fill: "green", shape: "dot", text: `Salvati in C:\\download\\${dataGiorno}` });
|
|
130
169
|
});
|
|
131
170
|
}
|
|
132
171
|
RED.nodes.registerType("hik-download", HikDownloadNode);
|