node-red-contrib-hik-media-buffer 1.1.64 → 1.1.65
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 +100 -63
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -173,82 +173,119 @@ module.exports = function(RED) {
|
|
|
173
173
|
let fileDaCancellare = [];
|
|
174
174
|
|
|
175
175
|
try {
|
|
176
|
-
|
|
176
|
+
for (let t of tracks) {
|
|
177
177
|
node.warn(`[warn] [hik-media-buffer:test] [STEP 4] Elaborazione traccia ${t.label} (ID: ${t.id})`);
|
|
178
|
+
|
|
179
|
+
const evMinuscolo = eventoNVR.toLowerCase();
|
|
178
180
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
node.warn(`[warn] [hik-media-buffer:test] [STEP 4.1] Invio POST /search per traccia ${t.id}. XML inviato:\n${searchXml}`);
|
|
184
|
-
try {
|
|
185
|
-
const resSearch = await camAuth.request({
|
|
186
|
-
method: 'POST',
|
|
187
|
-
url: `${baseUrl}/search`,
|
|
188
|
-
data: searchXml,
|
|
189
|
-
headers: { "Content-Type": "application/xml" }
|
|
190
|
-
});
|
|
181
|
+
if (t.isFoto) {
|
|
182
|
+
// --- SE È LA FOTO: USA IL TUO XML ORIGINALE IDENTICO ---
|
|
183
|
+
const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><maxResults>100</maxResults><metadataList><metadataDescriptor>recordType.meta.hikvision.com/${evMinuscolo}</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
191
184
|
|
|
192
|
-
node.warn(`[warn] [hik-media-buffer:test] [STEP 4.
|
|
185
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 4.1] Invio POST XML /search per FOTO. XML:\n${searchXml}`);
|
|
193
186
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
187
|
+
try {
|
|
188
|
+
const resSearch = await camAuth.request({
|
|
189
|
+
method: 'POST',
|
|
190
|
+
url: `${baseUrl}/search`,
|
|
191
|
+
data: searchXml,
|
|
192
|
+
headers: { "Content-Type": "application/xml" }
|
|
193
|
+
});
|
|
200
194
|
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
196
|
+
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
197
|
+
if (uriMatch) {
|
|
198
|
+
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
199
|
+
await scaricaIlFile(rawUri, t.isFoto);
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {
|
|
202
|
+
node.error(`[error] [hik-media-buffer:test] Errore FOTO XML: ${err.message}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
} else {
|
|
206
|
+
// --- SE È IL VIDEO: USA IL JSON DELL'ISPEZIONA NVR CHE ANDAVA ---
|
|
207
|
+
// Adattiamo le date al formato del JSON dell'NVR (senza Z, con spazio offset se necessario, o UTC pulito)
|
|
208
|
+
const startTimeJson = startTime.replace('Z', ' 02:00'); // adatta al fuso orario del tuo NVR se serve, o lascialo Z
|
|
209
|
+
const endTimeJson = endTime.replace('Z', ' 02:00');
|
|
210
|
+
|
|
211
|
+
const searchJsonPayload = {
|
|
212
|
+
"EventSearchDescription": {
|
|
213
|
+
"searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
|
|
214
|
+
"searchResultPosition": 0,
|
|
215
|
+
"maxResults": 30,
|
|
216
|
+
"timeSpanList": [{
|
|
217
|
+
"startTime": startTime.replace('Z', ''), // Prova formato pulito senza Z richiesto dal JSON
|
|
218
|
+
"endTime": endTime.replace('Z', '')
|
|
219
|
+
}],
|
|
220
|
+
"type": "all",
|
|
221
|
+
"channels": [parsedChannel],
|
|
222
|
+
"eventType": "behavior",
|
|
223
|
+
"behavior": {
|
|
224
|
+
"behaviorEventType": evMinuscolo
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 4.1] Invio POST JSON /eventRecordSearch per VIDEO...`);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const resSearchVideo = await camAuth.request({
|
|
233
|
+
method: 'POST',
|
|
234
|
+
url: `${baseUrl}/eventRecordSearch?format=json`,
|
|
235
|
+
data: searchJsonPayload,
|
|
236
|
+
headers: { "Content-Type": "application/json" }
|
|
207
237
|
});
|
|
208
238
|
|
|
209
|
-
|
|
210
|
-
|
|
239
|
+
const resDataStr = JSON.stringify(resSearchVideo.data);
|
|
240
|
+
const videoUriMatch = resDataStr.match(/rtsp:\/\/[^"]+/i) || resDataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
|
|
211
241
|
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
216
|
-
fileDaCancellare.push(fullImgPath);
|
|
217
|
-
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.3] Immagine salvata e convertita in Base64.`);
|
|
218
|
-
} else {
|
|
219
|
-
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
220
|
-
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
221
|
-
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
222
|
-
fs.writeFileSync(rawPath, buffer);
|
|
223
|
-
|
|
224
|
-
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.3] Video grezzo salvato. Avvio conversione FFMPEG...`);
|
|
225
|
-
await new Promise((resolve) => {
|
|
226
|
-
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
227
|
-
if (!err && fs.existsSync(fixedPath)) {
|
|
228
|
-
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
229
|
-
fileDaCancellare.push(fixedPath);
|
|
230
|
-
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.4] Conversione FFMPEG completata con successo.`);
|
|
231
|
-
} else {
|
|
232
|
-
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.4] Errore FFMPEG (uso il video grezzo di backup): ${err ? err.message : 'File mancante'}`);
|
|
233
|
-
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
234
|
-
}
|
|
235
|
-
fileDaCancellare.push(rawPath);
|
|
236
|
-
resolve();
|
|
237
|
-
});
|
|
238
|
-
});
|
|
242
|
+
if (videoUriMatch) {
|
|
243
|
+
let rawVideoUri = videoUriMatch[0].replace(/\\/g, '');
|
|
244
|
+
await scaricaIlFile(rawVideoUri, t.isFoto);
|
|
239
245
|
}
|
|
240
|
-
}
|
|
241
|
-
node.
|
|
246
|
+
} catch (err) {
|
|
247
|
+
node.error(`[error] [hik-media-buffer:test] Errore VIDEO JSON: ${err.message}`);
|
|
242
248
|
}
|
|
243
|
-
} catch (searchError) {
|
|
244
|
-
let errorDetails = searchError.message;
|
|
245
|
-
if (searchError.response) {
|
|
246
|
-
errorDetails += ` | Status: ${searchError.response.status} | Dati Risposta: ${JSON.stringify(searchError.response.data)}`;
|
|
247
|
-
}
|
|
248
|
-
node.error(`[error] [hik-media-buffer:test] [CATCH TRACCIA ${t.id}] Errore durante la POST di ricerca: ${errorDetails}`);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
// Funzione interna di supporto per evitare duplicazione di codice di download
|
|
253
|
+
async function scaricaIlFile(playbackUri, isFoto) {
|
|
254
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5] Scarico traccia. URI: ${playbackUri}`);
|
|
255
|
+
const resDown = await camAuth.request({
|
|
256
|
+
method: 'GET',
|
|
257
|
+
url: `${baseUrl}/download`,
|
|
258
|
+
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${playbackUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
259
|
+
responseType: 'arraybuffer'
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
let buffer = Buffer.from(resDown.data);
|
|
263
|
+
if (isFoto) {
|
|
264
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
265
|
+
fs.writeFileSync(fullImgPath, buffer);
|
|
266
|
+
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
267
|
+
fileDaCancellare.push(fullImgPath);
|
|
268
|
+
} else {
|
|
269
|
+
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
270
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
271
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
272
|
+
fs.writeFileSync(rawPath, buffer);
|
|
273
|
+
|
|
274
|
+
await new Promise((resolve) => {
|
|
275
|
+
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
276
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
277
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
278
|
+
fileDaCancellare.push(fixedPath);
|
|
279
|
+
} else {
|
|
280
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
281
|
+
}
|
|
282
|
+
fileDaCancellare.push(rawPath);
|
|
283
|
+
resolve();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
252
289
|
if (output.foto_base64 || output.video_base64) {
|
|
253
290
|
node.warn(`[warn] [hik-media-buffer:test] [STEP 6] Spedizione payload verso Node-RED effettuata con successo!`);
|
|
254
291
|
node.send({ payload: output });
|