node-red-contrib-hik-media-buffer 1.1.90 → 1.1.92
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 +37 -12
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -144,14 +144,19 @@ module.exports = function(RED) {
|
|
|
144
144
|
searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>23D1C3CB-8C68-41C0-8A89-2BCF5765B5D2</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><contentTypeList><contentType>metadata</contentType></contentTypeList><maxResults>30</maxResults><searchResultPostion>0</searchResultPostion><metadataList><metadataDescriptor>recordType.meta.hikvision.com/${evento}</metadataDescriptor></metadataList></CMSearchDescription>`.trim();
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
//
|
|
147
|
+
// Salviamo l'XML su un file temporaneo per non far incazzare la shell con i caratteri < e >
|
|
148
|
+
const tempXmlPath = path.join(baseStorage, `search_${t.id}_${timestamp}.xml`);
|
|
149
|
+
fs.writeFileSync(tempXmlPath, searchXml, 'utf8');
|
|
150
|
+
|
|
151
|
+
node.warn(`[DEBUG] Eseguo ricerca traccia ${t.id} tramite cURL via file temporaneo...`);
|
|
152
|
+
|
|
153
|
+
// Usiamo -d "@path" così cURL si legge il file da solo senza passare i simboli alla shell
|
|
148
154
|
const targetUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/search`;
|
|
149
|
-
const curlCommand = `curl -s -X POST --digest -u "${node.user}:${node.pass}" -H "Content-Type: application/xml; charset=UTF-8" -d
|
|
155
|
+
const curlCommand = `curl -s -X POST --digest -u "${node.user}:${node.pass}" -H "Content-Type: application/xml; charset=UTF-8" -d "@${tempXmlPath}" "${targetUrl}"`;
|
|
150
156
|
|
|
151
157
|
let xmlResponse = await new Promise((resolve) => {
|
|
152
158
|
exec(curlCommand, (err, stdout, stderr) => {
|
|
153
159
|
if (err) {
|
|
154
|
-
// STAMPIAMO IL VERO ERRORE DI LINUX/MIKROTIK
|
|
155
160
|
node.error(`[CRASH cURL]: ${err.message} | STDERR: ${stderr}`);
|
|
156
161
|
resolve(null);
|
|
157
162
|
} else {
|
|
@@ -160,7 +165,9 @@ module.exports = function(RED) {
|
|
|
160
165
|
});
|
|
161
166
|
});
|
|
162
167
|
|
|
163
|
-
//
|
|
168
|
+
// Cancelliamo subito il file XML temporaneo di ricerca
|
|
169
|
+
try { if (fs.existsSync(tempXmlPath)) fs.unlinkSync(tempXmlPath); } catch(e){}
|
|
170
|
+
|
|
164
171
|
node.warn(`[DEBUG NVR RESP] Risposta grezza per traccia ${t.id}:`);
|
|
165
172
|
node.warn(xmlResponse);
|
|
166
173
|
|
|
@@ -176,36 +183,54 @@ module.exports = function(RED) {
|
|
|
176
183
|
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
177
184
|
const downXml = `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`.trim();
|
|
178
185
|
|
|
186
|
+
const tempDownXmlPath = path.join(baseStorage, `down_${t.id}_${timestamp}.xml`);
|
|
187
|
+
fs.writeFileSync(tempDownXmlPath, downXml, 'utf8');
|
|
188
|
+
|
|
179
189
|
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
180
190
|
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
181
191
|
const targetFilePath = (t.id === trackI) ? fullImgPath : rawPath;
|
|
182
192
|
|
|
183
|
-
// cURL gestisce anche il download binario immenso salvando direttamente su file (-o)
|
|
184
193
|
const downUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/download`;
|
|
185
|
-
const curlDownCommand = `curl -s -X POST --digest -u "${node.user}:${node.pass}" -H "Content-Type: application/xml; charset=UTF-8" -d
|
|
194
|
+
const curlDownCommand = `curl -s -X POST --digest -u "${node.user}:${node.pass}" -H "Content-Type: application/xml; charset=UTF-8" -d "@${tempDownXmlPath}" "${downUrl}" -o "${targetFilePath}"`;
|
|
186
195
|
|
|
196
|
+
node.warn(`[DEBUG] Eseguo download traccia ${t.id} tramite cURL...`);
|
|
187
197
|
await new Promise((resolve) => {
|
|
188
198
|
exec(curlDownCommand, () => resolve());
|
|
189
199
|
});
|
|
190
200
|
|
|
201
|
+
try { if (fs.existsSync(tempDownXmlPath)) fs.unlinkSync(tempDownXmlPath); } catch(e){}
|
|
202
|
+
|
|
191
203
|
if (fs.existsSync(targetFilePath) && fs.statSync(targetFilePath).size > 0) {
|
|
192
204
|
if (t.id === trackI) {
|
|
193
205
|
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
194
206
|
fileDaCancellare.push(fullImgPath);
|
|
195
207
|
} else {
|
|
196
208
|
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
209
|
+
|
|
210
|
+
// Tagliamo via l'header proprietario IMKH se presente
|
|
211
|
+
let fileStats = fs.statSync(rawPath);
|
|
212
|
+
if (fileStats.size > 40) {
|
|
213
|
+
let fd = fs.openSync(rawPath, 'r');
|
|
214
|
+
let magicBuffer = Buffer.alloc(4);
|
|
215
|
+
fs.readSync(fd, magicBuffer, 0, 4, 0);
|
|
216
|
+
fs.closeSync(fd);
|
|
217
|
+
|
|
218
|
+
if (magicBuffer.toString() === 'IMKH') {
|
|
219
|
+
// Taglio dell'header senza leggere tutto in memoria (evita crash su file giganti)
|
|
220
|
+
exec(`ffmpeg -y -ss 00:00:00 -i "${rawPath}" -c copy -map 0 -an "${fixedPath}"`, () => {});
|
|
221
|
+
}
|
|
200
222
|
}
|
|
201
223
|
|
|
224
|
+
node.warn(`[DEBUG] Ritaglio il video dell'evento a 10 secondi per non saturare la memoria...`);
|
|
225
|
+
// USIAMO FFMPEG PER PRENDERE SOLO GLI ULTIMI 10 SECONDI ED EVITARE IL FILE DA 1GB IN BASE64
|
|
202
226
|
await new Promise((resolve) => {
|
|
203
|
-
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
204
|
-
if (!err && fs.existsSync(fixedPath)) {
|
|
227
|
+
exec(`ffmpeg -y -sseof -10 -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
228
|
+
if (!err && fs.existsSync(fixedPath) && fs.statSync(fixedPath).size > 0) {
|
|
229
|
+
// Leggiamo il file tagliato che ora peserà pochissimo!
|
|
205
230
|
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
206
231
|
fileDaCancellare.push(fixedPath);
|
|
207
232
|
} else {
|
|
208
|
-
|
|
233
|
+
node.error(`[DEBUG FFMPEG] Impossibile tagliare il video, traccia troppo pesante.`);
|
|
209
234
|
}
|
|
210
235
|
fileDaCancellare.push(rawPath);
|
|
211
236
|
resolve();
|