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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +37 -12
  2. package/package.json +1 -1
@@ -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
- // Chiamata cURL nativa del sistema operativo: se lo fa Chrome, lo fa anche cURL senza fallire sul Digest
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 '${searchXml}' "${targetUrl}"`;
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
- // >>> AGGIUNGI QUESTE RIGHE DI LOG QUI PER VEDERE COSA COZZO RISPONDE <<<
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(/&amp;/g, '&');
177
184
  const downXml = `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</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 '${downXml}' "${downUrl}" -o "${targetFilePath}"`;
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
- let videoBuffer = fs.readFileSync(rawPath);
198
- if (videoBuffer.slice(0, 4).toString() === 'IMKH') {
199
- try { fs.writeFileSync(rawPath, videoBuffer.slice(40)); } catch(e){}
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
- output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
233
+ node.error(`[DEBUG FFMPEG] Impossibile tagliare il video, traccia troppo pesante.`);
209
234
  }
210
235
  fileDaCancellare.push(rawPath);
211
236
  resolve();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.90",
3
+ "version": "1.1.92",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",