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 +4 -4
- package/hik-download.js +50 -29
- 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: "1" },
|
|
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:
|
|
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:
|
|
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";
|
|
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
|
-
//
|
|
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
|
-
|
|
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(/[
|
|
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
|
|
81
|
-
const
|
|
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
|
-
// 🌟
|
|
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=${
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
133
|
+
outMsg.payload = finalFilePath; // Spedisce il percorso del file salvato come testo
|
|
111
134
|
outMsg.channel = ch;
|
|
112
|
-
outMsg.filename =
|
|
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:
|
|
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);
|