node-red-contrib-hik-media-buffer 1.1.105 → 1.1.107
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-media-buffer.js +58 -45
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -97,25 +97,17 @@ module.exports = function(RED) {
|
|
|
97
97
|
const referenceTime = new Date();
|
|
98
98
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const month = pad(timeGoBack.getUTCMonth() + 1);
|
|
109
|
-
const day = pad(timeGoBack.getUTCDate());
|
|
110
|
-
const hours = pad(timeGoBack.getUTCHours());
|
|
111
|
-
const minutes = pad(timeGoBack.getUTCMinutes());
|
|
112
|
-
const seconds = pad(timeGoBack.getUTCSeconds());
|
|
113
|
-
|
|
114
|
-
// Output pulito: 20260624T085731Z
|
|
115
|
-
const rtspTimestamp = `${year}${month}${day}T${hours}${minutes}${seconds}Z`;
|
|
100
|
+
function toXmlDate(d) {
|
|
101
|
+
const pad = (num) => String(num).padStart(2, '0');
|
|
102
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}Z`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Cerchiamo il record video da 2 minuti prima a 1 minuto dopo l'evento
|
|
106
|
+
const startTime = toXmlDate(new Date(referenceTime.getTime() - (2 * 60 * 1000)));
|
|
107
|
+
const endTime = toXmlDate(new Date(referenceTime.getTime() + (1 * 60 * 1000)));
|
|
116
108
|
|
|
117
|
-
node.status({fill:"yellow", shape:"dot", text:`
|
|
118
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
109
|
+
node.status({fill:"yellow", shape:"dot", text:`Ricerca file NVR...`});
|
|
110
|
+
await new Promise(resolve => setTimeout(resolve, 5000)); // Aspetta che l'NVR crei il file
|
|
119
111
|
|
|
120
112
|
let output = {
|
|
121
113
|
tipo_messaggio: "evento", nome_cliente: node.name, nome_telecamera: nomeCamera,
|
|
@@ -127,43 +119,64 @@ module.exports = function(RED) {
|
|
|
127
119
|
|
|
128
120
|
try {
|
|
129
121
|
const trackId = parseInt(channelID) * 100 + 1;
|
|
122
|
+
const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>00000000-0000-0000-0000-000000000000</searchID><trackList><trackID>${trackId}</trackID></trackList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><maxResults>1</maxResults></CMSearchDescription>`.trim();
|
|
123
|
+
|
|
124
|
+
const tempXmlPath = path.join(baseStorage, `search_xml_${channelID}_${timestamp}.xml`);
|
|
125
|
+
fs.writeFileSync(tempXmlPath, searchXml, 'utf8');
|
|
126
|
+
|
|
127
|
+
const targetUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/search`;
|
|
128
|
+
const curlSearch = `curl -s -X POST --digest -u "${node.user}:${node.pass}" -H "Content-Type: application/xml" -d "@${tempXmlPath}" "${targetUrl}"`;
|
|
129
|
+
|
|
130
|
+
let xmlResponseRaw = await new Promise((resolve) => { exec(curlSearch, (err, stdout) => resolve(err ? null : stdout)); });
|
|
131
|
+
try { if (fs.existsSync(tempXmlPath)) fs.unlinkSync(tempXmlPath); } catch(e){}
|
|
132
|
+
|
|
133
|
+
const playbackUriMatch = xmlResponseRaw ? xmlResponseRaw.match(/<playbackURI>([^<]+)<\/playbackURI>/i) : null;
|
|
134
|
+
|
|
135
|
+
if (!playbackUriMatch) {
|
|
136
|
+
node.warn(`[DEBUG HTTP] Nessun file trovato nell'archivio per la cam ${channelID}.`);
|
|
137
|
+
updateNodeStatus();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const playbackUri = playbackUriMatch[1].trim();
|
|
142
|
+
node.warn(`[DEBUG HTTP] Trovato URI d'archivio: ${playbackUri}`);
|
|
143
|
+
|
|
144
|
+
const rawVideoPath = path.join(baseStorage, `raw_${channelID}_${timestamp}.mp4`);
|
|
130
145
|
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
131
146
|
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
132
147
|
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
// --- 1. DOWNLOAD DIRETTO VIA HTTP (ISAPI DOWNLOAD) ---
|
|
149
|
+
// Scarichiamo il file video originale memorizzato sull'NVR usando l'API di download nativa
|
|
150
|
+
node.warn(`[DEBUG HTTP] Avvio download del file originale dall'hard disk...`);
|
|
151
|
+
const downloadUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/download?playbackURI=${encodeURIComponent(playbackUri)}`;
|
|
152
|
+
const curlDownloadCmd = `curl -s -X GET --digest -u "${node.user}:${node.pass}" "${downloadUrl}" -o "${rawVideoPath}"`;
|
|
135
153
|
|
|
136
|
-
|
|
137
|
-
const ffmpegRtspCmd = `ffmpeg -y -i "${archiveRtspUrl}" -t 15 -c:v copy -c:a aac -movflags +faststart "${fixedPath}"`;
|
|
154
|
+
await new Promise((resolve) => { exec(curlDownloadCmd, () => resolve()); });
|
|
138
155
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (err) node.error(`[DEBUG FFMPEG VIDEO ERRORE]: ${err.message}`);
|
|
142
|
-
resolve();
|
|
143
|
-
});
|
|
144
|
-
});
|
|
156
|
+
if (fs.existsSync(rawVideoPath) && fs.statSync(rawVideoPath).size > 5000) {
|
|
157
|
+
node.warn(`[DEBUG HTTP] File scaricato (${fs.statSync(rawVideoPath).size} byte). Lo ritaglio in locale...`);
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
// --- 2. TAGLIO LOCALE E CONVERSIONE AUDIO CON FFMPEG ---
|
|
160
|
+
// Visto che il file è sul tuo PC, FFmpeg lavora in locale (velocità istantanea, zero timeout di rete!)
|
|
161
|
+
const ffmpegCutCmd = `ffmpeg -y -i "${rawVideoPath}" -t 15 -c:v copy -c:a aac -movflags +faststart "${fixedPath}"`;
|
|
162
|
+
await new Promise((resolve) => { exec(ffmpegCutCmd, () => resolve()); });
|
|
150
163
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const ffmpegImgCmd = `ffmpeg -y -i "${fixedPath}" -vframes 1 -q:v 2 "${fullImgPath}"`;
|
|
164
|
+
if (fs.existsSync(fixedPath)) {
|
|
165
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
166
|
+
fileDaCancellare.push(fixedPath);
|
|
155
167
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
// --- 3. ESTRAZIONE FOTO DAL VIDEO REGISTRATO ---
|
|
169
|
+
const ffmpegImgCmd = `ffmpeg -y -i "${fixedPath}" -vframes 1 -q:v 2 "${fullImgPath}"`;
|
|
170
|
+
await new Promise((resolve) => { exec(ffmpegImgCmd, () => resolve()); });
|
|
159
171
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
172
|
+
if (fs.existsSync(fullImgPath)) {
|
|
173
|
+
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
174
|
+
fileDaCancellare.push(fullImgPath);
|
|
175
|
+
}
|
|
164
176
|
}
|
|
177
|
+
fileDaCancellare.push(rawVideoPath);
|
|
165
178
|
} else {
|
|
166
|
-
node.warn(`[DEBUG
|
|
179
|
+
node.warn(`[DEBUG HTTP] Il download del file da NVR è fallito o il file è vuoto.`);
|
|
167
180
|
}
|
|
168
181
|
|
|
169
182
|
// --- SPEDIZIONE PAYLOAD ---
|
|
@@ -177,7 +190,7 @@ module.exports = function(RED) {
|
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
} catch (e) {
|
|
180
|
-
node.error(`Errore
|
|
193
|
+
node.error(`Errore nel download HTTP d'archivio: ${e.message}`);
|
|
181
194
|
}
|
|
182
195
|
updateNodeStatus();
|
|
183
196
|
}
|