-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathzm-s3-upload.js
656 lines (579 loc) · 29.3 KB
/
zm-s3-upload.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
'use strict';
/**
*
* This will scan for new alarm frames in ZoneMinder.
* If local object detection is enabled then it will upload the image and found objects to S3.
* If not then it will upload the image where remote object detection will be performed.
*
* Copyright (c) 2018, 2019 Lindo St. Angel.
*
* Inspired by Brian Roy's original work.
* See https://github.com/briantroy/Zoneminder-Alert-Image-Upload-to-Amazon-S3
*
*/
const fs = require('fs');
const util = require('util');
const zerorpc = require('zerorpc');
// AWS config.
const awsCreds = JSON.parse(fs.readFileSync('./aws-creds.json'));
const AWS = require('aws-sdk');
const s3 = new AWS.S3(awsCreds);
// Get configuration details.
const configObj = JSON.parse(fs.readFileSync('./zm-s3-upload-config.json'));
const zmConfig = configObj.zms3Config;
// Zoneminder's mysql database connection config.
const DB_HOST = zmConfig.DBHOST;
const DB_USR = zmConfig.DBUSR;
const DB_PWD = zmConfig.DBPWD;
const DB_NAME = zmConfig.DBNAME;
// Limit of records returned by mysql queries.
const MAX_RECS = zmConfig.MAXRECS;
// Filesystem path to where ZoneMinder events are stored.
const IMG_BASE_PATH = zmConfig.IMGBASEPATH;
// As event images are captured they are stored to the filesystem with a numerical index.
// This defines the number of digits in that index which must match your ZoneMinder configuration.
const ZM_EVENT_IMAGE_DIGITS = zmConfig.zmEventImageDigits;
// The maximum allowable concurrent uploads to S3.
// This also sets the number of images submitted for local object detection.
const MAX_CONCURRENT_UPLOAD = zmConfig.MAXCONCURRENTUPLOAD;
// Heartbeat interval for zerorpc client in ms.
// This should be greater than the time required to run local detection on MAX_CONCURRENT_UPLOAD frames.
// This must match the zerorpc server config.
const ZERORPC_HEARTBEAT = zmConfig.zerorpcHeartBeat;
// IPC (or TCP) socket for zerorpc object detect server.
// This must match the zerorpc obj det server config.
const OBJ_DET_ZERORPC_PIPE = zmConfig.objDetZerorpcPipe;
// IPC (or TCP) socket for zerorpc face detect server.
// This must match the zerorpc face det server config.
const FACE_DET_ZERORPC_PIPE = zmConfig.faceDetZerorpcPipe;
// Number of frames to skip for each one processed.
// Meant to save processing and upload time (possibly with lower accuracy).
const FRAME_SKIP = zmConfig.frameSkip;
// Flag to upload false positives to S3.
const UPLOAD_FALSE_POSITIVES = zmConfig.uploadFalsePositives;
// Flag to run local (Tensorflow) instead of remote (Amazon Rekognition) object detection.
const RUN_LOCAL_OBJ_DET = zmConfig.runLocalObjDet;
// How often to check for ZoneMinder alarm frames (in ms).
const CHECK_FOR_ALARMS_INTERVAL = zmConfig.checkForAlarmsInterval;
// Flag to run local face detection / recognition on people detected.
const RUN_FACE_DET_REC = zmConfig.runFaceDetRec;
// mongodb
// Log the disposition of all alarm frames to a mongo database?
const USE_MONGO = zmConfig.useMongo;
// URL of mongo server.
const MONGO_URL = zmConfig.mongoUrl;
// mongo collection name.
const MONGO_COLLECTION = zmConfig.mongoCollection;
// mysql database connection.
const mysql = require('mysql');
const dbConfig = {
host: DB_HOST,
user: DB_USR,
password: DB_PWD,
database: DB_NAME
};
let client = mysql.createConnection(dbConfig);
// Set mysql query constants to find ZoneMinder alarm frames.
// Queries will start from the current date and time.
const queryStartDate = new Date();
const dateTime = queryStartDate.getFullYear() + '-' +
('0' + (queryStartDate.getMonth() + 1)).slice(-2) + '-' +
('0' + queryStartDate.getDate()).slice(-2) + ' ' +
queryStartDate.getHours() + ':' +
queryStartDate.getMinutes() + ':' +
queryStartDate.getSeconds();
const zmQuery = 'select f.frameid, f.timestamp as frame_timestamp, f.score, ' +
'f.delta as frame_delta,' +
'e.name as event_name, e.starttime, m.name as monitor_name, ' +
'au.upload_timestamp, f.eventid ' +
'from Frames f ' +
'join Events e on f.eventid = e.id ' +
'join Monitors m on e.monitorid = m.id ' +
'left join alarm_uploaded au on (au.frameid = f.frameid and au.eventid = f.eventid) ' +
'where f.type = ? ' +
'and f.timestamp > \'' + dateTime + '\' and upload_timestamp is null limit 0,?';
const FTYPE = 'Alarm';
// Logger.
const logger = require('./logger');
console.log('Logger created...');
/**
* Returns an array with string and int representations of the
* difference between start and current process.hrtime().
*
* @param {int} start - Start time in ns from process.hrtime()
*/
const parseHrtime = start => {
const diff = process.hrtime(start);
const PRECISION = 1;
const MS_PER_NS = 1e6;
const NS_PER_S = 1e9;
const diffSecs = diff[0] + (diff[1] / NS_PER_S);
const diffString = `${diff[0]} s ${(diff[1] / MS_PER_NS).toFixed(PRECISION)} ms`;
return [diffString, diffSecs];
};
/**
* Main function to get and process alarm frames from zm's database.
*/
const getFrames = () => {
// startTime used to time code performance.
const startTime = process.hrtime();
// Initialize some variables to generate stats.
let alarmsProcessed = 0;
let alarmsFound = 0;
let alarmsSkipped = 0;
let alarmsUploaded = 0;
let alarmsNotUploaded = 0;
// Start the query.
const query = client.query(zmQuery, [FTYPE, MAX_RECS]);
query.on('error', err => {
logger.error(`mysql query error: ${err.stack}`);
// Restart processing if the mysql server disconnects.
// Connection to the MySQL server is usually lost due to either server restart, or a
// connection idle timeout (the wait_timeout server variable configures this)
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
logger.error('Mysql connection lost...restarting processing.');
client = mysql.createConnection(dbConfig);
return setTimeout(getFrames, CHECK_FOR_ALARMS_INTERVAL);
} else {
// Die if not a mysql server disconnect.
process.exit(1);
}
});
// Get alarm frames row by row from mysql.
// Add image path base and skip info to the alarm then push to aryRows.
const alarmsFromMonitor = {}; // alarms per monitor, used for frame skip
const aryRows = []; // array that holds the alarm images read from zm
query.on('result', row => {
row.image_base_path = IMG_BASE_PATH;
// Calculate which frames should be skipped.
// Need to keep track of alarms on a monitor basis since they arrive async.
if (FRAME_SKIP === 0) {
row.skip = false;
} else {
const monitor = row.monitor_name;
const monitorExists = alarmsFromMonitor.hasOwnProperty(monitor);
monitorExists ? alarmsFromMonitor[monitor]++ : alarmsFromMonitor[monitor] = 1;
row.skip = !(alarmsFromMonitor[monitor] % (FRAME_SKIP + 1));
}
aryRows.push(row); // add alarm to array
});
// When query ends start processing the alarm images.
query.on('end', () => {
if (aryRows.length === 0) {
// Nothing to upload, get more frames.
logger.debug('Getting more frames to process.');
return setTimeout(getFrames, CHECK_FOR_ALARMS_INTERVAL);
} else {
logger.info(`${aryRows.length} un-uploaded frames found in ${parseHrtime(startTime)[0]}.`);
alarmsFound = aryRows.length;
}
/**
* Build S3 path and key.
*
* @param {object} imgData - Object describing image.
* @returns {string} - S3 path and key.
*/
const buildS3PathKey = imgData => {
const dtFrame = new Date(imgData.frame_timestamp);
// Fractional part of frame delta is the number of milliseconds into a ZoneMinder Section Length.
// See http://zoneminder.readthedocs.io/en/stable/userguide/definemonitor.html#monitor-tab.
// Required since frame_timestamp does not have ms resolution which is needed for unique timestamps.
// NB: this assumes all ZoneMinder monitors are in Record’ or ‘Mocord’ mode.
// NB: resolution is limited to 10 ms due to default definition of ZM mysql delta field (decimal(8,2)).
const timestampMilliseconds = (imgData.frame_delta % 1).toFixed(3).substring(2);
const S3Path = imgData.monitor_name + '/' + dtFrame.getFullYear() +
'-' + (dtFrame.getMonth() + 1) + '-' + dtFrame.getDate() + '/hour-' + dtFrame.getHours();
if (imgData.event_name === 'New Event') {
imgData.event_name = 'New_Event';
} else {
imgData.event_name = imgData.event_name.replace('-', '_');
}
const S3Key = imgData.event_name + '-' + 'ID_' + imgData.eventid + '-' +
'Frame_' + imgData.frameid + '-' +
dtFrame.getHours() + '-' + dtFrame.getMinutes() + '-' +
dtFrame.getSeconds() + '-' + timestampMilliseconds + '.jpg';
return S3Path + '/' + S3Key;
};
/**
* Build filesystem path to image.
*
* Currently this only supports events stored with "deep" paths, e.g.,
* ../events/1/18/10/17/14/30/20/00102-capture.jpg
*
* @param {Object} imgData - Object describing image.
* @returns {string} - Filesystem path to image in "deep" format.
*/
const buildFilePath = imgData => {
const dtFrame = new Date(imgData.starttime);
// Get a two digit year for the file path.
const tYear = dtFrame.getFullYear().toString().slice(2);
// Month with leading zero.
let tMonth = (dtFrame.getMonth() + 1).toString();
if (tMonth.length === 1) tMonth = '0' + tMonth;
// Day...
let tDay = dtFrame.getDate().toString();
if (tDay.length === 1) tDay = '0' + tDay;
// Hours...
let tHour = dtFrame.getHours().toString();
if (tHour.length === 1) tHour = '0' + tHour;
// Minutes...
let tMin = dtFrame.getMinutes().toString();
if (tMin.length === 1) tMin = '0' + tMin;
// Seconds ...
let tSec = dtFrame.getSeconds().toString();
if (tSec.length === 1) tSec = '0' + tSec;
// Add leading zero(s) to frame ID to match ZoneMinder configuration.
let frameId = imgData.frameid.toString();
frameId = '0'.repeat(ZM_EVENT_IMAGE_DIGITS - frameId.length) + frameId;
/*
Check if default monitor names are being used.
If so then just use monitor number in the image path.
If not then use the name the user gave the monitor.
Default monitor names are in the form "Monitor-N",
where N is the monitor number.
Zoneminder creates symlinks between default monitor numbers
and non-default monitor names in the image store directory.
*/
const re = /Monitor-\d+/;
const monitorName = re.test(imgData.monitor_name) ?
imgData.monitor_name.split('-')[1] : imgData.monitor_name;
return imgData.image_base_path + '/' + monitorName +
'/' + tYear + '/' + tMonth +
'/' + tDay + '/' + tHour + '/' + tMin +
'/' + tSec + '/' + frameId + '-capture.jpg';
};
/**
* Write documents to a mongo database.
* Returns a promise for a mongodb write.
*
* @param {array} documents - An array of documents to write to mongo.
* @returns {Promise} - Pending write of documents.
*/
const writeToMongodb = documents => {
logger.debug(`mongodb docs: ${util.inspect(documents, false, null)}`);
const mongoClient = require('mongodb').MongoClient;
return new Promise((resolve, reject) => {
mongoClient.connect(MONGO_URL, (error, client) => {
if (error) reject(`writeToMongodb error: ${error}`);
const collection = client.db().collection(MONGO_COLLECTION);
collection.insertMany(documents, (error, result) => {
if (error) reject(`writeToMongodb error: ${error}`);
logger.debug(`mongodb result: ${util.inspect(result, false, null)}`);
logger.info(`Wrote ${result.insertedCount} doc(s) to mongodb.`);
client.close();
resolve();
});
});
});
};
/**
* Upload an image to S3 and mark in zm's database.
* Returns a Promise of a pending upload.
*
* @param {int} index - Index of image in aryRows to process.
* @param {boolean} skipUpload - If true upload should be skipped and marked only.
* @returns {Promise} - Pending upload of image.
*/
const uploadImage = (index, skipUpload) => {
// Get image data object.
let imgData = aryRows[index];
// Check for bad image file.
if (typeof(imgData) === 'undefined' || typeof(imgData.frame_timestamp) === 'undefined') {
logger.error(`Bad upload image: ${imgData}`);
return Promise.reject(new Error('Bad upload image: ' + imgData));
}
// Get path to image file name.
const fileName = buildFilePath(imgData);
// Read image from filesystem.
const getImage = file => {
return new Promise((resolve, reject) => {
fs.readFile(file, (error, data) => {
error ? reject(error) : resolve(data);
});
});
};
// Mark an alarm frame as uploaded in ZoneMinder's database.
const markAsUploaded = imgData => {
const uploadInsertQuery = 'insert into alarm_uploaded(frameid,eventid,upload_timestamp) ' +
'values(?,?,now())';
const aryBind = new Array(imgData.frameid, imgData.eventid);
return new Promise((resolve, reject) => {
client.query(uploadInsertQuery, aryBind, (error) => {
if (error) {
logger.error(`markAsUpload error: ${error.stack}`);
reject(error);
} else {
logger.debug(`Insert Query Complete. FrameID: ${imgData.frameid} EventID: ${imgData.eventid}`);
resolve(true);
}
});
});
};
/**
* Upload image and metadata to an S3 bucket.
*
* @param {Buffer} data - the data to be uploaded.
* @param {Object} imgData - alarm metadata from ZoneMinder.
*
*/
const uploadToS3 = (data, imgData) => {
// Build S3 path and key.
const S3PathKey = buildS3PathKey(imgData);
logger.info('The file: ' + fileName + ' will be saved to: ' + S3PathKey);
// Calculate frame datetime with ms resolution.
const dtFrame = new Date(imgData.frame_timestamp);
const timestampMs = (imgData.frame_delta % 1).toFixed(3).substring(2);
const dtFrameMs = new Date(dtFrame.getFullYear(), dtFrame.getMonth(),
dtFrame.getDate(), dtFrame.getHours(), dtFrame.getMinutes(),
dtFrame.getSeconds(), timestampMs);
let params = {
Bucket: 'zm-alarm-frames',
Key: 'upload/' + S3PathKey,
Body: data,
StorageClass: 'STANDARD_IA',
Metadata: {
'zmMonitorName': imgData.monitor_name,
'zmEventName': imgData.event_name,
'zmEventId': imgData.eventid.toString(),
'zmFrameId': imgData.frameid.toString(),
'zmFrameDatetime': dtFrameMs.toISOString(),
'zmScore': imgData.score.toString(),
'zmLocalEventPath': fileName.toString()
}
};
// Add metadata for local object detection if it exists from local obj det.
if (typeof(imgData.alert) !== 'undefined') {
params.Metadata.alert = imgData.alert;
if (imgData.alert === 'true') {
params.Metadata.labels = JSON.stringify(imgData.objLabels);
}
}
// Actual upload.
return new Promise((resolve, reject) => {
s3.putObject(params, (error, result) => {
if (error) {
reject(error);
} else {
alarmsUploaded++;
resolve(result);
}
});
});
};
// Chain promises and return the last one.
const firstPromise = getImage(fileName).then(result => {
if (!skipUpload) return uploadToS3(result, imgData);
return false;
});
const secondPromise = firstPromise.then(result => {
logger.debug('uploadToS3 result: '+util.inspect(result, false, null));
return markAsUploaded(imgData);
});
return secondPromise;
};
/**
* Perform local or remote object detection on images.
* If enabled, also perform local face detection and recognition.
* Parse results and finally upload everything to S3.
*
* @param {int} alarms - Number of new alarm images to process.
* @param {boolean} local - If true run local obj det, else remote.
*/
const detect = (alarms = 0, local = true) => {
logger.debug(`aryRows len: ${aryRows.length} alarms: ${alarms} alarmsProcessed: ${alarmsProcessed}`);
// Limit number of alarms concurrently processed.
aryRows.length < MAX_CONCURRENT_UPLOAD ?
alarms = aryRows.length : alarms = MAX_CONCURRENT_UPLOAD;
logger.info(`Processing ${alarms} alarm frame(s)...`);
const promises = []; // holds upload and mongodb promises
const mongodbDoc = []; // for local database of all alarm frame disposition
let skipped = 0; // alarm frames skipped during this run
/**
* Connection to zerorpc object or face detection server.
*
* @param {string} detectionType - Either 'detect_objects' or 'detect_faces'.
* @param {array} imagePaths - Paths to images for detection.
*/
const detectServer = (detectionType = 'detect_objects', imagePaths = []) => {
const dur1 = parseHrtime(startTime)[1];
// Return a resolved Promise with an empty array if imagePath is empty.
if (imagePaths.length === 0) return Promise.resolve([]);
// Set pipe to server based on detection type requested.
let pipe = '';
detectionType === 'detect_objects' ?
pipe = OBJ_DET_ZERORPC_PIPE : pipe = FACE_DET_ZERORPC_PIPE;
// Heartbeat must be greater than the time required to run detection on maxInit frames.
// TODO - make heartbeat automatic to keep connection open, latency might be better.
const zerorpcClient = new zerorpc.Client({heartbeatInterval: ZERORPC_HEARTBEAT});
zerorpcClient.connect(pipe);
return new Promise((resolve, reject) => {
zerorpcClient.on('error', error => {
reject(error);
});
zerorpcClient.invoke(detectionType, imagePaths, (error, data) => {
zerorpcClient.close();
if (error) {
reject(error);
} else {
const dur2 = parseHrtime(startTime)[1];
const rate = (imagePaths.length/(dur2 - dur1)).toFixed(1);
logger.debug(`${detectionType} processing rate ${rate} (images/s).`);
resolve(JSON.parse(data));
}
});
});
};
/**
* Wait for uploads to complete.
* Then check for new alarms else finish existing alarms in array.
*/
const waitForUploads = () => {
return Promise.all(promises).then(() => {
aryRows.splice(0, alarms); // Remove processed alarms from the array.
alarmsProcessed += alarms;
alarmsSkipped += skipped;
const dur = parseHrtime(startTime);
const fps = (alarmsProcessed / dur[1]).toFixed(1);
logger.info(`${alarmsProcessed} / ${alarmsFound} image(s) processed in ${dur[0]} (${fps} fps).`);
if (aryRows.length === 0) {
logger.info(`${alarmsUploaded} image(s) analyzed and uploaded.`);
if (alarmsNotUploaded !== 0)
logger.info(`${alarmsNotUploaded} image(s) analyzed but not uploaded (false positives).`);
logger.info(`${alarmsSkipped} image(s) skipped analysis.`);
logger.info('Waiting for new alarm frame(s)...');
return getFrames();
} else {
return detect(MAX_CONCURRENT_UPLOAD, local);
}
}).catch(error => {
logger.error(`waitForUploads error: ${error.stack}`);
process.exit(1); // die on error
});
};
/**
* Generate mongodb doc for image disposition stored locally.
*
* @param {string} image - Path to image.
* @param {object} labels - Image metadata.
* @param {string} status - Indicates if image was skipped or processed.
* @param {string} method - Indicates if local or remote obj detection was used.
*/
const genMongodbDoc = (image, labels, status, method) => {
const doc = {'image': image, 'labels': labels, 'status': status, 'objDet': method};
mongodbDoc.push(doc);
return;
};
// Run local object detection, then run face detection / recognition if enabled.
// After detection / recognition, parse results and upload to S3.
// Alarm images (number defined by the alarm variable) will be concurrently uploaded.
if (local) {
// Build set of test image paths.
const imagePaths = [];
for (let i = 0; i < alarms; i++) {
logger.debug(`Alarm frame info: ${util.inspect(aryRows[i], false, null)}`);
if (aryRows[i].skip) continue;
imagePaths.push(buildFilePath(aryRows[i]));
}
detectServer('detect_objects', imagePaths).then((detObjArr) => {
// detObjArr is an array containing objects detected in image(s).
// The ordering of items in the array matches the order that they were submitted.
logger.debug(`Obj detect results: ${util.inspect(detObjArr, false, null)}`);
if (RUN_FACE_DET_REC) return detectServer('detect_faces', detObjArr);
return detObjArr;
}).then((objectsFound) => {
// objectsFound is an array of detected objects and recognized faces (if enabled).
// The ordering of items in the array matched the order that they were submitted.
logger.debug(`Face + Obj detect results: ${util.inspect(objectsFound, false, null)}`);
// Scan objectsFound array for detected objects and upload true alarms to S3.
for (let i = 0; i < alarms; i++) {
const fileName = buildFilePath(aryRows[i]);
// Find alarm frames that were never sent for object detection and skip past those.
if (aryRows[i].skip) {
logger.info(`Skipped processing of ${fileName}`);
if (USE_MONGO) genMongodbDoc(fileName, [], 'skipped', 'local');
// Mark as uploaded in zm db but don't actually upload image.
promises.push(uploadImage(i, true));
skipped++;
continue;
}
const labels = []; // holds detected object and face labels
// Scan for detected objects and trigger uploads.
// ObjectsFound index must be adjusted for images skipped.
const numObjDet = objectsFound[i - skipped].labels.length;
if (!numObjDet) {
logger.info(`No objects detected in ${fileName}`);
aryRows[i].alert = 'false';
if (UPLOAD_FALSE_POSITIVES === false) {
logger.info('False positives will NOT be uploaded.');
// Mark as uploaded in zm db but don't actually upload image.
promises.push(uploadImage(i, true));
alarmsNotUploaded++;
} else {
// Upload and mark in db as so.
promises.push(uploadImage(i, false));
}
} else {
logger.info(`Processed ${fileName}.`);
objectsFound[i - skipped].labels.forEach(item => {
const labelData = {
Confidence: (100 * item.score),
Name: item.name,
Box: item.box
};
// If a person was detected then add (any) face data.
if (typeof(item.face) !== 'undefined') {
labelData.Face = item.face;
labelData.FaceConfidence = (100 * item.faceProba);
}
labels.push(labelData);
logger.info(`Image labels: ${util.inspect(labelData, false, null)}`);
});
aryRows[i].alert = 'true';
aryRows[i].objLabels = labels;
promises.push(uploadImage(i, false));
}
if (USE_MONGO) genMongodbDoc(fileName, labels, 'processed', 'local');
}
// Log the disposition of all alarms to mongodb.
if (USE_MONGO) promises.push(writeToMongodb(mongodbDoc));
// Wait until all uploads complete.
waitForUploads();
}).catch((error) => {
logger.error(`local object detect error: ${error.stack}`);
process.exit(1); // just die on error
});
} else {
// If not local then use remote object detection.
for (let i = 0; i < alarms; i++) {
logger.debug(`Alarm frame info: ${util.inspect(aryRows[i], false, null)}`);
const fileName = buildFilePath(aryRows[i]);
const skip = aryRows[i].skip;
if (skip) {
skipped++;
logger.info(`Skipped processing of ${fileName}`);
if (USE_MONGO) genMongodbDoc(fileName, [], 'skipped', 'remote');
} else {
if (USE_MONGO) genMongodbDoc(fileName, [], 'processed', 'remote');
}
promises.push(uploadImage(i, skip));
}
// Log the disposition of all alarms to mongodb.
if (USE_MONGO) promises.push(writeToMongodb(mongodbDoc));
waitForUploads();
}
return;
}; // end detect
// Start local or remote alarm frame detection and upload.
if (RUN_LOCAL_OBJ_DET) {
logger.info('Running with local object detection enabled.');
if (RUN_FACE_DET_REC) logger.info('Running with local face det / rec enabled.');
detect(MAX_CONCURRENT_UPLOAD, true);
} else {
logger.info('Running with remote object detection enabled.');
detect(MAX_CONCURRENT_UPLOAD, false);
}
});
}; // end getFrames()
// Start looking for alarm frames.
logger.info('Waiting for first alarm frame(s)...');
getFrames();