From 377805dce094199b9248987e8c23b5830ada5ba2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Jan 2020 17:30:51 -0500 Subject: [PATCH 001/501] convert some tags to classes instead of ids. Add margin: 0 auto so that monitor images are centered --- web/skins/classic/css/base/views/timeline.css | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web/skins/classic/css/base/views/timeline.css b/web/skins/classic/css/base/views/timeline.css index c2efed713..47e7af1ce 100644 --- a/web/skins/classic/css/base/views/timeline.css +++ b/web/skins/classic/css/base/views/timeline.css @@ -40,14 +40,11 @@ margin: 4px auto 6px; } -#topPanel #imagePanel -{ - width: 50%; - float: left; +#topPanel .imagePanel { text-align: right; } -#topPanel #image { +#topPanel .image { margin: 0 auto; text-align: right; margin-right: 2px; @@ -57,7 +54,7 @@ width: 100%; } -#topPanel #image img { +#topPanel .image img { width: auto; max-width: 100%; height: 100%; @@ -66,6 +63,7 @@ bottom: 0; left: 0; right: 0; + margin: 0 auto; } #topPanel #dataPanel { From 2854f8e0023fecaa5db583a61dedc56690a00b43 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Jan 2020 17:32:27 -0500 Subject: [PATCH 002/501] simplify some code, pass around zm_event json objects instead of event_id. Each monitor has it's own image so when loading event data put it into a div/img referenced by monitor Id --- web/skins/classic/views/js/timeline.js | 79 +++++++++----------------- 1 file changed, 26 insertions(+), 53 deletions(-) diff --git a/web/skins/classic/views/js/timeline.js b/web/skins/classic/views/js/timeline.js index 0b4715f75..1fe42c46b 100644 --- a/web/skins/classic/views/js/timeline.js +++ b/web/skins/classic/views/js/timeline.js @@ -36,12 +36,6 @@ function createEventHtml(zm_event, frame) { return eventHtml; } -function showEventDetail( eventHtml ) { - $('instruction').addClass( 'hidden' ); - $('eventData').empty(); - $('eventData').adopt( eventHtml ); - $('eventData').removeClass( 'hidden' ); -} function eventDataResponse( respObj, respText ) { var zm_event = respObj.event; @@ -79,18 +73,20 @@ function frameDataResponse( respObj, respText ) { zm_event['frames'][frame.FrameId] = frame; zm_event['frames'][frame.FrameId]['html'] = createEventHtml( zm_event, frame ); - showEventData(frame.EventId, frame.FrameId); + showEventData(zm_event, frame.FrameId); } -function showEventData(eventId, frameId) { - if ( events[eventId] ) { - var zm_event = events[eventId]; +function showEventData(zm_event, frameId) { + if ( zm_event ) { if ( zm_event['frames'] ) { if ( zm_event['frames'][frameId] ) { - showEventDetail( zm_event['frames'][frameId]['html'] ); + $('instruction').addClass('hidden'); + eventData = $('eventData'+zm_event.MonitorId); + eventData.empty(); + eventData.adopt(zm_event['frames'][frameId]['html']); + eventData.removeClass('hidden'); var imagePath = 'index.php?view=image&eid='+eventId+'&fid='+frameId; - var videoName = zm_event.DefaultVideo; - loadEventImage( imagePath, eventId, frameId, zm_event.Width, zm_event.Height, zm_event.Frames/zm_event.Length, videoName, zm_event.Length, zm_event.StartTime, monitors[zm_event.MonitorId]); + loadEventImage(imagePath, zm_event, frameId); return; } else { console.log('No frames for ' + frameId); @@ -99,7 +95,7 @@ function showEventData(eventId, frameId) { console.log('No frames'); } } else { - console.log('No event for ' + eventId); + console.log('No event'); } } @@ -133,50 +129,28 @@ function previewEvent(slot) { eventId = slot.getAttribute('data-event-id'); frameId = slot.getAttribute('data-frame-id'); if ( events[eventId] ) { - showEventData(eventId, frameId); + showEventData(events[eventId], frameId); } else { requestFrameData(eventId, frameId); } } -function loadEventImage( imagePath, eid, fid, width, height, fps, videoName, duration, startTime, Monitor ) { - var vid = $('preview'); - var imageSrc = $('imageSrc'); - if ( videoName && vid ) { - vid.show(); - imageSrc.hide(); - var newsource=imagePath.slice(0, imagePath.lastIndexOf('/'))+'/'+videoName; - //console.log(newsource); - //console.log(sources[0].src.slice(-newsource.length)); - if ( newsource != vid.currentSrc.slice(-newsource.length) || vid.readyState == 0 ) { - //console.log("loading new"); - //it is possible to set a long source list here will that be unworkable? - var sources = vid.getElementsByTagName('source'); - sources[0].src = newsource; - var tracks = vid.getElementsByTagName('track'); - if (tracks.length) { - tracks[0].parentNode.removeChild(tracks[0]); - } - vid.load(); - addVideoTimingTrack(vid, Monitor.LabelFormat, Monitor.Name, duration, startTime); - vid.currentTime = fid/fps; - } else { - if ( ! vid.seeking ) { - vid.currentTime=fid/fps; - } - } - } else { - if ( vid ) vid.hide(); - imageSrc.show(); - imageSrc.setProperty('src', imagePath); - imageSrc.setAttribute('data-event-id', eid); - imageSrc.setAttribute('data-frame-id', fid); - imageSrc.onclick=window['showEvent'].bind(imageSrc, imageSrc); - } +function loadEventImage( imagePath, zm_event, fid ) { + var imageSrc = $('imageSrc'+zm_event.MonitorId); - var eventData = $('eventData'); - eventData.removeEvent('click'); - eventData.addEvent('click', showEvent.pass()); + imageSrc.show(); + imageSrc.setProperty('src', imagePath); + imageSrc.setAttribute('data-event-id', zm_event.Id); + imageSrc.setAttribute('data-frame-id', fid); + imageSrc.onclick=window['showEvent'].bind(imageSrc, imageSrc); + + var eventData = $('eventData'.zm_event.MonitorId); + if ( eventData ) { + eventData.removeEvent('click'); + eventData.addEvent('click', showEvent.pass()); + } else { + console.log("No eventdata area found for monitor " + zm_event.MonitorId); + } } function tlZoomBounds( minTime, maxTime ) { @@ -204,4 +178,3 @@ window.addEventListener("DOMContentLoaded", function() { el.onmouseover = window[el.getAttribute('data-on-mouseover-this')].bind(el, el); }); }); - From b788c432d42290eb05f7c7b49fd2f48b7a808a9b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Jan 2020 17:33:36 -0500 Subject: [PATCH 003/501] Split the data by monitor and display a monitor image/info for each monitor. --- web/skins/classic/views/timeline.php | 40 +++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/web/skins/classic/views/timeline.php b/web/skins/classic/views/timeline.php index 2fe419efd..e07fbc893 100644 --- a/web/skins/classic/views/timeline.php +++ b/web/skins/classic/views/timeline.php @@ -494,7 +494,7 @@ for ( $i = 0; $i < $chart['graph']['width']; $i++ ) { } # end foreach MonitorId } # end foreach x -ZM\Logger::Debug(print_r( $monEventSlots,true )); +#ZM\Logger::Debug(print_r( $monEventSlots,true )); //print_r( $monFrameSlots ); //print_r( $chart ); @@ -683,23 +683,32 @@ xhtmlHeaders(__FILE__, translate('Timeline'));
+
+

+

+

+

+
-
-
- <?php echo translate('ViewEvent') ?> -
-
-
-
-
-

-

-

-

-
-
+ +
+
+
+ <?php echo translate('ViewEvent') ?>
+
+
+
+
+
+
+
+ -
From aa157bfd78c0704bf9c11b419cff44bc1f5c9317 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 11 Oct 2020 09:56:35 -0400 Subject: [PATCH 004/501] AddManufacturerId and ModelId to Monitors --- db/manufacturers.sql | 24 ++++++++++++++++++++ db/zm_create.sql.in | 2 ++ db/zm_update-1.35.10.sql | 47 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 db/manufacturers.sql create mode 100644 db/zm_update-1.35.10.sql diff --git a/db/manufacturers.sql b/db/manufacturers.sql new file mode 100644 index 000000000..be15f9c01 --- /dev/null +++ b/db/manufacturers.sql @@ -0,0 +1,24 @@ +INSERT INTO Manufacturers VALUES (1, 'Acti'); +INSERT INTO Manufacturers VALUES (2, 'Amcrest'); +INSERT INTO Manufacturers VALUES (3, 'Airlink101'); +INSERT INTO Manufacturers VALUES (4, 'Arecont Vision'); +INSERT INTO Manufacturers VALUES (5, 'Axis'); +INSERT INTO Manufacturers VALUES (6, 'Dahua'); +INSERT INTO Manufacturers VALUES (7, 'D-Link'); +INSERT INTO Manufacturers VALUES (8, 'Edimax'); +INSERT INTO Manufacturers VALUES (9, 'Foscam'); +INSERT INTO Manufacturers VALUES (10, 'Gadspot'); +INSERT INTO Manufacturers VALUES (11, 'GrandStream'); +INSERT INTO Manufacturers VALUES (12, 'HikVision'); +INSERT INTO Manufacturers VALUES (13, 'JVC'); +INSERT INTO Manufacturers VALUES (14, 'Maginon'); +INSERT INTO Manufacturers VALUES (15, 'Mobotix'); +INSERT INTO Manufacturers VALUES (16, 'Oncam Grandeye'); +INSERT INTO Manufacturers VALUES (17, 'Panasonic'); +INSERT INTO Manufacturers VALUES (18, 'Pelco'); +INSERT INTO Manufacturers VALUES (19, 'Sony'); +INSERT INTO Manufacturers VALUES (20, 'TP-Link'); +INSERT INTO Manufacturers VALUES (21, 'Trendnet'); +INSERT INTO Manufacturers VALUES (22, 'VisionTek'); +INSERT INTO Manufacturers VALUES (23, 'Vivotek'); +INSERT INTO Manufacturers VALUES (24, 'Wansview'); diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index af6a17182..af4e37136 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -441,6 +441,8 @@ CREATE TABLE `Monitors` ( `Notes` TEXT, `ServerId` int(10) unsigned, `StorageId` smallint(5) unsigned default 0, + `ManufacturerId` int unsigned, FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id), + `ModelId` int unsigned, FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id), `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', diff --git a/db/zm_update-1.35.10.sql b/db/zm_update-1.35.10.sql new file mode 100644 index 000000000..341c5e162 --- /dev/null +++ b/db/zm_update-1.35.10.sql @@ -0,0 +1,47 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'Column ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; From a6de3e15f4eb0e9bed6682a6521f26a3b2c77947 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 11 Oct 2020 09:57:08 -0400 Subject: [PATCH 005/501] AddManufacturerId and ModelId to Monitor view --- web/skins/classic/views/monitor.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index d64880dff..13ec55633 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -462,7 +462,9 @@ if ( $tab != 'general' ) { ?> - + + + GroupIds() as $group_id ) { @@ -627,6 +629,24 @@ switch ( $tab ) { + + + ManufacturerId(), array('class'=>'chosen')); +?> + + + + + $monitor->ManufacturerId())); +echo htmlSelect('newMonitor[ModelId]', $models, $monitor->ModelId(), array('class'=>'chosen')); +?> + + Date: Sun, 11 Oct 2020 09:57:51 -0400 Subject: [PATCH 006/501] Add object classes for Manufacturer and Model --- web/includes/Manufacturer.php | 23 +++++++++++++++++++++++ web/includes/Model.php | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 web/includes/Manufacturer.php create mode 100644 web/includes/Model.php diff --git a/web/includes/Manufacturer.php b/web/includes/Manufacturer.php new file mode 100644 index 000000000..73c46ad6b --- /dev/null +++ b/web/includes/Manufacturer.php @@ -0,0 +1,23 @@ + null, + 'Name' => '', + ); + + public static function find( $parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } + + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); + } +} # end class Manufacturer +?> diff --git a/web/includes/Model.php b/web/includes/Model.php new file mode 100644 index 000000000..c9b22db8c --- /dev/null +++ b/web/includes/Model.php @@ -0,0 +1,22 @@ + null, + 'Name' => '', + ); + + public static function find( $parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } + + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); + } +} # end class Model +?> From cc486beed50d12a1513db8d8312af4edfe825bb4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:45:23 -0400 Subject: [PATCH 007/501] Add some Acti models --- db/manufacturers.sql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/db/manufacturers.sql b/db/manufacturers.sql index be15f9c01..eaee2630c 100644 --- a/db/manufacturers.sql +++ b/db/manufacturers.sql @@ -1,4 +1,23 @@ INSERT INTO Manufacturers VALUES (1, 'Acti'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A21'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A23'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A24'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A28'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A31'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A310'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A311'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A32'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A41'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A415'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A416'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A418'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A42'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A421'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A43'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A45'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A46'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A48'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A74'); INSERT INTO Manufacturers VALUES (2, 'Amcrest'); INSERT INTO Manufacturers VALUES (3, 'Airlink101'); INSERT INTO Manufacturers VALUES (4, 'Arecont Vision'); From 8df915e7a47c0b739d0ac39e3e5e7d2dff55d973 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:45:53 -0400 Subject: [PATCH 008/501] Fix merge. --- db/zm_update-1.35.29.sql | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/db/zm_update-1.35.29.sql b/db/zm_update-1.35.29.sql index 9030e5863..341c5e162 100644 --- a/db/zm_update-1.35.29.sql +++ b/db/zm_update-1.35.29.sql @@ -1,4 +1,3 @@ -<<<<<<< HEAD SET @s = (SELECT IF( (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() AND table_name = 'Monitors' @@ -42,19 +41,6 @@ SET @s = (SELECT IF( ) > 0, "SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", "ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" -======= --- --- Add AutoUnarchive action to Filters --- - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() - AND table_name = 'Filters' - AND column_name = 'AutoUnarchive' - ) > 0, -"SELECT 'Column AutoUunarchive already exists in Filters'", -"ALTER TABLE Filters ADD `AutoUnarchive` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoArchive`" ->>>>>>> master )); PREPARE stmt FROM @s; From 24a77d7fb365768c200559cc3353343e2b57a8e6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:46:12 -0400 Subject: [PATCH 009/501] Add ManufacturerId to Model --- web/includes/Model.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/includes/Model.php b/web/includes/Model.php index c9b22db8c..35562f9f9 100644 --- a/web/includes/Model.php +++ b/web/includes/Model.php @@ -9,6 +9,7 @@ class Model extends ZM_Object { protected $defaults = array( 'Id' => null, 'Name' => '', + 'ManufacturerId' => null, ); public static function find( $parameters = array(), $options = array() ) { From 3bb2b804b3a69f158e54149dc9e707a96cd611b0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:46:36 -0400 Subject: [PATCH 010/501] Include Model and Manufacturer to includes in Monitor.php --- web/includes/Monitor.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index ad3e95a0d..4f9450a3d 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -1,11 +1,13 @@ AlarmCommand('disable'); } + function Model() { + if (!$this->{'Model'}) { + $this->{'Model'} = Model::find_one(array('Id'=>$this->ModelId())); + if (!$this->{'Model'}) $this->{'Model'} = new Model(); + } + return $this->{'Model'}; + } + function Manufacturer() { + if (!$this->{'Manufacturer'}) { + $this->{'Manufacturer'} = Manufacturer::find_one(array('Id'=>$this->ManufacturerId())); + if (!$this->{'Manufacturer'}) $this->{'Manufacturer'} = new Manufacturer(); + } + return $this->{'Manufacturer'}; + } } // end class Monitor ?> From c6209ce460e4b942d42e9c0a84c10e6f06f38ab5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:47:00 -0400 Subject: [PATCH 011/501] Store new Model and Manufacturer when saving Monitor --- web/includes/actions/monitor.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/web/includes/actions/monitor.php b/web/includes/actions/monitor.php index 1c6e1f72f..3fafb0f82 100644 --- a/web/includes/actions/monitor.php +++ b/web/includes/actions/monitor.php @@ -45,6 +45,25 @@ if ( $action == 'save' ) { $x10Monitor = array(); } } + if ( !$_REQUEST['newMonitor[ManufacturerId'] and ($_REQUEST['newMonitor[Manufacturer'] != '') ) { + # Need to add a new Manufacturer entry + $newManufacturer = ZM\Manufacturer::find_one(array('Name'=>$_REQUEST['newMonitor[Manufacturer'])); + if (!$newManufacturer) { + $newManufacturer = new ZM\Manufacturer(); + $newManufacturer->save(array('Name'=>$_REQUEST['newMonitor[Manufacturer'])); + } + $_REQUEST['newMonitor[ManufacturerId'] = $newManufacturer->Id(); + } + + if ( !$_REQUEST['newMonitor[ModelId'] and ($_REQUEST['newMonitor[Model'] != '') ) { + # Need to add a new Model entry + $newModel = ZM\Model::find_one(array('Name'=>$_REQUEST['newMonitor[Model'])); + if (!$newModel) { + $newModel = new ZM\Model(); + $newMdoel->save(array('Name'=>$_REQUEST['newMonitor[Model'], 'ManufacturerId'=>$_REQUEST['newMonitor[ManufacturerId'])); + } + $_REQUEST['newMonitor[ModelId'] = $newModel->Id(); + } $monitor = new ZM\Monitor($mid); From 54f676a501b63bec148b2e951011b50bd0ae1592 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:47:32 -0400 Subject: [PATCH 012/501] Add ManufacturerId_onchange and ModelId_onchange to hide/show the text input for custom entry --- web/skins/classic/views/js/monitor.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index 4b41d8dd2..d28d21bf5 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -323,7 +323,6 @@ function update_estimated_ram_use() { var max_buffer_count = parseInt(document.querySelectorAll('input[name="newMonitor[MaxImageBufferCount]"]')[0].value); if (max_buffer_count) { var max_buffer_size = (min_buffer_count + max_buffer_count) * width * height * colours; - console.log(max_buffer_size); document.getElementById('estimated_ram_use').innerHTML += ' Max: ' + human_filesize(max_buffer_size); } else { document.getElementById('estimated_ram_use').innerHTML += ' Max: Unlimited'; @@ -345,4 +344,17 @@ function getLocation() { } } +function ManufacturerId_onchange(ManufacturerId_select) { + if (ManufacturerId_select.value()) + $j('newMonitor[Manufacturer]').hide(); + else + $j('newMonitor[Manufacturer]').show(); +} +function ModelId_onchange(ModelId_select) { + if (ModelId_select.value()) + $j('newMonitor[Model]').hide(); + else + $j('newMonitor[Model]').show(); +} + window.addEventListener('DOMContentLoaded', initPage); From 4ff4e1f7807d650b009384758487527ffc8c7ec5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:47:55 -0400 Subject: [PATCH 013/501] add Manufacturer and Model dropdown/text inputs to monitor edit view --- web/skins/classic/views/monitor.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 5e30db969..e6645dec5 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -492,18 +492,25 @@ switch ( $name ) { ManufacturerId(), array('class'=>'chosen')); +$manufacturers = array_merge( + array(''=>translate('unknown')), + ZM\Manufacturer::find()); +echo htmlSelect('newMonitor[ManufacturerId]', $manufacturers, $monitor->ManufacturerId(), array('class'=>'chosen','data-on-change-this'=>'ManufacturerId_onchange')); ?> + $monitor->ManufacturerId())); -echo htmlSelect('newMonitor[ModelId]', $models, $monitor->ModelId(), array('class'=>'chosen')); +$models = array_merge( + array(''=>translate('unknown')), + ZM\Model::find(array('ManufacturerId'=>$monitor->ManufacturerId())) +); +echo htmlSelect('newMonitor[ModelId]', $models, $monitor->ModelId(), array('class'=>'chosen', 'data-on-change-this'=>'ModelId_onchange')); ?> + From 413ac984eb3e1f496b38c08eecfda3104e56e8f3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Jul 2021 09:48:24 -0400 Subject: [PATCH 014/501] add models.sql --- db/models.sql | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 db/models.sql diff --git a/db/models.sql b/db/models.sql new file mode 100644 index 000000000..b7150d816 --- /dev/null +++ b/db/models.sql @@ -0,0 +1,45 @@ +/* INSERT INTO Manufacturers VALUES (1, 'Acti'); */ +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A21'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A23'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A24'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A28'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A31'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A310'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A311'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A32'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A41'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A415'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A416'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A418'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A42'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A421'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A43'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A45'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A46'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A48'); +INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A74'); +/* +INSERT INTO Manufacturers VALUES (2, 'Amcrest'); +INSERT INTO Manufacturers VALUES (3, 'Airlink101'); +INSERT INTO Manufacturers VALUES (4, 'Arecont Vision'); +INSERT INTO Manufacturers VALUES (5, 'Axis'); +INSERT INTO Manufacturers VALUES (6, 'Dahua'); +INSERT INTO Manufacturers VALUES (7, 'D-Link'); +INSERT INTO Manufacturers VALUES (8, 'Edimax'); +INSERT INTO Manufacturers VALUES (9, 'Foscam'); +INSERT INTO Manufacturers VALUES (10, 'Gadspot'); +INSERT INTO Manufacturers VALUES (11, 'GrandStream'); +INSERT INTO Manufacturers VALUES (12, 'HikVision'); +INSERT INTO Manufacturers VALUES (13, 'JVC'); +INSERT INTO Manufacturers VALUES (14, 'Maginon'); +INSERT INTO Manufacturers VALUES (15, 'Mobotix'); +INSERT INTO Manufacturers VALUES (16, 'Oncam Grandeye'); +INSERT INTO Manufacturers VALUES (17, 'Panasonic'); +INSERT INTO Manufacturers VALUES (18, 'Pelco'); +INSERT INTO Manufacturers VALUES (19, 'Sony'); +INSERT INTO Manufacturers VALUES (20, 'TP-Link'); +INSERT INTO Manufacturers VALUES (21, 'Trendnet'); +INSERT INTO Manufacturers VALUES (22, 'VisionTek'); +INSERT INTO Manufacturers VALUES (23, 'Vivotek'); +INSERT INTO Manufacturers VALUES (24, 'Wansview'); +*/ From 7ca7d40b6fb94333c6d06a11f03aaf632b8c96cc Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 10:32:04 -0400 Subject: [PATCH 015/501] Add defaults for ModelId and ManufacturerId. Fixes the methods for loading them --- web/includes/Monitor.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 54bf605cb..bd9987b2c 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -49,6 +49,8 @@ class Monitor extends ZM_Object { 'Notes' => '', 'ServerId' => 0, 'StorageId' => 0, + 'ManufacturerId' => null, + 'ModelId' => null, 'Type' => 'Ffmpeg', 'Function' => 'Mocord', 'Enabled' => array('type'=>'boolean','default'=>1), @@ -685,16 +687,26 @@ class Monitor extends ZM_Object { $output = $this->AlarmCommand('disable'); } function Model() { - if (!$this->{'Model'}) { - $this->{'Model'} = Model::find_one(array('Id'=>$this->ModelId())); - if (!$this->{'Model'}) $this->{'Model'} = new Model(); + if (!property_exists($this, 'Model')) { + if ($this->{'ModelId'}) { + $this->{'Model'} = Model::find_one(array('Id'=>$this->ModelId())); + if (!$this->{'Model'}) + $this->{'Model'} = new Model(); + } else { + $this->{'Model'} = new Model(); + } } return $this->{'Model'}; } function Manufacturer() { - if (!$this->{'Manufacturer'}) { - $this->{'Manufacturer'} = Manufacturer::find_one(array('Id'=>$this->ManufacturerId())); - if (!$this->{'Manufacturer'}) $this->{'Manufacturer'} = new Manufacturer(); + if (!property_exists($this, 'Manufacturer')) { + if ($this->{'ManufacturerId'}) { + $this->{'Manufacturer'} = Manufacturer::find_one(array('Id'=>$this->ManufacturerId())); + if (!$this->{'Manufacturer'}) + $this->{'Manufacturer'} = new Manufacturer(); + } else { + $this->{'Manufacturer'} = new Manufacturer(); + } } return $this->{'Manufacturer'}; } From 2f12615f088f8026bba909cb16e874947c353d9c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 10:33:16 -0400 Subject: [PATCH 016/501] assign REQUEST['newMonitor'] to a variable to simplify code. Fixup ModelId and ManufacturerId saving. --- web/includes/actions/monitor.php | 68 +++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/web/includes/actions/monitor.php b/web/includes/actions/monitor.php index 3fafb0f82..f6cbeca5f 100644 --- a/web/includes/actions/monitor.php +++ b/web/includes/actions/monitor.php @@ -45,24 +45,31 @@ if ( $action == 'save' ) { $x10Monitor = array(); } } - if ( !$_REQUEST['newMonitor[ManufacturerId'] and ($_REQUEST['newMonitor[Manufacturer'] != '') ) { + + # For convenience + $newMonitor = $_REQUEST['newMonitor']; + + if ( !$newMonitor['ManufacturerId'] and ($newMonitor['Manufacturer'] != '') ) { # Need to add a new Manufacturer entry - $newManufacturer = ZM\Manufacturer::find_one(array('Name'=>$_REQUEST['newMonitor[Manufacturer'])); + $newManufacturer = ZM\Manufacturer::find_one(array('Name'=>$newMonitor['Manufacturer'])); if (!$newManufacturer) { $newManufacturer = new ZM\Manufacturer(); - $newManufacturer->save(array('Name'=>$_REQUEST['newMonitor[Manufacturer'])); + $newManufacturer->save(array('Name'=>$newMonitor['Manufacturer'])); } - $_REQUEST['newMonitor[ManufacturerId'] = $newManufacturer->Id(); + $newMonitor['ManufacturerId'] = $newManufacturer->Id(); } - if ( !$_REQUEST['newMonitor[ModelId'] and ($_REQUEST['newMonitor[Model'] != '') ) { + if ( !$newMonitor['ModelId'] and ($newMonitor['Model'] != '') ) { # Need to add a new Model entry - $newModel = ZM\Model::find_one(array('Name'=>$_REQUEST['newMonitor[Model'])); + $newModel = ZM\Model::find_one(array('Name'=>$newMonitor['Model'])); if (!$newModel) { $newModel = new ZM\Model(); - $newMdoel->save(array('Name'=>$_REQUEST['newMonitor[Model'], 'ManufacturerId'=>$_REQUEST['newMonitor[ManufacturerId'])); + $newModel->save(array( + 'Name'=>$newMonitor['Model'], + 'ManufacturerId'=>$newMonitor['ManufacturerId'] + )); } - $_REQUEST['newMonitor[ModelId'] = $newModel->Id(); + $newMonitor['ModelId'] = $newModel->Id(); } $monitor = new ZM\Monitor($mid); @@ -88,22 +95,22 @@ if ( $action == 'save' ) { # Checkboxes don't return an element in the POST data, so won't be present in newMonitor. # So force a value for these fields foreach ( $types as $field => $value ) { - if ( ! isset($_REQUEST['newMonitor'][$field] ) ) { - $_REQUEST['newMonitor'][$field] = $value; + if ( ! isset($newMonitor[$field] ) ) { + $newMonitor[$field] = $value; } } # end foreach type - if ( $_REQUEST['newMonitor']['ServerId'] == 'auto' ) { - $_REQUEST['newMonitor']['ServerId'] = dbFetchOne( + if ( $newMonitor['ServerId'] == 'auto' ) { + $newMonitor['ServerId'] = dbFetchOne( 'SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id'); - ZM\Debug('Auto selecting server: Got ' . $_REQUEST['newMonitor']['ServerId']); - if ( ( !$_REQUEST['newMonitor'] ) and defined('ZM_SERVER_ID') ) { - $_REQUEST['newMonitor']['ServerId'] = ZM_SERVER_ID; + ZM\Debug('Auto selecting server: Got ' . $newMonitor['ServerId']); + if ((!$newMonitor['ServerId']) and defined('ZM_SERVER_ID')) { + $newMonitor['ServerId'] = ZM_SERVER_ID; ZM\Debug('Auto selecting server to ' . ZM_SERVER_ID); } } - $changes = $monitor->changes($_REQUEST['newMonitor']); + $changes = $monitor->changes($newMonitor); $restart = false; if ( count($changes) ) { @@ -134,13 +141,13 @@ if ( $action == 'save' ) { if ( file_exists($OldStorage->Path().'/'.$saferOldName) ) unlink($OldStorage->Path().'/'.$saferOldName); - $NewStorage = new ZM\Storage($_REQUEST['newMonitor']['StorageId']); + $NewStorage = new ZM\Storage($newMonitor['StorageId']); if ( !file_exists($NewStorage->Path().'/'.$mid) ) { if ( !mkdir($NewStorage->Path().'/'.$mid, 0755) ) { ZM\Error('Unable to mkdir ' . $NewStorage->Path().'/'.$mid); } } - $saferNewName = basename($_REQUEST['newMonitor']['Name']); + $saferNewName = basename($newMonitor['Name']); $link_path = $NewStorage->Path().'/'.$saferNewName; // Use a relative path for the target so the link continues to work from backups or directory changes. if ( !symlink($mid, $link_path) ) { @@ -151,8 +158,8 @@ if ( $action == 'save' ) { } // end if Name or Storage Area Change if ( isset($changes['Width']) || isset($changes['Height']) ) { - $newW = $_REQUEST['newMonitor']['Width']; - $newH = $_REQUEST['newMonitor']['Height']; + $newW = $newMonitor['Width']; + $newH = $newMonitor['Height']; $zones = dbFetchAll('SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid)); @@ -236,14 +243,27 @@ if ( $action == 'save' ) { if ( $monitor->insert($changes) ) { $mid = $monitor->Id(); - $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; - dbQuery("INSERT INTO Zones SET MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); - //$view = 'none'; + $zoneArea = $newMonitor['Width'] * $newMonitor['Height']; + dbQuery("INSERT INTO Zones SET MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, + sprintf( '%d,%d %d,%d %d,%d %d,%d', 0, 0, + $newMonitor['Width']-1, + 0, + $newMonitor['Width']-1, + $newMonitor['Height']-1, + 0, + $newMonitor['Height']-1), + $zoneArea, + intval(($zoneArea*3)/100), + intval(($zoneArea*75)/100), + intval(($zoneArea*3)/100), + intval(($zoneArea*75)/100), + intval(($zoneArea*2)/100) + )); $Storage = $monitor->Storage(); error_reporting(0); mkdir($Storage->Path().'/'.$mid, 0755); - $saferName = basename($_REQUEST['newMonitor']['Name']); + $saferName = basename($newMonitor['Name']); symlink($mid, $Storage->Path().'/'.$saferName); } else { From 12783f6edfe71dee6b8bf8a92e2c4fcf71404297 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 20:43:53 -0400 Subject: [PATCH 017/501] Add manufacturers, models and servers routes --- web/api/app/Config/routes.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/api/app/Config/routes.php b/web/api/app/Config/routes.php index 8dd2c1f63..6b3314d09 100644 --- a/web/api/app/Config/routes.php +++ b/web/api/app/Config/routes.php @@ -23,8 +23,6 @@ /** * Load the API / REST routes */ - /* Add new API to retrieve camera controls - for PTZ */ - /* refer to https://github.com/ZoneMinder/ZoneMinder/issues/799#issuecomment-105233112 */ Router::mapResources('configs'); Router::mapResources('controls'); Router::mapResources('events'); @@ -32,7 +30,10 @@ Router::mapResources('groups'); Router::mapResources('host'); Router::mapResources('logs'); + Router::mapResources('manufacturers'); + Router::mapResources('models'); Router::mapResources('monitors'); + Router::mapResources('servers'); Router::mapResources('states'); Router::mapResources('users'); Router::mapResources('zonepresets'); From 34d9f87d6dee2916f7ada7edb7f4ef791c1fbe62 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 20:44:16 -0400 Subject: [PATCH 018/501] remove cruft from ServersController.php --- web/api/app/Controller/ServersController.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/web/api/app/Controller/ServersController.php b/web/api/app/Controller/ServersController.php index c3ac6fad7..bbde27fae 100644 --- a/web/api/app/Controller/ServersController.php +++ b/web/api/app/Controller/ServersController.php @@ -89,8 +89,6 @@ class ServersController extends AppController { $this->Server->create(); if ( $this->Server->save($this->request->data) ) { - # Might be nice to send it a start request - #$this->daemonControl($this->Server->id, 'start', $this->request->data); return $this->flash(__('The server has been saved.'), array('action' => 'index')); } } @@ -126,8 +124,6 @@ class ServersController extends AppController { 'message' => $message, '_serialize' => array('message') )); - // - restart this server after change - #$this->daemonControl($this->Server->id, 'restart', $this->request->data); } /** @@ -151,8 +147,6 @@ class ServersController extends AppController { } $this->request->allowMethod('post', 'delete'); - #$this->daemonControl($this->Server->id, 'stop'); - if ( $this->Server->delete() ) { return $this->flash(__('The server has been deleted.'), array('action' => 'index')); } else { From fe72056d73d84f527437c09dfdfd1c68841a4c47 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 20:48:14 -0400 Subject: [PATCH 019/501] fixup populating Models dropdown after Manufacturer select --- web/skins/classic/views/js/monitor.js | 53 ++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index c32b66a0b..44bcddb5b 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -345,17 +345,52 @@ function getLocation() { } } -function ManufacturerId_onchange(ManufacturerId_select) { - if (ManufacturerId_select.value()) - $j('newMonitor[Manufacturer]').hide(); - else - $j('newMonitor[Manufacturer]').show(); +function populate_models(ManufacturerId) { + let dropdown = $j('[name="newMonitor[ModelId]"]'); + if (!dropdown.length) { + console.log("No element found for ModelId"); + return; + } + + dropdown.empty(); + dropdown.append(''); + dropdown.prop('selectedIndex', 0); + + // Populate dropdown with list of provinces + $j.getJSON(thisUrl+'?request=models&ManufacturerId='+ManufacturerId, function (data) { + if (data.result == 'Ok') { + $j.each(data.models, function (key, entry) { + dropdown.append($j('').attr('value', entry.Id).text(entry.Name)); + }); + dropdown.chosen("destroy"); + dropdown.chosen(); + } else { + alert(data.result); + } + }); } + +function ManufacturerId_onchange(ManufacturerId_select) { + if (ManufacturerId_select.value) { + ManufacturerId_select.form.elements['newMonitor[Manufacturer]'].style['display'] = 'none'; + populate_models(ManufacturerId_select.value); + } else { + ManufacturerId_select.form.elements['newMonitor[Manufacturer]'].style['display'] = 'inline'; + // Set models dropdown to Unknown, text area visible + let ModelId_dropdown = $j('[name="newMonitor[ModelId]"]'); + ModelId_dropdown.empty(); + ModelId_dropdown.append(''); + ModelId_dropdown.prop('selectedIndex', 0); + $j('[name="newMonitor[Model]"]').show(); + } +} + function ModelId_onchange(ModelId_select) { - if (ModelId_select.value()) - $j('newMonitor[Model]').hide(); - else - $j('newMonitor[Model]').show(); + if (parseInt(ModelId_select.value)) { + $j('[name="newMonitor[Model]"]').hide(); + } else { + $j('[name="newMonitor[Model]"]').show(); + } } window.addEventListener('DOMContentLoaded', initPage); From d98d20958ce11244d0775b3b987c51b11ee662dc Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 20:48:43 -0400 Subject: [PATCH 020/501] fixup the Manufacturer and Model ddms and text inputs --- web/skins/classic/views/monitor.php | 40 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index fa4b3ba4f..327de5def 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -457,27 +457,37 @@ switch ( $name ) { - translate('unknown')), - ZM\Manufacturer::find()); -echo htmlSelect('newMonitor[ManufacturerId]', $manufacturers, $monitor->ManufacturerId(), array('class'=>'chosen','data-on-change-this'=>'ManufacturerId_onchange')); + +translate('unknown')); + foreach ( ZM\Manufacturer::find( null, array('order'=>'lower(Name)')) as $Manufacturer ) { + $manufacturers[$Manufacturer->Id()] = $Manufacturer->Name(); + } + echo htmlSelect('newMonitor[ManufacturerId]', $manufacturers, $monitor->ManufacturerId(), + array('class'=>'chosen','data-on-change-this'=>'ManufacturerId_onchange')); ?> - + ManufacturerId() ? ' style="display:none"' : '' ?> + /> - translate('unknown')), - ZM\Model::find(array('ManufacturerId'=>$monitor->ManufacturerId())) -); -echo htmlSelect('newMonitor[ModelId]', $models, $monitor->ModelId(), array('class'=>'chosen', 'data-on-change-this'=>'ModelId_onchange')); + +translate('unknown')); + foreach ( ZM\Model::find(array('ManufacturerId'=>$monitor->ManufacturerId()), array('order'=>'lower(Name)')) as $Model ) { + $models[$Model->Id()] = $Model->Name(); + } + echo htmlSelect('newMonitor[ModelId]', $models, $monitor->ModelId(), + array('class'=>'chosen', 'data-on-change-this'=>'ModelId_onchange')); ?> - + ModelId() ? ' style="display:none"':'' ?>/> From c66489fb303de460fac00c33aeb659f84635db49 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 20:49:30 -0400 Subject: [PATCH 021/501] rough in api support for Models and Manufacturers --- .../app/Controller/CameraModelsController.php | 156 +++++++++++++++++ .../Controller/ManufacturersController.php | 162 ++++++++++++++++++ web/api/app/Model/CameraModel.php | 70 ++++++++ web/api/app/Model/Manufacturer.php | 77 +++++++++ web/api/app/View/Manufacturers/json/edit.ctp | 2 + web/api/app/View/Manufacturers/json/index.ctp | 1 + web/api/app/View/Manufacturers/json/view.ctp | 1 + web/api/app/View/Manufacturers/xml/edit.ctp | 2 + web/api/app/View/Manufacturers/xml/index.ctp | 2 + web/api/app/View/Manufacturers/xml/view.ctp | 2 + 10 files changed, 475 insertions(+) create mode 100644 web/api/app/Controller/CameraModelsController.php create mode 100644 web/api/app/Controller/ManufacturersController.php create mode 100644 web/api/app/Model/CameraModel.php create mode 100644 web/api/app/Model/Manufacturer.php create mode 100644 web/api/app/View/Manufacturers/json/edit.ctp create mode 100644 web/api/app/View/Manufacturers/json/index.ctp create mode 100644 web/api/app/View/Manufacturers/json/view.ctp create mode 100644 web/api/app/View/Manufacturers/xml/edit.ctp create mode 100644 web/api/app/View/Manufacturers/xml/index.ctp create mode 100644 web/api/app/View/Manufacturers/xml/view.ctp diff --git a/web/api/app/Controller/CameraModelsController.php b/web/api/app/Controller/CameraModelsController.php new file mode 100644 index 000000000..691fd0767 --- /dev/null +++ b/web/api/app/Controller/CameraModelsController.php @@ -0,0 +1,156 @@ +CameraModel->recursive = 0; + + $options = ''; + $models = $this->CameraModel->find('all', $options); + $this->set(array( + 'models' => $models, + '_serialize' => array('models') + )); + } + +/** + * view method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function view($id = null) { + $this->CameraModel->recursive = 0; + if ( !$this->CameraModel->exists($id) ) { + throw new NotFoundException(__('Invalid model')); + } + $restricted = ''; + + $options = array('conditions' => array( + array('CameraModel.'.$this->CameraModel->primaryKey => $id), + $restricted + ) + ); + $model = $this->CameraModel->find('first', $options); + $this->set(array( + 'model' => $model, + '_serialize' => array('model') + )); + } + +/** + * add method + * + * @return void + */ + public function add() { + if ($this->request->is('post')) { + + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if (!$canEdit) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + + $this->CameraModel->create(); + if ($this->CameraModel->save($this->request->data)) { + return $this->flash(__('The model has been saved.'), array('action' => 'index')); + } + } + } + +/** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { + $this->CameraModel->id = $id; + + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if (!$canEdit) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + + if (!$this->CameraModel->exists($id)) { + throw new NotFoundException(__('Invalid model')); + } + if ($this->CameraModel->save($this->request->data)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + + $this->set(array( + 'message' => $message, + '_serialize' => array('message') + )); + } + +/** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if (!$canEdit) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + + $this->CameraModel->id = $id; + if (!$this->CameraModel->exists()) { + throw new NotFoundException(__('Invalid model')); + } + $this->request->allowMethod('post', 'delete'); + + if ($this->CameraModel->delete()) { + return $this->flash(__('The model has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The model could not be deleted. Please, try again.'), array('action' => 'index')); + } + } +} diff --git a/web/api/app/Controller/ManufacturersController.php b/web/api/app/Controller/ManufacturersController.php new file mode 100644 index 000000000..0249af6af --- /dev/null +++ b/web/api/app/Controller/ManufacturersController.php @@ -0,0 +1,162 @@ +Manufacturer->recursive = 0; + + $options = ''; + $manufacturers = $this->Manufacturer->find('all', $options); + $this->set(array( + 'manufacturers' => $manufacturers, + '_serialize' => array('manufacturers') + )); + } + +/** + * view method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function view($id = null) { + $this->Manufacturer->recursive = 0; + if ( !$this->Manufacturer->exists($id) ) { + throw new NotFoundException(__('Invalid manufacturer')); + } + $restricted = ''; + + $options = array('conditions' => array( + array('Manufacturer.'.$this->Manufacturer->primaryKey => $id), + $restricted + ) + ); + $manufacturer = $this->Manufacturer->find('first', $options); + $this->set(array( + 'manufacturer' => $manufacturer, + '_serialize' => array('manufacturer') + )); + } + +/** + * add method + * + * @return void + */ + public function add() { + if ( $this->request->is('post') ) { + + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + + $this->Manufacturer->create(); + if ( $this->Manufacturer->save($this->request->data) ) { + # Might be nice to send it a start request + #$this->daemonControl($this->Manufacturer->id, 'start', $this->request->data); + return $this->flash(__('The manufacturer has been saved.'), array('action' => 'index')); + } + } + } + +/** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { + $this->Manufacturer->id = $id; + + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + + if ( !$this->Manufacturer->exists($id) ) { + throw new NotFoundException(__('Invalid manufacturer')); + } + if ( $this->Manufacturer->save($this->request->data) ) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + + $this->set(array( + 'message' => $message, + '_serialize' => array('message') + )); + // - restart this manufacturer after change + #$this->daemonControl($this->Manufacturer->id, 'restart', $this->request->data); + } + +/** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + + $this->Manufacturer->id = $id; + if ( !$this->Manufacturer->exists() ) { + throw new NotFoundException(__('Invalid manufacturer')); + } + $this->request->allowMethod('post', 'delete'); + + #$this->daemonControl($this->Manufacturer->id, 'stop'); + + if ( $this->Manufacturer->delete() ) { + return $this->flash(__('The manufacturer has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The manufacturer could not be deleted. Please, try again.'), array('action' => 'index')); + } + } +} diff --git a/web/api/app/Model/CameraModel.php b/web/api/app/Model/CameraModel.php new file mode 100644 index 000000000..e953b5db8 --- /dev/null +++ b/web/api/app/Model/CameraModel.php @@ -0,0 +1,70 @@ + array( + 'notBlank' => array( + 'rule' => array('notBlank'))), + 'Id' => array( + 'numeric' => array( + 'rule' => array('numeric'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + ); + + //The Associations below have been created with all possible keys, those that are not needed can be removed + +/** + * hasMany associations + * + * @var array + */ + public $hasOne = array( + 'Manufacturer' => array( + 'className' => 'Manufacturer', + 'joinTable' => 'Manufacturers', + 'foreignKey' => 'Id', + ), + ); + //var $actsAs = array( 'Containable' ); +} diff --git a/web/api/app/Model/Manufacturer.php b/web/api/app/Model/Manufacturer.php new file mode 100644 index 000000000..34a6c88c0 --- /dev/null +++ b/web/api/app/Model/Manufacturer.php @@ -0,0 +1,77 @@ + array( + 'numeric' => array( + 'rule' => array('numeric'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'Name' => array( + 'notBlank' => array( + 'rule' => array('notBlank'))), + ); + + //The Associations below have been created with all possible keys, those that are not needed can be removed + +/** + * hasMany associations + * + * @var array + */ + public $hasMany = array( + 'Model' => array( + 'className' => 'Model', + 'foreignKey' => 'ManufacturerId', + 'dependent' => false, + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ) + ); +} diff --git a/web/api/app/View/Manufacturers/json/edit.ctp b/web/api/app/View/Manufacturers/json/edit.ctp new file mode 100644 index 000000000..a32d9cfad --- /dev/null +++ b/web/api/app/View/Manufacturers/json/edit.ctp @@ -0,0 +1,2 @@ +echo json_encode($message); +echo json_encode($manufacturer); diff --git a/web/api/app/View/Manufacturers/json/index.ctp b/web/api/app/View/Manufacturers/json/index.ctp new file mode 100644 index 000000000..4c54bdf95 --- /dev/null +++ b/web/api/app/View/Manufacturers/json/index.ctp @@ -0,0 +1 @@ +echo json_encode($manufacturers); diff --git a/web/api/app/View/Manufacturers/json/view.ctp b/web/api/app/View/Manufacturers/json/view.ctp new file mode 100644 index 000000000..4e0a14ae6 --- /dev/null +++ b/web/api/app/View/Manufacturers/json/view.ctp @@ -0,0 +1 @@ +echo json_encode($manufacturer); diff --git a/web/api/app/View/Manufacturers/xml/edit.ctp b/web/api/app/View/Manufacturers/xml/edit.ctp new file mode 100644 index 000000000..09fb8979a --- /dev/null +++ b/web/api/app/View/Manufacturers/xml/edit.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $message)); +echo $xml->asXML(); diff --git a/web/api/app/View/Manufacturers/xml/index.ctp b/web/api/app/View/Manufacturers/xml/index.ctp new file mode 100644 index 000000000..9bb514fff --- /dev/null +++ b/web/api/app/View/Manufacturers/xml/index.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $servers)); +echo $xml->asXML(); diff --git a/web/api/app/View/Manufacturers/xml/view.ctp b/web/api/app/View/Manufacturers/xml/view.ctp new file mode 100644 index 000000000..3b2a3fdad --- /dev/null +++ b/web/api/app/View/Manufacturers/xml/view.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $server)); +echo $xml->asXML(); From 292b3c1d3767fb9c57ccc2778f20d9e269c31754 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 20:49:53 -0400 Subject: [PATCH 022/501] add an ajax request for querying the available models --- web/ajax/models.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 web/ajax/models.php diff --git a/web/ajax/models.php b/web/ajax/models.php new file mode 100644 index 000000000..3a4f54c5b --- /dev/null +++ b/web/ajax/models.php @@ -0,0 +1,24 @@ +$_REQUEST['ManufacturerId']), array('order'=>'lower(Name)')); +ajaxResponse(array('models'=>$models)); +?> From 93c5ad0939df10685fe6042a7c8c13a0cf17213f Mon Sep 17 00:00:00 2001 From: Peter Keresztes Schmidt Date: Sun, 12 Sep 2021 11:28:57 +0200 Subject: [PATCH 023/501] Image: Fix Wclobbered warnings From C99 spec: [...] objects of automatic storage duration that are local to the function containing the invocation of the corresponding setjmp macro that do not have volatile-qualified type and have been changed between the setjmp invocation and longjmp call are indeterminate Remove the variables in question or pass them as const refs. --- src/zm_image.cpp | 104 +++++++++++++++++++++++------------------------ src/zm_image.h | 2 +- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index a837138bc..f0d94a542 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -935,14 +935,13 @@ bool Image::WriteRaw(const char *filename) const { bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - struct jpeg_decompress_struct *cinfo = readjpg_dcinfo; - if ( !cinfo ) { - cinfo = readjpg_dcinfo = new jpeg_decompress_struct; - cinfo->err = jpeg_std_error(&jpg_err.pub); + if ( !readjpg_dcinfo ) { + readjpg_dcinfo = new jpeg_decompress_struct; + readjpg_dcinfo->err = jpeg_std_error(&jpg_err.pub); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; - jpeg_create_decompress(cinfo); + jpeg_create_decompress(readjpg_dcinfo); } FILE *infile; @@ -952,30 +951,30 @@ bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsign } if ( setjmp(jpg_err.setjmp_buffer) ) { - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); return false; } - jpeg_stdio_src(cinfo, infile); + jpeg_stdio_src(readjpg_dcinfo, infile); - jpeg_read_header(cinfo, TRUE); + jpeg_read_header(readjpg_dcinfo, TRUE); - if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) { + if ( (readjpg_dcinfo->num_components != 1) && (readjpg_dcinfo->num_components != 3) ) { Error("Unexpected colours when reading jpeg image: %d", colours); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); return false; } /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ - if ( cinfo->dc_huff_tbl_ptrs[0] == nullptr ) { - zm_use_std_huff_tables(cinfo); + if ( readjpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr ) { + zm_use_std_huff_tables(readjpg_dcinfo); } - new_width = cinfo->image_width; - new_height = cinfo->image_height; + new_width = readjpg_dcinfo->image_width; + new_height = readjpg_dcinfo->image_height; if ( (width != new_width) || (height != new_height) ) { Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height); @@ -983,7 +982,7 @@ bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsign switch ( p_colours ) { case ZM_COLOUR_GRAY8: - cinfo->out_color_space = JCS_GRAYSCALE; + readjpg_dcinfo->out_color_space = JCS_GRAYSCALE; new_colours = ZM_COLOUR_GRAY8; new_subpixelorder = ZM_SUBPIX_ORDER_NONE; break; @@ -991,17 +990,17 @@ bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsign #ifdef JCS_EXTENSIONS new_colours = ZM_COLOUR_RGB32; if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - cinfo->out_color_space = JCS_EXT_BGRX; + readjpg_dcinfo->out_color_space = JCS_EXT_BGRX; new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - cinfo->out_color_space = JCS_EXT_XRGB; + readjpg_dcinfo->out_color_space = JCS_EXT_XRGB; new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - cinfo->out_color_space = JCS_EXT_XBGR; + readjpg_dcinfo->out_color_space = JCS_EXT_XBGR; new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; } else { /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; + readjpg_dcinfo->out_color_space = JCS_EXT_RGBX; new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; } break; @@ -1013,7 +1012,7 @@ bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsign new_colours = ZM_COLOUR_RGB24; if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; + readjpg_dcinfo->out_color_space = JCS_EXT_BGR; new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); @@ -1029,7 +1028,7 @@ cinfo->out_color_space = JCS_EXT_RGB; cinfo->out_color_space = JCS_RGB; #endif */ - cinfo->out_color_space = JCS_RGB; + readjpg_dcinfo->out_color_space = JCS_RGB; new_subpixelorder = ZM_SUBPIX_ORDER_RGB; } break; @@ -1037,20 +1036,20 @@ cinfo->out_color_space = JCS_RGB; if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { Error("Failed requesting writeable buffer for reading JPEG image."); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); return false; } - jpeg_start_decompress(cinfo); + jpeg_start_decompress(readjpg_dcinfo); JSAMPROW row_pointer = buffer; - while ( cinfo->output_scanline < cinfo->output_height ) { - jpeg_read_scanlines(cinfo, &row_pointer, 1); + while ( readjpg_dcinfo->output_scanline < readjpg_dcinfo->output_height ) { + jpeg_read_scanlines(readjpg_dcinfo, &row_pointer, 1); row_pointer += linesize; } - jpeg_finish_decompress(cinfo); + jpeg_finish_decompress(readjpg_dcinfo); fclose(infile); @@ -1078,7 +1077,7 @@ bool Image::WriteJpeg(const std::string &filename, int quality_override, SystemT } bool Image::WriteJpeg(const std::string &filename, - int quality_override, + const int &quality_override, SystemTimePoint timestamp, bool on_blocking_abort) const { if ( config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8) ) { @@ -1247,39 +1246,38 @@ bool Image::DecodeJpeg( unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - struct jpeg_decompress_struct *cinfo = decodejpg_dcinfo; - if ( !cinfo ) { - cinfo = decodejpg_dcinfo = new jpeg_decompress_struct; - cinfo->err = jpeg_std_error( &jpg_err.pub ); + if ( !decodejpg_dcinfo ) { + decodejpg_dcinfo = new jpeg_decompress_struct; + decodejpg_dcinfo->err = jpeg_std_error( &jpg_err.pub ); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; - jpeg_create_decompress( cinfo ); + jpeg_create_decompress( decodejpg_dcinfo ); } if ( setjmp(jpg_err.setjmp_buffer) ) { - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(decodejpg_dcinfo); return false; } - zm_jpeg_mem_src(cinfo, inbuffer, inbuffer_size); + zm_jpeg_mem_src(decodejpg_dcinfo, inbuffer, inbuffer_size); - jpeg_read_header(cinfo, TRUE); + jpeg_read_header(decodejpg_dcinfo, TRUE); - if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) { + if ( (decodejpg_dcinfo->num_components != 1) && (decodejpg_dcinfo->num_components != 3) ) { Error("Unexpected colours when reading jpeg image: %d", colours); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(decodejpg_dcinfo); return false; } /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ - if ( cinfo->dc_huff_tbl_ptrs[0] == nullptr ) { - zm_use_std_huff_tables(cinfo); + if ( decodejpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr ) { + zm_use_std_huff_tables(decodejpg_dcinfo); } - new_width = cinfo->image_width; - new_height = cinfo->image_height; + new_width = decodejpg_dcinfo->image_width; + new_height = decodejpg_dcinfo->image_height; if ( (width != new_width) || (height != new_height) ) { Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", @@ -1288,7 +1286,7 @@ bool Image::DecodeJpeg( switch (p_colours) { case ZM_COLOUR_GRAY8: - cinfo->out_color_space = JCS_GRAYSCALE; + decodejpg_dcinfo->out_color_space = JCS_GRAYSCALE; new_colours = ZM_COLOUR_GRAY8; new_subpixelorder = ZM_SUBPIX_ORDER_NONE; break; @@ -1296,17 +1294,17 @@ bool Image::DecodeJpeg( #ifdef JCS_EXTENSIONS new_colours = ZM_COLOUR_RGB32; if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - cinfo->out_color_space = JCS_EXT_BGRX; + decodejpg_dcinfo->out_color_space = JCS_EXT_BGRX; new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - cinfo->out_color_space = JCS_EXT_XRGB; + decodejpg_dcinfo->out_color_space = JCS_EXT_XRGB; new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - cinfo->out_color_space = JCS_EXT_XBGR; + decodejpg_dcinfo->out_color_space = JCS_EXT_XBGR; new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; } else { /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; + decodejpg_dcinfo->out_color_space = JCS_EXT_RGBX; new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; } break; @@ -1318,7 +1316,7 @@ bool Image::DecodeJpeg( new_colours = ZM_COLOUR_RGB24; if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; + decodejpg_dcinfo->out_color_space = JCS_EXT_BGR; new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); @@ -1334,7 +1332,7 @@ cinfo->out_color_space = JCS_EXT_RGB; cinfo->out_color_space = JCS_RGB; #endif */ - cinfo->out_color_space = JCS_RGB; + decodejpg_dcinfo->out_color_space = JCS_RGB; new_subpixelorder = ZM_SUBPIX_ORDER_RGB; } break; @@ -1342,19 +1340,19 @@ cinfo->out_color_space = JCS_RGB; if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { Error("Failed requesting writeable buffer for reading JPEG image."); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(decodejpg_dcinfo); return false; } - jpeg_start_decompress(cinfo); + jpeg_start_decompress(decodejpg_dcinfo); JSAMPROW row_pointer = buffer; /* pointer to a single row */ - while ( cinfo->output_scanline < cinfo->output_height ) { - jpeg_read_scanlines(cinfo, &row_pointer, 1); + while ( decodejpg_dcinfo->output_scanline < decodejpg_dcinfo->output_height ) { + jpeg_read_scanlines(decodejpg_dcinfo, &row_pointer, 1); row_pointer += linesize; } - jpeg_finish_decompress(cinfo); + jpeg_finish_decompress(decodejpg_dcinfo); return true; } diff --git a/src/zm_image.h b/src/zm_image.h index 7778747e4..ccbae9f86 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -241,7 +241,7 @@ class Image { bool WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const; bool WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const; bool WriteJpeg(const std::string &filename, - int quality_override, + const int &quality_override, SystemTimePoint timestamp, bool on_blocking_abort) const; From a4ebf533a93e8834b24455541a48735a6dcbf20e Mon Sep 17 00:00:00 2001 From: Peter Keresztes Schmidt Date: Sun, 12 Sep 2021 11:38:44 +0200 Subject: [PATCH 024/501] Image: Codestyle changes --- src/zm_image.cpp | 304 +++++++++++++++++++++++------------------------ 1 file changed, 151 insertions(+), 153 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index f0d94a542..09c05550e 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -936,7 +936,7 @@ bool Image::WriteRaw(const char *filename) const { bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - if ( !readjpg_dcinfo ) { + if (!readjpg_dcinfo) { readjpg_dcinfo = new jpeg_decompress_struct; readjpg_dcinfo->err = jpeg_std_error(&jpg_err.pub); jpg_err.pub.error_exit = zm_jpeg_error_exit; @@ -945,12 +945,12 @@ bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsign } FILE *infile; - if ( (infile = fopen(filename.c_str(), "rb")) == nullptr ) { + if ((infile = fopen(filename.c_str(), "rb")) == nullptr) { Error("Can't open %s: %s", filename.c_str(), strerror(errno)); return false; } - if ( setjmp(jpg_err.setjmp_buffer) ) { + if (setjmp(jpg_err.setjmp_buffer)) { jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); return false; @@ -958,9 +958,9 @@ bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsign jpeg_stdio_src(readjpg_dcinfo, infile); - jpeg_read_header(readjpg_dcinfo, TRUE); + jpeg_read_header(readjpg_dcinfo, true); - if ( (readjpg_dcinfo->num_components != 1) && (readjpg_dcinfo->num_components != 3) ) { + if ((readjpg_dcinfo->num_components != 1) && (readjpg_dcinfo->num_components != 3)) { Error("Unexpected colours when reading jpeg image: %d", colours); jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); @@ -969,72 +969,72 @@ bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsign /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ - if ( readjpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr ) { + if (readjpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) { zm_use_std_huff_tables(readjpg_dcinfo); } new_width = readjpg_dcinfo->image_width; new_height = readjpg_dcinfo->image_height; - if ( (width != new_width) || (height != new_height) ) { + if ((width != new_width) || (height != new_height)) { Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height); } - switch ( p_colours ) { + switch (p_colours) { case ZM_COLOUR_GRAY8: - readjpg_dcinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; + readjpg_dcinfo->out_color_space = JCS_GRAYSCALE; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; case ZM_COLOUR_RGB32: #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - readjpg_dcinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - readjpg_dcinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - readjpg_dcinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - readjpg_dcinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + readjpg_dcinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + readjpg_dcinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + readjpg_dcinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + readjpg_dcinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); + Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); #endif case ZM_COLOUR_RGB24: default: - new_colours = ZM_COLOUR_RGB24; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { + new_colours = ZM_COLOUR_RGB24; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - readjpg_dcinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + readjpg_dcinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ - /* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS cinfo->out_color_space = JCS_EXT_RGB; #else cinfo->out_color_space = JCS_RGB; #endif - */ - readjpg_dcinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; + */ + readjpg_dcinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; } // end switch p_colours - if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { + if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) { Error("Failed requesting writeable buffer for reading JPEG image."); jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); @@ -1044,13 +1044,12 @@ cinfo->out_color_space = JCS_RGB; jpeg_start_decompress(readjpg_dcinfo); JSAMPROW row_pointer = buffer; - while ( readjpg_dcinfo->output_scanline < readjpg_dcinfo->output_height ) { + while (readjpg_dcinfo->output_scanline < readjpg_dcinfo->output_height) { jpeg_read_scanlines(readjpg_dcinfo, &row_pointer, 1); row_pointer += linesize; } jpeg_finish_decompress(readjpg_dcinfo); - fclose(infile); return true; @@ -1080,34 +1079,37 @@ bool Image::WriteJpeg(const std::string &filename, const int &quality_override, SystemTimePoint timestamp, bool on_blocking_abort) const { - if ( config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8) ) { + if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) { Image temp_image(*this); temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort); } int quality = quality_override ? quality_override : config.jpeg_file_quality; - struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; + jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; FILE *outfile = nullptr; int raw_fd = 0; - if ( !cinfo ) { + if (!cinfo) { cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct; cinfo->err = jpeg_std_error(&jpg_err.pub); jpeg_create_compress(cinfo); } - if ( !on_blocking_abort ) { + if (!on_blocking_abort) { jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; } else { jpg_err.pub.error_exit = zm_jpeg_error_silent; jpg_err.pub.emit_message = zm_jpeg_emit_silence; - if ( setjmp(jpg_err.setjmp_buffer) ) { + if (setjmp(jpg_err.setjmp_buffer)) { jpeg_abort_compress(cinfo); - Debug(1, "Aborted a write mid-stream and %s and %d", (outfile == nullptr) ? "closing file" : "file not opened", raw_fd); - if ( raw_fd ) + Debug(1, + "Aborted a write mid-stream and %s and %d", + (outfile == nullptr) ? "closing file" : "file not opened", + raw_fd); + if (raw_fd) close(raw_fd); - if ( outfile ) + if (outfile) fclose(outfile); return false; } @@ -1134,58 +1136,58 @@ bool Image::WriteJpeg(const std::string &filename, cinfo->image_width = width; /* image width and height, in pixels */ cinfo->image_height = height; - switch ( colours ) { + switch (colours) { case ZM_COLOUR_GRAY8: - cinfo->input_components = 1; - cinfo->in_color_space = JCS_GRAYSCALE; - break; + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + break; case ZM_COLOUR_RGB32: #ifdef JCS_EXTENSIONS - cinfo->input_components = 4; - if ( subpixelorder == ZM_SUBPIX_ORDER_RGBA ) { - cinfo->in_color_space = JCS_EXT_RGBX; - } else if ( subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - cinfo->in_color_space = JCS_EXT_BGRX; - } else if ( subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - cinfo->in_color_space = JCS_EXT_XRGB; - } else if ( subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - cinfo->in_color_space = JCS_EXT_XBGR; - } else { - Warning("Unknwon subpixelorder %d", subpixelorder); - /* Assume RGBA */ - cinfo->in_color_space = JCS_EXT_RGBX; - } - break; + cinfo->input_components = 4; + if (subpixelorder == ZM_SUBPIX_ORDER_RGBA) { + cinfo->in_color_space = JCS_EXT_RGBX; + } else if (subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->in_color_space = JCS_EXT_BGRX; + } else if (subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->in_color_space = JCS_EXT_XRGB; + } else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->in_color_space = JCS_EXT_XBGR; + } else { + Warning("Unknwon subpixelorder %d", subpixelorder); + /* Assume RGBA */ + cinfo->in_color_space = JCS_EXT_RGBX; + } + break; #else - Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + jpeg_abort_compress(cinfo); + fclose(outfile); + return false; +#endif + case ZM_COLOUR_RGB24: + default: + cinfo->input_components = 3; + if (subpixelorder == ZM_SUBPIX_ORDER_BGR) { +#ifdef JCS_EXTENSIONS + cinfo->in_color_space = JCS_EXT_BGR; +#else + Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); jpeg_abort_compress(cinfo); fclose(outfile); return false; #endif - case ZM_COLOUR_RGB24: - default: - cinfo->input_components = 3; - if ( subpixelorder == ZM_SUBPIX_ORDER_BGR) { -#ifdef JCS_EXTENSIONS - cinfo->in_color_space = JCS_EXT_BGR; -#else - Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); - jpeg_abort_compress(cinfo); - fclose(outfile); - return false; -#endif - } else { - /* Assume RGB */ - /* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS cinfo->out_color_space = JCS_EXT_RGB; #else cinfo->out_color_space = JCS_RGB; #endif */ - cinfo->in_color_space = JCS_RGB; - } - break; + cinfo->in_color_space = JCS_RGB; + } + break; } // end switch(colours) jpeg_set_defaults(cinfo); @@ -1193,7 +1195,7 @@ cinfo->out_color_space = JCS_RGB; cinfo->dct_method = JDCT_FASTEST; jpeg_start_compress(cinfo, TRUE); - if ( config.add_jpeg_comments && !annotation_.empty() ) { + if (config.add_jpeg_comments && !annotation_.empty()) { jpeg_write_marker(cinfo, JPEG_COM, reinterpret_cast(annotation_.c_str()), annotation_.size()); } // If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal @@ -1217,19 +1219,19 @@ cinfo->out_color_space = JCS_RGB; snprintf(msbuf, sizeof msbuf, "%06d", static_cast(ts_usec.count())); unsigned char exiftimes[82] = { - 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92, - 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00 }; - memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf,EXIFTIMES_LEN); + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92, + 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00}; + memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf, EXIFTIMES_LEN); memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN); - jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *)exiftimes, sizeof(exiftimes)); + jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *) exiftimes, sizeof(exiftimes)); } JSAMPROW row_pointer = buffer; /* pointer to a single row */ - while ( cinfo->next_scanline < cinfo->image_height ) { + while (cinfo->next_scanline < cinfo->image_height) { jpeg_write_scanlines(cinfo, &row_pointer, 1); row_pointer += linesize; } @@ -1239,23 +1241,19 @@ cinfo->out_color_space = JCS_RGB; return true; } -bool Image::DecodeJpeg( - const JOCTET *inbuffer, - int inbuffer_size, - unsigned int p_colours, - unsigned int p_subpixelorder) +bool Image::DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - if ( !decodejpg_dcinfo ) { + if (!decodejpg_dcinfo) { decodejpg_dcinfo = new jpeg_decompress_struct; - decodejpg_dcinfo->err = jpeg_std_error( &jpg_err.pub ); + decodejpg_dcinfo->err = jpeg_std_error(&jpg_err.pub); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; - jpeg_create_decompress( decodejpg_dcinfo ); + jpeg_create_decompress(decodejpg_dcinfo); } - if ( setjmp(jpg_err.setjmp_buffer) ) { + if (setjmp(jpg_err.setjmp_buffer)) { jpeg_abort_decompress(decodejpg_dcinfo); return false; } @@ -1264,7 +1262,7 @@ bool Image::DecodeJpeg( jpeg_read_header(decodejpg_dcinfo, TRUE); - if ( (decodejpg_dcinfo->num_components != 1) && (decodejpg_dcinfo->num_components != 3) ) { + if ((decodejpg_dcinfo->num_components != 1) && (decodejpg_dcinfo->num_components != 3)) { Error("Unexpected colours when reading jpeg image: %d", colours); jpeg_abort_decompress(decodejpg_dcinfo); return false; @@ -1272,73 +1270,73 @@ bool Image::DecodeJpeg( /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ - if ( decodejpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr ) { + if (decodejpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) { zm_use_std_huff_tables(decodejpg_dcinfo); } new_width = decodejpg_dcinfo->image_width; new_height = decodejpg_dcinfo->image_height; - if ( (width != new_width) || (height != new_height) ) { + if ((width != new_width) || (height != new_height)) { Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", - width, height, new_width, new_height); + width, height, new_width, new_height); } switch (p_colours) { case ZM_COLOUR_GRAY8: decodejpg_dcinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; case ZM_COLOUR_RGB32: #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - decodejpg_dcinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - decodejpg_dcinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - decodejpg_dcinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - decodejpg_dcinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + decodejpg_dcinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + decodejpg_dcinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + decodejpg_dcinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + decodejpg_dcinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); + Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); #endif case ZM_COLOUR_RGB24: default: - new_colours = ZM_COLOUR_RGB24; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { + new_colours = ZM_COLOUR_RGB24; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - decodejpg_dcinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + decodejpg_dcinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ - /* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS cinfo->out_color_space = JCS_EXT_RGB; #else cinfo->out_color_space = JCS_RGB; #endif - */ - decodejpg_dcinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; + */ + decodejpg_dcinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; } // end switch - if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { + if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) { Error("Failed requesting writeable buffer for reading JPEG image."); jpeg_abort_decompress(decodejpg_dcinfo); return false; @@ -1347,7 +1345,7 @@ cinfo->out_color_space = JCS_RGB; jpeg_start_decompress(decodejpg_dcinfo); JSAMPROW row_pointer = buffer; /* pointer to a single row */ - while ( decodejpg_dcinfo->output_scanline < decodejpg_dcinfo->output_height ) { + while (decodejpg_dcinfo->output_scanline < decodejpg_dcinfo->output_height) { jpeg_read_scanlines(decodejpg_dcinfo, &row_pointer, 1); row_pointer += linesize; } From 4b4152b837f07eba1bcb0ce0dfadc23546d6c6f2 Mon Sep 17 00:00:00 2001 From: Andrea Vezzali Date: Mon, 13 Sep 2021 13:28:48 +0200 Subject: [PATCH 025/501] Update it_it translation (#3350) --- web/lang/it_it.php | 274 ++++++++++++++++++++++----------------------- 1 file changed, 137 insertions(+), 137 deletions(-) diff --git a/web/lang/it_it.php b/web/lang/it_it.php index 62e881d45..b51b85300 100644 --- a/web/lang/it_it.php +++ b/web/lang/it_it.php @@ -98,8 +98,8 @@ $SLANG = array( 'AlarmRefImageBlendPct'=> 'Alarm Reference Image Blend %ge', // Added - 2015-04-18 'Alert' => 'Attenzione', 'All' => 'Tutto', - 'AnalysisFPS' => 'Analysis FPS', // Added - 2015-07-22 - 'AnalysisUpdateDelay' => 'Analysis Update Delay', // Added - 2015-07-23 + 'AnalysisFPS' => 'Analisi FPS', // Added - 2015-07-22 + 'AnalysisUpdateDelay' => 'Intervallo aggiornamento analisi', // Added - 2015-07-23 'Apply' => 'Applica', 'ApplyingStateChange' => 'Sto applicando le modifiche', 'ArchArchived' => 'Archiviato', @@ -112,31 +112,31 @@ $SLANG = array( 'AttrArchiveStatus' => 'Stato Archivio', 'AttrAvgScore' => 'Punteggio medio', 'AttrCause' => 'Causa', - 'AttrDiskBlocks' => 'Blocchi del Disco', - 'AttrDiskPercent' => 'Percentuale del Disco', - 'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30 + 'AttrDiskBlocks' => 'Blocchi disco', + 'AttrDiskPercent' => 'Percentuale disco', + 'AttrDiskSpace' => 'Spazio disco', // Added - 2018-08-30 'AttrDuration' => 'Durata', 'AttrEndDate' => 'End Date', // Added - 2018-08-30 'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30 'AttrEndTime' => 'End Time', // Added - 2018-08-30 'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30 - 'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30 + 'AttrFilterServer' => 'Filtro attivo su Server', // Added - 2018-08-30 'AttrFrames' => 'Immagini', 'AttrId' => 'Id', 'AttrMaxScore' => 'Punteggio massimo', 'AttrMonitorId' => 'Id Monitor', 'AttrMonitorName' => 'Nome Monitor', - 'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30 + 'AttrMonitorServer' => 'Monitor attivo su Server', // Added - 2018-08-30 'AttrName' => 'Nome', 'AttrNotes' => 'Note', - 'AttrStartDate' => 'Start Date', // Added - 2018-08-30 - 'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30 - 'AttrStartTime' => 'Start Time', // Added - 2018-08-30 - 'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30 - 'AttrStateId' => 'Run State', // Added - 2018-08-30 - 'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30 - 'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30 - 'AttrSystemLoad' => 'System Load', + 'AttrStartDate' => 'Inizio - Data', // Added - 2018-08-30 + 'AttrStartDateTime' => 'Inizio - Data/orario', // Added - 2018-08-30 + 'AttrStartTime' => 'Inizio - Orario', // Added - 2018-08-30 + 'AttrStartWeekday' => 'Inizio - Giorno della settimana', // Added - 2018-08-30 + 'AttrStateId' => 'Stato esecuzione', // Added - 2018-08-30 + 'AttrStorageArea' => 'Area Salvataggio', // Added - 2018-08-30 + 'AttrStorageServer' => 'Server Salvataggio remoto', // Added - 2018-08-30 + 'AttrSystemLoad' => 'Carico di sistema', 'AttrTotalScore' => 'Punteggio totale', 'Auto' => 'Auto', 'AutoStopTimeout' => 'Auto Stop Timeout', @@ -181,56 +181,56 @@ $SLANG = array( 'BlobPx' => 'Blob Px', 'BlobSizes' => 'Dimensioni Blob', 'Blobs' => 'Blobs', - 'Brightness' => 'Luminosità', + 'Brightness' => 'Luminosità;', 'Buffer' => 'Buffer', // Added - 2015-04-18 'Buffers' => 'Buffers', 'CSSDescription' => 'Change the default css for this computer', // Added - 2015-04-18 - 'CanAutoFocus' => 'Puo\' Auto Focus', - 'CanAutoGain' => 'Puo\' Auto Gains', - 'CanAutoIris' => 'Puo\' Auto Iris', - 'CanAutoWhite' => 'Puo\' Auto bil bianco', - 'CanAutoZoom' => 'Puo\' Auto Zoom', - 'CanFocus' => 'Puo\' Fuoco', - 'CanFocusAbs' => 'Puo\' Fuoco Assoluto', - 'CanFocusCon' => 'Puo\' Fuoco Continuo ', - 'CanFocusRel' => 'Puo\' Fuoco Relativo', - 'CanGain' => 'Puo\' Gain ', - 'CanGainAbs' => 'Puo\' Gain Assoluto', - 'CanGainCon' => 'Puo\' Gain Continuo ', - 'CanGainRel' => 'Puo\' Gain Relativo', - 'CanIris' => 'Puo\' Iris', - 'CanIrisAbs' => 'Puo\' Iris Assoluto', - 'CanIrisCon' => 'Puo\' Iris Continuo ', - 'CanIrisRel' => 'Puo\' Iris Relativo', - 'CanMove' => 'Puo\' Mov.', - 'CanMoveAbs' => 'Puo\' Mov. Assoluto', - 'CanMoveCon' => 'Puo\' Mov. Continuo ', - 'CanMoveDiag' => 'Puo\' Mov. Diagonale ', - 'CanMoveMap' => 'Puo\' Mov Mappato', - 'CanMoveRel' => 'Puo\' Mov. Relativo', - 'CanPan' => 'Puo\' Pan' , - 'CanReset' => 'Puo\' Reset', - 'CanReboot' => 'Can Reboot', - 'CanSetPresets' => 'Puo\' impostare preset', - 'CanSleep' => 'Puo\' andare in sleep', - 'CanTilt' => 'Puo\' Tilt', - 'CanWake' => 'Puo\' essere riattivato', - 'CanWhite' => 'Puo\' bilanciare il bianco', - 'CanWhiteAbs' => 'Puo\' bilanciare il bianco assoluto', - 'CanWhiteBal' => 'Puo\' bilanciare il bianco', - 'CanWhiteCon' => 'Puo\' bilanciare il bianco Continuo', - 'CanWhiteRel' => 'Puo\' bilanciare il bianco Relativo', - 'CanZoom' => 'Puo\' Zoom', - 'CanZoomAbs' => 'Puo\' Zoom Assoluto', - 'CanZoomCon' => 'Puo\' Zoom Continuo', - 'CanZoomRel' => 'Puo\' Zoom Relativo', + 'CanAutoFocus' => 'Può Auto Focus', + 'CanAutoGain' => 'Può Auto Gains', + 'CanAutoIris' => 'Può Auto Iris', + 'CanAutoWhite' => 'Può Auto bil bianco', + 'CanAutoZoom' => 'Può Auto Zoom', + 'CanFocus' => 'Può Fuoco', + 'CanFocusAbs' => 'Può Fuoco Assoluto', + 'CanFocusCon' => 'Può Fuoco Continuo ', + 'CanFocusRel' => 'Può Fuoco Relativo', + 'CanGain' => 'Può Gain ', + 'CanGainAbs' => 'Può Gain Assoluto', + 'CanGainCon' => 'Può Gain Continuo ', + 'CanGainRel' => 'Può Gain Relativo', + 'CanIris' => 'Può Iris', + 'CanIrisAbs' => 'Può Iris Assoluto', + 'CanIrisCon' => 'Può Iris Continuo ', + 'CanIrisRel' => 'Può Iris Relativo', + 'CanMove' => 'Può Mov.', + 'CanMoveAbs' => 'Può Mov. Assoluto', + 'CanMoveCon' => 'Può Mov. Continuo ', + 'CanMoveDiag' => 'Può Mov. Diagonale ', + 'CanMoveMap' => 'Può Mov Mappato', + 'CanMoveRel' => 'Può Mov. Relativo', + 'CanPan' => 'Può Pan' , + 'CanReset' => 'Può Reset', + 'CanReboot' => 'Può Riavviare', + 'CanSetPresets' => 'Può impostare preset', + 'CanSleep' => 'Può andare in sleep', + 'CanTilt' => 'Può Tilt', + 'CanWake' => 'Può essere riattivato', + 'CanWhite' => 'Può bilanciare il bianco', + 'CanWhiteAbs' => 'Può bilanciare il bianco assoluto', + 'CanWhiteBal' => 'Può bilanciare il bianco', + 'CanWhiteCon' => 'Può bilanciare il bianco Continuo', + 'CanWhiteRel' => 'Può bilanciare il bianco Relativo', + 'CanZoom' => 'Può Zoom', + 'CanZoomAbs' => 'Può Zoom Assoluto', + 'CanZoomCon' => 'Può Zoom Continuo', + 'CanZoomRel' => 'Può Zoom Relativo', 'Cancel' => 'Annulla', 'CancelForcedAlarm' => 'Annulla Allarme Forzato', - 'CaptureHeight' => 'Altezza img catturata', - 'CaptureMethod' => 'Metodo di cattura', // Added - 2009-02-08 - 'CapturePalette' => 'Paletta img catturata', - 'CaptureResolution' => 'Risoluzione di cattura', // Added - 2015-04-18 - 'CaptureWidth' => 'Larghezza img Catturata', + 'CaptureHeight' => 'Altezza Cattura Immagine', + 'CaptureMethod' => 'Metodo Cattura Immagine', // Added - 2009-02-08 + 'CapturePalette' => 'Tavolozza Cattura Immagine', + 'CaptureResolution' => 'Risoluzione Cattura Immagine', // Added - 2015-04-18 + 'CaptureWidth' => 'Larghezza Cattura Immagine', 'Cause' => 'Causa', 'CheckMethod' => 'Metodo di Controllo Allarme', 'ChooseDetectedCamera' => 'Scegli telecamera rilevata', // Added - 2009-03-31 @@ -244,7 +244,7 @@ $SLANG = array( 'Colour' => 'Colori', 'Command' => 'Comando', 'Component' => 'Component', // Added - 2011-06-16 - 'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30 + 'ConcurrentFilter' => 'Esegui filtro contemporaneamente', // Added - 2018-08-30 'Config' => 'Configura', 'ConfiguredFor' => 'Configurato per', 'ConfirmDeleteEvents' => 'Sei sicuro di voler cancellare gli eventi selezionati', @@ -279,7 +279,7 @@ $SLANG = array( 'DeleteSavedFilter' => 'Elimina il filtro salvato', 'Description' => 'Descrizione', 'DetectedCameras' => 'Telecamere Rilevate', // Added - 2009-03-31 - 'DetectedProfiles' => 'Detected Profiles', // Added - 2015-04-18 + 'DetectedProfiles' => 'Profili Rilevati', // Added - 2015-04-18 'Device' => 'Periferica', // Added - 2009-02-08 'DeviceChannel' => 'Canale Periferica', 'DeviceFormat' => 'Formato', @@ -287,14 +287,14 @@ $SLANG = array( 'DevicePath' => 'Percorso Dispositivo', 'Devices' => 'Dispositivi', 'Dimensions' => 'Dimensioni', - 'DisableAlarms' => 'Disabil Allarme', + 'DisableAlarms' => 'Disabilita Allarme', 'Disk' => 'Utilizzo Disco', - 'Display' => 'Display', // Added - 2011-01-30 - 'Displaying' => 'Displaying', // Added - 2011-06-16 - 'DoNativeMotionDetection'=> 'Do Native Motion Detection', + 'Display' => 'Mostra', // Added - 2011-01-30 + 'Displaying' => 'Visualizzazione', // Added - 2011-06-16 + 'DoNativeMotionDetection'=> 'Attiva Motion Detection Nativo', 'Donate' => 'Donate,per favore', 'DonateAlready' => 'No, ho gia donato... ', - 'DonateEnticement' => 'Stai usando ZoneMinder da un po\' di tempo e spero che tu lo stia trovando utile per la sicurezza di casa tua o del tuo posto di lavoro..Anche se ZoneMinder e\' distribuito liberamente come software libero,costa soldi sia svilupparlo che supportarlo. Se preferisci che questo software continui ad avere supporto e sviluppo in futuro allora considera l\idea di fare una piccola donazione. Donare e\' ovviamente opzionale, ma apprezzato e puoi donare quanto vuoi,quel poco o tanto che tu desideri.

Se hai voglia per cortesia seleziona l\'opzione sotto o punta il tuo browser a https://zoneminder.com/donate/ .

Grazie per usare ZoneMinder e non dimenticare di visitare il forum in ZoneMinder.com se cerchi supporto o hai suggerimenti riguardo a come rendere migliore Zoneminder.', + 'DonateEnticement' => 'Stai usando ZoneMinder da un pò di tempo e spero che tu lo stia trovando utile per la sicurezza di casa tua o del tuo posto di lavoro..Anche se ZoneMinder e\' distribuito liberamente come software libero,costa soldi sia svilupparlo che supportarlo. Se preferisci che questo software continui ad avere supporto e sviluppo in futuro allora considera l\idea di fare una piccola donazione. Donare e\' ovviamente opzionale, ma apprezzato e puoi donare quanto vuoi,quel poco o tanto che tu desideri.

Se hai voglia per cortesia seleziona l\'opzione sotto o punta il tuo browser a https://zoneminder.com/donate/ .

Grazie per usare ZoneMinder e non dimenticare di visitare il forum in ZoneMinder.com se cerchi supporto o hai suggerimenti riguardo a come rendere migliore Zoneminder.', 'DonateRemindDay' => 'Non ancora, ricordamelo ancora tra 1 giorno', 'DonateRemindHour' => 'Non ancora, ricordamelo ancora tra 1 ora', 'DonateRemindMonth' => 'Non ancora, ricordamelo ancora tra 1 mese', @@ -325,24 +325,24 @@ $SLANG = array( 'Execute' => 'Esegui', 'Exif' => 'Includi dati EXIF nell\'immagine', // Added - 2018-08-30 'Export' => 'Esporta', - 'ExportDetails' => 'Esp. dettagli eventi', - 'ExportFailed' => 'Esp. Fallita ', - 'ExportFormat' => 'Formato File Esp. ', + 'ExportDetails' => 'Esporta dettagli eventi', + 'ExportFailed' => 'Esportazione Fallita ', + 'ExportFormat' => 'Formato File Esportazione', 'ExportFormatTar' => 'Tar', 'ExportFormatZip' => 'Zip', - 'ExportFrames' => 'Dettagli frame espo.', + 'ExportFrames' => 'Esporta dettagli immagini', 'ExportImageFiles' => 'Esporta le immagini', - 'ExportLog' => 'Export Log', // Added - 2011-06-17 - 'ExportMiscFiles' => 'Esporto Altri file (se presenti)', + 'ExportLog' => 'Esporta Log', // Added - 2011-06-17 + 'ExportMiscFiles' => 'Esporta Altri file (se presenti)', 'ExportOptions' => 'Opzioni Esportazione', - 'ExportSucceeded' => 'Export completata con successo', // Added - 2009-02-08 - 'ExportVideoFiles' => 'Esporto File Video (se presenti)', - 'Exporting' => 'In corso.', + 'ExportSucceeded' => 'Esportazione completata con successo', // Added - 2009-02-08 + 'ExportVideoFiles' => 'Esporta File Video (se presenti)', + 'Exporting' => 'In corso', 'FPS' => 'fps', 'FPSReportInterval' => 'Intervallo Report FPS', 'FTP' => 'FTP', 'Far' => 'Lontano', - 'FastForward' => 'Fast Forward', + 'FastForward' => 'Avanzamento veloce', 'Feed' => 'Feed', 'Ffmpeg' => 'Ffmpeg', // Added - 2009-02-08 'File' => 'File', @@ -351,7 +351,7 @@ $SLANG = array( 'FilterDeleteEvents' => 'Elimina gli eventi', 'FilterEmailEvents' => 'Invia dettagli via email', 'FilterExecuteEvents' => 'Esegui un comando', - 'FilterLog' => 'Filter log', // Added - 2015-04-18 + 'FilterLog' => 'Filtra log', // Added - 2015-04-18 'FilterMessageEvents' => 'Invia dettagli tramite messaggio', 'FilterMoveEvents' => 'Sposta tutti gli eventi', // Added - 2018-08-30 'FilterPx' => 'Px Filtro', @@ -388,19 +388,19 @@ $SLANG = array( 'Grey' => 'Grigio', 'Group' => 'Gruppo', 'Groups' => 'Gruppi', - 'HasFocusSpeed' => 'Ha velocita\' di focus', - 'HasGainSpeed' => 'Ha velocita\' di guadagno', + 'HasFocusSpeed' => 'Ha velocità di focus', + 'HasGainSpeed' => 'Ha velocità di guadagno', 'HasHomePreset' => 'Ha posizioni di present', - 'HasIrisSpeed' => 'Ha velocota\' di iris', - 'HasPanSpeed' => 'Ha velocita\' di Pan', + 'HasIrisSpeed' => 'Ha velocotà di iris', + 'HasPanSpeed' => 'Ha velocità di Pan', 'HasPresets' => 'Ha preset', - 'HasTiltSpeed' => 'Ha velocita\' di Tilt', + 'HasTiltSpeed' => 'Ha velocità di Tilt', 'HasTurboPan' => 'Ha il Turbo Pan', 'HasTurboTilt' => 'Ha il Turbo Tilt', - 'HasWhiteSpeed' => 'Ha velocita\' di bilanciamento del bianco', - 'HasZoomSpeed' => 'Ha velocita\' di zoom', + 'HasWhiteSpeed' => 'Ha velocità di bilanciamento del bianco', + 'HasZoomSpeed' => 'Ha velocità di zoom', 'High' => 'Alta', - 'HighBW' => 'Banda Alta', + 'HighBW' => 'Banda Alta', 'Home' => 'Home', 'Hostname' => 'Nome Host', // Added - 2018-08-30 'Hour' => 'Ora', @@ -445,31 +445,31 @@ $SLANG = array( 'Mark' => 'Seleziona', 'Max' => 'Massima', 'MaxBandwidth' => 'Banda Massima', - 'MaxBrScore' => 'Punteggio
Massimo', + 'MaxBrScore' => 'Punteggio Massimo', 'MaxFocusRange' => 'Massimo range del focus', - 'MaxFocusSpeed' => 'Massima velocita\' del focus', + 'MaxFocusSpeed' => 'Massima velocità del focus', 'MaxFocusStep' => 'Massimo step del focus', 'MaxGainRange' => 'Massimo range del guadagno', - 'MaxGainSpeed' => 'Massima velocita\' del guadagno', + 'MaxGainSpeed' => 'Massima velocità del guadagno', 'MaxGainStep' => 'Massimo step del guadagno', 'MaxIrisRange' => 'Massima range dell\'Iris', - 'MaxIrisSpeed' => 'Massima velocita\' dell\'Iris', + 'MaxIrisSpeed' => 'Massima velocità dell\'Iris', 'MaxIrisStep' => 'Massimo step dell\'Iris', 'MaxPanRange' => 'Massimo range del pan', - 'MaxPanSpeed' => 'Massima velocita\' del tilt', + 'MaxPanSpeed' => 'Massima velocità del tilt', 'MaxPanStep' => 'Massimo step del pan', 'MaxTiltRange' => 'Massimo range del tilt', - 'MaxTiltSpeed' => 'Massima velocita\' del tilt', + 'MaxTiltSpeed' => 'Massima velocità del tilt', 'MaxTiltStep' => 'Massimo passo del tilt', 'MaxWhiteRange' => 'Massimo range del bilanciamento del bianco', - 'MaxWhiteSpeed' => 'Massima velocita\' del bilanciamento del bianco', + 'MaxWhiteSpeed' => 'Massima velocità del bilanciamento del bianco', 'MaxWhiteStep' => 'Massimo Step del bilanciamento del bianco', 'MaxZoomRange' => 'Massimo range dello zoom', - 'MaxZoomSpeed' => 'Massima velocita\' dello zoom', + 'MaxZoomSpeed' => 'Massima velocità dello zoom', 'MaxZoomStep' => 'Massimo step dello zoom', 'MaximumFPS' => 'Massimi FPS', 'Medium' => 'Media', - 'MediumBW' => 'Banda Media', + 'MediumBW' => 'Larghezza Banda Media', 'Message' => 'Message', // Added - 2011-06-16 'MinAlarmAreaLtMax' => 'L\'area minima dell\'allarme deve essere minore di quella massima', 'MinAlarmAreaUnset' => 'Devi specificare il numero minimo di pixel per l\'allarme', @@ -482,47 +482,47 @@ $SLANG = array( 'MinFilterAreaUnset' => 'Devi specificare il numero minimo di pixel per il filtro', 'MinFilterLtMinAlarm' => 'L\'area minima di filtro deve essere minore o uguale dell\area minima di allarme', 'MinFocusRange' => 'Range minimo del Focus', - 'MinFocusSpeed' => 'Velocita\' minima del Focus', + 'MinFocusSpeed' => 'Velocità minima del Focus', 'MinFocusStep' => 'Minimo step del Focus', 'MinGainRange' => 'Minimo range del Guadagno', - 'MinGainSpeed' => 'Velocita\' minima del Guadagno', + 'MinGainSpeed' => 'Velocità minima del Guadagno', 'MinGainStep' => 'Step minimo del guadagno', 'MinIrisRange' => 'Range minimo dell\'Iris', - 'MinIrisSpeed' => 'Velocita\' minima dell\'Iris', + 'MinIrisSpeed' => 'Velocità minima dell\'Iris', 'MinIrisStep' => 'Step minimo dell\'Iris', 'MinPanRange' => 'Range minimo del pan', - 'MinPanSpeed' => 'Velocita\' minima del Pan', + 'MinPanSpeed' => 'Velocità minima del Pan', 'MinPanStep' => 'Step minimo del Pan', 'MinPixelThresLtMax' => 'I pixel minimi della soglia devono essere minori dei pixel massimi della soglia', 'MinPixelThresUnset' => 'Devi specificare una soglia minima di pixel', // Added - 2009-02-08 'MinTiltRange' => 'Range minimo del Tilt', - 'MinTiltSpeed' => 'Velocita\' minima del Tilt', + 'MinTiltSpeed' => 'Velocità minima del Tilt', 'MinTiltStep' => 'Step minimo del Tilt', 'MinWhiteRange' => 'Range minimo del bilanciamento del bianco', - 'MinWhiteSpeed' => 'Velocita\' minima del bialnciamento del bianco', + 'MinWhiteSpeed' => 'Velocità minima del bialnciamento del bianco', 'MinWhiteStep' => 'Minimo step del bilanciamento del bianco', 'MinZoomRange' => 'Range minimo dello zoom', - 'MinZoomSpeed' => 'Velocita\' minima dello zoom', + 'MinZoomSpeed' => 'Velocità minima dello zoom', 'MinZoomStep' => 'Step minimo dello zoom', 'Misc' => 'Altro', 'Mode' => 'Modalità', // Added - 2015-04-18 'Monitor' => 'Monitor', - 'MonitorIds' => 'Monitor Ids', + 'MonitorIds' => 'Monitor Ids', 'MonitorPreset' => 'Monitor Presenti', 'MonitorPresetIntro' => 'Selezionare un appropriato pre settaggio dalla lista riportata qui sotto.

Per favore notare che questo potrebbe sovrascrivere ogni valore che hai già configurato su questo monitor.

', 'MonitorProbe' => 'Prova Monitor', // Added - 2009-03-31 'MonitorProbeIntro' => 'The list below shows detected analog and network cameras and whether they are already being used or available for selection.

Select the desired entry from the list below.

Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', // Added - 2009-03-31 'Monitors' => 'Monitors', 'Montage' => 'Montaggio', - 'MontageReview' => 'Montage Review', // Added - 2018-08-30 + 'MontageReview' => 'Revisione del montaggio', // Added - 2018-08-30 'Month' => 'Mese', - 'More' => 'More', // Added - 2011-06-16 - 'MotionFrameSkip' => 'Motion Frame Skip', + 'More' => 'Più', // Added - 2011-06-16 + 'MotionFrameSkip' => 'Salta/scarta fotogramma', 'Move' => 'Sposta', - 'Mtg2widgrd' => '2-wide grid', // Added 2013.08.15. - 'Mtg3widgrd' => '3-wide grid', // Added 2013.08.15. - 'Mtg3widgrx' => '3-wide grid, scaled, enlarge on alarm', // Added 2013.08.15. - 'Mtg4widgrd' => '4-wide grid', // Added 2013.08.15. + 'Mtg2widgrd' => 'Griglia 2 colonne', // Added 2013.08.15. + 'Mtg3widgrd' => 'Griglia 3 colonne', // Added 2013.08.15. + 'Mtg3widgrx' => 'Griglia 3 colonne, scalata, ingrandita su allarme', // Added 2013.08.15. + 'Mtg4widgrd' => 'Griglia 4 colonne', // Added 2013.08.15. 'MtgDefault' => 'Predefinito', // Added 2013.08.15. 'MustBeGe' => 'deve essere superiore a', 'MustBeLe' => 'deve essere inferiore o pari a', @@ -609,7 +609,7 @@ $SLANG = array( 'ProfileProbeIntro' => 'L\'elenco seguente mostra i profili di streaming esistenti della telecamera selezionata.

Selezionare la voce desiderata dall\'elenco seguente.

Si noti che ZoneMinder non è in grado di configurare profili aggiuntivi e che la scelta di una telecamera qui può sovrascrivere qualsiasi valore già configurato per il monitor corrente.

', // Added - 2015-04-18 'Progress' => 'Progresso', // Added - 2015-04-18 'Protocol' => 'Protocollo', - 'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30 + 'RTSPDescribe' => 'Usa URL multimediale di risposta RTSP', // Added - 2018-08-30 'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30 'Rate' => 'Velocità', 'Real' => 'Reale', @@ -622,15 +622,15 @@ $SLANG = array( 'RemoteHostName' => 'Nome dell\'Host Remoto', 'RemoteHostPath' => 'Percorso dell\'Host Remoto', 'RemoteHostPort' => 'Porta dell\'Host Remoto', - 'RemoteHostSubPath' => 'SubPath host remoto', // Added - 2009-02-08 + 'RemoteHostSubPath' => 'Percorso secondario dell\'Host remoto', // Added - 2009-02-08 'RemoteImageColours' => 'Colori delle immagini Remote', 'RemoteMethod' => 'Metodo Remoto', // Added - 2009-02-08 'RemoteProtocol' => 'Protocollo Remoto', // Added - 2009-02-08 'Rename' => 'Rinomina', - 'Replay' => 'Replay', - 'ReplayAll' => 'All Events', - 'ReplayGapless' => 'Gapless Events', - 'ReplaySingle' => 'Single Event', + 'Replay' => 'Riproduci', + 'ReplayAll' => 'Tutti gli Eventi', + 'ReplayGapless' => 'Eventi continui', + 'ReplaySingle' => 'Evento singolo', 'ReportEventAudit' => 'Rapporto Eventi di controllo', // Added - 2018-08-30 'Reset' => 'Resetta', 'ResetEventCounts' => 'Resetta Contatore Eventi', @@ -644,7 +644,7 @@ $SLANG = array( 'RotateLeft' => 'Ruota a Sinista', 'RotateRight' => 'Ruota a Destra', 'RunLocalUpdate' => 'Eseguire zmupdate.pl per l\'aggiornamento', // Added - 2011-05-25 - 'RunMode' => 'Modalita\' funzionamento', + 'RunMode' => 'Modalità funzionamento', 'RunState' => 'Stato di funzionamento', 'Running' => 'Attivo', 'Save' => 'Salva', @@ -679,25 +679,25 @@ $SLANG = array( 'SourcePath' => 'Percorso della Sorgente', // Added - 2009-02-08 'SourceType' => 'Tipo Sorgente', 'Speed' => 'Velocita\'', - 'SpeedHigh' => 'Alta Velocita\'', - 'SpeedLow' => 'Bassa Velocita\'', - 'SpeedMedium' => 'Media Velocita\'', - 'SpeedTurbo' => 'Turbo Velocita\'', + 'SpeedHigh' => 'Alta Velocità', + 'SpeedLow' => 'Bassa Velocità', + 'SpeedMedium' => 'Media Velocità', + 'SpeedTurbo' => 'Turbo Velocità', 'Start' => 'Avvia', 'State' => 'Stato', 'Stats' => 'Statistiche', 'Status' => 'Stato', - 'StatusConnected' => 'Capturing', // Added - 2018-08-30 - 'StatusNotRunning' => 'Not Running', // Added - 2018-08-30 - 'StatusRunning' => 'Not Capturing', // Added - 2018-08-30 - 'StatusUnknown' => 'Unknown', // Added - 2018-08-30 + 'StatusConnected' => 'Registrazione in corso', // Added - 2018-08-30 + 'StatusNotRunning' => 'Non in esecuzione', // Added - 2018-08-30 + 'StatusRunning' => 'Registrazione in pausa', // Added - 2018-08-30 + 'StatusUnknown' => 'Sconosciuto', // Added - 2018-08-30 'Step' => 'Passo', 'StepBack' => 'Passo indietro', 'StepForward' => 'Passo avanti', - 'StepLarge' => 'Lungo passo', - 'StepMedium' => 'Medio passo', - 'StepNone' => 'No passo', - 'StepSmall' => 'Piccolo passo', + 'StepLarge' => 'Passo lungo', + 'StepMedium' => 'Passo medio', + 'StepNone' => 'Nessun passo', + 'StepSmall' => 'Passo piccolo', 'Stills' => 'Foto', 'Stop' => 'Stop', 'Stopped' => 'Inattivo', @@ -728,12 +728,12 @@ $SLANG = array( 'Today' => 'Oggi ', 'Tools' => 'Strumenti', 'Total' => 'Totale', // Added - 2011-06-16 - 'TotalBrScore' => 'Punteggio
Totale', + 'TotalBrScore' => 'Punteggio Totale', 'TrackDelay' => 'Track Delay', 'TrackMotion' => 'Track Motion', 'Triggers' => 'Triggers', - 'TurboPanSpeed' => 'Velocita\' Turbo Pan', - 'TurboTiltSpeed' => 'Velocita\' Turbo Tilt', + 'TurboPanSpeed' => 'Velocità Turbo Pan', + 'TurboTiltSpeed' => 'Velocità Turbo Tilt', 'Type' => 'Tipo', 'Unarchive' => 'Togli dall\'archivio', 'Undefined' => 'Non specificato', // Added - 2009-02-08 @@ -745,7 +745,7 @@ $SLANG = array( 'Updated' => 'Updated', // Added - 2011-06-16 'Upload' => 'Upload', // Added - 2011-08-23 'UseFilter' => 'Usa Filtro', - 'UseFilterExprsPost' => ' espressioni filtri', // This is used at the end of the phrase 'use N filter expressions' + 'UseFilterExprsPost' => ' espressioni filtri', // This is used at the end of the phrase 'use N filter expressions' 'UseFilterExprsPre' => 'Usa ', // This is used at the beginning of the phrase 'use N filter expressions' 'UsedPlugins' => 'Used Plugins', 'User' => 'Utente', @@ -767,7 +767,7 @@ $SLANG = array( 'VideoGenFiles' => 'File Video Esistenti', 'VideoGenNoFiles' => 'Non ho trovato file ', 'VideoGenParms' => 'Parametri Generazione Video', - 'VideoGenSucceeded' => 'Successo: Generato Video !', + 'VideoGenSucceeded' => 'Successo: Video Generato!', 'VideoSize' => 'Dimensioni Video', 'VideoWriter' => 'Scrittore video', // Added - 2018-08-30 'View' => 'Vedi', @@ -782,7 +782,7 @@ $SLANG = array( 'WebSiteUrl' => 'Website URL', // Added - 2018-08-30 'Week' => 'Settimana', 'White' => 'Bianco', - 'WhiteBalance' => 'Bil. Bianco ', + 'WhiteBalance' => 'Bilanciamento del Bianco', 'Wide' => 'Larghezza', 'X' => 'X', 'X10' => 'X10', @@ -795,7 +795,7 @@ $SLANG = array( 'Zone' => 'Zona', 'ZoneAlarmColour' => 'Colore Allarme (RGB)', 'ZoneArea' => 'Zone Area', - 'ZoneExtendAlarmFrames' => 'Extend Alarm Frame Count', + 'ZoneExtendAlarmFrames' => 'Estendi conteggio immagini allarme', 'ZoneFilterSize' => 'Larghezza/Altezza Filtro (pixels)', 'ZoneMinMaxAlarmArea' => 'Min/Max Area Allarmata', 'ZoneMinMaxBlobArea' => 'Min/Max Area di Blob', @@ -803,7 +803,7 @@ $SLANG = array( 'ZoneMinMaxFiltArea' => 'Min/Max Area Filtrata', 'ZoneMinMaxPixelThres' => 'Min/Max Soglia Pixel (0-255)', 'ZoneMinderLog' => 'ZoneMinder Log', // Added - 2011-06-17 - 'ZoneOverloadFrames' => 'Overload Frame Ignore Count', + 'ZoneOverloadFrames' => 'Sovraccarico - contatore immagini ignorate', 'Zones' => 'Zone', 'Zoom' => 'Zoom', 'ZoomIn' => 'Ingrandisci', From 6dbfd2219076afb377381babb75ac6ec39ba5066 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 13 Sep 2021 15:03:36 -0400 Subject: [PATCH 026/501] Add missing update_function_pointers so that we use SSE blend functions. Significantly reduces cpu use in motion detection. --- src/zm_image.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 09c05550e..2b7bc69e2 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -313,6 +313,7 @@ bool Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *t return false; } zm_dump_video_frame(temp_frame, "dest frame after convert"); + update_function_pointers(); return true; } // end Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame) @@ -687,6 +688,7 @@ void Image::AssignDirect( subpixelorder = p_subpixelorder; pixels = width * height; size = new_buffer_size; + update_function_pointers(); } // end void Image::AssignDirect void Image::Assign( @@ -788,6 +790,7 @@ void Image::Assign(const Image &image) { linesize = image.linesize; } + update_function_pointers(); if ( image.buffer != buffer ) (*fptr_imgbufcpy)(buffer, image.buffer, size); } From c4a49721d2f77128a3e3404a1a22fbabd55a08d1 Mon Sep 17 00:00:00 2001 From: Andrea Vezzali Date: Tue, 14 Sep 2021 15:16:25 +0200 Subject: [PATCH 027/501] Update it_it.php --- web/lang/it_it.php | 140 ++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/web/lang/it_it.php b/web/lang/it_it.php index b51b85300..f06b18cc4 100644 --- a/web/lang/it_it.php +++ b/web/lang/it_it.php @@ -84,7 +84,7 @@ $SLANG = array( 'AddNewControl' => 'Aggiungi nuovo Controllo', 'AddNewMonitor' => 'Aggiungi nuovo Monitor', 'AddNewServer' => 'Aggiungi nuovo Server', // Added - 2018-08-30 - 'AddNewStorage' => 'Aggiungi nuovo Storage', // Added - 2018-08-30 + 'AddNewStorage' => 'Aggiungi nuovo Archivio', // Added - 2018-08-30 'AddNewUser' => 'Aggiungi nuovo Utente', 'AddNewZone' => 'Aggiungi nuova Zona', 'Alarm' => 'Allarme', @@ -95,7 +95,7 @@ $SLANG = array( 'AlarmMaximumFPS' => 'FPS massimi durante l\'allarme', 'AlarmPx' => 'Pixel Allarme', 'AlarmRGBUnset' => 'Devi settare un colore RGB di allarme', - 'AlarmRefImageBlendPct'=> 'Alarm Reference Image Blend %ge', // Added - 2015-04-18 + 'AlarmRefImageBlendPct'=> 'Riferimento Allarme - Fusione Immagine %', // Added - 2015-04-18 'Alert' => 'Attenzione', 'All' => 'Tutto', 'AnalysisFPS' => 'Analisi FPS', // Added - 2015-07-22 @@ -103,14 +103,14 @@ $SLANG = array( 'Apply' => 'Applica', 'ApplyingStateChange' => 'Sto applicando le modifiche', 'ArchArchived' => 'Archiviato', - 'ArchUnarchived' => 'Non archiviato', + 'ArchUnarchived' => 'Non Archiviato', 'Archive' => 'Archivio', 'Archived' => 'Archiviato', 'Area' => 'Area', 'AreaUnits' => 'Area (px/%)', - 'AttrAlarmFrames' => 'Immagini in Allarme', + 'AttrAlarmFrames' => 'Immagini Allarme', 'AttrArchiveStatus' => 'Stato Archivio', - 'AttrAvgScore' => 'Punteggio medio', + 'AttrAvgScore' => 'Punteggio Medio', 'AttrCause' => 'Causa', 'AttrDiskBlocks' => 'Blocchi disco', 'AttrDiskPercent' => 'Percentuale disco', @@ -123,7 +123,7 @@ $SLANG = array( 'AttrFilterServer' => 'Filtro attivo su Server', // Added - 2018-08-30 'AttrFrames' => 'Immagini', 'AttrId' => 'Id', - 'AttrMaxScore' => 'Punteggio massimo', + 'AttrMaxScore' => 'Punteggio Massimo', 'AttrMonitorId' => 'Id Monitor', 'AttrMonitorName' => 'Nome Monitor', 'AttrMonitorServer' => 'Monitor attivo su Server', // Added - 2018-08-30 @@ -133,11 +133,11 @@ $SLANG = array( 'AttrStartDateTime' => 'Inizio - Data/orario', // Added - 2018-08-30 'AttrStartTime' => 'Inizio - Orario', // Added - 2018-08-30 'AttrStartWeekday' => 'Inizio - Giorno della settimana', // Added - 2018-08-30 - 'AttrStateId' => 'Stato esecuzione', // Added - 2018-08-30 - 'AttrStorageArea' => 'Area Salvataggio', // Added - 2018-08-30 - 'AttrStorageServer' => 'Server Salvataggio remoto', // Added - 2018-08-30 - 'AttrSystemLoad' => 'Carico di sistema', - 'AttrTotalScore' => 'Punteggio totale', + 'AttrStateId' => 'Stato Esecuzione', // Added - 2018-08-30 + 'AttrStorageArea' => 'Area Archiviazione', // Added - 2018-08-30 + 'AttrStorageServer' => 'Server Archiviazione remota', // Added - 2018-08-30 + 'AttrSystemLoad' => 'Carico Sistema', + 'AttrTotalScore' => 'Punteggio Totale', 'Auto' => 'Auto', 'AutoStopTimeout' => 'Auto Stop Timeout', 'Available' => 'Disponibile', // Added - 2009-03-31 @@ -181,7 +181,7 @@ $SLANG = array( 'BlobPx' => 'Blob Px', 'BlobSizes' => 'Dimensioni Blob', 'Blobs' => 'Blobs', - 'Brightness' => 'Luminosità;', + 'Brightness' => 'Luminosità', 'Buffer' => 'Buffer', // Added - 2015-04-18 'Buffers' => 'Buffers', 'CSSDescription' => 'Change the default css for this computer', // Added - 2015-04-18 @@ -213,7 +213,7 @@ $SLANG = array( 'CanReboot' => 'Può Riavviare', 'CanSetPresets' => 'Può impostare preset', 'CanSleep' => 'Può andare in sleep', - 'CanTilt' => 'Può Tilt', + 'CanTilt' => 'Può inclinare', 'CanWake' => 'Può essere riattivato', 'CanWhite' => 'Può bilanciare il bianco', 'CanWhiteAbs' => 'Può bilanciare il bianco assoluto', @@ -238,8 +238,8 @@ $SLANG = array( 'ChooseLogFormat' => 'Scegli un formato di registro', // Added - 2011-06-17 'ChooseLogSelection' => 'Scegli una selezione del registro', // Added - 2011-06-17 'ChoosePreset' => 'Scegli Preset', - 'Clear' => 'Clear', // Added - 2011-06-16 - 'CloneMonitor' => 'Clone', // Added - 2018-08-30 + 'Clear' => 'Pulisci', // Added - 2011-06-16 + 'CloneMonitor' => 'Clona', // Added - 2018-08-30 'Close' => 'Chiudi', 'Colour' => 'Colori', 'Command' => 'Comando', @@ -257,25 +257,25 @@ $SLANG = array( 'Contrast' => 'Contrasto', 'Control' => 'Controllo', 'ControlAddress' => 'Indirizzo di controllo', - 'ControlCap' => 'Capacita\' di controllo', - 'ControlCaps' => 'Capacita\' di controllo', + 'ControlCap' => 'Capacità di controllo', + 'ControlCaps' => 'Capacità di controllo', 'ControlDevice' => 'Dispositivo di controllo', 'ControlType' => 'Tipo Controllo', 'Controllable' => 'Controllabile', - 'Current' => 'Current', // Added - 2015-04-18 + 'Current' => 'Corrente', // Added - 2015-04-18 'Cycle' => 'Cicla', 'CycleWatch' => 'Vista Ciclica', - 'DateTime' => 'Date/Time', // Added - 2011-06-16 + 'DateTime' => 'Data/Orario', // Added - 2011-06-16 'Day' => 'Giorno', 'Debug' => 'Debug', - 'DefaultRate' => 'Default Rate', + 'DefaultRate' => 'Rateo predefinito', 'DefaultScale' => 'Scala di default', - 'DefaultView' => 'Visualizzazione di default', - 'Deinterlacing' => 'Deinterlacing', // Added - 2015-04-18 - 'Delay' => 'Delay', // Added - 2015-04-18 + 'DefaultView' => 'Visualizzazione predefinita', + 'Deinterlacing' => 'Deinterlacciamento', // Added - 2015-04-18 + 'Delay' => 'Ritardo', // Added - 2015-04-18 'Delete' => 'Elimina', - 'DeleteAndNext' => 'Elimina & Prossimo', - 'DeleteAndPrev' => 'Elimina & Precedente', + 'DeleteAndNext' => 'Elimina e Prossimo', + 'DeleteAndPrev' => 'Elimina e Precedente', 'DeleteSavedFilter' => 'Elimina il filtro salvato', 'Description' => 'Descrizione', 'DetectedCameras' => 'Telecamere Rilevate', // Added - 2009-03-31 @@ -298,7 +298,7 @@ $SLANG = array( 'DonateRemindDay' => 'Non ancora, ricordamelo ancora tra 1 giorno', 'DonateRemindHour' => 'Non ancora, ricordamelo ancora tra 1 ora', 'DonateRemindMonth' => 'Non ancora, ricordamelo ancora tra 1 mese', - 'DonateRemindNever' => 'No, io non voglio donare, non lo faro\' mai', + 'DonateRemindNever' => 'No, io non voglio donare, non lo farò mai', 'DonateRemindWeek' => 'Non ancora, ricordamelo ancora tra 1 settimana', 'DonateYes' => 'Si,mi piacerebbe donare qualcosa ora', 'Download' => 'Scarica', @@ -363,12 +363,12 @@ $SLANG = array( 'First' => 'Primo', 'FlippedHori' => 'ribaltato orizzontale', 'FlippedVert' => 'ribaltato verticale', - 'FnMocord' => 'Mocord', // Added 2013.08.16. - 'FnModect' => 'Modect', // Added 2013.08.16. - 'FnMonitor' => 'Monitor', // Added 2013.08.16. - 'FnNodect' => 'Nodect', // Added 2013.08.16. - 'FnNone' => 'None', // Added 2013.08.16. - 'FnRecord' => 'Record', // Added 2013.08.16. + 'FnMocord' => 'Mocord - Registrazione continua (con evidenziazione eventi)', // Added 2013.08.16. + 'FnModect' => 'Modect - MOtion DEteCTtion (registrazione su rilevamento movimento)', // Added 2013.08.16. + 'FnMonitor' => 'Monitor - Visualizza Live', // Added 2013.08.16. + 'FnNodect' => 'Nodect - No DEteCTtion (registrazione su evento esterno)', // Added 2013.08.16. + 'FnNone' => 'None - Nessuno (Monitor disabilitato)', // Added 2013.08.16. + 'FnRecord' => 'Record - Registrazione continua', // Added 2013.08.16. 'Focus' => 'Focus', 'ForceAlarm' => 'Forza Allarme', 'Format' => 'Formato', @@ -379,7 +379,7 @@ $SLANG = array( 'Frames' => 'Immagini', 'Func' => 'Funz', 'Function' => 'Funzione', - 'Gain' => 'Gain', + 'Gain' => 'Guadagno', 'General' => 'Generale', 'GenerateDownload' => 'Genera download', // Added - 2018-08-30 'GenerateVideo' => 'Genera video', @@ -391,7 +391,7 @@ $SLANG = array( 'HasFocusSpeed' => 'Ha velocità di focus', 'HasGainSpeed' => 'Ha velocità di guadagno', 'HasHomePreset' => 'Ha posizioni di present', - 'HasIrisSpeed' => 'Ha velocotà di iris', + 'HasIrisSpeed' => 'Ha velocità di iris', 'HasPanSpeed' => 'Ha velocità di Pan', 'HasPresets' => 'Ha preset', 'HasTiltSpeed' => 'Ha velocità di Tilt', @@ -414,7 +414,7 @@ $SLANG = array( 'In' => 'In', 'Include' => 'Includi', 'Inverted' => 'Invertito', - 'Iris' => 'Iris', + 'Iris' => 'Iride', 'KeyString' => 'Stringa Chiave', 'Label' => 'Etichetta', 'Language' => 'Linguaggio', @@ -438,7 +438,7 @@ $SLANG = array( 'Logout' => 'Logout', 'Logs' => 'Logs', // Added - 2011-06-17 'Low' => 'Bassa', - 'LowBW' => 'Banda Bassa', + 'LowBW' => 'Banda Bassa', 'Main' => 'Principale', 'Man' => 'Man', 'Manual' => 'Manuale', @@ -452,9 +452,9 @@ $SLANG = array( 'MaxGainRange' => 'Massimo range del guadagno', 'MaxGainSpeed' => 'Massima velocità del guadagno', 'MaxGainStep' => 'Massimo step del guadagno', - 'MaxIrisRange' => 'Massima range dell\'Iris', - 'MaxIrisSpeed' => 'Massima velocità dell\'Iris', - 'MaxIrisStep' => 'Massimo step dell\'Iris', + 'MaxIrisRange' => 'Massima range dell\'Iride', + 'MaxIrisSpeed' => 'Massima velocità dell\'Iride', + 'MaxIrisStep' => 'Massimo step dell\'Iride', 'MaxPanRange' => 'Massimo range del pan', 'MaxPanSpeed' => 'Massima velocità del tilt', 'MaxPanStep' => 'Massimo step del pan', @@ -470,7 +470,7 @@ $SLANG = array( 'MaximumFPS' => 'Massimi FPS', 'Medium' => 'Media', 'MediumBW' => 'Larghezza Banda Media', - 'Message' => 'Message', // Added - 2011-06-16 + 'Message' => 'Messaggio', // Added - 2011-06-16 'MinAlarmAreaLtMax' => 'L\'area minima dell\'allarme deve essere minore di quella massima', 'MinAlarmAreaUnset' => 'Devi specificare il numero minimo di pixel per l\'allarme', 'MinBlobAreaLtMax' => 'L\'area di blob minima deve essere minore dell\'area di blob massima', @@ -487,10 +487,10 @@ $SLANG = array( 'MinGainRange' => 'Minimo range del Guadagno', 'MinGainSpeed' => 'Velocità minima del Guadagno', 'MinGainStep' => 'Step minimo del guadagno', - 'MinIrisRange' => 'Range minimo dell\'Iris', - 'MinIrisSpeed' => 'Velocità minima dell\'Iris', - 'MinIrisStep' => 'Step minimo dell\'Iris', - 'MinPanRange' => 'Range minimo del pan', + 'MinIrisRange' => 'Range minimo dell\'Iride', + 'MinIrisSpeed' => 'Velocità minima dell\'Iride', + 'MinIrisStep' => 'Step minimo dell\'Iride', + 'MinPanRange' => 'Range minimo del Pan', 'MinPanSpeed' => 'Velocità minima del Pan', 'MinPanStep' => 'Step minimo del Pan', 'MinPixelThresLtMax' => 'I pixel minimi della soglia devono essere minori dei pixel massimi della soglia', @@ -550,7 +550,7 @@ $SLANG = array( 'NoneAvailable' => 'Nessuno disponibile', 'Normal' => 'Normale', 'Notes' => 'Note', - 'NumPresets' => 'Num Presets', + 'NumPresets' => 'Num redefiniti', 'Off' => 'Off', 'On' => 'On', 'OnvifCredentialsIntro'=> 'Fornire nome utente e password per la telecamera selezionata.
Se non è stato creato alcun utente per la videocamera, l\'utente qui indicato verrà creato con la password specificata.

', // Added - 2015-04-18 @@ -589,7 +589,7 @@ $SLANG = array( 'Paths' => 'Percorsi', 'Pause' => 'Pause', 'Phone' => 'Telefono', - 'PhoneBW' => 'Banda Tel', + 'PhoneBW' => 'Banda Tel', 'Pid' => 'PID', // Added - 2011-06-16 'PixelDiff' => 'Pixel Diff', 'Pixels' => 'pixels', @@ -598,11 +598,11 @@ $SLANG = array( 'PleaseWait' => 'Attendere prego', 'Plugins' => 'Plugins', 'Point' => 'Punto', - 'PostEventImageBuffer' => 'Buffer di immagini Dopo Evento', - 'PreEventImageBuffer' => 'Buffer di immagini Pre Evento', + 'PostEventImageBuffer' => 'Buffer immagini Dopo Evento', + 'PreEventImageBuffer' => 'Buffer immagini Pre Evento', 'PreserveAspect' => 'Preserve Aspect Ratio', 'Preset' => 'Preset', - 'Presets' => 'Presets', + 'Presets' => 'Predefiniti', 'Prev' => 'Prec', 'Probe' => 'Prova la telecamera', // Added - 2009-03-31 'ProfileProbe' => 'Prova lo stream', // Added - 2015-04-18 @@ -632,8 +632,8 @@ $SLANG = array( 'ReplayGapless' => 'Eventi continui', 'ReplaySingle' => 'Evento singolo', 'ReportEventAudit' => 'Rapporto Eventi di controllo', // Added - 2018-08-30 - 'Reset' => 'Resetta', - 'ResetEventCounts' => 'Resetta Contatore Eventi', + 'Reset' => 'Reset', + 'ResetEventCounts' => 'Reset Contatore Eventi', 'Restart' => 'Riavvia', 'Restarting' => 'Sto riavviando', 'RestrictedCameraIds' => 'Camera Ids Riservati', @@ -698,30 +698,30 @@ $SLANG = array( 'StepMedium' => 'Passo medio', 'StepNone' => 'Nessun passo', 'StepSmall' => 'Passo piccolo', - 'Stills' => 'Foto', + 'Stills' => 'Immagini fisse', 'Stop' => 'Stop', 'Stopped' => 'Inattivo', - 'StorageArea' => 'Area di salvataggio', // Added - 2018-08-30 - 'StorageScheme' => 'Scheme', // Added - 2018-08-30 - 'Stream' => 'Flusso', - 'StreamReplayBuffer' => 'Stream Replay Image Buffer', + 'StorageArea' => 'Area Archiviazione', // Added - 2018-08-30 + 'StorageScheme' => 'Schema Archiviazione', // Added - 2018-08-30 + 'Stream' => 'Stream', + 'StreamReplayBuffer' => 'Buffer immagini riproduzione stream', 'Submit' => 'Accetta', 'System' => 'Sistema', 'SystemLog' => 'Log di sistema', // Added - 2011-06-16 'TargetColorspace' => 'Target colorspace', // Added - 2015-04-18 'Tele' => 'Tele', 'Thumbnail' => 'Anteprima', - 'Tilt' => 'Tilt', + 'Tilt' => 'Tilt - Inclinazione', 'Time' => 'Ora', - 'TimeDelta' => 'Tempo di Delta', - 'TimeStamp' => 'Time Stamp', + 'TimeDelta' => 'Differenza orario', + 'TimeStamp' => 'Sovraimpressione data/orario', 'Timeline' => 'Linea Temporale', 'TimelineTip1' => 'Passa il mouse sul grafico per visualizzare un\'immagine dell\'istantanea e i dettagli dell\'evento.', // Added 2013.08.15. 'TimelineTip2' => 'Fai clic sulle sezioni colorate del grafico o sull\'immagine per visualizzare l\'evento.', // Added 2013.08.15. 'TimelineTip3' => 'Fare clic sullo sfondo per ingrandire un periodo di tempo più piccolo basato sul clic.', // Added 2013.08.15. 'TimelineTip4' => 'Utilizzare i controlli seguenti per ridurre o spostarsi avanti e indietro nell\'intervallo di tempo.', // Added 2013.08.15. - 'Timestamp' => 'Timestamp', - 'TimestampLabelFormat' => 'Formato etichetta timestamp', + 'Timestamp' => 'Sovraimpressione data/orario', + 'TimestampLabelFormat' => 'Formato etichetta Sovraimpressione data/orario', 'TimestampLabelSize' => 'Dimensione carattere', // Added - 2018-08-30 'TimestampLabelX' => 'coordinata X etichetta', 'TimestampLabelY' => 'coordinata Y etichetta', @@ -729,9 +729,9 @@ $SLANG = array( 'Tools' => 'Strumenti', 'Total' => 'Totale', // Added - 2011-06-16 'TotalBrScore' => 'Punteggio Totale', - 'TrackDelay' => 'Track Delay', - 'TrackMotion' => 'Track Motion', - 'Triggers' => 'Triggers', + 'TrackDelay' => 'Ritardo traccia', + 'TrackMotion' => 'Segui movimento', + 'Triggers' => 'Inneschi/Interruttori', 'TurboPanSpeed' => 'Velocità Turbo Pan', 'TurboTiltSpeed' => 'Velocità Turbo Tilt', 'Type' => 'Tipo', @@ -742,17 +742,17 @@ $SLANG = array( 'Update' => 'Aggiorna', 'UpdateAvailable' => 'Un aggiornamento di ZoneMinder è disponibilie.', 'UpdateNotNecessary' => 'Nessun aggiornamento necessario.', - 'Updated' => 'Updated', // Added - 2011-06-16 - 'Upload' => 'Upload', // Added - 2011-08-23 + 'Updated' => 'Aggiornato', // Added - 2011-06-16 + 'Upload' => 'Carica', // Added - 2011-08-23 'UseFilter' => 'Usa Filtro', 'UseFilterExprsPost' => ' espressioni filtri', // This is used at the end of the phrase 'use N filter expressions' 'UseFilterExprsPre' => 'Usa ', // This is used at the beginning of the phrase 'use N filter expressions' - 'UsedPlugins' => 'Used Plugins', + 'UsedPlugins' => 'Plugins in uso', 'User' => 'Utente', 'Username' => 'Nome Utente', 'Users' => 'Utenti', 'V4L' => 'V4L', // Added - 2015-04-18 - 'V4LCapturesPerFrame' => 'Captures Per Frame', // Added - 2015-04-18 + 'V4LCapturesPerFrame' => 'Rilevamenti per immagine', // Added - 2015-04-18 'V4LMultiBuffer' => 'Multi Buffering', // Added - 2015-04-18 'Value' => 'Valore', 'Version' => 'Versione', @@ -769,7 +769,7 @@ $SLANG = array( 'VideoGenParms' => 'Parametri Generazione Video', 'VideoGenSucceeded' => 'Successo: Video Generato!', 'VideoSize' => 'Dimensioni Video', - 'VideoWriter' => 'Scrittore video', // Added - 2018-08-30 + 'VideoWriter' => 'Scrittore Video', // Added - 2018-08-30 'View' => 'Vedi', 'ViewAll' => 'Vedi Tutto', 'ViewEvent' => 'Vedi Evento', @@ -789,7 +789,7 @@ $SLANG = array( 'X10ActivationString' => 'Stringa attivazione X10', 'X10InputAlarmString' => 'Stringa allarme input X10', 'X10OutputAlarmString' => 'Stringa allarme output X10', - 'Y' => 'Y', + 'Y' => 'S', 'Yes' => 'Si', 'YouNoPerms' => 'Non hai i permessi per accedere a questa risorsa.', 'Zone' => 'Zona', From 787b7f1874141ca675081db0fe6b945d88fe078a Mon Sep 17 00:00:00 2001 From: Andrea Vezzali Date: Tue, 14 Sep 2021 15:39:53 +0200 Subject: [PATCH 028/501] Update it_it.php --- web/lang/it_it.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/lang/it_it.php b/web/lang/it_it.php index f06b18cc4..4cdb24ee9 100644 --- a/web/lang/it_it.php +++ b/web/lang/it_it.php @@ -631,7 +631,7 @@ $SLANG = array( 'ReplayAll' => 'Tutti gli Eventi', 'ReplayGapless' => 'Eventi continui', 'ReplaySingle' => 'Evento singolo', - 'ReportEventAudit' => 'Rapporto Eventi di controllo', // Added - 2018-08-30 + 'ReportEventAudit' => 'Controllo Eventi', // Added - 2018-08-30 'Reset' => 'Reset', 'ResetEventCounts' => 'Reset Contatore Eventi', 'Restart' => 'Riavvia', @@ -671,14 +671,14 @@ $SLANG = array( 'Size' => 'grandezza', 'SkinDescription' => 'Cambia la skin predefinita per questo computer', // Added - 2011-01-30 'Sleep' => 'Sleep', - 'SortAsc' => 'Cresc', + 'SortAsc' => 'Crescente', 'SortBy' => 'Ordina per', - 'SortDesc' => 'Decr', + 'SortDesc' => 'Decrescente', 'Source' => 'Sorgente', 'SourceColours' => 'Colori della Sorgente', // Added - 2009-02-08 'SourcePath' => 'Percorso della Sorgente', // Added - 2009-02-08 'SourceType' => 'Tipo Sorgente', - 'Speed' => 'Velocita\'', + 'Speed' => 'Velocità', 'SpeedHigh' => 'Alta Velocità', 'SpeedLow' => 'Bassa Velocità', 'SpeedMedium' => 'Media Velocità', @@ -708,10 +708,10 @@ $SLANG = array( 'Submit' => 'Accetta', 'System' => 'Sistema', 'SystemLog' => 'Log di sistema', // Added - 2011-06-16 - 'TargetColorspace' => 'Target colorspace', // Added - 2015-04-18 + 'TargetColorspace' => 'Spazio dei colori obiettivo', // Added - 2015-04-18 'Tele' => 'Tele', 'Thumbnail' => 'Anteprima', - 'Tilt' => 'Tilt - Inclinazione', + 'Tilt' => 'Tilt (Inclinazione)', 'Time' => 'Ora', 'TimeDelta' => 'Differenza orario', 'TimeStamp' => 'Sovraimpressione data/orario', From c7aa41502aea2b6ab65fcaec5fb8c5ffa2981897 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 14 Sep 2021 10:26:07 -0400 Subject: [PATCH 029/501] Fix js error in montage review when using scaled mode. Fixes #3351 --- web/skins/classic/views/js/montagereview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index 4650e7e21..787a910d0 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -498,8 +498,8 @@ function redrawScreen() { drawGraph(); } + var monitors = $j('#monitors'); if ( fitMode == 1 ) { - var monitors = $j('#monitors'); var fps = $j('#fps'); var vh = window.innerHeight; var mh = (vh - monitors.position().top - fps.outerHeight()); From 120d9764bce6796e6e0e5bd979ae2104603a63df Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 14 Sep 2021 13:38:55 -0400 Subject: [PATCH 030/501] notify anyone waiting in packetqueue before waiting on a packet in motion detection. Should fix decode lockup --- src/zm_monitor.cpp | 1 + src/zm_packetqueue.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 44e032979..5a2c646ab 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1846,6 +1846,7 @@ bool Monitor::Analyse() { while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { // Need to wait for the decoder thread. Debug(1, "Waiting for decode"); + packetqueue.notify_all(); // decode might be waiting packet_lock->wait(); if (!snap->image and snap->decoded) { Debug(1, "No image but was decoded, giving up"); diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index 7e2f367fc..2fc29ea33 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -81,6 +81,7 @@ class PacketQueue { ); bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr &zm_packet); void unlock(ZMLockedPacket *lp); + void notify_all() { condition.notify_all(); }; }; #endif /* ZM_PACKETQUEUE_H */ From 8272411bb2320233035fff04be01a3e9b0e96e48 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 14 Sep 2021 15:47:35 -0400 Subject: [PATCH 031/501] Revert "use get_packet_and_increment_it instead of the two step to improve locking" This reverts commit a44bbf8e345d3eefea92e2b05bb1034ae1bd46b7. --- src/zm_monitor.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 5a2c646ab..eb65ac6f0 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1723,7 +1723,7 @@ bool Monitor::Analyse() { // if have event, send frames until we find a video packet, at which point do analysis. Adaptive skip should only affect which frames we do analysis on. // get_analysis_packet will lock the packet and may wait if analysis_it is at the end - ZMLockedPacket *packet_lock = packetqueue.get_packet_and_increment_it(analysis_it); + ZMLockedPacket *packet_lock = packetqueue.get_packet(analysis_it); if (!packet_lock) return false; std::shared_ptr snap = packet_lock->packet_; @@ -1731,11 +1731,13 @@ bool Monitor::Analyse() { if (snap->score != -1) { Error("skipping because score was %d", snap->score); packetqueue.unlock(packet_lock); + packetqueue.increment_it(analysis_it); return false; } // Store the it that points to our snap we will need it later - packetqueue_iterator snap_it = std::prev(*analysis_it); + packetqueue_iterator snap_it = *analysis_it; + packetqueue.increment_it(analysis_it); // signal is set by capture bool signal = shared_data->signal; From de299ab8829d28e8fbe0e1cca1419f439c1590ac Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 14 Sep 2021 16:21:32 -0400 Subject: [PATCH 032/501] More properly fix the threading lock. Instead of waiting on a packet, release it and wait on the packetqueue. --- src/zm_monitor.cpp | 30 +++++++++++++++++------------- src/zm_packetqueue.cpp | 9 +++++++++ src/zm_packetqueue.h | 3 ++- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index eb65ac6f0..38160e354 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1735,10 +1735,6 @@ bool Monitor::Analyse() { return false; } - // Store the it that points to our snap we will need it later - packetqueue_iterator snap_it = *analysis_it; - packetqueue.increment_it(analysis_it); - // signal is set by capture bool signal = shared_data->signal; bool signal_change = (signal != last_signal); @@ -1848,8 +1844,15 @@ bool Monitor::Analyse() { while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { // Need to wait for the decoder thread. Debug(1, "Waiting for decode"); + packetqueue.unlock(packet_lock); packetqueue.notify_all(); // decode might be waiting - packet_lock->wait(); + packetqueue.wait(); + + // Another thread may have moved our it. Unlikely but possible + packet_lock = packetqueue.get_packet(analysis_it); + if (!packet_lock) return false; + snap = packet_lock->packet_; + if (!snap->image and snap->decoded) { Debug(1, "No image but was decoded, giving up"); delete packet_lock; @@ -1947,14 +1950,14 @@ bool Monitor::Analyse() { // Must start on a keyframe so rewind. Only for passthrough though I guess. // FIXME this iterator is not protected from invalidation packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - snap_it, 0 /* pre_event_count */ + *analysis_it, 0 /* pre_event_count */ ); // This gets a lock on the starting packet ZMLockedPacket *starting_packet_lock = nullptr; std::shared_ptr starting_packet = nullptr; - if (*start_it != snap_it) { + if (*start_it != *analysis_it) { starting_packet_lock = packetqueue.get_packet(start_it); if (!starting_packet_lock) { Warning("Unable to get starting packet lock"); @@ -1968,12 +1971,12 @@ bool Monitor::Analyse() { event = new Event(this, starting_packet->timestamp, "Continuous", noteSetMap); // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (starting_packet and ((*start_it) != snap_it)) { + while (starting_packet and ((*start_it) != *analysis_it)) { event->AddPacket(starting_packet); // Have added the packet, don't want to unlock it until we have locked the next packetqueue.increment_it(start_it); - if ((*start_it) == snap_it) { + if ((*start_it) == *analysis_it) { if (starting_packet_lock) delete starting_packet_lock; break; } @@ -2051,12 +2054,12 @@ bool Monitor::Analyse() { if (!event) { packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - snap_it, + *analysis_it, (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) ); ZMLockedPacket *starting_packet_lock = nullptr; std::shared_ptr starting_packet = nullptr; - if (*start_it != snap_it) { + if (*start_it != *analysis_it) { starting_packet_lock = packetqueue.get_packet(start_it); if (!starting_packet_lock) return false; starting_packet = starting_packet_lock->packet_; @@ -2071,11 +2074,11 @@ bool Monitor::Analyse() { shared_data->state = state = ALARM; // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (*start_it != snap_it) { + while (*start_it != *analysis_it) { event->AddPacket(starting_packet); packetqueue.increment_it(start_it); - if ( (*start_it) == snap_it ) { + if ( (*start_it) == (*analysis_it) ) { if (starting_packet_lock) delete starting_packet_lock; break; } @@ -2262,6 +2265,7 @@ bool Monitor::Analyse() { if (function == MODECT or function == MOCORD) UpdateAnalysisFPS(); } + packetqueue.increment_it(analysis_it); packetqueue.unlock(packet_lock); shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 67ba35c0f..43ab20d1e 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -663,3 +663,12 @@ void PacketQueue::setPreEventVideoPackets(int p) { pre_event_video_packet_count = 1; // We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue } + +void PacketQueue::notify_all() { + condition.notify_all(); +}; + +void PacketQueue::wait() { + std::unique_lock lck(mutex); + condition.wait(lck); +} diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index 2fc29ea33..20cfdfe85 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -81,7 +81,8 @@ class PacketQueue { ); bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr &zm_packet); void unlock(ZMLockedPacket *lp); - void notify_all() { condition.notify_all(); }; + void notify_all(); + void wait(); }; #endif /* ZM_PACKETQUEUE_H */ From cddb9d88bf4eb59e70fb2e87ce18e093521f0457 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 14 Sep 2021 18:08:50 -0400 Subject: [PATCH 033/501] Add support for package version --- utils/packpack/startpackpack.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index e8a8a182d..2ea949078 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -223,7 +223,7 @@ setdebpkgname () { if [ "" == "$VERSION" ]; then export VERSION="${versionfile}~${thedate}.${numcommits}" fi - export RELEASE="${DIST}" + export RELEASE="${DIST}${PACKAGE_VERSION}" checkvars From 38cda24b5383b79b92ed659455ee9f33a5c6ce9b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 15 Sep 2021 12:53:41 -0400 Subject: [PATCH 034/501] wait won't wake up other threads, so notify first. Since we have the lock, this should be ok --- src/zm_packetqueue.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 43ab20d1e..cef7e8e2f 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -133,6 +133,7 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { if (max_video_packet_count > 0) { while (packet_counts[video_stream_id] > max_video_packet_count) { Error("Unable to free up older packets. Waiting."); + condition.notify_all(); condition.wait(lck); if (deleting or zm_terminate) return false; From c5f46231042195d25dad3c321bcb1a5579d77c39 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 15 Sep 2021 12:55:49 -0400 Subject: [PATCH 035/501] Don't crash when unable to create source. erase will call the desctructor. Fixes #3344 --- src/zm_rtsp_server.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/zm_rtsp_server.cpp b/src/zm_rtsp_server.cpp index 2276850cd..ea6f4e2b7 100644 --- a/src/zm_rtsp_server.cpp +++ b/src/zm_rtsp_server.cpp @@ -269,9 +269,8 @@ int main(int argc, char *argv[]) { Warning("Unknown format in %s", videoFifoPath.c_str()); } if (videoSource == nullptr) { - Error("Unable to create source"); + Error("Unable to create source for %s", videoFifoPath.c_str()); rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId()); - delete sessions[monitor->Id()]; sessions.erase(monitor->Id()); continue; } From e63be2b18ed3b362b5db198d7da16204f00a6cd7 Mon Sep 17 00:00:00 2001 From: Mike Dussault Date: Wed, 15 Sep 2021 10:13:02 -0700 Subject: [PATCH 036/501] Fixed a bug in Image::Buffer that would return the wrong location in the image if the image had > 1 channels (and if the request were for x > 0). --- src/zm_image.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_image.h b/src/zm_image.h index ccbae9f86..24626d789 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -181,7 +181,7 @@ class Image { /* Internal buffer should not be modified from functions outside of this class */ inline const uint8_t* Buffer() const { return buffer; } - inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize)+x]; } + inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize) + x*colours]; } /* Request writeable buffer */ uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); // Is only acceptable on a pre-allocated buffer From c233ec3b8c63f7c602395ccdb88bed0cc362b247 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 15 Sep 2021 13:38:00 -0400 Subject: [PATCH 037/501] Remove redundant notify_all, spelling mistake --- src/zm_monitor.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 38160e354..9a5144623 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1844,8 +1844,7 @@ bool Monitor::Analyse() { while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { // Need to wait for the decoder thread. Debug(1, "Waiting for decode"); - packetqueue.unlock(packet_lock); - packetqueue.notify_all(); // decode might be waiting + packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all packetqueue.wait(); // Another thread may have moved our it. Unlikely but possible @@ -3070,7 +3069,7 @@ int Monitor::PrimeCapture() { Debug(1, "Creating decoder thread"); decoder = zm::make_unique(this); } else { - Debug(1, "Restartg decoder thread"); + Debug(1, "Restarting decoder thread"); decoder->Start(); } } From 874552c06b2de7b5fd329d09a88bbd96febc3475 Mon Sep 17 00:00:00 2001 From: ColorfullyZhang <36832299+ColorfullyZhang@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:41:24 +0800 Subject: [PATCH 038/501] Set mysql character set to utf8 explicitly to support chinese characters (or other special characters). --- src/zm_db.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_db.cpp b/src/zm_db.cpp index 0d499edbd..c6cc452b9 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -102,6 +102,7 @@ bool zmDbConnect() { if ( mysql_query(&dbconn, "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") ) { Error("Can't set isolation level: %s", mysql_error(&dbconn)); } + mysql_set_character_set(&dbconn, "utf8"); zmDbConnected = true; return zmDbConnected; } From 9e4f203632bad8193f07bd9cc64834a00a9b684d Mon Sep 17 00:00:00 2001 From: ColorfullyZhang <36832299+ColorfullyZhang@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:24:37 +0800 Subject: [PATCH 039/501] Set character set as utf8 when connect to mysql to avoid mistakes when there are Chinese characters in storage path. --- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 18df301f3..35d2dc6fa 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -107,6 +107,7 @@ sub zmDbConnect { .$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '') , $ZoneMinder::Config::Config{ZM_DB_USER} , $ZoneMinder::Config::Config{ZM_DB_PASS} + , { mysql_enable_utf8 => 1, } ); }; if ( !$dbh or $@ ) { From 0bf417bae0736673ce987d2924a8f610b89b0eaf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 16 Sep 2021 10:03:21 -0400 Subject: [PATCH 040/501] Reduce logging level to debug dealing with index == -1, which is used in zmu to mean the last captured image. Fixes #3354 --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 9a5144623..723d4f7b5 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1153,7 +1153,7 @@ void Monitor::AddPrivacyBitmask() { int Monitor::GetImage(int32_t index, int scale) { if (index < 0 || index > image_buffer_count) { - Warning("Invalid index %d passed. image_buffer_count = %d", index, image_buffer_count); + Debug(1, "Invalid index %d passed. image_buffer_count = %d", index, image_buffer_count); index = shared_data->last_write_index; } if (!image_buffer.size() or static_cast(index) >= image_buffer.size()) { From e75d5a89e928c9f47a6e3ac17027498e5e0eedc8 Mon Sep 17 00:00:00 2001 From: Andrea Vezzali Date: Mon, 20 Sep 2021 14:02:35 +0200 Subject: [PATCH 041/501] Update italian (it_it) translation (#3357) --- web/lang/it_it.php | 105 +++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/web/lang/it_it.php b/web/lang/it_it.php index 4cdb24ee9..55fb3b34a 100644 --- a/web/lang/it_it.php +++ b/web/lang/it_it.php @@ -184,46 +184,46 @@ $SLANG = array( 'Brightness' => 'Luminosità', 'Buffer' => 'Buffer', // Added - 2015-04-18 'Buffers' => 'Buffers', - 'CSSDescription' => 'Change the default css for this computer', // Added - 2015-04-18 - 'CanAutoFocus' => 'Può Auto Focus', - 'CanAutoGain' => 'Può Auto Gains', - 'CanAutoIris' => 'Può Auto Iris', - 'CanAutoWhite' => 'Può Auto bil bianco', - 'CanAutoZoom' => 'Può Auto Zoom', - 'CanFocus' => 'Può Fuoco', - 'CanFocusAbs' => 'Può Fuoco Assoluto', - 'CanFocusCon' => 'Può Fuoco Continuo ', - 'CanFocusRel' => 'Può Fuoco Relativo', - 'CanGain' => 'Può Gain ', - 'CanGainAbs' => 'Può Gain Assoluto', - 'CanGainCon' => 'Può Gain Continuo ', - 'CanGainRel' => 'Può Gain Relativo', - 'CanIris' => 'Può Iris', - 'CanIrisAbs' => 'Può Iris Assoluto', - 'CanIrisCon' => 'Può Iris Continuo ', - 'CanIrisRel' => 'Può Iris Relativo', - 'CanMove' => 'Può Mov.', - 'CanMoveAbs' => 'Può Mov. Assoluto', - 'CanMoveCon' => 'Può Mov. Continuo ', - 'CanMoveDiag' => 'Può Mov. Diagonale ', - 'CanMoveMap' => 'Può Mov Mappato', - 'CanMoveRel' => 'Può Mov. Relativo', - 'CanPan' => 'Può Pan' , - 'CanReset' => 'Può Reset', - 'CanReboot' => 'Può Riavviare', - 'CanSetPresets' => 'Può impostare preset', - 'CanSleep' => 'Può andare in sleep', + 'CSSDescription' => 'Modificare il css predefinito per questo computer', // Added - 2015-04-18 + 'CanAutoFocus' => 'Può impostare Auto Focus', + 'CanAutoGain' => 'Può impostare Auto Gains', + 'CanAutoIris' => 'Può impostare Auto Iris', + 'CanAutoWhite' => 'Può impostare Auto bil bianco', + 'CanAutoZoom' => 'Può impostare Auto Zoom', + 'CanFocus' => 'Può impostare Fuoco', + 'CanFocusAbs' => 'Può impostare Fuoco Assoluto', + 'CanFocusCon' => 'Può impostare Fuoco Continuo', + 'CanFocusRel' => 'Può impostare Fuoco Relativo', + 'CanGain' => 'Può impostare Guadagno', + 'CanGainAbs' => 'Può impostare Guadagno Assoluto', + 'CanGainCon' => 'Può impostare Guadagno Continuo ', + 'CanGainRel' => 'Può impostare Guadagno Relativo', + 'CanIris' => 'Può impostare Iride', + 'CanIrisAbs' => 'Può impostare Iride Assoluto', + 'CanIrisCon' => 'Può impostare Iride Continuo', + 'CanIrisRel' => 'Può impostare Iride Relativo', + 'CanMove' => 'Può impostare Movimento', + 'CanMoveAbs' => 'Può impostare Movimento Assoluto', + 'CanMoveCon' => 'Può impostare Movimento Continuo', + 'CanMoveDiag' => 'Può impostare Movimento Diagonale', + 'CanMoveMap' => 'Può impostare Movimento Mappato', + 'CanMoveRel' => 'Può impostare Movimento Relativo', + 'CanPan' => 'Può impostare Panoramica' , + 'CanReset' => 'Può effettuare Reset', + 'CanReboot' => 'Può Riavviare', + 'CanSetPresets' => 'Può impostare Preset', + 'CanSleep' => 'Può sospendere', 'CanTilt' => 'Può inclinare', - 'CanWake' => 'Può essere riattivato', + 'CanWake' => 'Può riattivare', 'CanWhite' => 'Può bilanciare il bianco', 'CanWhiteAbs' => 'Può bilanciare il bianco assoluto', 'CanWhiteBal' => 'Può bilanciare il bianco', 'CanWhiteCon' => 'Può bilanciare il bianco Continuo', 'CanWhiteRel' => 'Può bilanciare il bianco Relativo', - 'CanZoom' => 'Può Zoom', - 'CanZoomAbs' => 'Può Zoom Assoluto', - 'CanZoomCon' => 'Può Zoom Continuo', - 'CanZoomRel' => 'Può Zoom Relativo', + 'CanZoom' => 'Può impostare Zoom', + 'CanZoomAbs' => 'Può impostare Zoom Assoluto', + 'CanZoomCon' => 'Può impostare Zoom Continuo', + 'CanZoomRel' => 'Può impostare Zoom Relativo', 'Cancel' => 'Annulla', 'CancelForcedAlarm' => 'Annulla Allarme Forzato', 'CaptureHeight' => 'Altezza Cattura Immagine', @@ -401,6 +401,7 @@ $SLANG = array( 'HasZoomSpeed' => 'Ha velocità di zoom', 'High' => 'Alta', 'HighBW' => 'Banda Alta', + 'Hight' => 'Altezza', 'Home' => 'Home', 'Hostname' => 'Nome Host', // Added - 2018-08-30 'Hour' => 'Ora', @@ -508,10 +509,10 @@ $SLANG = array( 'Mode' => 'Modalità', // Added - 2015-04-18 'Monitor' => 'Monitor', 'MonitorIds' => 'Monitor Ids', - 'MonitorPreset' => 'Monitor Presenti', - 'MonitorPresetIntro' => 'Selezionare un appropriato pre settaggio dalla lista riportata qui sotto.

Per favore notare che questo potrebbe sovrascrivere ogni valore che hai già configurato su questo monitor.

', + 'MonitorPreset' => 'Monitor Preset', + 'MonitorPresetIntro' => 'Selezionare un preset appropriato dalla lista riportata qui sotto.

Notare che questo potrebbe sovrascrivere ogni valore che hai già configurato su questo monitor.

', 'MonitorProbe' => 'Prova Monitor', // Added - 2009-03-31 - 'MonitorProbeIntro' => 'The list below shows detected analog and network cameras and whether they are already being used or available for selection.

Select the desired entry from the list below.

Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', // Added - 2009-03-31 + 'MonitorProbeIntro' => 'L\'elenco seguente mostra le telecamere analogiche e di rete rilevate e se sono già in uso o disponibili per la selezione.

Selezionare la voce desiderata dall\'elenco seguente.

Si noti che non tutte le telecamere possono essere rilevate e che la scelta di una telecamera qui potrebbe sovrascrivere tutti i valori già configurati per il monitor corrente.

', // Added - 2009-03-31 'Monitors' => 'Monitors', 'Montage' => 'Montaggio', 'MontageReview' => 'Revisione del montaggio', // Added - 2018-08-30 @@ -661,16 +662,17 @@ $SLANG = array( 'SelectMonitors' => 'Monitor Selezionati', 'SelfIntersecting' => 'I vertici del poligono non devono intersecarsi', 'Set' => 'Imposta', - 'SetNewBandwidth' => 'Imposta nuova Banda', - 'SetPreset' => 'Imposta Preset', + 'SetNewBandwidth' => 'Imposta Nuova Banda', + 'SetPreset' => 'Imposta Predefiniti', 'Settings' => 'Impostazioni', - 'ShowFilterWindow' => 'MostraFinestraFiltri', + 'ShowFilterWindow' => 'Mostra Finestra Filtri', 'ShowTimeline' => 'Mostra linea temporale', + 'Show Zones' => 'Visualizza Zone', 'SignalCheckColour' => 'Colore controllo segnale', 'SignalCheckPoints' => 'Punti di controllo segnale', // Added - 2018-08-30 'Size' => 'grandezza', 'SkinDescription' => 'Cambia la skin predefinita per questo computer', // Added - 2011-01-30 - 'Sleep' => 'Sleep', + 'Sleep' => 'Sospendi', 'SortAsc' => 'Crescente', 'SortBy' => 'Ordina per', 'SortDesc' => 'Decrescente', @@ -747,7 +749,7 @@ $SLANG = array( 'UseFilter' => 'Usa Filtro', 'UseFilterExprsPost' => ' espressioni filtri', // This is used at the end of the phrase 'use N filter expressions' 'UseFilterExprsPre' => 'Usa ', // This is used at the beginning of the phrase 'use N filter expressions' - 'UsedPlugins' => 'Plugins in uso', + 'UsedPlugins' => 'Plugins in uso', 'User' => 'Utente', 'Username' => 'Nome Utente', 'Users' => 'Utenti', @@ -784,6 +786,7 @@ $SLANG = array( 'White' => 'Bianco', 'WhiteBalance' => 'Bilanciamento del Bianco', 'Wide' => 'Larghezza', + 'Width' => 'Larghezza', 'X' => 'X', 'X10' => 'X10', 'X10ActivationString' => 'Stringa attivazione X10', @@ -937,17 +940,17 @@ function zmVlang( $langVarArray, $count ) // So for example, to override the help text for ZM_LANG_DEFAULT do $OLANG = array( 'OPTIONS_FFMPEG' => array( - 'Help' => "Parameters in this field are passed on to FFmpeg. Multiple parameters can be separated by ,~~ ". - "Examples (do not enter quotes)~~~~". - "\"allowed_media_types=video\" Set datatype to request fromcam (audio, video, data)~~~~". - "\"reorder_queue_size=nnn\" Set number of packets to buffer for handling of reordered packets~~~~". - "\"loglevel=debug\" Set verbosity of FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug)" + 'Help' => "I parametri in questo campo vengono passati a FFmpeg. Più parametri possono essere separati da ,~~ ". + "Esempi (non inserire virgolette)~~~~". + "\"allowed_media_types=video\" Imposta il tipo di dati da richiedere dalla telecamera (audio, video, data)~~~~". + "\"reorder_queue_size=nnn\" Imposta il numero di pacchetti nel buffer per la gestione dei pacchetti riordinati~~~~". + "\"loglevel=debug\" Imposta la verbosità di FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug)" ), 'OPTIONS_LIBVLC' => array( - 'Help' => "Parameters in this field are passed on to libVLC. Multiple parameters can be separated by ,~~ ". - "Examples (do not enter quotes)~~~~". - "\"--rtp-client-port=nnn\" Set local port to use for rtp data~~~~". - "\"--verbose=2\" Set verbosity of libVLC" + 'Help' => "I parametri in questo campo vengono passati a libVLC. Più parametri possono essere separati da ,~~ ". + "Esempi (non inserire virgolette)~~~~". + "\"--rtp-client-port=nnn\" Imposta la porta locale da utilizzare per i dati RTP~~~~". + "\"--verbose=2\" Imposta la verbosità di of libVLC" ), From d81be9701a64a61a603dc0be58ca60d080ad9584 Mon Sep 17 00:00:00 2001 From: 5472qaywsx <39193575+5472qaywsx@users.noreply.github.com> Date: Mon, 20 Sep 2021 14:04:07 +0200 Subject: [PATCH 042/501] Docs: Fix a typo (#3358) --- docs/installationguide/packpack.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installationguide/packpack.rst b/docs/installationguide/packpack.rst index fc1d6c077..64a6b4004 100644 --- a/docs/installationguide/packpack.rst +++ b/docs/installationguide/packpack.rst @@ -77,7 +77,7 @@ To start the build, simply execute the following command from the root folder of OS= DIST= utils/packpack/startpackpack.sh -Where is the name of the distro you wish to build on, such as fedora, and is release name or number of the distro you wish to build on. Redhat distros expect a number for while Debian and Ubuntu distros expect a name. For example: +Where is the name of the distro you wish to build on, such as fedora, and is the release name or number of the distro you wish to build on. Redhat distros expect a number for while Debian and Ubuntu distros expect a name. For example: :: From 2a0ddc6337c4b46ac859a0ffa6179e20d5b34dfc Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 20 Sep 2021 13:48:13 -0400 Subject: [PATCH 043/501] Add open collective username --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8804e639c..5994f0e6b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -2,7 +2,7 @@ github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: zoneminder # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username +open_collective: zoneminder # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry From 4bdf965dcb453273069dd0f4ce7a8233064f41f2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 20 Sep 2021 16:02:37 -0400 Subject: [PATCH 044/501] Set rows on email textarea --- web/skins/classic/views/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index efb3eed15..60cc585d4 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -493,7 +493,7 @@ if ( ZM_OPT_EMAIL ) {

- +

Date: Tue, 21 Sep 2021 12:59:42 -0400 Subject: [PATCH 045/501] Always include the download button so that we can assume that it exists in the js. So avoid console errors when no mp4. --- web/skins/classic/views/event.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index 81ff15075..a07c95042 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -149,13 +149,10 @@ if ( $Event->Id() and !file_exists($Event->Path()) ) -DefaultVideo() ) { -?> - - + DefaultVideo() ? '' : 'style="display:none;"' ?> +> From 92f6d3cbae0e41ffe4f093b593ce117b14720cab Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 21 Sep 2021 13:00:47 -0400 Subject: [PATCH 046/501] Restore the download button's behaviour. It is a simple link to the mp4, not an export. Also add a handler for the video.js rate control to sync up our non video.js rate dropdown and stored cookie. --- web/skins/classic/views/js/event.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index e2d3c9ce3..727976e76 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -891,6 +891,12 @@ function initPage() { vid.on('timeupdate', function() { $j('#progressValue').html(secsToTime(Math.floor(vid.currentTime()))); }); + vid.on('ratechange', function() { + rate = vid.playbackRate() * 100; + console.log("rate change " + rate); + $j('select[name="rate"]').val(rate); + setCookie('zmEventRate', rate, 3600); + }); // rate is in % so 100 would be 1x if (rate > 0) { @@ -1012,19 +1018,6 @@ function initPage() { window.location.assign('?view=export&eids[]='+eventData.Id); }); - // Manage the DOWNLOAD VIDEO button - bindButton('#downloadBtn', 'click', null, function onDownloadClick(evt) { - evt.preventDefault(); - $j.getJSON(thisUrl + '?request=modal&modal=download&eids[]='+eventData.Id) - .done(function(data) { - insertModalHtml('downloadModal', data.html); - $j('#downloadModal').modal('show'); - // Manage the GENERATE DOWNLOAD button - $j('#exportButton').click(exportEvent); - }) - .fail(logAjaxFail); - }); - // Manage the Event STATISTICS Button bindButton('#statsBtn', 'click', null, function onStatsClick(evt) { evt.preventDefault(); From 568be3fc549f2e8d258077982494c82c560bc135 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 21 Sep 2021 14:29:05 -0400 Subject: [PATCH 047/501] initialize video_first_pts and when setting it need to specify microseconds otherwise we get nanoseconds. White space. --- src/zm_ffmpeg_camera.cpp | 4 ++-- src/zm_packet.cpp | 2 +- src/zm_packet.h | 3 ++- src/zm_videostore.cpp | 8 +++++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 1484d5234..82471c3dc 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -231,8 +231,8 @@ int FfmpegCamera::Capture(std::shared_ptr &zm_packet) { zm_packet->set_packet(&packet); zm_packet->stream = stream; zm_packet->pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q); - if ( packet.pts != AV_NOPTS_VALUE ) { - if ( stream == mVideoStream ) { + if (packet.pts != AV_NOPTS_VALUE) { + if (stream == mVideoStream) { if (mFirstVideoPTS == AV_NOPTS_VALUE) mFirstVideoPTS = packet.pts; diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp index 3361667e0..55a78721e 100644 --- a/src/zm_packet.cpp +++ b/src/zm_packet.cpp @@ -83,7 +83,7 @@ ZMPacket::ZMPacket(ZMPacket &p) : av_init_packet(&packet); packet.size = 0; packet.data = nullptr; - if ( zm_av_packet_ref(&packet, &p.packet) < 0 ) { + if (zm_av_packet_ref(&packet, &p.packet) < 0) { Error("error refing packet"); } } diff --git a/src/zm_packet.h b/src/zm_packet.h index 66a6250ca..9cce4dd03 100644 --- a/src/zm_packet.h +++ b/src/zm_packet.h @@ -66,7 +66,7 @@ class ZMPacket { Image *set_image(Image *); int is_keyframe() { return keyframe; }; - int decode( AVCodecContext *ctx ); + int decode(AVCodecContext *ctx); explicit ZMPacket(Image *image, SystemTimePoint tv); explicit ZMPacket(ZMPacket &packet); ZMPacket(); @@ -88,6 +88,7 @@ class ZMLockedPacket { lck_(packet_->mutex_, std::defer_lock), locked(false) { } + ~ZMLockedPacket() { if (locked) unlock(); } diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 67f31cabc..1bd0724d5 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -93,6 +93,7 @@ VideoStore::VideoStore( converted_in_samples(nullptr), filename(filename_in), format(format_in), + video_first_pts(0), video_first_dts(0), audio_first_pts(0), audio_first_dts(0), @@ -990,23 +991,24 @@ int VideoStore::writeVideoFramePacket(const std::shared_ptr &zm_packet frame->pkt_duration = 0; if (!video_first_pts) { - video_first_pts = zm_packet->timestamp.time_since_epoch().count(); + video_first_pts = static_cast(std::chrono::duration_cast(zm_packet->timestamp.time_since_epoch()).count()); Debug(2, "No video_first_pts, set to (%" PRId64 ") secs(%.2f)", video_first_pts, FPSeconds(zm_packet->timestamp.time_since_epoch()).count()); frame->pts = 0; } else { + Microseconds useconds = std::chrono::duration_cast( zm_packet->timestamp - SystemTimePoint(Microseconds(video_first_pts))); frame->pts = av_rescale_q(useconds.count(), AV_TIME_BASE_Q, video_out_ctx->time_base); Debug(2, - "Setting pts for frame(%d) to (%" PRId64 ") from (start %" PRIu64 " - %" PRIu64 " - us(%" PRIi64 ") @ %d/%d", + "Setting pts for frame(%d) to (%" PRId64 ") from (zm_packet->timestamp(%" PRIi64 " - first %" PRId64 " us %" PRId64 " ) @ %d/%d", frame_count, frame->pts, + static_cast(std::chrono::duration_cast(zm_packet->timestamp.time_since_epoch()).count()), video_first_pts, static_cast(std::chrono::duration_cast(useconds).count()), - static_cast(std::chrono::duration_cast(zm_packet->timestamp.time_since_epoch()).count()), video_out_ctx->time_base.num, video_out_ctx->time_base.den); } From e98728c529a2f541b8fc59914c1b75ed7c8b1681 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 23 Sep 2021 16:04:36 -0400 Subject: [PATCH 048/501] Set new defaults for various settings --- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 0c8aec4a2..31d92bf11 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -308,7 +308,7 @@ our @options = ( }, { name => 'ZM_AUTH_HASH_IPS', - default => 'yes', + default => 'no', description => 'Include IP addresses in the authentication hash', help => q` When ZoneMinder is running in hashed authenticated mode it can @@ -346,7 +346,7 @@ our @options = ( }, { name => 'ZM_AUTH_HASH_LOGINS', - default => 'no', + default => 'yes', description => 'Allow login by authentication hash', help => q` The normal process for logging into ZoneMinder is via the login @@ -508,7 +508,7 @@ our @options = ( }, { name => 'ZM_SYSTEM_SHUTDOWN', - default => 'yes', + default => 'no', description => 'Allow Admin users to power off or restart the system from the ZoneMinder UI.', help => 'The system will need to have sudo installed and the following added to /etc/sudoers~~ ~~ @@ -1096,7 +1096,7 @@ our @options = ( }, { name => 'ZM_LOG_LEVEL_SYSLOG', - default => '0', + default => '-1', description => 'Save logging output to the system log', help => q` ZoneMinder logging is now more integrated between @@ -1604,7 +1604,7 @@ our @options = ( }, { name => 'ZM_WEB_EVENT_DISK_SPACE', - default => 'no', + default => 'yes', description => 'Whether to show disk space used by each event.', help => q` Adds another column to the listing of events @@ -1634,7 +1634,7 @@ our @options = ( }, { name => 'ZM_WEB_ID_ON_CONSOLE', - default => 'no', + default => 'yes', description => 'Should the console list the monitor id', help => q` Some find it useful to have the id always visible @@ -2270,7 +2270,7 @@ our @options = ( }, { name => 'ZM_MAX_RESTART_DELAY', - default => '600', + default => '30', description => 'Maximum delay (in seconds) for daemon restart attempts.', help => q` The zmdc (zm daemon control) process controls when processeses From b8022cdda266817191e01d768080455f02829ddb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 23 Sep 2021 16:06:18 -0400 Subject: [PATCH 049/501] More new defaults. The navbar refreshes every 60 secs so make full page refresh be 240sec. Ajax timeout needs to be 10 seconds. Large event listings can take longer than 3 --- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 31d92bf11..ff802dd15 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -2855,7 +2855,7 @@ our @options = ( }, { name => 'ZM_WEB_H_REFRESH_MAIN', - default => '60', + default => '240', introduction => q` There are now a number of options that are grouped into bandwidth categories, this allows you to configure the @@ -3113,7 +3113,7 @@ our @options = ( }, { name => 'ZM_WEB_H_AJAX_TIMEOUT', - default => '3000', + default => '10000', description => 'How long to wait for Ajax request responses (ms)', help => q` The newer versions of the live feed and event views use Ajax to From 655daf4fbeaac2821e23c0ca8b28073a77d1ab30 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 23 Sep 2021 16:39:28 -0400 Subject: [PATCH 050/501] Add mmal device/pix fmt type --- src/zm_ffmpeg_camera.cpp | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 82471c3dc..6415b2134 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -55,31 +55,24 @@ static enum AVPixelFormat get_hw_format( } #if !LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) { - enum AVPixelFormat fmt; switch (type) { case AV_HWDEVICE_TYPE_VAAPI: - fmt = AV_PIX_FMT_VAAPI; - break; + return AV_PIX_FMT_VAAPI; case AV_HWDEVICE_TYPE_DXVA2: - fmt = AV_PIX_FMT_DXVA2_VLD; - break; + return AV_PIX_FMT_DXVA2_VLD; case AV_HWDEVICE_TYPE_D3D11VA: - fmt = AV_PIX_FMT_D3D11; - break; + return = AV_PIX_FMT_D3D11; case AV_HWDEVICE_TYPE_VDPAU: - fmt = AV_PIX_FMT_VDPAU; - break; + return AV_PIX_FMT_VDPAU; case AV_HWDEVICE_TYPE_CUDA: - fmt = AV_PIX_FMT_CUDA; - break; + return AV_PIX_FMT_CUDA; + case AV_HWDEVICE_TYPE_MMAL: + return AV_PIX_FMT_MMAL; case AV_HWDEVICE_TYPE_VIDEOTOOLBOX: - fmt = AV_PIX_FMT_VIDEOTOOLBOX; - break; + return AV_PIX_FMT_VIDEOTOOLBOX; default: - fmt = AV_PIX_FMT_NONE; - break; + return AV_PIX_FMT_NONE; } - return fmt; } #endif #endif From 2e4bb73204b977123a72aef4784dfa890c1ea8a3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 14:27:10 -0400 Subject: [PATCH 051/501] remove useless commit. --- scripts/zmfilter.pl.in | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 64a20b4d5..1e62cb229 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -399,7 +399,6 @@ sub checkFilter { ) { $Event->save(); } - $ZoneMinder::Database::dbh->commit() if !$$filter{LockRows}; } # end if UpdateDiskSpace } # end foreach event ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows}; From cbc376bb5a9df2f3b1d6cf02d5452c73053d7c17 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 16:08:58 -0400 Subject: [PATCH 052/501] Add zm_rtsp_server to ignores --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e617d937c..3d61b4f54 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ src/zmc src/zmf src/zms src/zmu +src/zm_rtsp_server src/zoneminder-zmc.8 src/zoneminder-zmc.8.gz src/zoneminder-zmf.8 From 43dfeb5b847fa47fbe191de7e1a00b2fe16b6891 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 16:10:50 -0400 Subject: [PATCH 053/501] whitespace --- web/skins/classic/views/monitor.php | 32 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 327de5def..e7afc8cf6 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -21,44 +21,50 @@ require_once('includes/Server.php'); require_once('includes/Storage.php'); -if ( !canEdit('Monitors', empty($_REQUEST['mid'])?0:$_REQUEST['mid']) ) { +if (!canEdit('Monitors', empty($_REQUEST['mid'])?0:$_REQUEST['mid'])) { $view = 'error'; return; } $Server = null; -if ( defined('ZM_SERVER_ID') ) { +if (defined('ZM_SERVER_ID')) { $Server = dbFetchOne('SELECT * FROM Servers WHERE Id=?', NULL, array(ZM_SERVER_ID)); } -if ( !$Server ) { +if (!$Server) { $Server = array('Id' => ''); } $mid = null; $monitor = null; -if ( !empty($_REQUEST['mid']) ) { +if (!empty($_REQUEST['mid'])) { $mid = validInt($_REQUEST['mid']); $monitor = new ZM\Monitor($mid); - if ( $monitor and ZM_OPT_X10 ) - $x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId = ?', NULL, array($mid)); + if ($monitor->Id()) { + if (ZM_OPT_X10) { + $x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId = ?', NULL, array($mid)); + } + } else { + $monitor->Name(translate('Monitor').'-'.$mid); + $monitor->WebColour(random_colour()); + } } -if ( !$monitor ) { +if (!$monitor) { $monitor = new ZM\Monitor(); $monitor->Name(translate('Monitor').'-'.getTableAutoInc('Monitors')); $monitor->WebColour(random_colour()); } # end if $_REQUEST['mid'] -if ( isset($_REQUEST['dupId']) ) { +if (isset($_REQUEST['dupId'])) { $monitor = new ZM\Monitor($_REQUEST['dupId']); $monitor->GroupIds(); // have to load before we change the Id - if ( ZM_OPT_X10 ) + if (ZM_OPT_X10) $x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId = ?', NULL, array($_REQUEST['dupId'])); $clonedName = $monitor->Name(); $monitor->Name('Clone of '.$monitor->Name()); $monitor->Id($mid); } -if ( ZM_OPT_X10 && empty($x10Monitor) ) { +if (ZM_OPT_X10 && empty($x10Monitor)) { $x10Monitor = array( 'Activation' => '', 'AlarmInput' => '', @@ -69,14 +75,14 @@ if ( ZM_OPT_X10 && empty($x10Monitor) ) { function fourcc($a, $b, $c, $d) { return ord($a) | (ord($b) << 8) | (ord($c) << 16) | (ord($d) << 24); } -if ( isset($_REQUEST['newMonitor']) ) { +if (isset($_REQUEST['newMonitor'])) { # Update the monitor object with whatever has been set so far. $monitor->set($_REQUEST['newMonitor']); - if ( ZM_OPT_X10 ) + if (ZM_OPT_X10) $newX10Monitor = $_REQUEST['newX10Monitor']; } else { - if ( ZM_OPT_X10 ) + if (ZM_OPT_X10) $newX10Monitor = $x10Monitor; } From f40e2be28a4238ce104c155a4fd71389233d9670 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 25 Sep 2021 20:54:35 -0400 Subject: [PATCH 054/501] Add an input for an Id to assign to the new monitor. List 10 available Ids. --- web/skins/classic/views/monitor.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index e7afc8cf6..05462ace9 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -355,6 +355,7 @@ $codecs = array( 'MJPEG' => translate('MJPEG'), ); +$monitors = dbFetchAll('SELECT Id, Name FROM Monitors ORDER BY Name,Sequence ASC'); $controls = ZM\Control::find(null, array('order'=>'lower(Name)')); xhtmlHeaders(__FILE__, translate('Monitor').' - '.validHtmlStr($monitor->Name())); @@ -452,6 +453,21 @@ foreach ( $tabs as $name=>$value ) { switch ( $name ) { case 'general' : { + if (!$monitor->Id()) { + $monitor_ids = array(); + foreach ($monitors as $m) { $monitor_ids[] = $m['Id']; } + $available_monitor_ids = array_diff(range(min($monitor_ids),max($monitor_ids)), $monitor_ids); +?> + + +
+10 Available Ids: + + + +Id() ?> @@ -559,7 +575,6 @@ switch ( $name ) { Id() || ($monitor->Id()!= $linked_monitor['Id'])) && visibleMonitor($linked_monitor['Id']) ) { From ee609ad28bdeeec89f27b3c3c7d40f2fdfc040b3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 26 Sep 2021 14:46:56 -0400 Subject: [PATCH 055/501] Further develop behavour when typing in text input for new manufacturer or model. If it already exists, select it. --- web/skins/classic/views/js/monitor.js | 49 ++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index 44bcddb5b..88fae0a67 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -346,7 +346,7 @@ function getLocation() { } function populate_models(ManufacturerId) { - let dropdown = $j('[name="newMonitor[ModelId]"]'); + const dropdown = $j('[name="newMonitor[ModelId]"]'); if (!dropdown.length) { console.log("No element found for ModelId"); return; @@ -356,18 +356,22 @@ function populate_models(ManufacturerId) { dropdown.append(''); dropdown.prop('selectedIndex', 0); - // Populate dropdown with list of provinces - $j.getJSON(thisUrl+'?request=models&ManufacturerId='+ManufacturerId, function (data) { + if (ManufacturerId) { + // Populate dropdown with list of provinces + $j.getJSON(thisUrl+'?request=models&ManufacturerId='+ManufacturerId, function(data) { if (data.result == 'Ok') { - $j.each(data.models, function (key, entry) { - dropdown.append($j('').attr('value', entry.Id).text(entry.Name)); - }); + $j.each(data.models, function(key, entry) { + dropdown.append($j('').attr('value', entry.Id).text(entry.Name)); + }); dropdown.chosen("destroy"); dropdown.chosen(); } else { alert(data.result); } - }); + }); + } + dropdown.chosen("destroy"); + dropdown.chosen(); } function ManufacturerId_onchange(ManufacturerId_select) { @@ -377,7 +381,7 @@ function ManufacturerId_onchange(ManufacturerId_select) { } else { ManufacturerId_select.form.elements['newMonitor[Manufacturer]'].style['display'] = 'inline'; // Set models dropdown to Unknown, text area visible - let ModelId_dropdown = $j('[name="newMonitor[ModelId]"]'); + const ModelId_dropdown = $j('[name="newMonitor[ModelId]"]'); ModelId_dropdown.empty(); ModelId_dropdown.append(''); ModelId_dropdown.prop('selectedIndex', 0); @@ -385,6 +389,31 @@ function ManufacturerId_onchange(ManufacturerId_select) { } } +function select_by_value_case_insensitive(dropdown, value) { + const test_value = value.toLowerCase(); + for (i=1; i < dropdown.options.length; i++) { + if (dropdown.options[i].text.toLowerCase() == test_value) { + dropdown.selectedIndex = i; + dropdown.options[i].selected = true; + $j(dropdown).chosen("destroy").chosen(); + return; + } + } + if (dropdown.selectedIndex != 0) { + dropdown.selectedIndex = 0; + $j(dropdown).chosen("destroy").chosen(); + } +} + +function Manufacturer_onchange(input) { + if (!input.value) { + return; + } + ManufacturerId_select = input.form.elements['newMonitor[ManufacturerId]']; + select_by_value_case_insensitive(ManufacturerId_select, input.value); + populate_models(ManufacturerId_select.value); +} + function ModelId_onchange(ModelId_select) { if (parseInt(ModelId_select.value)) { $j('[name="newMonitor[Model]"]').hide(); @@ -393,4 +422,8 @@ function ModelId_onchange(ModelId_select) { } } +function Model_onchange(input) { + select_by_value_case_insensitive(input.form.elements['newMonitor[ModelId]'], input.value); +} + window.addEventListener('DOMContentLoaded', initPage); From 1c01936f754b7cd4c72b9b01bb5ac9abef3f998d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 26 Sep 2021 14:47:29 -0400 Subject: [PATCH 056/501] add oninput methods to text inputs for new Manufacturer/Model --- web/skins/classic/views/monitor.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 05462ace9..f47e29912 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -482,7 +482,7 @@ switch ( $name ) { translate('unknown')); + $manufacturers = array(''=>translate('Unknown')); foreach ( ZM\Manufacturer::find( null, array('order'=>'lower(Name)')) as $Manufacturer ) { $manufacturers[$Manufacturer->Id()] = $Manufacturer->Name(); } @@ -492,6 +492,7 @@ switch ( $name ) { ManufacturerId() ? ' style="display:none"' : '' ?> + data-on-input-this="Manufacturer_onchange" /> @@ -500,7 +501,7 @@ switch ( $name ) { translate('unknown')); + $models = array(''=>translate('Unknown')); foreach ( ZM\Model::find(array('ManufacturerId'=>$monitor->ManufacturerId()), array('order'=>'lower(Name)')) as $Model ) { $models[$Model->Id()] = $Model->Name(); } @@ -509,7 +510,9 @@ switch ( $name ) { ?> ModelId() ? ' style="display:none"':'' ?>/> + value="Model()->Name() ?>"ModelId() ? ' style="display:none"':'' ?> + data-on-input-this="Model_onchange" + /> From fe734d4e1ec745aab4640de2f8f7ac3a413804de Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 27 Sep 2021 10:56:50 -0400 Subject: [PATCH 057/501] Add Manufacturer and CameraModel hasOne relationships --- web/api/app/Model/Monitor.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/api/app/Model/Monitor.php b/web/api/app/Model/Monitor.php index de9a7685a..7a7d03588 100644 --- a/web/api/app/Model/Monitor.php +++ b/web/api/app/Model/Monitor.php @@ -139,6 +139,16 @@ class Monitor extends AppModel { 'className' => 'Event_Summary', 'foreignKey' => 'MonitorId', 'joinTable' => 'Event_Summaries', + ), + 'Manufacturer' => array( + 'className' => 'Manufacturer', + 'foreignKey' => 'Id', + 'joinTable' => 'Manufacturers', + ), + 'CameraModel' => array( + 'className' => 'CameraModel', + 'foreignKey' => 'Id', + 'joinTable' => 'Models', ) ); From ee65d3e3dcf6f6e1a2b8fe8810cc2b7fa9ebbad6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 27 Sep 2021 10:57:06 -0400 Subject: [PATCH 058/501] add cmaeramodels route --- web/api/app/Config/routes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/api/app/Config/routes.php b/web/api/app/Config/routes.php index 6b3314d09..7c6352602 100644 --- a/web/api/app/Config/routes.php +++ b/web/api/app/Config/routes.php @@ -32,6 +32,7 @@ Router::mapResources('logs'); Router::mapResources('manufacturers'); Router::mapResources('models'); + Router::mapResources('cameramodels'); Router::mapResources('monitors'); Router::mapResources('servers'); Router::mapResources('states'); From 9cf4e892545e1460f7e5b4bc37e802a5cdebd76f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 27 Sep 2021 11:03:16 -0400 Subject: [PATCH 059/501] add update to 1.37.3 --- db/zm_update-1.37.3.sql | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 db/zm_update-1.37.3.sql diff --git a/db/zm_update-1.37.3.sql b/db/zm_update-1.37.3.sql new file mode 100644 index 000000000..341c5e162 --- /dev/null +++ b/db/zm_update-1.37.3.sql @@ -0,0 +1,47 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'Column ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; From 5f27c124c371f58ae09d12c2dc44219c05d57b37 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Nov 2021 18:00:18 -0400 Subject: [PATCH 060/501] Ignore bootstrap-4.5.0.js --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 4682851df..91b0fd196 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,7 +4,7 @@ web/api/lib web/includes/csrf/ web/js/videojs.zoomrotate.js -web/skins/classic/js/bootstrap.js +web/skins/classic/js/bootstrap-4.5.0.js web/skins/classic/js/chosen web/skins/classic/js/dateTimePicker web/skins/classic/js/jquery-*.js From 9d71f1192a0dac30f273942bbd062b453349e478 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 29 Sep 2021 09:30:04 -0400 Subject: [PATCH 061/501] merge manufacturer update into existing zm_update-1.37.2 and then move it to .in --- db/manufacturers.sql | 19 ----------------- db/zm_update-1.37.3.sql | 47 ----------------------------------------- 2 files changed, 66 deletions(-) delete mode 100644 db/zm_update-1.37.3.sql diff --git a/db/manufacturers.sql b/db/manufacturers.sql index eaee2630c..be15f9c01 100644 --- a/db/manufacturers.sql +++ b/db/manufacturers.sql @@ -1,23 +1,4 @@ INSERT INTO Manufacturers VALUES (1, 'Acti'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A21'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A23'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A24'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A28'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A31'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A310'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A311'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A32'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A41'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A415'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A416'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A418'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A42'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A421'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A43'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A45'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A46'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A48'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A74'); INSERT INTO Manufacturers VALUES (2, 'Amcrest'); INSERT INTO Manufacturers VALUES (3, 'Airlink101'); INSERT INTO Manufacturers VALUES (4, 'Arecont Vision'); diff --git a/db/zm_update-1.37.3.sql b/db/zm_update-1.37.3.sql deleted file mode 100644 index 341c5e162..000000000 --- a/db/zm_update-1.37.3.sql +++ /dev/null @@ -1,47 +0,0 @@ -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ManufacturerId' - ) > 0, -"SELECT 'Column ManufacturerId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ManufacturerId' - ) > 0, -"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ModelId' - ) > 0, -"SELECT 'Column ModelId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ModelId' - ) > 0, -"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; From dc756ad670373e7cbe6dd3201fabbb44f1c922a3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 19:20:39 -0500 Subject: [PATCH 062/501] add back update for Manufacturers and Models --- db/zm_update-1.37.3.sql | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 db/zm_update-1.37.3.sql diff --git a/db/zm_update-1.37.3.sql b/db/zm_update-1.37.3.sql new file mode 100644 index 000000000..341c5e162 --- /dev/null +++ b/db/zm_update-1.37.3.sql @@ -0,0 +1,47 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'Column ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; From 8868a0fc41735174b00380c338109ea5d11e1d69 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Oct 2021 14:34:17 -0400 Subject: [PATCH 063/501] enforce default action --- utils/do_debian_package.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 2a4dd6989..6c8c06c00 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -230,12 +230,11 @@ rm .gitignore cd ../ -if [ !-e "$DIRECTORY.orig.tar.gz" ]; then -read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]" - if [[ $REPLY == [yY] ]]; then - - tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig -fi; +if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then + read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]" + if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then + tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig + fi; fi; IFS=',' ;for DISTRO in `echo "$DISTROS"`; do From caeaf91cad332d37d641e05e86a9ce6ef6462e50 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 6 Nov 2021 09:58:31 -0400 Subject: [PATCH 064/501] Only list available ids if there are some --- web/skins/classic/views/monitor.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index f47e29912..7d599904a 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -461,9 +461,12 @@ switch ( $name ) {
-10 Available Ids: - - + + Date: Sun, 7 Nov 2021 11:28:34 -0500 Subject: [PATCH 065/501] Pretty up the v4l field names --- web/ajax/modals/settings.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web/ajax/modals/settings.php b/web/ajax/modals/settings.php index de42d6c87..e0bdf679c 100644 --- a/web/ajax/modals/settings.php +++ b/web/ajax/modals/settings.php @@ -38,7 +38,7 @@ if ($zmuOutput) { $ctls = shell_exec('v4l2-ctl -d '.$monitor->Device().' --list-ctrls'); if (!$ctls) { -ZM\Warning("Guessing v4l ctrls. We need v4l2-ctl please install it"); +ZM\Warning('Guessing v4l ctrls. We need v4l2-ctl please install it'); $ctls = ' brightness 0x00980900 (int) : min=-10 max=10 step=1 default=0 value=8 contrast 0x00980901 (int) : min=0 max=20 step=1 default=10 value=12 @@ -83,10 +83,15 @@ foreach ($ctls as $line) { } } + $label = translate($setting_uc); + if ($label == $setting_uc) { + $label = ucwords(str_replace('_', ' ', $label)); + } + if ($setting == 'brightness' or $setting == 'colour' or $setting == 'contrast' or $setting == 'hue') { echo ' - '.translate($setting_uc).' + '.$label.' '.$min.''.$max.' '; @@ -94,7 +99,7 @@ foreach ($ctls as $line) { if ($type == '(bool)') { echo ' - '.translate($setting_uc).' + '.$label.' '.html_radio('new'.$setting_uc, array('0'=>translate('True'), '1', translate('False')), $value, array('disabled'=>'disabled')).' @@ -102,14 +107,14 @@ foreach ($ctls as $line) { } else if ($type == '(int)') { echo ' - '.translate($setting_uc).' + '.$label.' '; } else { echo ' - '.translate($setting_uc).' + '.$label.' '.$value.' '; From 01eac4a277f650057bcb0f54f46abc1c60738e8c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 09:48:57 -0500 Subject: [PATCH 066/501] Default to now instead of ... epoch? when endtime is null. Fixes video playing when event is incomplete --- src/zm_eventstream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 5e1ad605d..af9dd8639 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -141,7 +141,7 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0; event_data->frame_count = dbrow[2] == nullptr ? 0 : atoi(dbrow[2]); event_data->start_time = SystemTimePoint(Seconds(atoi(dbrow[3]))); - event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : SystemTimePoint(); + event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : std::chrono::system_clock::now(); event_data->duration = std::chrono::duration_cast(event_data->end_time - event_data->start_time); event_data->frames_duration = std::chrono::duration_cast(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0)); From 9036728bdc9b7ca8dddaa0638d2402f487034d5c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 10:48:50 -0500 Subject: [PATCH 067/501] Report error if sql fails. Add check for access to specific event. --- web/ajax/events.php | 63 +++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/web/ajax/events.php b/web/ajax/events.php index 4e364242a..090fe476e 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -67,20 +67,19 @@ if (isset($_REQUEST['sort'])) { // Offset specifies the starting row to return, used for pagination $offset = 0; -if ( isset($_REQUEST['offset']) ) { - if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { +if (isset($_REQUEST['offset'])) { + if ((!is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']))) { ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); } else { $offset = $_REQUEST['offset']; } } - // Limit specifies the number of rows to return // Set the default to 0 for events view, to prevent an issue with ALL pagination $limit = 0; -if ( isset($_REQUEST['limit']) ) { - if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { +if (isset($_REQUEST['limit'])) { + if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) { ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); } else { $limit = $_REQUEST['limit']; @@ -91,25 +90,24 @@ if ( isset($_REQUEST['limit']) ) { // MAIN LOOP // -switch ( $task ) { +switch ($task) { case 'archive' : - foreach ( $eids as $eid ) archiveRequest($task, $eid); + foreach ($eids as $eid) archiveRequest($task, $eid); break; case 'unarchive' : # The idea is that anyone can archive, but only people with Event Edit permission can unarchive.. - if ( !canEdit('Events') ) { + if (!canEdit('Events')) { ajaxError('Insufficient permissions for user '.$user['Username']); return; } - foreach ( $eids as $eid ) archiveRequest($task, $eid); + foreach ($eids as $eid) archiveRequest($task, $eid); break; case 'delete' : - if ( !canEdit('Events') ) { + if (!canEdit('Events')) { ajaxError('Insufficient permissions for user '.$user['Username']); return; } - - foreach ( $eids as $eid ) $data[] = deleteRequest($eid); + foreach ($eids as $eid) $data[] = deleteRequest($eid); break; case 'query' : $data = queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit); @@ -139,6 +137,8 @@ function deleteRequest($eid) { $message[] = array($eid=>'Event not found.'); } else if ( $event->Archived() ) { $message[] = array($eid=>'Event is archived, cannot delete it.'); + } else if (!$event->canEdit()) { + $message[] = array($eid=>'You do not have permission to delete event '.$event->Id()); } else { $event->delete(); } @@ -147,7 +147,6 @@ function deleteRequest($eid) { } function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) { - $data = array( 'total' => 0, 'totalNotFiltered' => 0, @@ -156,7 +155,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim ); $failed = !$filter->test_pre_sql_conditions(); - if ( $failed ) { + if ($failed) { ZM\Debug('Pre conditions failed, not doing sql'); return $data; } @@ -171,7 +170,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim // The names of columns shown in the event view that are NOT dB columns in the database $col_alt = array('Monitor', 'Storage'); - if ( !in_array($sort, array_merge($columns, $col_alt)) ) { + if (!in_array($sort, array_merge($columns, $col_alt))) { ZM\Error('Invalid sort field: ' . $sort); $sort = 'Id'; } @@ -186,7 +185,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $storage_areas = ZM\Storage::find(); $StorageById = array(); - foreach ( $storage_areas as $S ) { + foreach ($storage_areas as $S) { $StorageById[$S->Id()] = $S; } @@ -195,41 +194,43 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim ZM\Debug('Calling the following sql query: ' .$sql); $query = dbQuery($sql, $values); - if ( $query ) { - while ( $row = dbFetchNext($query) ) { - $event = new ZM\Event($row); - $event->remove_from_cache(); - if ( !$filter->test_post_sql_conditions($event) ) { - continue; - } - $event_ids[] = $event->Id(); - $unfiltered_rows[] = $row; - } # end foreach row + if (!$query) { + ajaxError(dbError($sql)); + return; } + while ($row = dbFetchNext($query)) { + $event = new ZM\Event($row); + $event->remove_from_cache(); + if (!$filter->test_post_sql_conditions($event)) { + continue; + } + $event_ids[] = $event->Id(); + $unfiltered_rows[] = $row; + } # end foreach row ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); $filtered_rows = null; - if ( count($advsearch) or $search != '' ) { + if (count($advsearch) or $search != '') { $search_filter = new ZM\Filter(); $search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids)); // There are two search bars in the log view, normal and advanced // Making an exuctive decision to ignore the normal search, when advanced search is in use // Alternatively we could try to do both - if ( count($advsearch) ) { + if (count($advsearch)) { $terms = array(); - foreach ( $advsearch as $col=>$text ) { + foreach ($advsearch as $col=>$text) { $terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text); } # end foreach col in advsearch $terms[0]['obr'] = 1; $terms[count($terms)-1]['cbr'] = 1; $search_filter->addTerms($terms); - } else if ( $search != '' ) { + } else if ($search != '') { $search = '%' .$search. '%'; $terms = array(); - foreach ( $columns as $col ) { + foreach ($columns as $col) { $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search); } $terms[0]['obr'] = 1; From 944c04e5b46cfa2c7bf883f69df025b78c17f0d0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 10:49:01 -0500 Subject: [PATCH 068/501] Whitespace --- web/includes/database.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/includes/database.php b/web/includes/database.php index 34ea384fa..01926a26e 100644 --- a/web/includes/database.php +++ b/web/includes/database.php @@ -112,7 +112,7 @@ function dbLog($sql, $update=false) { function dbError($sql) { global $dbConn; $error = $dbConn->errorInfo(); - if ( !$error[0] ) + if (!$error[0]) return ''; $message = "SQL-ERR '".implode("\n", $dbConn->errorInfo())."', statement was '".$sql."'"; @@ -130,17 +130,17 @@ function dbEscape( $string ) { function dbQuery($sql, $params=NULL, $debug = false) { global $dbConn; - if ( dbLog($sql, true) ) + if (dbLog($sql, true)) return; $result = NULL; try { - if ( isset($params) ) { - if ( ! $result = $dbConn->prepare($sql) ) { + if (isset($params)) { + if (!$result = $dbConn->prepare($sql)) { ZM\Error("SQL: Error preparing $sql: " . $pdo->errorInfo); return NULL; } - if ( ! $result->execute($params) ) { + if (!$result->execute($params)) { ZM\Error("SQL: Error executing $sql: " . print_r($result->errorInfo(), true)); return NULL; } From 71931f007a9a90f3ca66c9550939c0b8c691c656 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 10:50:39 -0500 Subject: [PATCH 069/501] alert error message is an error is returned instead of rows --- web/skins/classic/views/js/events.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/events.js b/web/skins/classic/views/js/events.js index 9f6d34f75..6139ba3e4 100644 --- a/web/skins/classic/views/js/events.js +++ b/web/skins/classic/views/js/events.js @@ -35,12 +35,16 @@ var params = // Called by bootstrap-table to retrieve zm event data function ajaxRequest(params) { - if ( params.data && params.data.filter ) { + if (params.data && params.data.filter) { params.data.advsearch = params.data.filter; delete params.data.filter; } $j.getJSON(thisUrl + '?view=request&request=events&task=query'+filterQuery, params.data) .done(function(data) { + if (data.result == 'Error') { + alert(data.message); + return; + } var rows = processRows(data.rows); // rearrange the result into what bootstrap-table expects params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows}); From 0573c09b5097ce18816b182eefd8bed9e1cf1923 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 13:51:32 -0500 Subject: [PATCH 070/501] Cleanup and spacing. Rework last_motion_score to be a bit more efficient and use fewer lines. Fix case where MOCORD was not ending/starting event on alarm. --- src/zm_monitor.cpp | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 679ea27b7..cdf544682 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1862,8 +1862,6 @@ bool Monitor::Analyse() { Debug(3, "signal and active and modect"); Event::StringSet zoneSet; - int motion_score = last_motion_score; - if (analysis_fps_limit) { double capture_fps = get_capture_fps(); motion_frame_skip = capture_fps / analysis_fps_limit; @@ -1880,7 +1878,7 @@ bool Monitor::Analyse() { } else { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. - motion_score = DetectMotion(*(snap->image), zoneSet); + int motion_score = DetectMotion(*(snap->image), zoneSet); snap->zone_stats.reserve(zones.size()); for (const Zone &zone : zones) { @@ -1892,21 +1890,20 @@ bool Monitor::Analyse() { Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", score, last_motion_score, motion_score); motion_frame_count += 1; - // Why are we updating the last_motion_score too? last_motion_score = motion_score; + if (motion_score) { + if (cause.length()) cause += ", "; + cause += MOTION_CAUSE; + noteSetMap[MOTION_CAUSE] = zoneSet; + } // end if motion_score } } else { Debug(1, "no image so skipping motion detection"); } // end if has image } else { - Debug(1, "Skipped motion detection last motion score was %d", motion_score); + Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); } - if (motion_score) { - score += motion_score; - if (cause.length()) cause += ", "; - cause += MOTION_CAUSE; - noteSetMap[MOTION_CAUSE] = zoneSet; - } // end if motion_score + score += last_motion_score; } else { Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d", Active(), enabled, shared_data->active, @@ -2007,7 +2004,7 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if (score and (function == MODECT or function == NODECT)) { + if (score and (function == MODECT or function == NODECT or function == MOCORD)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() @@ -2142,7 +2139,8 @@ bool Monitor::Analyse() { shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); } else { Debug(1, - "State %s because image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", + "State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", + state, State_Strings[state].c_str(), analysis_image_count, last_alarm_count, @@ -2161,12 +2159,10 @@ bool Monitor::Analyse() { // Generate analysis images if necessary if ((savejpegs > 1) and snap->image) { for (const Zone &zone : zones) { - if (zone.Alarmed()) { - if (zone.AlarmImage()) { + if (zone.Alarmed() and zone.AlarmImage()) { if (!snap->analysis_image) snap->analysis_image = new Image(*(snap->image)); snap->analysis_image->Overlay(*(zone.AlarmImage())); - } } // end if zone is alarmed } // end foreach zone } // end if savejpegs From bdf55f105e0dc1a18d0de38ba742ec4ff816447b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 13:54:33 -0500 Subject: [PATCH 071/501] Spacing --- src/zm_event.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 442033c50..d52f606a6 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -323,32 +323,32 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { bool update = false; //Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() ); - if ( newNoteSetMap.size() > 0 ) { - if ( noteSetMap.size() == 0 ) { + if (newNoteSetMap.size() > 0) { + if (noteSetMap.size() == 0) { noteSetMap = newNoteSetMap; update = true; } else { - for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); + for (StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); - ++newNoteSetMapIter ) { + ++newNoteSetMapIter) { const std::string &newNoteGroup = newNoteSetMapIter->first; const StringSet &newNoteSet = newNoteSetMapIter->second; //Info( "Got %d new strings", newNoteSet.size() ); - if ( newNoteSet.size() > 0 ) { + if (newNoteSet.size() > 0) { StringSetMap::iterator noteSetMapIter = noteSetMap.find(newNoteGroup); - if ( noteSetMapIter == noteSetMap.end() ) { - //Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() ); + if (noteSetMapIter == noteSetMap.end()) { + //Debug(3, "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size()); noteSetMap.insert(StringSetMap::value_type(newNoteGroup, newNoteSet)); update = true; } else { StringSet ¬eSet = noteSetMapIter->second; - //Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() ); - for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); + //Debug(3, "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size()); + for (StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); - ++newNoteSetIter ) { + ++newNoteSetIter) { const std::string &newNote = *newNoteSetIter; StringSet::iterator noteSetIter = noteSet.find(newNote); - if ( noteSetIter == noteSet.end() ) { + if (noteSetIter == noteSet.end()) { noteSet.insert(newNote); update = true; } From 82a26a1f83bd8c55b05e9fcf039d6d71e07f5299 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 13:59:05 -0500 Subject: [PATCH 072/501] use != Monitor instead of all the other cases --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index cdf544682..81eacc956 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2004,7 +2004,7 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if (score and (function == MODECT or function == NODECT or function == MOCORD)) { + if (score and (function != MONITOR)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() From 53c57478b812f555289c20065ac4c2b19a1c597c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 14:00:04 -0500 Subject: [PATCH 073/501] Ignore versioned bootstrap --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 4682851df..91b0fd196 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,7 +4,7 @@ web/api/lib web/includes/csrf/ web/js/videojs.zoomrotate.js -web/skins/classic/js/bootstrap.js +web/skins/classic/js/bootstrap-4.5.0.js web/skins/classic/js/chosen web/skins/classic/js/dateTimePicker web/skins/classic/js/jquery-*.js From c76e688f05a8c67914c18b44766dc6fd799437b6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 15:49:25 -0500 Subject: [PATCH 074/501] Don't exit(0) on QUIT. Instead set zm_terminate=true so that all the cleanup routines run. --- src/zm_monitorstream.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 5c32b8e05..9bc04a311 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -229,6 +229,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; case CMD_QUIT : Info("User initiated exit - CMD_QUIT"); + zm_terminate = true; break; case CMD_QUERY : Debug(1, "Got QUERY command, sending STATUS"); @@ -315,16 +316,6 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } } Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes); - - // quit after sending a status, if this was a quit request - if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) { - zm_terminate = true; - Debug(2, "Quitting"); - return; - } - - //Debug(2,"Updating framerate"); - //updateFrameRate(monitor->GetFPS()); } // end void MonitorStream::processCommand(const CmdMsg *msg) bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint timestamp) { From acff4fb9c0900fce29c14487a25384bec3e84b90 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 14:32:06 -0500 Subject: [PATCH 075/501] rough in fullscreen mode in watch view --- web/skins/classic/js/skin.js | 26 ++++++++++++++++++++++++++ web/skins/classic/views/js/watch.js | 5 +++++ web/skins/classic/views/watch.php | 3 +++ 3 files changed, 34 insertions(+) diff --git a/web/skins/classic/js/skin.js b/web/skins/classic/js/skin.js index c7e2afedf..6ede08479 100644 --- a/web/skins/classic/js/skin.js +++ b/web/skins/classic/js/skin.js @@ -947,3 +947,29 @@ function initThumbAnimation() { }); } } + +/* View in fullscreen */ +function openFullscreen(elem) { + if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.webkitRequestFullscreen) { + /* Safari */ + elem.webkitRequestFullscreen(); + } else if (elem.msRequestFullscreen) { + /* IE11 */ + elem.msRequestFullscreen(); + } +} + +/* Close fullscreen */ +function closeFullscreen() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + /* Safari */ + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + /* IE11 */ + document.msExitFullscreen(); + } +} diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index aacad5126..5ba4b407c 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -970,5 +970,10 @@ function initPage() { }); } // initPage +function watchFullscreen() { + const content = document.getElementById('content'); + openFullscreen(content); +} + // Kick everything off $j(document).ready(initPage); diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index e1e54c3c7..beab28ac1 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -142,6 +142,9 @@ if ( $streamMode == 'jpeg' ) { + From dc9f7b4d1d5960282f46c39c445b3a675753e240 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 16:12:39 -0500 Subject: [PATCH 076/501] Rough in fullscreen mode on montage --- web/skins/classic/views/js/montage.js | 5 +++++ web/skins/classic/views/montage.php | 3 +++ 2 files changed, 8 insertions(+) diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 5312928a6..75b3a74d2 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -317,5 +317,10 @@ function initPage() { } selectLayout('#zmMontageLayout'); } + +function watchFullscreen() { + const content = document.getElementById('content'); + openFullscreen(content); +} // Kick everything off $j(document).ready(initPage); diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index 3256cfcc8..b0b10f190 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -206,6 +206,9 @@ if ( canView('System') ) {   +
From 9d37fbcd8e7e66e76396200df8e757548aeed668 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 9 Nov 2021 17:01:04 -0500 Subject: [PATCH 077/501] Fix some build warnings on arm --- src/zm_fifo.cpp | 4 ++-- src/zm_monitor.cpp | 22 +++++++++++----------- src/zm_packetqueue.cpp | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/zm_fifo.cpp b/src/zm_fifo.cpp index 319c35c31..a999adc23 100644 --- a/src/zm_fifo.cpp +++ b/src/zm_fifo.cpp @@ -143,8 +143,8 @@ bool Fifo::writePacket(std::string filename, const ZMPacket &packet) { bool Fifo::write(uint8_t *data, size_t bytes, int64_t pts) { if (!(outfile or open())) return false; // Going to write a brief header - Debug(1, "Writing header ZM %lu %" PRId64, bytes, pts); - if ( fprintf(outfile, "ZM %lu %" PRId64 "\n", bytes, pts) < 0 ) { + Debug(1, "Writing header ZM %zu %" PRId64, bytes, pts); + if (fprintf(outfile, "ZM %zu %" PRId64 "\n", bytes, pts) < 0) { if (errno != EAGAIN) { Error("Problem during writing: %s", strerror(errno)); } else { diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 81eacc956..b6e69a19f 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -155,7 +155,7 @@ bool Monitor::MonitorLink::connect() { mem_size = sizeof(SharedData) + sizeof(TriggerData); - Debug(1, "link.mem.size=%jd", mem_size); + Debug(1, "link.mem.size=%jd", static_cast(mem_size)); #if ZM_MEM_MAPPED map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600); if (map_fd < 0) { @@ -182,14 +182,14 @@ bool Monitor::MonitorLink::connect() { disconnect(); return false; } else if (map_stat.st_size < mem_size) { - Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size); + Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast(mem_size)); disconnect(); return false; } mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); if (mem_ptr == MAP_FAILED) { - Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), mem_size, strerror(errno)); + Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast(mem_size), strerror(errno)); disconnect(); return false; } @@ -947,7 +947,7 @@ bool Monitor::connect() { map_fd = -1; return false; } else { - Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size); + Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast(mem_size)); close(map_fd); map_fd = -1; return false; @@ -959,18 +959,18 @@ bool Monitor::connect() { mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0); if (mem_ptr == MAP_FAILED) { if (errno == EAGAIN) { - Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), mem_size); + Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), static_cast(mem_size)); #endif mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); - Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), mem_size); + Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), static_cast(mem_size)); #ifdef MAP_LOCKED } else { - Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), mem_size, strerror(errno)); + Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), static_cast(mem_size), strerror(errno)); } } #endif if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) { - Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), mem_size, strerror(errno), errno); + Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), static_cast(mem_size), strerror(errno), errno); close(map_fd); map_fd = -1; mem_ptr = nullptr; @@ -2310,7 +2310,7 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { while ( 1 ) { dest_ptr = link_id_str; while ( *src_ptr >= '0' && *src_ptr <= '9' ) { - if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { + if ( (unsigned int)(dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { *dest_ptr++ = *src_ptr++; } else { break; @@ -2741,7 +2741,7 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const { const char *s_ptr = label_time_text; char *d_ptr = label_text; - while (*s_ptr && ((d_ptr - label_text) < (unsigned int) sizeof(label_text))) { + while (*s_ptr && ((unsigned int)(d_ptr - label_text) < (unsigned int) sizeof(label_text))) { if ( *s_ptr == config.timestamp_code_char[0] ) { bool found_macro = false; switch ( *(s_ptr+1) ) { @@ -2757,7 +2757,7 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const { typedef std::chrono::duration Centiseconds; Centiseconds centi_sec = std::chrono::duration_cast( ts_time.time_since_epoch() - std::chrono::duration_cast(ts_time.time_since_epoch())); - d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02ld", centi_sec.count()); + d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02lld", static_cast(centi_sec.count())); found_macro = true; break; } diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index ceef421db..509a25ee6 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -209,7 +209,7 @@ void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { --it; } } - Debug(1, "Tail count is %d, queue size is %lu", tail_count, pktQueue.size()); + Debug(1, "Tail count is %d, queue size is %zu", tail_count, pktQueue.size()); if (!keep_keyframes) { // If not doing passthrough, we don't care about starting with a keyframe so logic is simpler From 98e29e7ef6d68b01ac2ac81ab04fd6869dcb39d6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:18:58 -0500 Subject: [PATCH 078/501] implement UrlToZMS in Monitor --- web/includes/Monitor.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index bd9987b2c..6423af11f 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -499,6 +499,10 @@ class Monitor extends ZM_Object { return $this->Server()->UrlToIndex($port); } + public function UrlToZMS($port=null) { + return $this->Server()->UrlToZMS($port).'?mid='.$this->Id(); + } + public function sendControlCommand($command) { // command is generally a command option list like --command=blah but might be just the word quit From 8d0463bbff5e9cef3d954f30e40fab752d8a7020 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:20:19 -0500 Subject: [PATCH 079/501] Implement getElement, setScale in MonitorStream.js --- web/js/MonitorStream.js | 75 ++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index a10d90008..feb4b3611 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -3,8 +3,10 @@ function MonitorStream(monitorData) { this.id = monitorData.id; this.connKey = monitorData.connKey; this.url = monitorData.url; + this.url_to_zms = monitorData.url_to_zms; this.width = monitorData.width; this.height = monitorData.height; + this.scale = 100; this.status = null; this.alarmState = STATE_IDLE; this.lastAlarmState = STATE_IDLE; @@ -15,19 +17,68 @@ function MonitorStream(monitorData) { }; this.type = monitorData.type; this.refresh = monitorData.refresh; + this.element = null; + this.getElement = function() { + if (this.element) return this.element; + this.element = document.getElementById('liveStream'+this.id); + if (!this.element) { + console.error("No img for #liveStream"+this.id); + } + return this.element; + }; + + /* if the img element didn't have a src, this would fill it in, causing it to show. */ + this.show = function() { + const stream = this.getElement(); + if (!stream.src) { + stream.src = this.url_to_zms+"&mode=single&scale=100&connkey="+this.connKey; + } + }; + + this.setScale = function(newscale) { + const img = this.getElement(); + if (!img) return; + + this.scale = newscale; + + const oldSrc = img.getAttribute('src'); + let newSrc = ''; + + img.setAttribute('src', ''); + console.log("Scaling to: " + newscale); + + if (newscale == '0' || newscale == 'auto') { + let bottomElement = document.getElementById('replayStatus'); + if (!bottomElement) { + bottomElement = document.getElementById('monitorState'); + } + var newSize = scaleToFit(this.width, this.height, $j(img), $j(bottomElement)); + + console.log(newSize); + newWidth = newSize.width; + newHeight = newSize.height; + autoScale = parseInt(newSize.autoScale); + // This is so that we don't waste bandwidth and let the browser do all the scaling. + if (autoScale > 100) autoScale = 100; + if (autoScale) { + newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+autoScale); + } + } else { + newWidth = this.width * newscale / SCALE_BASE; + newHeight = this.height * newscale / SCALE_BASE; + img.width(newWidth); + img.height(newHeight); + if (newscale > 100) newscale = 100; + newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+newscale); + } + img.setAttribute('src', newSrc); + }; this.start = function(delay) { // Step 1 make sure we are streaming instead of a static image - var stream = $j('#liveStream'+this.id); - if (!stream.length) { - console.log('No live stream'); - return; - } - stream = stream[0]; - if ( !stream ) { - console.log('No live stream'); - return; - } - if ( !stream.src ) { + const stream = this.getElement(); + if (!stream) return; + + if (!stream.src) { // Website Monitors won't have an img tag console.log('No src for #liveStream'+this.id); console.log(stream); @@ -38,7 +89,7 @@ function MonitorStream(monitorData) { src += '&connkey='+this.connKey; } if ( stream.src != src ) { - console.log("Setting to streaming"); + console.log("Setting to streaming: " + src); stream.src = ''; stream.src = src; } From 6f3e22f2a0ed27140512d13d785bf7401d6729df Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:21:12 -0500 Subject: [PATCH 080/501] If no bottom element is specified, take the last child of content in scaleToFit --- web/skins/classic/js/skin.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/js/skin.js b/web/skins/classic/js/skin.js index 6ede08479..a12005e76 100644 --- a/web/skins/classic/js/skin.js +++ b/web/skins/classic/js/skin.js @@ -584,10 +584,21 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl) { $j(window).on('resize', endOfResize); //set delayed scaling when Scale to Fit is selected var ratio = baseWidth / baseHeight; var container = $j('#content'); + if (!container) { + console.error("No container found"); + return; + } + + if (!bottomEl || !bottomEl.length) { + bottomEl = $j(container[0].lastElementChild); + } + //console.log(bottomEl); var viewPort = $j(window); - // jquery does not provide a bottom offet, and offset dows not include margins. outerHeight true minus false gives total vertical margins. + // jquery does not provide a bottom offset, and offset does not include margins. outerHeight true minus false gives total vertical margins. var bottomLoc = bottomEl.offset().top + (bottomEl.outerHeight(true) - bottomEl.outerHeight()) + bottomEl.outerHeight(true); + //console.log("bottomLoc: " + bottomEl.offset().top + " + (" + bottomEl.outerHeight(true) + ' - ' + bottomEl.outerHeight() +') + '+bottomEl.outerHeight(true)); var newHeight = viewPort.height() - (bottomLoc - scaleEl.outerHeight(true)); + //console.log("newHeight = " + viewPort.height() +" - " + bottomLoc + ' - ' + scaleEl.outerHeight(true)); var newWidth = ratio * newHeight; if (newWidth > container.innerWidth()) { newWidth = container.innerWidth(); @@ -598,13 +609,15 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl) { return parseInt($j(this).val()); }).get(); scales.shift(); - var closest; + var closest = null; $j(scales).each(function() { //Set zms scale to nearest regular scale. Zoom does not like arbitrary scale values. if (closest == null || Math.abs(this - autoScale) < Math.abs(closest - autoScale)) { closest = this.valueOf(); } }); - autoScale = closest; + if (closest) { + autoScale = closest; + } return {width: Math.floor(newWidth), height: Math.floor(newHeight), autoScale: autoScale}; } From a7fd65d844134b2be36dfa303b606763c6b54800 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:21:38 -0500 Subject: [PATCH 081/501] Put SCALE_BASE in skin.js.php as it is used in many places. --- web/skins/classic/js/skin.js.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 802a21095..87e08310e 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -54,6 +54,7 @@ foreach ( $perms as $perm ) { ?> var ANIMATE_THUMBS = ; +var SCALE_BASE = ; var refreshParent = Date: Wed, 10 Nov 2021 14:22:05 -0500 Subject: [PATCH 082/501] Implement Exit Fullscreen using same button --- web/skins/classic/views/js/watch.js | 14 ++++++++++++-- web/skins/classic/views/js/watch.js.php | 6 +++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index 5ba4b407c..324e7f7e0 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -971,8 +971,18 @@ function initPage() { } // initPage function watchFullscreen() { - const content = document.getElementById('content'); - openFullscreen(content); + const btn = document.getElementById('fullscreenBtn'); + console.log(btn); + if (btn.firstElementChild.innerHTML=='fullscreen') { + const content = document.getElementById('content'); + openFullscreen(content); + btn.firstElementChild.innerHTML='fullscreen_exit'; + btn.setAttribute('title', translate["Exit Fullscreen"]); + } else { + closeFullscreen(); + btn.firstElementChild.innerHTML='fullscreen'; + btn.setAttribute('title', translate["Fullscreen"]); + } } // Kick everything off diff --git a/web/skins/classic/views/js/watch.js.php b/web/skins/classic/views/js/watch.js.php index 258a1da73..5b4568dfc 100644 --- a/web/skins/classic/views/js/watch.js.php +++ b/web/skins/classic/views/js/watch.js.php @@ -97,9 +97,13 @@ foreach( dbFetchAll( 'SELECT * FROM ControlPresets WHERE MonitorId = ?', NULL, a $labels[$row['Preset']] = $row['Label']; } -foreach ( $labels as $index=>$label ) { +foreach ($labels as $index=>$label) { ?> labels[] = ''; +var translate = { + "Fullscreen": "", + "Exit Fullscreen": "", +}; From 529e889d9912ca015277931e7988f32c39599061 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:23:36 -0500 Subject: [PATCH 083/501] remove extra , --- web/ajax/event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/ajax/event.php b/web/ajax/event.php index 906fe255a..a0b9cb57a 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -93,7 +93,7 @@ if ( canView('Events') or canView('Snapshots') ) { $exportFormat, $exportCompress, $exportStructure, - (!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport'), + (!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport') )) { ajaxResponse(array('exportFile'=>$exportFile)); } else { From ac03a88550357aa3ed56f2fa68bdda93f93ad2d7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:25:12 -0500 Subject: [PATCH 084/501] Include url_to_zms in monitorData --- web/skins/classic/views/js/zone.js.php | 1 + web/skins/classic/views/js/zones.js.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/zone.js.php b/web/skins/classic/views/js/zone.js.php index fc6616327..37eb6db82 100644 --- a/web/skins/classic/views/js/zone.js.php +++ b/web/skins/classic/views/js/zone.js.php @@ -66,6 +66,7 @@ monitorData[monitorData.length] = { 'width': ViewWidth() ?>, 'height':ViewHeight() ?>, 'url': 'UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', + 'url_to_zms': 'UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', 'type': 'Type() ?>', 'refresh': 'Refresh() ?>' }; diff --git a/web/skins/classic/views/js/zones.js.php b/web/skins/classic/views/js/zones.js.php index 180a19d81..338095552 100644 --- a/web/skins/classic/views/js/zones.js.php +++ b/web/skins/classic/views/js/zones.js.php @@ -9,6 +9,7 @@ monitorData[monitorData.length] = { 'width': ViewWidth() ?>, 'height':ViewHeight() ?>, 'url': 'UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', + 'url_to_zms': 'UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', 'type': 'Type() ?>', 'refresh': 'Refresh() ?>' }; @@ -24,7 +25,7 @@ var STATE_TAPE = ; var stateStrings = new Array(); stateStrings[STATE_IDLE] = ""; -stateStrings[STATE_PREALARM] = ""; +stateStrings[STATE_PREALARM] = ""; stateStrings[STATE_ALARM] = ""; stateStrings[STATE_ALERT] = ""; stateStrings[STATE_TAPE] = ""; From 795c5bb7d728dbe0f7ae68d346dd0f67827009ce Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:25:29 -0500 Subject: [PATCH 085/501] setScale to auto --- web/skins/classic/views/js/zone.js | 1 + web/skins/classic/views/js/zones.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/web/skins/classic/views/js/zone.js b/web/skins/classic/views/js/zone.js index 6282cbf8a..58c286b43 100644 --- a/web/skins/classic/views/js/zone.js +++ b/web/skins/classic/views/js/zone.js @@ -661,6 +661,7 @@ function initPage() { // Start the fps and status updates. give a random delay so that we don't assault the server var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); + monitors[i].setScale('auto'); monitors[i].start(delay); } diff --git a/web/skins/classic/views/js/zones.js b/web/skins/classic/views/js/zones.js index 0ad91942d..67dcb094a 100644 --- a/web/skins/classic/views/js/zones.js +++ b/web/skins/classic/views/js/zones.js @@ -12,6 +12,7 @@ function initPage() { // Start the fps and status updates. give a random delay so that we don't assault the server var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); + monitors[i].setScale('auto'); monitors[i].start(delay); } @@ -31,5 +32,12 @@ function initPage() { }); } +function streamCmdQuit() { + for ( var i = 0, length = monitorData.length; i < length; i++ ) { + monitors[i] = new MonitorStream(monitorData[i]); + monitors[i].stop(); + } +} + window.addEventListener('DOMContentLoaded', initPage); From 8872b8be6325a0f90ec5de3c93770fb93f37ac39 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 14:25:45 -0500 Subject: [PATCH 086/501] spacing --- web/skins/classic/views/zones.php | 67 +++++++++++++++---------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/web/skins/classic/views/zones.php b/web/skins/classic/views/zones.php index f457ad2fc..423326a98 100644 --- a/web/skins/classic/views/zones.php +++ b/web/skins/classic/views/zones.php @@ -80,15 +80,14 @@ xhtmlHeaders(__FILE__, translate('Zones')); Sorry, your browser does not support inline SVG @@ -96,37 +95,37 @@ xhtmlHeaders(__FILE__, translate('Zones'));  -  fps
-
- - - - - - - - - - - - - - - - - - - -
 / ViewWidth()*$monitor->ViewHeight()) ) ?> disabled="disabled"/>
-
- - -
-
-
+
+ + + + + + + + + + + + + + + + + + + +
 / ViewWidth()*$monitor->ViewHeight()) ) ?> disabled="disabled"/>
+
+ + +
+
+
Date: Wed, 10 Nov 2021 16:53:07 -0500 Subject: [PATCH 087/501] Use event->StartTime instead of GetVideoWriterStartTime. Add some parenthesis to make logic clearer and add more info to debug statements --- src/zm_monitor.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index b6e69a19f..d808ddba0 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1916,7 +1916,7 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length) + if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) || std::chrono::duration_cast(timestamp.time_since_epoch()) % section_length == Seconds(0))) { @@ -1925,8 +1925,8 @@ bool Monitor::Analyse() { image_count, event->Id(), static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); } // end if section_length @@ -2009,21 +2009,22 @@ bool Monitor::Analyse() { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() && !event->AlarmFrames() - && event_close_mode == CLOSE_ALARM - && timestamp - GetVideoWriterStartTime() >= min_section_length - && (!pre_event_count || Event::PreAlarmCount() >= alarm_frame_count - 1)) { + && (event_close_mode == CLOSE_ALARM) + && ((timestamp - event->StartTime()) >= min_section_length) + && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", name.c_str(), image_count, event->Id()); closeEvent(); } else if (event) { // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames Debug(3, - "pre_alarm_count in event %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min", - Event::PreAlarmCount(), + "pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d", + Event::PreAlarmCount(), pre_event_count, event->Frames(), event->AlarmFrames(), - static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), - static_cast(Seconds(min_section_length).count())); + static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(Seconds(min_section_length).count()), + (event_close_mode == CLOSE_ALARM)); } if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { // lets construct alarm cause. It will contain cause + names of zones alarmed @@ -2120,8 +2121,10 @@ bool Monitor::Analyse() { Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); shared_data->state = state = ALERT; } else if (state == ALERT) { - if (analysis_image_count - last_alarm_count > post_event_count - && timestamp - GetVideoWriterStartTime() >= min_section_length) { + if ( + ((analysis_image_count - last_alarm_count) > post_event_count) + && + ((timestamp - event->StartTime()) >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) @@ -2168,7 +2171,6 @@ bool Monitor::Analyse() { } // end if savejpegs // incremement pre alarm image count - //have_pre_alarmed_frames ++; Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr); } else if (state == ALARM) { for (const Zone &zone : zones) { @@ -2183,7 +2185,7 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length)) { + if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), From e35dc3902ef5f10268603412f190fe3adeb6172c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 16:53:29 -0500 Subject: [PATCH 088/501] Make state enum start at 0 as we are indexing into an array for StateStrings --- src/zm_monitor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 56785405f..5b9a2ce46 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -100,7 +100,7 @@ public: } Deinterlace; typedef enum { - UNKNOWN=-1, + UNKNOWN, IDLE, PREALARM, ALARM, From c84f42e2804519bc6e11a1bb58b579af9bc97f65 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Nov 2021 17:05:46 -0500 Subject: [PATCH 089/501] Set to never timeout while generating video --- web/skins/classic/views/js/video.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/skins/classic/views/js/video.js b/web/skins/classic/views/js/video.js index 970119f4f..c764f33b5 100644 --- a/web/skins/classic/views/js/video.js +++ b/web/skins/classic/views/js/video.js @@ -16,6 +16,9 @@ function generateVideoResponse( data, responseText ) { } function generateVideo() { + $j.ajaxSetup({ + timeout: 0 + }); var form = $j('#videoForm').serialize(); $j.getJSON(thisUrl + '?view=request&request=event&action=video', form) .done(generateVideoResponse) From d00430f799c450f7f213e9aff364b26c6a7ac6bb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Nov 2021 09:47:11 -0500 Subject: [PATCH 090/501] Add ModelId to MonitorPresets --- db/zm_create.sql.in | 1 + db/zm_update-1.37.3.sql | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index ecc2d477d..9a048886d 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -412,6 +412,7 @@ CREATE TABLE `Models` ( DROP TABLE IF EXISTS `MonitorPresets`; CREATE TABLE `MonitorPresets` ( `Id` int(10) unsigned NOT NULL auto_increment, + `ModelId` int unsigned, FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id), `Name` varchar(64) NOT NULL default '', `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local', `Device` tinytext, diff --git a/db/zm_update-1.37.3.sql b/db/zm_update-1.37.3.sql index 341c5e162..9002ce8e2 100644 --- a/db/zm_update-1.37.3.sql +++ b/db/zm_update-1.37.3.sql @@ -45,3 +45,26 @@ SET @s = (SELECT IF( PREPARE stmt FROM @s; EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'MonitorPresets' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in MonitorPresets'", +"ALTER TABLE `MonitorPresets` ADD `ModelId` int(10) unsigned AFTER `Id`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'MonitorPresets' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in MonitorPresets'", +"ALTER TABLE `MonitorPresets` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; From e63222d7337edfa8643e0e9064bbf83253e30266 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Nov 2021 16:30:03 -0500 Subject: [PATCH 091/501] Add IGNORE so it does UPSERT --- db/manufacturers.sql | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/db/manufacturers.sql b/db/manufacturers.sql index be15f9c01..3761f5cbe 100644 --- a/db/manufacturers.sql +++ b/db/manufacturers.sql @@ -1,24 +1,24 @@ -INSERT INTO Manufacturers VALUES (1, 'Acti'); -INSERT INTO Manufacturers VALUES (2, 'Amcrest'); -INSERT INTO Manufacturers VALUES (3, 'Airlink101'); -INSERT INTO Manufacturers VALUES (4, 'Arecont Vision'); -INSERT INTO Manufacturers VALUES (5, 'Axis'); -INSERT INTO Manufacturers VALUES (6, 'Dahua'); -INSERT INTO Manufacturers VALUES (7, 'D-Link'); -INSERT INTO Manufacturers VALUES (8, 'Edimax'); -INSERT INTO Manufacturers VALUES (9, 'Foscam'); -INSERT INTO Manufacturers VALUES (10, 'Gadspot'); -INSERT INTO Manufacturers VALUES (11, 'GrandStream'); -INSERT INTO Manufacturers VALUES (12, 'HikVision'); -INSERT INTO Manufacturers VALUES (13, 'JVC'); -INSERT INTO Manufacturers VALUES (14, 'Maginon'); -INSERT INTO Manufacturers VALUES (15, 'Mobotix'); -INSERT INTO Manufacturers VALUES (16, 'Oncam Grandeye'); -INSERT INTO Manufacturers VALUES (17, 'Panasonic'); -INSERT INTO Manufacturers VALUES (18, 'Pelco'); -INSERT INTO Manufacturers VALUES (19, 'Sony'); -INSERT INTO Manufacturers VALUES (20, 'TP-Link'); -INSERT INTO Manufacturers VALUES (21, 'Trendnet'); -INSERT INTO Manufacturers VALUES (22, 'VisionTek'); -INSERT INTO Manufacturers VALUES (23, 'Vivotek'); -INSERT INTO Manufacturers VALUES (24, 'Wansview'); +INSERT IGNORE INTO Manufacturers VALUES (1, 'Acti'); +INSERT IGNORE INTO Manufacturers VALUES (2, 'Amcrest'); +INSERT IGNORE INTO Manufacturers VALUES (3, 'Airlink101'); +INSERT IGNORE INTO Manufacturers VALUES (4, 'Arecont Vision'); +INSERT IGNORE INTO Manufacturers VALUES (5, 'Axis'); +INSERT IGNORE INTO Manufacturers VALUES (6, 'Dahua'); +INSERT IGNORE INTO Manufacturers VALUES (7, 'D-Link'); +INSERT IGNORE INTO Manufacturers VALUES (8, 'Edimax'); +INSERT IGNORE INTO Manufacturers VALUES (9, 'Foscam'); +INSERT IGNORE INTO Manufacturers VALUES (10, 'Gadspot'); +INSERT IGNORE INTO Manufacturers VALUES (11, 'GrandStream'); +INSERT IGNORE INTO Manufacturers VALUES (12, 'HikVision'); +INSERT IGNORE INTO Manufacturers VALUES (13, 'JVC'); +INSERT IGNORE INTO Manufacturers VALUES (14, 'Maginon'); +INSERT IGNORE INTO Manufacturers VALUES (15, 'Mobotix'); +INSERT IGNORE INTO Manufacturers VALUES (16, 'Oncam Grandeye'); +INSERT IGNORE INTO Manufacturers VALUES (17, 'Panasonic'); +INSERT IGNORE INTO Manufacturers VALUES (18, 'Pelco'); +INSERT IGNORE INTO Manufacturers VALUES (19, 'Sony'); +INSERT IGNORE INTO Manufacturers VALUES (20, 'TP-Link'); +INSERT IGNORE INTO Manufacturers VALUES (21, 'Trendnet'); +INSERT IGNORE INTO Manufacturers VALUES (22, 'VisionTek'); +INSERT IGNORE INTO Manufacturers VALUES (23, 'Vivotek'); +INSERT IGNORE INTO Manufacturers VALUES (24, 'Wansview'); From 4e9a56624d8d7f86f3ad83059044f77599af42a1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Nov 2021 16:30:17 -0500 Subject: [PATCH 092/501] Use UPSERTS and add some more models --- db/models.sql | 49 ++++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/db/models.sql b/db/models.sql index b7150d816..ffafb76a8 100644 --- a/db/models.sql +++ b/db/models.sql @@ -1,30 +1,41 @@ /* INSERT INTO Manufacturers VALUES (1, 'Acti'); */ -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A21'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A23'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A24'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A28'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A31'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A310'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A311'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A32'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A41'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A415'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A416'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A418'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A42'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A421'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A43'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A45'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A46'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A48'); -INSERT INTO Models (ManufacturerId,Name) VALUES (1, 'A74'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A21'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A23'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A24'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A28'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A31'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A310'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A311'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A32'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A41'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A415'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A416'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A418'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A42'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A421'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A43'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A45'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A46'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A48'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A74'); /* INSERT INTO Manufacturers VALUES (2, 'Amcrest'); +*/ +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (2, 'IP8M-T2499EW'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (2, 'ASH42-B'); +/* INSERT INTO Manufacturers VALUES (3, 'Airlink101'); INSERT INTO Manufacturers VALUES (4, 'Arecont Vision'); INSERT INTO Manufacturers VALUES (5, 'Axis'); INSERT INTO Manufacturers VALUES (6, 'Dahua'); INSERT INTO Manufacturers VALUES (7, 'D-Link'); +*/ +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-930L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-932L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-933L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-942L'); +INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-5020L'); +/* INSERT INTO Manufacturers VALUES (8, 'Edimax'); INSERT INTO Manufacturers VALUES (9, 'Foscam'); INSERT INTO Manufacturers VALUES (10, 'Gadspot'); From 6cbc4c0a7af6d1e977997283a4f7e214842966be Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Nov 2021 16:30:39 -0500 Subject: [PATCH 093/501] Update MonirorPresets to link to Amcrest cams --- db/zm_update-1.37.3.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/db/zm_update-1.37.3.sql b/db/zm_update-1.37.3.sql index 9002ce8e2..5ae4aa1b1 100644 --- a/db/zm_update-1.37.3.sql +++ b/db/zm_update-1.37.3.sql @@ -68,3 +68,6 @@ SET @s = (SELECT IF( PREPARE stmt FROM @s; EXECUTE stmt; + +UPDATE `MonitorPresets` SET `ModelId`=(SELECT `Id` FROM `Models` WHERE `Name`='IP8M-T2499EW') WHERE `Name` like 'Amcrest, IP8M-T2499EW +%'; From f48511acba1dd033feeab533d502dcc4aadbf45f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 16 Nov 2021 09:19:42 -0500 Subject: [PATCH 094/501] Add NULL for ModelId column when adding MonitorPresets. --- db/zm_create.sql.in | 150 ++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 9a048886d..d9d4f96c3 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -974,81 +974,81 @@ INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0 -- Add some monitor preset values -- -INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 640x480, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=1',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 3840x2160, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=0',NULL,3840,2160,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&color=0',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&color=0',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,1,4,NULL,':',100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, unicast','Remote','rtsp',0,255,'rtsp','rtpUni','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple',':@','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,':@',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,1,5,NULL,':',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video','0',255,'http','simple','','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT into MonitorPresets VALUES (NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp?videocodec=h264',NULL,NULL,NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Vivotek FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://:554/live.sdp',NULL,NULL,NULL,352,240,NULL,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Axis FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp',NULL,NULL,NULL,640,480,NULL,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'ACTi TCM FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://admin:123456@:7070',NULL,NULL,NULL,320,240,NULL,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 320x240','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 320x240, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 640x480','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 640x480, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 320x240','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 640x480','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 320x240','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 320x240, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 640x480','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 640x480, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 320x240','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 640x480','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Remote ZoneMinder','Remote',NULL,NULL,NULL,'http','simple','',80,'/cgi-bin/nph-zms?mode=jpeg&monitor=&scale=100&maxfps=5&buffer=0',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI8620 FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,704,576,0,NULL,1,'10','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI8608W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,640,480,0,NULL,1,'11','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:88/videoMain',NULL,1280,720,0,NULL,1,'12','','',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'','80','/videostream.cgi?user=&pwd=&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','',':@',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,':@','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','',':@',100,100); -INSERT INTO MonitorPresets VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video','0',255,'','rtpMulti','','80','rtsp://:554/11','',1920,1080,0,0.00,1,'16','-speed=64',':',100,33); -INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100); -INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Amcrest, IP8M-T2499EW 640x480, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=1',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Amcrest, IP8M-T2499EW 3840x2160, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=0',NULL,3840,2160,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&color=0',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg, B&W','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&color=0',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,1,4,NULL,':',100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, unicast','Remote','rtsp',0,255,'rtsp','rtpUni','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple',':@','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,':@',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,1,5,NULL,':',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video','0',255,'http','simple','','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp?videocodec=h264',NULL,NULL,NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Vivotek FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://:554/live.sdp',NULL,NULL,NULL,352,240,NULL,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp:///axis-media/media.amp',NULL,NULL,NULL,640,480,NULL,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'ACTi TCM FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://admin:123456@:7070',NULL,NULL,NULL,320,240,NULL,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 320x240','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 320x240, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 640x480','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 640x480, max 5 FPS','Local','/dev/video',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 320x240','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 640x480','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 320x240','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 320x240, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 640x480','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 640x480, max 5 FPS','Local','/dev/video',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 320x240','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 320x240, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 640x480','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 640x480, max 5 FPS','Local','/dev/video',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Remote ZoneMinder','Remote',NULL,NULL,NULL,'http','simple','',80,'/cgi-bin/nph-zms?mode=jpeg&monitor=&scale=100&maxfps=5&buffer=0',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI8620 FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,704,576,0,NULL,1,'10','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI8608W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:554/11',NULL,640,480,0,NULL,1,'11','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://:@:88/videoMain',NULL,1280,720,0,NULL,1,'12','','',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'','80','/videostream.cgi?user=&pwd=&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','',':@',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,':@','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','',':@',100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video','0',255,'','rtpMulti','','80','rtsp://:554/11','',1920,1080,0,0.00,1,'16','-speed=64',':',100,33); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100); -- -- Add some zone preset values From 20629fdf5a77505708842f2f910822799102e641 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 16 Nov 2021 09:20:14 -0500 Subject: [PATCH 095/501] Include Manufacturer and Model in telemetry --- scripts/zmtelemetry.pl.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/zmtelemetry.pl.in b/scripts/zmtelemetry.pl.in index a3debabdb..c164df7e4 100644 --- a/scripts/zmtelemetry.pl.in +++ b/scripts/zmtelemetry.pl.in @@ -263,7 +263,10 @@ sub countQuery { sub getMonitorRef { my $dbh = shift; - my $sql = 'SELECT `Id`,`Name`,`Type`,`Function`,`Width`,`Height`,`Colours`,`MaxFPS`,`AlarmMaxFPS` FROM `Monitors`'; + my $sql = 'SELECT `Id`,`Name`,`Type`,`Function`,`Width`,`Height`,`Colours`,`MaxFPS`,`AlarmMaxFPS`, + (SELECT Name FROM Manufacturers WHERE Manufacturers.Id = ManufacturerId), + (SELECT Name FROM Models WHERE Models.Id = ModelId) + FROM `Monitors`'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my $arrayref = $sth->fetchall_arrayref({}); From 78a803abf87bd724dcaa69de4401b561a847b8a5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 16 Nov 2021 09:47:34 -0500 Subject: [PATCH 096/501] Add Model and Manufacturer to telemetry listing --- web/lang/en_gb.php | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 8b412cd39..8c6ce9adb 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -663,8 +663,33 @@ $SLANG = array( 'PrivacyCookiesText' => 'Whether you use a web browser or a mobile app to communicate with the ZoneMinder server, a ZMSESSID cookie is created on the client to uniquely identify a session with the ZoneMinder server. ZmCSS and zmSkin cookies are created to remember your style and skin choices.', 'PrivacyTelemetry' => 'Telemetry', 'PrivacyTelemetryText' => 'Because ZoneMinder is open-source, anyone can install it without registering. This makes it difficult to answer questions such as: how many systems are out there, what is the largest system out there, what kind of systems are out there, or where are these systems located? Knowing the answers to these questions, helps users who ask us these questions, and it helps us set priorities based on the majority user base.', - 'PrivacyTelemetryList' => 'The ZoneMinder Telemetry daemon collects the following data about your system:
  • A unique identifier (UUID)
  • City based location is gathered by querying ipinfo.io. City, region, country, latitude, and longitude parameters are saved. The latitude and longitude coordinates are accurate down to the city or town level only!
  • Current time
  • Total number of monitors
  • Total number of events
  • System architecture
  • Operating system kernel, distro, and distro version
  • Version of ZoneMinder
  • Total amount of memory
  • Number of cpu cores
', - 'PrivacyMonitorList' => 'The following configuration parameters from each monitor are collected:
  • Id
  • Name
  • Type
  • Function
  • Width
  • Height
  • Colours
  • MaxFPS
  • AlarmMaxFPS
', + 'PrivacyTelemetryList' => 'The ZoneMinder Telemetry daemon collects the following data about your system: +
    +
  • A unique identifier (UUID)
  • +
  • City based location is gathered by querying ipinfo.io. City, region, country, latitude, and longitude parameters are saved. The latitude and longitude coordinates are accurate down to the city or town level only!
  • +
  • Current time
  • +
  • Total number of monitors
  • +
  • Total number of events
  • +
  • System architecture
  • +
  • Operating system kernel, distro, and distro version
  • +
  • Version of ZoneMinder
  • +
  • Total amount of memory
  • +
  • Number of cpu cores
  • +
', + 'PrivacyMonitorList' => 'The following configuration parameters from each monitor are collected: +
    +
  • Id
  • +
  • Name
  • +
  • Manufacturer
  • +
  • Model
  • +
  • Type
  • +
  • Function
  • +
  • Width
  • +
  • Height
  • +
  • Colours
  • +
  • MaxFPS
  • +
  • AlarmMaxFPS
  • +
', 'PrivacyConclusionText' => 'We are NOT collecting any image specific data from your cameras. We don’t know what your cameras are watching. This data will not be sold or used for any purpose not stated herein. By clicking accept, you agree to send us this data to help make ZoneMinder a better product. By clicking decline, you can still freely use ZoneMinder and all its features.', 'Probe' => 'Probe', 'ProfileProbe' => 'Stream Probe', From ab9c538c370e3d2817dd13448017bea788315c1a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 16 Nov 2021 14:59:18 -0500 Subject: [PATCH 097/501] Allow NOW or CURRENT for PACKAGE_VERSION similar to snapshot --- utils/do_debian_package.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 6c8c06c00..a77702fca 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -146,6 +146,14 @@ else fi; fi +if [ "$PACKAGE_VERSION" == "NOW" ]; then + PACKAGE_VERSION=`date +%Y%m%d%H%M%S`; +else + if [ "$PACKAGE_VERSION" == "CURRENT" ]; then + PACKAGE_VERSION="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" + fi; +fi; + IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" if [ "$PPA" == "" ]; then if [ "$RELEASE" != "" ]; then From 4945a016cf7383bdb51d8190247c083640b10615 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 11:04:28 -0500 Subject: [PATCH 098/501] Restore inclusion of video files in export when not including images. Fixes #3324 --- web/skins/classic/includes/export_functions.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/includes/export_functions.php b/web/skins/classic/includes/export_functions.php index 0e348152f..f34d53546 100644 --- a/web/skins/classic/includes/export_functions.php +++ b/web/skins/classic/includes/export_functions.php @@ -786,7 +786,7 @@ function exportFileList( } closedir($dir); } - ZM\Debug(print_r($files, true)); + ZM\Debug('All available files: '.print_r($files, true)); $exportFileList = array(); @@ -843,6 +843,18 @@ function exportFileList( ZM\Debug('Not including frame images'); } # end if exportImages + if ($exportVideo) { + $filesLeft = array(); + foreach ($files as $file) { + if (preg_match('/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file)) { + $exportFileList[$file] = $file; + } else { + $filesLeft[$file] = $file; + } + } + $files = $filesLeft; + } + if ($exportMisc) { foreach ($files as $file) { $exportFileList[$file] = $file; From 96b4af6255b0f1d9e5306c74bca4e808987d4659 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 14:43:13 -0500 Subject: [PATCH 099/501] Enable multi-threading on decode --- src/zm_ffmpeg_camera.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index b122e0f2b..728b51783 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -458,6 +458,17 @@ int FfmpegCamera::OpenFfmpeg() { #endif } // end if hwaccel_name + // set codec to automatically determine how many threads suits best for the decoding job + mVideoCodecContext->thread_count = 0; + + if (mVideoCodec->capabilities | AV_CODEC_CAP_FRAME_THREADS) { + mVideoCodecContext->thread_type = FF_THREAD_FRAME; + } else if (mVideoCodec->capabilities | AV_CODEC_CAP_SLICE_THREADS) { + mVideoCodecContext->thread_type = FF_THREAD_SLICE; + } else { + mVideoCodecContext->thread_count = 1; //don't use multithreading + } + ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts); e = nullptr; From c37d8eeded5e5c7562537de64b76f59d9d318e1e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 17:45:38 -0500 Subject: [PATCH 100/501] spacing and report save errors --- web/includes/actions/monitors.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/web/includes/actions/monitors.php b/web/includes/actions/monitors.php index 118af1533..c133ac83b 100644 --- a/web/includes/actions/monitors.php +++ b/web/includes/actions/monitors.php @@ -19,24 +19,27 @@ // // Monitor edit actions, monitor id derived, require edit permissions for that monitor -if ( ! canEdit('Monitors') ) { +if (!canEdit('Monitors')) { ZM\Warning("Monitor actions require Monitors Permissions"); return; } -if ( $action == 'save' ) { - foreach ( $_REQUEST['mids'] as $mid ) { +if ($action == 'save') { + foreach ($_REQUEST['mids'] as $mid) { $mid = ValidInt($mid); - if ( ! canEdit('Monitors', $mid) ) { - ZM\Warning("Cannot edit monitor $mid"); + if (!canEdit('Monitors', $mid)) { + ZM\Warning('Cannot edit monitor '.$mid); continue; } $Monitor = new ZM\Monitor($mid); - if ( $Monitor->Type() != 'WebSite' ) { + if ($Monitor->Type() != 'WebSite') { $Monitor->zmcControl('stop'); } - $Monitor->save($_REQUEST['newMonitor']); - if ( $Monitor->Function() != 'None' && $Monitor->Type() != 'WebSite' ) { + if (!$Monitor->save($_REQUEST['newMonitor'])) { + global $error_message; + $error_message .= 'Error saving monitor: ' . $Monitor->get_last_error().'
'; + } + if ($Monitor->Function() != 'None' && $Monitor->Type() != 'WebSite') { $Monitor->zmcControl('start'); } } // end foreach mid From b1d881b11833e44485c0117ff4ddfa13506542ef Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 18:57:36 -0500 Subject: [PATCH 101/501] Report more saving errors to ui --- web/includes/actions/monitor.php | 69 ++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/web/includes/actions/monitor.php b/web/includes/actions/monitor.php index 50e354978..2d900433a 100644 --- a/web/includes/actions/monitor.php +++ b/web/includes/actions/monitor.php @@ -19,55 +19,62 @@ // // Monitor edit actions, monitor id derived, require edit permissions for that monitor -if ( !canEdit('Monitors') ) { +if (!canEdit('Monitors')) { ZM\Warning('Monitor actions require Monitors Permissions'); return; } -if ( $action == 'save' ) { +global $error_message; + +if ($action == 'save') { $mid = 0; - if ( !empty($_REQUEST['mid']) ) { + if (!empty($_REQUEST['mid'])) { $mid = validInt($_REQUEST['mid']); - if ( !canEdit('Monitors', $mid) ) { + if (!canEdit('Monitors', $mid)) { ZM\Warning('You do not have permission to edit this monitor'); return; } - if ( ZM_OPT_X10 ) { + if (ZM_OPT_X10) { $x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid)); - if ( !$x10Monitor ) $x10Monitor = array(); + if (!$x10Monitor) $x10Monitor = array(); } } else { - if ( $user['MonitorIds'] ) { + if ($user['MonitorIds']) { ZM\Warning('You are restricted to certain monitors so cannot add a new one.'); return; } - if ( ZM_OPT_X10 ) { + if (ZM_OPT_X10) { $x10Monitor = array(); } } # For convenience $newMonitor = $_REQUEST['newMonitor']; + ZM\Debug("newMonitor: ". print_r($newMonitor, true)); - if ( !$newMonitor['ManufacturerId'] and ($newMonitor['Manufacturer'] != '') ) { + if (!$newMonitor['ManufacturerId'] and ($newMonitor['Manufacturer'] != '')) { # Need to add a new Manufacturer entry $newManufacturer = ZM\Manufacturer::find_one(array('Name'=>$newMonitor['Manufacturer'])); if (!$newManufacturer) { $newManufacturer = new ZM\Manufacturer(); - $newManufacturer->save(array('Name'=>$newMonitor['Manufacturer'])); + if (!$newManufacturer->save(array('Name'=>$newMonitor['Manufacturer']))) { + $error_message .= "Error saving new Manufacturer: " . $newManufacturer->get_last_error().'
'; + } } $newMonitor['ManufacturerId'] = $newManufacturer->Id(); } - if ( !$newMonitor['ModelId'] and ($newMonitor['Model'] != '') ) { + if (!$newMonitor['ModelId'] and ($newMonitor['Model'] != '')) { # Need to add a new Model entry $newModel = ZM\Model::find_one(array('Name'=>$newMonitor['Model'])); if (!$newModel) { $newModel = new ZM\Model(); - $newModel->save(array( + if (!$newModel->save(array( 'Name'=>$newMonitor['Model'], 'ManufacturerId'=>$newMonitor['ManufacturerId'] - )); + ))) { + $error_message .= "Error saving new Model: " . $newModel->get_last_error().'
'; + } } $newMonitor['ModelId'] = $newModel->Id(); } @@ -94,13 +101,13 @@ if ( $action == 'save' ) { # Checkboxes don't return an element in the POST data, so won't be present in newMonitor. # So force a value for these fields - foreach ( $types as $field => $value ) { - if ( ! isset($newMonitor[$field] ) ) { + foreach ($types as $field => $value) { + if (!isset($newMonitor[$field])) { $newMonitor[$field] = $value; } } # end foreach type - if ( $newMonitor['ServerId'] == 'auto' ) { + if ($newMonitor['ServerId'] == 'auto') { $newMonitor['ServerId'] = dbFetchOne( 'SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id'); ZM\Debug('Auto selecting server: Got ' . $newMonitor['ServerId']); @@ -110,17 +117,19 @@ if ( $action == 'save' ) { } } + ZM\Debug("newMonitor: ". print_r($newMonitor, true)); $changes = $monitor->changes($newMonitor); + ZM\Debug("Changes: ". print_r($changes, true)); $restart = false; - if ( count($changes) ) { + if (count($changes)) { // monitor->Id() has a value when the db record exists - if ( $monitor->Id() ) { + if ($monitor->Id()) { # If we change anything that changes the shared mem size, zma can complain. So let's stop first. - if ( $monitor->Type() != 'WebSite' ) { + if ($monitor->Type() != 'WebSite') { $monitor->zmcControl('stop'); - if ( $monitor->Controllable() ) { + if ($monitor->Controllable()) { $monitor->sendControlCommand('stop'); } } @@ -129,8 +138,7 @@ if ( $action == 'save' ) { $oldW = $monitor->Width(); $oldH = $monitor->Height(); - if ( $monitor->save($changes) ) { - + if ($monitor->save($changes)) { // Groups will be added below if ( isset($changes['Name']) or isset($changes['StorageId']) ) { // creating symlinks when symlink already exists reports errors, but is perfectly ok @@ -138,26 +146,26 @@ if ( $action == 'save' ) { $OldStorage = $monitor->Storage(); $saferOldName = basename($monitor->Name()); - if ( file_exists($OldStorage->Path().'/'.$saferOldName) ) + if (file_exists($OldStorage->Path().'/'.$saferOldName)) unlink($OldStorage->Path().'/'.$saferOldName); $NewStorage = new ZM\Storage($newMonitor['StorageId']); - if ( !file_exists($NewStorage->Path().'/'.$mid) ) { - if ( !mkdir($NewStorage->Path().'/'.$mid, 0755) ) { - ZM\Error('Unable to mkdir ' . $NewStorage->Path().'/'.$mid); + if (!file_exists($NewStorage->Path().'/'.$mid)) { + if (!mkdir($NewStorage->Path().'/'.$mid, 0755)) { + ZM\Error('Unable to mkdir '.$NewStorage->Path().'/'.$mid); } } $saferNewName = basename($newMonitor['Name']); $link_path = $NewStorage->Path().'/'.$saferNewName; // Use a relative path for the target so the link continues to work from backups or directory changes. - if ( !symlink($mid, $link_path) ) { - if ( ! ( file_exists($link_path) and is_link($link_path) ) ) { + if (!symlink($mid, $link_path)) { + if (!(file_exists($link_path) and is_link($link_path))) { ZM\Warning('Unable to symlink ' . $NewStorage->Path().'/'.$mid . ' to ' . $NewStorage->Path().'/'.$saferNewName); } } } // end if Name or Storage Area Change - if ( isset($changes['Width']) || isset($changes['Height']) ) { + if (isset($changes['Width']) || isset($changes['Height'])) { $newW = $newMonitor['Width']; $newH = $newMonitor['Height']; @@ -230,8 +238,7 @@ if ( $action == 'save' ) { } // end if rotation or just size change } // end if changes in width or height } else { - global $error_message; - $error_message = dbError('unknown'); + $error_message .= $monitor->get_last_error(); } // end if successful save $restart = true; } else { // new monitor From 836fe8c2b1db41070f60df0a11ac51c7ad589fc3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 18:57:44 -0500 Subject: [PATCH 102/501] spacing --- web/includes/Object.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/web/includes/Object.php b/web/includes/Object.php index 96a572c01..1a9011586 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -28,7 +28,7 @@ class ZM_Object { $this->{$k} = $v; } global $object_cache; - if ( ! isset($object_cache[$class]) ) { + if (!isset($object_cache[$class])) { $object_cache[$class] = array(); } $cache = &$object_cache[$class]; @@ -103,13 +103,13 @@ class ZM_Object { } $sql .= implode(' AND ', $fields ); } - if ( $options ) { - if ( isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; + if ($options) { + if (isset($options['order'])) { + $sql .= ' ORDER BY '.$options['order']; } - if ( isset($options['limit']) ) { - if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { - $sql .= ' LIMIT ' . $options['limit']; + if (isset($options['limit'])) { + if (is_integer($options['limit']) or ctype_digit($options['limit'])) { + $sql .= ' LIMIT '.$options['limit']; } else { $backTrace = debug_backtrace(); Error('Invalid value for limit('.$options['limit'].') passed to '.get_class()."::find from ".print_r($backTrace,true)); @@ -119,8 +119,8 @@ class ZM_Object { } $rows = dbFetchAll($sql, NULL, $values); $results = array(); - if ( $rows ) { - foreach ( $rows as $row ) { + if ($rows) { + foreach ($rows as $row) { array_push($results , new $class($row)); } } @@ -129,7 +129,7 @@ class ZM_Object { public static function _find_one($class, $parameters = array(), $options = array() ) { global $object_cache; - if ( ! isset($object_cache[$class]) ) { + if (!isset($object_cache[$class])) { $object_cache[$class] = array(); } $cache = &$object_cache[$class]; @@ -179,11 +179,11 @@ class ZM_Object { } public function set($data) { - foreach ( $data as $field => $value ) { - if ( method_exists($this, $field) and is_callable(array($this, $field), false) ) { + foreach ($data as $field => $value) { + if (method_exists($this, $field) and is_callable(array($this, $field), false)) { $this->$field($value); } else { - if ( is_array($value) ) { + if (is_array($value)) { # perhaps should turn into a comma-separated string $this->{$field} = implode(',', $value); } else if (is_string($value)) { @@ -212,11 +212,11 @@ class ZM_Object { } else { $this->{$field} = $value; } - } else if ( is_integer($value) ) { + } else if (is_integer($value)) { $this->{$field} = $value; - } else if ( is_bool($value) ) { + } else if (is_bool($value)) { $this->{$field} = $value; - } else if ( is_null($value) ) { + } else if (is_null($value)) { $this->{$field} = $value; } else { Error("Unknown type $field => $value of var " . gettype($value)); @@ -307,7 +307,7 @@ class ZM_Object { $class = get_class($this); $table = $class::$table; - if ( $new_values ) { + if ($new_values) { $this->set($new_values); } From 9160ee932f37e9d848eade19c36c82b7e8d3555f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 18:57:53 -0500 Subject: [PATCH 103/501] remove debug --- web/skins/classic/views/_monitor_filters.php | 1 - 1 file changed, 1 deletion(-) diff --git a/web/skins/classic/views/_monitor_filters.php b/web/skins/classic/views/_monitor_filters.php index f80bd1f5b..6fdde8be1 100644 --- a/web/skins/classic/views/_monitor_filters.php +++ b/web/skins/classic/views/_monitor_filters.php @@ -178,7 +178,6 @@ $html .= ' ' . ( count($conditions) ? ' WHERE ' . implode(' AND ', $conditions) : '' ).' ORDER BY Sequence ASC'; $monitors = dbFetchAll($sql, null, $values); - ZM\Debug(print_r($monitors, true)); $displayMonitors = array(); $monitors_dropdown = array(); From 7b18b22f82931db21ee51328710cf8e67b9b9ba7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 18:58:02 -0500 Subject: [PATCH 104/501] Fix value of unknown Model --- web/skins/classic/views/js/monitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index 88fae0a67..38d685367 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -353,7 +353,7 @@ function populate_models(ManufacturerId) { } dropdown.empty(); - dropdown.append(''); + dropdown.append(''); dropdown.prop('selectedIndex', 0); if (ManufacturerId) { From 805ef16e7345495ed04d1798d5b2df18a80edc2a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Nov 2021 18:59:37 -0500 Subject: [PATCH 105/501] bump version --- distros/redhat/zoneminder.spec | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 69e7fbc03..0c38e7d30 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.1 +Version: 1.37.3 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index 9cf86ad0f..d2829d87d 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.1 +1.37.3 From 18610fd601daddc1ce74dac606685d086a9d8259 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 18 Nov 2021 09:55:19 -0500 Subject: [PATCH 106/501] Add an update script that includes manufacturers and models --- db/CMakeLists.txt | 3 +++ db/zm_update-1.37.4.sql.in | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 db/zm_update-1.37.4.sql.in diff --git a/db/CMakeLists.txt b/db/CMakeLists.txt index 18f440fc8..eb9e690bd 100644 --- a/db/CMakeLists.txt +++ b/db/CMakeLists.txt @@ -4,6 +4,7 @@ configure_file(zm_create.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" @ONLY) configure_file(zm_update-1.31.30.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.31.30.sql" @ONLY) configure_file(zm_update-1.35.24.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.35.24.sql" @ONLY) +configure_file(zm_update-1.37.4.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.37.4.sql" @ONLY) # Glob all database upgrade scripts file(GLOB dbfileslist RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "zm_update-*.sql") @@ -15,6 +16,8 @@ install(FILES ${dbfileslist} DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.31.30.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") # install zm_update-1.35.24.sql install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.35.24.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") +# install zm_update-1.37.4.sql +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.37.4.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") # install zm_create.sql install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") diff --git a/db/zm_update-1.37.4.sql.in b/db/zm_update-1.37.4.sql.in new file mode 100644 index 000000000..d0ab7b36c --- /dev/null +++ b/db/zm_update-1.37.4.sql.in @@ -0,0 +1,2 @@ +source @PKGDATADIR@/db/manufacturers.sql +source @PKGDATADIR@/db/models.sql From f63c124988ceb565ce00deb5651d23bba6883ff7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 18 Nov 2021 09:55:39 -0500 Subject: [PATCH 107/501] include manufacturers and models --- db/zm_create.sql.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index d9d4f96c3..fe4d93dde 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -1118,6 +1118,9 @@ CREATE TABLE Snapshot_Events ( -- We generally don't alter triggers, we drop and re-create them, so let's keep them in a separate file that we can just source in update scripts. source @PKGDATADIR@/db/triggers.sql + +source @PKGDATADIR@/db/manufacturers.sql +source @PKGDATADIR@/db/models.sql -- -- Apply the initial configuration -- From 0a30c55042982af42fefe73ccf5c91f51786b43f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 18 Nov 2021 11:09:49 -0500 Subject: [PATCH 108/501] install manufacturers.sql and models.sql --- db/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/db/CMakeLists.txt b/db/CMakeLists.txt index eb9e690bd..00cc7d8a4 100644 --- a/db/CMakeLists.txt +++ b/db/CMakeLists.txt @@ -25,3 +25,8 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" DESTINATION "${CMAKE_I # install triggers.sql install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/triggers.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") +# install manufacturers.sql +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/manufacturers.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") + +# install models.sql +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/models.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") From 2c798069d3d9b5a9efc04e16bb5a9188bfdba0c8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 18 Nov 2021 18:12:07 -0500 Subject: [PATCH 109/501] improve debug logging when loading Control in Monitor --- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 26 ++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index c5e09c137..da3a56373 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -327,17 +327,23 @@ sub resumeMotionDetection { sub Control { my $self = shift; if ( ! exists $$self{Control}) { - require ZoneMinder::Control; - my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); - if ($Control) { - require Module::Load::Conditional; - if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) { - Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR"); - return undef; + if ($$self{ControlId}) { + require ZoneMinder::Control; + my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); + if ($Control) { + require Module::Load::Conditional; + if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) { + Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR"); + return undef; + } + bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol}; + $$Control{MonitorId} = $$self{Id}; + $$self{Control} = $Control; + } else { + Error("Unable to load control for control $$self{ControlId} for monitor $$self{Id}"); } - bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol}; - $$Control{MonitorId} = $$self{Id}; - $$self{Control} = $Control; + } else { + Info("No ControlId set in monitor $$self{Id}") } } return $$self{Control}; From 2529765df3616af550ac55e86c93e4db810020a6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 19 Nov 2021 11:59:19 -0500 Subject: [PATCH 110/501] timestamp image before scaling. Fixes lack of scaling when TIMESTAMP_ON_CAPTURE is off --- src/zm_monitorstream.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 9bc04a311..7c2aeeaa2 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -377,10 +377,10 @@ bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint times } bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) { - Image *send_image = prepareImage(image); if (!config.timestamp_on_capture) { - monitor->TimestampImage(send_image, timestamp); + monitor->TimestampImage(image, timestamp); } + Image *send_image = prepareImage(image); fputs("--" BOUNDARY "\r\n", stdout); if ( type == STREAM_MPEG ) { @@ -854,16 +854,16 @@ void MonitorStream::SingleImage(int scale) { int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index); Image *snap_image = monitor->image_buffer[index]; + if (!config.timestamp_on_capture) { + monitor->TimestampImage(snap_image, + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index]))); + } if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign(*snap_image); scaled_image.Scale(scale); snap_image = &scaled_image; } - if (!config.timestamp_on_capture) { - monitor->TimestampImage(snap_image, - SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index]))); - } snap_image->EncodeJpeg(img_buffer, &img_buffer_size); fprintf(stdout, From 40e7f607f5d3dd73fcd714acc3cd7d73e06d9188 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 11:38:40 -0500 Subject: [PATCH 111/501] If no protocol defined, fall back to the name of the Control --- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index da3a56373..5646fb897 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -326,17 +326,23 @@ sub resumeMotionDetection { sub Control { my $self = shift; - if ( ! exists $$self{Control}) { + if (!exists $$self{Control}) { if ($$self{ControlId}) { require ZoneMinder::Control; my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); if ($Control) { + my $Protocol = $$Control{Protocol}; + + if (!$Protocol) { + Error("No protocol set in control $$Control{Id}, trying Name $$Control{Name}"); + $Protocol = $$Control{Name}; + } require Module::Load::Conditional; - if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) { - Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR"); + if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$Protocol => undef})) { + Error("Can't load ZoneMinder::Control::$Protocol\n$Module::Load::Conditional::ERROR"); return undef; } - bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol}; + bless $Control, 'ZoneMinder::Control::'.$Protocol; $$Control{MonitorId} = $$self{Id}; $$self{Control} = $Control; } else { From cc65c99791ee2ce5c9617dae41d9f7cae8f9a100 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 12:53:51 -0500 Subject: [PATCH 112/501] Move init of ctx up before we setup the monitors. I think in some cases we can calls functions that assume ctx has a value. Uncaught%20TypeError%3A%20Cannot%20read%20properties%20of%20undefined%20(reading%20'getImageData') --- web/skins/classic/views/js/montagereview.js | 25 ++++++++++--------- .../classic/views/js/montagereview.js.php | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index 787a910d0..da3301d4d 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -985,6 +985,19 @@ function initPage() { }); }); + if ( !liveMode ) { + canvas = document.getElementById('timeline'); + + canvas.addEventListener('mousemove', mmove, false); + canvas.addEventListener('touchmove', tmove, false); + canvas.addEventListener('mousedown', mdown, false); + canvas.addEventListener('mouseup', mup, false); + canvas.addEventListener('mouseout', mout, false); + + ctx = canvas.getContext('2d'); + drawGraph(); + } + for ( var i = 0, len = monitorPtr.length; i < len; i += 1 ) { var monId = monitorPtr[i]; if ( !monId ) continue; @@ -1006,18 +1019,6 @@ function initPage() { } } // end foreach monitor - if ( !liveMode ) { - canvas = document.getElementById('timeline'); - - canvas.addEventListener('mousemove', mmove, false); - canvas.addEventListener('touchmove', tmove, false); - canvas.addEventListener('mousedown', mdown, false); - canvas.addEventListener('mouseup', mup, false); - canvas.addEventListener('mouseout', mout, false); - - ctx = canvas.getContext('2d'); - drawGraph(); - } setSpeed(speedIndex); //setFit(fitMode); // will redraw //setLive(liveMode); // will redraw diff --git a/web/skins/classic/views/js/montagereview.js.php b/web/skins/classic/views/js/montagereview.js.php index d82f103ed..f4b6222f4 100644 --- a/web/skins/classic/views/js/montagereview.js.php +++ b/web/skins/classic/views/js/montagereview.js.php @@ -239,6 +239,6 @@ echo "];\n"; var cWidth; // save canvas width var cHeight; // save canvas height var canvas; // global canvas definition so we don't have to keep looking it up -var ctx; +var ctx = null; var underSlider; // use this to hold what is hidden by the slider var underSliderX; // Where the above was taken from (left side, Y is zero) From 1f75b017ccb4688bfda4726c3ac4bdb3a64bd996 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 14:17:50 -0500 Subject: [PATCH 113/501] kill the background timer when switching to history so that we don't cause a javascript error. comment out debugging and use native javascript instead of jquery. --- web/skins/classic/views/js/montagereview.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index da3301d4d..60856f4ca 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -57,7 +57,7 @@ function getFrame(monId, time, last_Frame) { var events_for_monitor = events_by_monitor_id[monId]; if ( !events_for_monitor ) { - console.log("No events for monitor " + monId); + //console.log("No events for monitor " + monId); return; } @@ -648,8 +648,11 @@ function setSpeed(speed_index) { } function setLive(value) { + // When we submit the context etc goes away but we may still be trying to update + // So kill the timer. + clearInterval(timerObj); liveMode = value; - var form = $j('#montagereview_form')[0]; + var form = document.getElementById('montagereview_form'); form.elements['live'].value = value; form.submit(); return false; From 46a835b28ab80ec2128d5d4acc2f109f1fb29890 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 14:26:48 -0500 Subject: [PATCH 114/501] fix error when no monitors defined and we are adding one.Fixes #3385 --- web/skins/classic/views/monitor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 7d599904a..3dc6937e8 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -453,7 +453,7 @@ foreach ( $tabs as $name=>$value ) { switch ( $name ) { case 'general' : { - if (!$monitor->Id()) { + if (!$monitor->Id() and count($monitors)) { $monitor_ids = array(); foreach ($monitors as $m) { $monitor_ids[] = $m['Id']; } $available_monitor_ids = array_diff(range(min($monitor_ids),max($monitor_ids)), $monitor_ids); @@ -470,7 +470,7 @@ if (count($available_monitor_ids)) { Id() + } # end if ! $monitor->Id() and count($monitors) ?> From ffdb0f98249819c5a75c123da4709e995deeee80 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Nov 2021 11:05:39 -0500 Subject: [PATCH 115/501] If we are starting a process that is waiting to term, mark it to get started by the reaper. Fixes case where zmdc thought the process was still running and so didn't start it. We never noticed because zmwatch would eventually notice. The result is instant restart. --- scripts/zmdc.pl.in | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 5cf866e56..793049479 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -429,10 +429,20 @@ sub start { # It's not running, or at least it's not been started by us $process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef }; } elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) { - dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " + if ($process->{term_sent_at}) { + dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' was told to term at " + .strftime('%y/%m/%d %H:%M:%S', localtime($process->{term_sent_at})) + .", pid = $process->{pid}\n" + ); + $process->{keepalive} = !undef; + $process->{delay} = 0; + delete $terminating_processes{$command}; + } else { + dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .", pid = $process->{pid}\n" - ); + ); + } return; } @@ -523,7 +533,7 @@ sub send_stop { ."\n" ); sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; - return(); + return (); } my $pid = $process->{pid}; @@ -586,7 +596,7 @@ sub check_for_processes_to_kill { sub stop { my ( $daemon, @args ) = @_; - my $command = join(' ', $daemon, @args ); + my $command = join(' ', $daemon, @args); my $process = $cmd_hash{$command}; if ( !$process ) { dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'"); From 81ffc6df4e763474035d56c098089c7ca6339f8d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 12:06:13 -0500 Subject: [PATCH 116/501] Remove text-nowrap from cause/notes column --- web/skins/classic/views/js/events.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/js/events.js b/web/skins/classic/views/js/events.js index 6139ba3e4..bafd16763 100644 --- a/web/skins/classic/views/js/events.js +++ b/web/skins/classic/views/js/events.js @@ -63,13 +63,13 @@ function processRows(rows) { row.Id = '' + eid + ''; row.Name = '' + row.Name + '' + - '
' + archived + emailed + '
'; + '
' + archived + emailed + '
'; if ( canEdit.Monitors ) row.Monitor = '' + row.Monitor + ''; if ( canEdit.Events ) row.Cause = '' + row.Cause + ''; if ( row.Notes.indexOf('detected:') >= 0 ) { - row.Cause = row.Cause + '
' + row.Notes + '
'; + row.Cause = row.Cause + '
' + row.Notes + '
'; } else if ( row.Notes != 'Forced Web: ' ) { - row.Cause = row.Cause + '
' + row.Notes + '
'; + row.Cause = row.Cause + '
' + row.Notes + '
'; } row.Frames = '' + row.Frames + ''; row.AlarmFrames = '' + row.AlarmFrames + ''; From 4be9c6cdd28c87fdaecec89342b758a6876ee6d7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 12:42:31 -0500 Subject: [PATCH 117/501] Code comments and make warning when the first packet in queue is locked. --- src/zm_packetqueue.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 509a25ee6..8b831ec2d 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -116,14 +116,15 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { , max_video_packet_count); for ( - auto it = ++pktQueue.begin(); - it != pktQueue.end() and *it != add_packet; + auto it = ++pktQueue.begin(); + it != pktQueue.end() and *it != add_packet; + // iterator is incremented by erase ) { std::shared_ptr zm_packet = *it; ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); if (!lp->trylock()) { - Debug(1, "Found locked packet when trying to free up video packets. Skipping to next one"); + Warning("Found locked packet when trying to free up video packets. This basically means that decoding is not keeping up."); delete lp; ++it; continue; @@ -312,7 +313,6 @@ void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { pktQueue.size()); pktQueue.pop_front(); packet_counts[zm_packet->packet.stream_index] -= 1; - //delete zm_packet; } } // end if have at least max_video_packet_count video packets remaining // We signal on every packet because someday we may analyze sound From 67556430c661a4e801d306b819442ebc2588844d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 12:44:07 -0500 Subject: [PATCH 118/501] Add option ZM_NO_PCRE to disable testing for libpcre3. debian wants to remove it so this allows us to test building without it. Remove libpcre3 from depends and set ZM_NO_PCRE=ON in debian build config --- CMakeLists.txt | 34 ++++++++++++++++++++-------------- distros/ubuntu2004/control | 2 -- distros/ubuntu2004/rules | 1 + 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c34a9f808..3b789e4c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,8 @@ set(ZM_NO_X10 "OFF" CACHE BOOL set(ZM_ONVIF "ON" CACHE BOOL "Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not work with all cameras claiming to be ONVIF compliant. default: ON") +set(ZM_NO_PCRE "OFF" CACHE BOOL + "Set to ON to skip libpcre3 checks and force building ZM without libpcre3. default: OFF") set(ZM_NO_RTSPSERVER "OFF" CACHE BOOL "Set to ON to skip building ZM with rtsp server support. default: OFF") set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING @@ -407,21 +409,24 @@ else() message(FATAL_ERROR "ZoneMinder requires pthread but it was not found on your system") endif() -# pcre (using find_library and find_path) -find_library(PCRE_LIBRARIES pcre) -if(PCRE_LIBRARIES) - set(HAVE_LIBPCRE 1) - list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") - find_path(PCRE_INCLUDE_DIR pcre.h) - if(PCRE_INCLUDE_DIR) - include_directories("${PCRE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") +# Do not check for cURL if ZM_NO_CURL is on +if(NOT ZM_NO_PRCE) + # pcre (using find_library and find_path) + find_library(PCRE_LIBRARIES pcre) + if(PCRE_LIBRARIES) + set(HAVE_LIBPCRE 1) + list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") + find_path(PCRE_INCLUDE_DIR pcre.h) + if(PCRE_INCLUDE_DIR) + include_directories("${PCRE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) + check_include_file("pcre.h" HAVE_PCRE_H) + set(optlibsfound "${optlibsfound} PCRE") + else() + set(optlibsnotfound "${optlibsnotfound} PCRE") endif() - mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) - check_include_file("pcre.h" HAVE_PCRE_H) - set(optlibsfound "${optlibsfound} PCRE") -else() - set(optlibsnotfound "${optlibsnotfound} PCRE") endif() # mysqlclient (using find_library and find_path) @@ -540,6 +545,7 @@ set(ZM_PCRE 0) if(HAVE_LIBPCRE AND HAVE_PCRE_H) set(ZM_PCRE 1) endif() + # Check for mmap and enable in all components set(ZM_MEM_MAPPED 0) set(ENABLE_MMAP no) diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index a4683bfde..d14c3fb52 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -16,7 +16,6 @@ Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-ap ,libjpeg-turbo8-dev | libjpeg62-turbo-dev | libjpeg8-dev | libjpeg9-dev ,libturbojpeg0-dev ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat - ,libpcre3-dev ,libpolkit-gobject-1-dev ,libv4l-dev [!hurd-any] ,libvlc-dev @@ -70,7 +69,6 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,policykit-1 ,rsyslog | system-log-daemon ,zip - ,libpcre3 ,libcrypt-eksblowfish-perl ,libdata-entropy-perl ,libvncclient1|libvncclient0 diff --git a/distros/ubuntu2004/rules b/distros/ubuntu2004/rules index c137a9da2..af75a409a 100755 --- a/distros/ubuntu2004/rules +++ b/distros/ubuntu2004/rules @@ -19,6 +19,7 @@ override_dh_auto_configure: -DCMAKE_VERBOSE_MAKEFILE=ON \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_MAN=0 \ + -DZM_NO_PCRE=ON \ -DZM_CONFIG_DIR="/etc/zm" \ -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ -DZM_RUNDIR="/run/zm" \ From 77d3109152ab6a238589ec82853d864403af4833 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 13:44:45 -0500 Subject: [PATCH 119/501] Increase to 20 before warning about db queue size. Put lock in it's own scope so that we unlock before notifying --- src/zm_db.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/zm_db.cpp b/src/zm_db.cpp index e3b737c07..f0b13d538 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -251,11 +251,11 @@ void zmDbQueue::process() { mCondition.wait(lock); } while (!mQueue.empty()) { - if (mQueue.size() > 10) { + if (mQueue.size() > 20) { Logger *log = Logger::fetch(); Logger::Level db_level = log->databaseLevel(); log->databaseLevel(Logger::NOLOG); - Warning("db queue size has grown larger %zu than 10 entries", mQueue.size()); + Warning("db queue size has grown larger %zu than 20 entries", mQueue.size()); log->databaseLevel(db_level); } std::string sql = mQueue.front(); @@ -271,8 +271,10 @@ void zmDbQueue::process() { void zmDbQueue::push(std::string &&sql) { if (mTerminate) return; - std::unique_lock lock(mMutex); - mQueue.push(std::move(sql)); + { + std::unique_lock lock(mMutex); + mQueue.push(std::move(sql)); + } mCondition.notify_all(); } From e061f3b34a6e230850120c99bdecdafe7be4ac4b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 14:28:15 -0500 Subject: [PATCH 120/501] typo --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d808ddba0..d9f932d75 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1873,7 +1873,7 @@ bool Monitor::Analyse() { if (snap->image) { // decoder may not have been able to provide an image if (!ref_image.Buffer()) { - Debug(1, "Assigning instead of Dectecting"); + Debug(1, "Assigning instead of Detecting"); ref_image.Assign(*(snap->image)); } else { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); From e8bb095730a7ba7430ffe27703926c9cd71a7a43 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 14:28:31 -0500 Subject: [PATCH 121/501] include monitor dimensions when logging about zone mismatch --- src/zm_zone.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index f0c09ec78..4fc1bf61b 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -878,16 +878,23 @@ std::vector Zone::Load(Monitor *monitor) { continue; } - if (polygon.Extent().Lo().x_ < 0 || polygon.Extent().Hi().x_ > static_cast(monitor->Width()) - || polygon.Extent().Lo().y_ < 0 || polygon.Extent().Hi().y_ > static_cast(monitor->Height())) { - Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), fixing", + if (polygon.Extent().Lo().x_ < 0 + || + polygon.Extent().Hi().x_ > static_cast(monitor->Width()) + || + polygon.Extent().Lo().y_ < 0 + || + polygon.Extent().Hi().y_ > static_cast(monitor->Height())) { + Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d) != (%d,%d), fixing", Id, Name, monitor->Name(), polygon.Extent().Lo().x_, polygon.Extent().Lo().y_, polygon.Extent().Hi().x_, - polygon.Extent().Hi().y_); + polygon.Extent().Hi().y_, + monitor->Width(), + monitor->Height()); polygon.Clip(Box( {0, 0}, From af5436d009732c4a2656eb32bbe3b6d3d12036a7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 10:31:54 -0500 Subject: [PATCH 122/501] Handle bug where a value of '' will prevent special case handling. Allow '' to mean NULL when specifying Storage Area --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 3a3308938..0db46ff0d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -230,8 +230,8 @@ sub Sql { # PostCondition, so no further SQL } else { ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; - foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - + # Empty value will result in () from split + foreach my $temp_value ( $stripped_value ? split( /["'\s]*?,["'\s]*?/, $stripped_value ) : $stripped_value ) { if ( $term->{attr} eq 'AlarmedZoneId' ) { $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')'; } elsif ( $term->{attr} =~ /^MonitorName/ ) { @@ -250,7 +250,8 @@ sub Sql { $$self{Server} = new ZoneMinder::Server($temp_value); } } elsif ( $term->{attr} eq 'StorageId' ) { - $value = "'$temp_value'"; + # Empty means NULL, otherwise must be an integer + $value = $temp_value ne '' ? int($temp_value) : 'NULL'; $$self{Storage} = new ZoneMinder::Storage($temp_value); } elsif ( $term->{attr} eq 'Name' || $term->{attr} eq 'Cause' From a1bf8f7f5bf2b271ab7d1e598f572206a5bd54de Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 10:35:15 -0500 Subject: [PATCH 123/501] Fix NULL and add special 0 case for Storage area specification in filter --- web/skins/classic/views/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index b425f7896..b441db349 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -152,7 +152,7 @@ $booleanValues = array( $focusWindow = true; -$storageareas = array('' => 'All') + ZM\ZM_Object::Objects_Indexed_By_Id('ZM\Storage'); +$storageareas = array('' => array('Name'=>'NULL Unspecified'), '0' => array('Name'=>'Zero')) + ZM\ZM_Object::Objects_Indexed_By_Id('ZM\Storage'); $weekdays = array(); for ( $i = 0; $i < 7; $i++ ) { From f9f2615d485381dcacc04977860d9f6fa31d9b61 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 15:26:38 -0500 Subject: [PATCH 124/501] Return if unable to lock the event record. Improve code around CopyTo call. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 0276af097..c5e3994a7 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -733,19 +733,22 @@ sub MoveTo { my $was_in_transaction = !$ZoneMinder::Database::dbh->{AutoCommit}; $ZoneMinder::Database::dbh->begin_work() if !$was_in_transaction; - $self->lock_and_load(); # The fact that we are in a transaction might not imply locking + if (!$self->lock_and_load()) { + Warning('Unable to lock event record '.$$self{Id}); # The fact that we are in a transaction might not imply locking + $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; + return 'Unable to lock event record'; + } my $OldStorage = $self->Storage(undef); - my $error = $self->CopyTo($NewStorage); - return $error if $error; + if (!$error) { + # Succeeded in copying all files, so we may now update the Event. + $$self{StorageId} = $$NewStorage{Id}; + $self->Storage($NewStorage); + $error .= $self->save(); - # Succeeded in copying all files, so we may now update the Event. - $$self{StorageId} = $$NewStorage{Id}; - $self->Storage($NewStorage); - $error .= $self->save(); - - # Going to leave it to upper layer as to whether we rollback or not + # Going to leave it to upper layer as to whether we rollback or not + } $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; return $error if $error; From d51eb63947d66619cae917663f6a0601f5b0f92b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 19:21:24 -0500 Subject: [PATCH 125/501] Add EventStartCommand and EventEndCommand to monitors table --- db/zm_create.sql.in | 2 ++ db/zm_update-1.37.5.sql | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 db/zm_update-1.37.5.sql diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index fe4d93dde..bf598b646 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -456,6 +456,8 @@ CREATE TABLE `Monitors` ( `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1', `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', + `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '', + `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '', `ONVIF_URL` VARCHAR(255) NOT NULL DEFAULT '', `ONVIF_Username` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '', diff --git a/db/zm_update-1.37.5.sql b/db/zm_update-1.37.5.sql new file mode 100644 index 000000000..1f40eb923 --- /dev/null +++ b/db/zm_update-1.37.5.sql @@ -0,0 +1,31 @@ +-- +-- This update adds EventStartCommand and EventEndCommand +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'AlarmEndCommand' + ) > 0, +"SELECT 'Column EventEndCommand already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'AlarmStartCommand' + ) > 0, +"SELECT 'Column EventStartCommand already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; From b626de50dca26a2d5e9ce28977d24aed9a91a5de Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 19:21:51 -0500 Subject: [PATCH 126/501] Add code to fork and exec EventStartCommand and EventEndCommand --- src/zm_monitor.cpp | 32 +++++++++++++++++++++++++++++--- src/zm_monitor.h | 2 ++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d9f932d75..d50e4e6eb 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -70,7 +70,7 @@ // It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended std::string load_monitor_sql = "SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, " -"`LinkedMonitors`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," +"`LinkedMonitors`, `EventStartCommand`, `EventEndCommand`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," "`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings "`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `SecondPath`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " "`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " @@ -435,7 +435,7 @@ Monitor::Monitor() /* std::string load_monitor_sql = - "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, LinkedMonitors, " + "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, LinkedMonitors, `EventStartCommand`, `EventEndCommand`, " "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " @@ -489,6 +489,8 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { // See below after save_jpegs for a recalculation of decoding_enabled ReloadLinkedMonitors(dbrow[col]); col++; + event_start_command = dbrow[col] ? dbrow[col] : ""; col++; + event_end_command = dbrow[col] ? dbrow[col] : ""; col++; /* "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," */ analysis_fps_limit = dbrow[col] ? strtod(dbrow[col], nullptr) : 0.0; col++; @@ -1994,6 +1996,12 @@ bool Monitor::Analyse() { alarm_cause = cause+" Continuous "+alarm_cause; strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); SetVideoWriterStartTime(event->StartTime()); + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } + } Info("%s: %03d - Opened new event %" PRIu64 ", section start", name.c_str(), analysis_image_count, event->Id()); @@ -2084,6 +2092,13 @@ bool Monitor::Analyse() { delete start_it; start_it = nullptr; + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } + } + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); } else { shared_data->state = state = ALARM; @@ -2786,7 +2801,18 @@ void Monitor::closeEvent() { Debug(1, "close event thread is not joinable"); } Debug(1, "Starting thread to close event"); - close_event_thread = std::thread([](Event *e){ delete e; }, event); + close_event_thread = std::thread([](Event *e, const std::string &command){ + int64_t event_id = e->Id(); + delete e; + + if (!command.empty()) { + if (fork() == 0) { + execlp(command.c_str(), command.c_str(), std::to_string(event_id).c_str(), nullptr); + Error("Error execing %s", command.c_str()); + } + } + + }, event, event_end_command); Debug(1, "Nulling event"); event = nullptr; if (shared_data) video_store_data->recording = {}; diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 5b9a2ce46..d670911bb 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -404,6 +404,8 @@ protected: int n_linked_monitors; MonitorLink **linked_monitors; + std::string event_start_command; + std::string event_end_command; std::vector groups; From 21218491f7e1f94a5e7ff5bd520b79e57a4211a8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 19:22:07 -0500 Subject: [PATCH 127/501] Add EventStartCommand and EventEndCommand to monitors ui --- web/includes/Monitor.php | 2 ++ web/skins/classic/views/monitor.php | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 6423af11f..b5e2ef853 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -57,6 +57,8 @@ class Monitor extends ZM_Object { 'DecodingEnabled' => array('type'=>'boolean','default'=>1), 'LinkedMonitors' => array('type'=>'set', 'default'=>null), 'Triggers' => array('type'=>'set','default'=>''), + 'EventStartCommand' => '', + 'EventEndCommand' => '', 'ONVIF_URL' => '', 'ONVIF_Username' => '', 'ONVIF_Password' => '', diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 3dc6937e8..657c2ce8c 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -675,6 +675,14 @@ if (count($available_monitor_ids)) { } ?> + + + + + + + + Date: Fri, 26 Nov 2021 19:22:53 -0500 Subject: [PATCH 128/501] bump version 1.37.5 --- distros/redhat/zoneminder.spec | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 0c38e7d30..8d49c0d7f 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.3 +Version: 1.37.5 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index d2829d87d..e2ba07049 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.3 +1.37.5 From 74a227667122a8dbe6ed0a1eac69046575fac660 Mon Sep 17 00:00:00 2001 From: pkubaj Date: Mon, 29 Nov 2021 00:03:45 +0000 Subject: [PATCH 129/501] Fix build on FreeBSD/armv7 1. FreeBSD uses elf_aux_info instead of getauxval. 2. FreeBSD uses HWCAP_NEON macro for Neon. --- src/zm_utils.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 5da5509ff..409429667 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -252,8 +252,15 @@ void HwCapsDetect() { #elif defined(__arm__) // ARM processor in 32bit mode // To see if it supports NEON, we need to get that information from the kernel + #ifdef __linux__ unsigned long auxval = getauxval(AT_HWCAP); if (auxval & HWCAP_ARM_NEON) { + #elif defined(__FreeBSD__) + unsigned long auxval = 0; + elf_aux_info(AT_HWCAP, &auxval, sizeof(auxval)); + if (auxval & HWCAP_NEON) { + #error Unsupported OS. + #endif Debug(1,"Detected ARM (AArch32) processor with Neon"); neonversion = 1; } else { From b47e96d7cfde58a0c84553d88116736e5fffa423 Mon Sep 17 00:00:00 2001 From: maddios Date: Mon, 29 Nov 2021 00:20:25 -0500 Subject: [PATCH 130/501] Fix Copy/Move to Default Storage When moving from a 2nd storage to Default it fails with "New storage does not have an id. Moving will not happen" because the default ID is 0. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index c5e3994a7..026ff6cea 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -43,6 +43,7 @@ require Date::Parse; require POSIX; use Date::Format qw(time2str); use Time::HiRes qw(gettimeofday tv_interval stat); +use Scalar::Util qw(looks_like_number); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -601,7 +602,7 @@ sub CopyTo { # First determine if we can move it to the dest. # We do this before bothering to lock the event my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint - if ( ! $$NewStorage{Id} ) { + if ( ! looks_like_number($$NewStorage{Id}) ) { return 'New storage does not have an id. Moving will not happen.'; } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { return 'Event is already located at ' . $NewPath; From 01f4aee45034cea6e027dc6be031e0277a794133 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 27 Nov 2021 19:10:09 -0500 Subject: [PATCH 131/501] Fix underline --- docs/installationguide/debian.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index f7325fe1f..e6a3404df 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -4,7 +4,7 @@ Debian .. contents:: Easy Way: Debian 11 (Bullseye) ------------------------- +------------------------------ This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye). From 1f19ad7c9d9b483a92679f225c4efbafd9f3e290 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 27 Nov 2021 19:16:32 -0500 Subject: [PATCH 132/501] fix by removing code block --- docs/installationguide/debian.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index e6a3404df..b92ff267a 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -104,7 +104,7 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file You can do this using: -.. code-block:: +:: echo "deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list From ea6a84ae66ea3a3d7ae72f6ba5047b815bb26db7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 12:53:44 -0500 Subject: [PATCH 133/501] Fix AlarmEndCommand => EventEndCommand --- db/zm_update-1.37.5.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/zm_update-1.37.5.sql b/db/zm_update-1.37.5.sql index 1f40eb923..0f205ff77 100644 --- a/db/zm_update-1.37.5.sql +++ b/db/zm_update-1.37.5.sql @@ -7,7 +7,7 @@ SET @s = (SELECT IF( FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'Monitors' AND table_schema = DATABASE() - AND column_name = 'AlarmEndCommand' + AND column_name = 'EventEndCommand' ) > 0, "SELECT 'Column EventEndCommand already exists in Monitors'", "ALTER TABLE `Monitors` ADD COLUMN `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" From 072d181f79c41889ba44107ade0f48254157c2ae Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 12:54:46 -0500 Subject: [PATCH 134/501] Fix AlarmStartCommand => EventStartCommand --- db/zm_update-1.37.5.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/zm_update-1.37.5.sql b/db/zm_update-1.37.5.sql index 0f205ff77..035a73a1a 100644 --- a/db/zm_update-1.37.5.sql +++ b/db/zm_update-1.37.5.sql @@ -21,7 +21,7 @@ SET @s = (SELECT IF( FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'Monitors' AND table_schema = DATABASE() - AND column_name = 'AlarmStartCommand' + AND column_name = 'EventStartCommand' ) > 0, "SELECT 'Column EventStartCommand already exists in Monitors'", "ALTER TABLE `Monitors` ADD COLUMN `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" From 82a4cbaec520cc46ad2e75aba5e433013da80f46 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 13:48:44 -0500 Subject: [PATCH 135/501] Fix task=>action so that deleting works. Pause streaming before delete to prevent errors being logged due to missing files --- web/skins/classic/views/js/event.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 05914f911..94e132cc1 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -773,8 +773,9 @@ function manageDelConfirmModalBtns() { return; } + pauseClicked(); evt.preventDefault(); - $j.getJSON(thisUrl + '?request=event&task=delete&id='+eventData.Id) + $j.getJSON(thisUrl + '?request=event&action=delete&id='+eventData.Id) .done(function(data) { $j('#deleteConfirm').modal('hide'); streamNext(true); From 089563d1cecb5d04c8b7876f585e050390af5d73 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 14:14:56 -0500 Subject: [PATCH 136/501] rework do_debian_package to properly support the CURRENT style of snapshots and make the code a little easier to read --- utils/do_debian_package.sh | 122 ++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 6c8c06c00..91ecf520b 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -116,52 +116,6 @@ else echo "Defaulting to ZoneMinder upstream git" GITHUB_FORK="ZoneMinder" fi; - if [ "$SNAPSHOT" == "stable" ]; then - if [ "$BRANCH" == "" ]; then - #REV=$(git rev-list --tags --max-count=1) - BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; - if [ -z "$BRANCH" ]; then - # This should only happen in CI environments where tag info isn't available - BRANCH=`cat version` - echo "Building branch $BRANCH" - fi - if [ "$BRANCH" == "" ]; then - echo "Unable to determine latest stable branch!" - exit 0; - fi - echo "Latest stable branch is $BRANCH"; - fi; - else - if [ "$BRANCH" == "" ]; then - echo "Defaulting to master branch"; - BRANCH="master"; - fi; - if [ "$SNAPSHOT" == "NOW" ]; then - SNAPSHOT=`date +%Y%m%d%H%M%S`; - else - if [ "$SNAPSHOT" == "CURRENT" ]; then - SNAPSHOT="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" - fi; - fi; - fi; -fi - -IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" -if [ "$PPA" == "" ]; then - if [ "$RELEASE" != "" ]; then - # We need to use our official tarball for the original source, so grab it and overwrite our generated one. - if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then - PPA="ppa:iconnor/zoneminder-stable" - else - PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" - fi; - else - if [ "$BRANCH" == "" ]; then - PPA="ppa:iconnor/zoneminder-master"; - else - PPA="ppa:iconnor/zoneminder-$BRANCH"; - fi; - fi; fi; # Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. @@ -171,15 +125,8 @@ if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then cd "${GITHUB_FORK}_ZoneMinder.git" echo "git fetch..." git fetch - echo "git checkout $BRANCH" - git checkout $BRANCH - if [ $? -ne 0 ]; then - echo "Failed to switch to branch." - exit 1; - fi; - echo "git pull..." - git pull cd ../ + echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" else @@ -192,14 +139,59 @@ else fi; cd "${GITHUB_FORK}_zoneminder_release" - git checkout $BRANCH -cd ../ -VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` +if [ "$SNAPSHOT" == "stable" ]; then + if [ "$BRANCH" == "" ]; then + #REV=$(git rev-list --tags --max-count=1) + BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; + if [ -z "$BRANCH" ]; then + # This should only happen in CI environments where tag info isn't available + BRANCH=`cat version` + echo "Building branch $BRANCH" + fi + if [ "$BRANCH" == "" ]; then + echo "Unable to determine latest stable branch!" + exit 0; + fi + echo "Latest stable branch is $BRANCH"; + fi; +else + if [ "$BRANCH" == "" ]; then + echo "Defaulting to master branch"; + BRANCH="master"; + fi; + if [ "$SNAPSHOT" == "NOW" ]; then + SNAPSHOT=`date +%Y%m%d%H%M%S`; + else + if [ "$SNAPSHOT" == "CURRENT" ]; then + # git the latest (short) commit hash of the version file + versionhash=$(git log -n1 --pretty=format:%h version) + # Number of commits since the version file was last changed + numcommits=$(git rev-list ${versionhash}..HEAD --count) + SNAPSHOT="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" + fi; + fi; +fi; + + +echo "git checkout $BRANCH" +git checkout $BRANCH +if [ $? -ne 0 ]; then + echo "Failed to switch to branch." + exit 1; +fi; +echo "git pull..." +git pull +# Grab the ZoneMinder version from the contents of the version file +VERSION=$(cat version) if [ -z "$VERSION" ]; then exit 1; fi; +IFS='.' read -r -a VERSION_PARTS <<< "$VERSION" + +cd ../ + if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then VERSION="$VERSION~$SNAPSHOT"; fi; @@ -357,6 +349,22 @@ EOF fi; else SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; + if [ "$PPA" == "" ]; then + if [ "$RELEASE" != "" ]; then + # We need to use our official tarball for the original source, so grab it and overwrite our generated one. + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then + PPA="ppa:iconnor/zoneminder-stable" + else + PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" + fi; + else + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; + fi; + fi; dput="Y"; if [ "$INTERACTIVE" != "no" ]; then From 7b9c86111c094662e485a94417755a7e230de8b2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 16:21:34 -0500 Subject: [PATCH 137/501] Move Cleanup and framebuffer freeing into Close() so that we don't crash on Reload --- src/zm_libvnc_camera.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/zm_libvnc_camera.cpp b/src/zm_libvnc_camera.cpp index 6fb414686..3ff7804b6 100644 --- a/src/zm_libvnc_camera.cpp +++ b/src/zm_libvnc_camera.cpp @@ -23,7 +23,7 @@ void bind_libvnc_symbols() { libvnc_lib = dlopen("libvncclient.so", RTLD_LAZY | RTLD_GLOBAL); if (!libvnc_lib) { - Error("Error loading libvncclient: %s", dlerror()); + Error("Error loading libvncclient.so: %s", dlerror()); return; } @@ -135,11 +135,6 @@ VncCamera::VncCamera( } VncCamera::~VncCamera() { - if (capture and mRfb) { - if (mRfb->frameBuffer) - free(mRfb->frameBuffer); - (*rfbClientCleanup_f)(mRfb); - } if (libvnc_lib) { dlclose(libvnc_lib); libvnc_lib = nullptr; @@ -253,6 +248,12 @@ int VncCamera::PostCapture() { } int VncCamera::Close() { + if (capture and mRfb) { + if (mRfb->frameBuffer) + free(mRfb->frameBuffer); + (*rfbClientCleanup_f)(mRfb); + mRfb = nullptr; + } return 1; } #endif From b5098a1ab90a9cbefa2a9942254421ca36a03f6a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 16:26:23 -0500 Subject: [PATCH 138/501] Do not guess multiple distros. release building will have automated distro specification. --- utils/do_debian_package.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 91ecf520b..29ebdc0ef 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -87,11 +87,7 @@ else fi; if [ "$DISTROS" == "" ]; then - if [ "$RELEASE" != "" ]; then - DISTROS="bionic,focal,hirsute,impish" - else - DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; - fi; + DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; echo "Defaulting to $DISTROS for distribution"; else echo "Building for $DISTROS"; From c927ef4b52b3ede8835e65f1e0c9aa7d62ca59df Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:32:48 -0500 Subject: [PATCH 139/501] Aim to do db updates every 5 seconds instead of 1 second --- src/zm_event.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 9f1124e7b..2d95ec23a 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -546,7 +546,7 @@ void Event::AddFrame(Image *image, or (frame_type == BULK) or - (fps and (frame_data.size() > fps))) { + (fps and (frame_data.size() > 5*fps))) { Debug(1, "Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK(%d)", frame_data.size(), write_to_db, fps, (frame_type == BULK)); WriteDbFrames(); From 1cddac4efd45cd6ea965d8d7e90c3815060332d8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:34:06 -0500 Subject: [PATCH 140/501] remove remaining signal blocking cruft, add ignoring sigchld so that anything we spawn doesn't become a zombie. --- src/zmc.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zmc.cpp b/src/zmc.cpp index 68c6227af..c2f14e254 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -220,12 +220,13 @@ int main(int argc, char *argv[]) { zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); - sigset_t block_set; - sigemptyset(&block_set); - - sigaddset(&block_set, SIGHUP); - sigaddset(&block_set, SIGUSR1); - sigaddset(&block_set, SIGUSR2); + struct sigaction sa; + sa.sa_handler = SIG_IGN; //handle signal by ignoring + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + Error("Unable to set SIGCHLD to ignore. There may be zombies."); + } int result = 0; int prime_capture_log_count = 0; @@ -286,7 +287,6 @@ int main(int argc, char *argv[]) { Microseconds sleep_time = Microseconds(0); while (!zm_terminate) { - //sigprocmask(SIG_BLOCK, &block_set, 0); for (size_t i = 0; i < monitors.size(); i++) { monitors[i]->CheckAction(); From 37069cb6fc4cc2650ebaa8c0e5ed547a66a8443d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:59:44 -0500 Subject: [PATCH 141/501] add cmake module to find libFmt --- cmake/Modules/FindFmt.cmake | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 cmake/Modules/FindFmt.cmake diff --git a/cmake/Modules/FindFmt.cmake b/cmake/Modules/FindFmt.cmake new file mode 100644 index 000000000..b426d8c77 --- /dev/null +++ b/cmake/Modules/FindFmt.cmake @@ -0,0 +1,100 @@ +# FindFmt +# ------- +# Finds the Fmt library +# +# This will define the following variables:: +# +# FMT_FOUND - system has Fmt +# FMT_INCLUDE_DIRS - the Fmt include directory +# FMT_LIBRARIES - the Fmt libraries +# +# and the following imported targets:: +# +# Fmt::Fmt - The Fmt library + +if(ENABLE_INTERNAL_FMT) + include(ExternalProject) + file(STRINGS ${CMAKE_SOURCE_DIR}/tools/depends/target/libfmt/Makefile VER REGEX "^[ ]*VERSION[ ]*=.+$") + string(REGEX REPLACE "^[ ]*VERSION[ ]*=[ ]*" "" FMT_VERSION "${VER}") + + # allow user to override the download URL with a local tarball + # needed for offline build envs + if(FMT_URL) + get_filename_component(FMT_URL "${FMT_URL}" ABSOLUTE) + else() + set(FMT_URL http://mirrors.kodi.tv/build-deps/sources/fmt-${FMT_VERSION}.tar.gz) + endif() + if(VERBOSE) + message(STATUS "FMT_URL: ${FMT_URL}") + endif() + + if(APPLE) + set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + endif() + + set(FMT_LIBRARY ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/lib/libfmt.a) + set(FMT_INCLUDE_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include) + externalproject_add(fmt + URL ${FMT_URL} + DOWNLOAD_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/download + PREFIX ${CORE_BUILD_DIR}/fmt + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_INSTALL_LIBDIR=lib + -DFMT_DOC=OFF + -DFMT_TEST=OFF + "${EXTRA_ARGS}" + BUILD_BYPRODUCTS ${FMT_LIBRARY}) + set_target_properties(fmt PROPERTIES FOLDER "External Projects") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR + VERSION_VAR FMT_VERSION) + + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + +else() + +find_package(FMT 6.1.2 CONFIG REQUIRED QUIET) + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FMT libfmt QUIET) + if(PC_FMT_VERSION AND NOT FMT_VERSION) + set(FMT_VERSION ${PC_FMT_VERSION}) + endif() +endif() + +find_path(FMT_INCLUDE_DIR NAMES fmt/format.h + PATHS ${PC_FMT_INCLUDEDIR}) + +find_library(FMT_LIBRARY_RELEASE NAMES fmt + PATHS ${PC_FMT_LIBDIR}) +find_library(FMT_LIBRARY_DEBUG NAMES fmtd + PATHS ${PC_FMT_LIBDIR}) + +include(SelectLibraryConfigurations) +select_library_configurations(FMT) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR FMT_VERSION + VERSION_VAR FMT_VERSION) + +if(FMT_FOUND) + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + + if(NOT TARGET fmt) + add_library(fmt UNKNOWN IMPORTED) + set_target_properties(fmt PROPERTIES + IMPORTED_LOCATION "${FMT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") + endif() +endif() + +endif() +mark_as_advanced(FMT_INCLUDE_DIR FMT_LIBRARY) From 5647c224d1d6407b6a44288c91a8c9384c9f2f08 Mon Sep 17 00:00:00 2001 From: Petter Reinholdtsen Date: Thu, 2 Dec 2021 15:18:04 +0100 Subject: [PATCH 142/501] Make config file comment on unix socket option a bit clearer Initially I took the comment for granted, and the 'unix_socket' string as a magic string to tell zoneminder to use the mysql default socket. Alas, this do not work, as the setting really need to point to the path of the socket. Rewrite the comment to make this more clear, and less confusing for the future users. --- zm.conf.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zm.conf.in b/zm.conf.in index 312c9aeae..d48dfce56 100644 --- a/zm.conf.in +++ b/zm.conf.in @@ -37,7 +37,8 @@ ZM_WEB_GROUP=@WEB_GROUP@ ZM_DB_TYPE=@ZM_DB_TYPE@ # ZoneMinder database hostname or ip address and optionally port or unix socket -# Acceptable formats include hostname[:port], ip_address[:port], or localhost:unix_socket +# Acceptable formats include hostname[:port], ip_address[:port], or +# localhost:/path/to/unix_socket ZM_DB_HOST=@ZM_DB_HOST@ # ZoneMinder database name From 1277e752783ed8d2dc061e1ae633a3670cc4b617 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 12:03:23 -0500 Subject: [PATCH 143/501] Detect group hierarchy loops and break them. --- web/includes/Group.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/web/includes/Group.php b/web/includes/Group.php index 026d1f7f4..34db0e869 100644 --- a/web/includes/Group.php +++ b/web/includes/Group.php @@ -167,9 +167,15 @@ class Group extends ZM_Object { public function Parents() { $Parents = array(); $Parent = $this->Parent(); - while( $Parent ) { + $seen_parents = array(); + while ($Parent) { + $seen_parents[$Parent->Id()] = $Parent; array_unshift($Parents, $Parent); $Parent = $Parent->Parent(); + if ($Parent and isset($seen_parents[$Parent->Id()])) { + Warning("Detected hierarchy loop in group {$Parent->Name()}"); + break; + } } return $Parents; } @@ -189,6 +195,9 @@ class Group extends ZM_Object { public function canView($u=null) { global $user; if (!$u) $u = $user; + if (!count($this->Monitors()) and !count($this->Children())) { + return true; + } # Can view if we can view any of the monitors in it. foreach ($this->Monitors() as $monitor) { if ($monitor->canView($u)) return true; From eef172379c11c60a68236d3768495e962bef398f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 13:25:21 -0500 Subject: [PATCH 144/501] Move all the opening of events code into one function called openEvent. --- src/zm_monitor.cpp | 208 ++++++++++++++++----------------------------- 1 file changed, 75 insertions(+), 133 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d50e4e6eb..0410d39bc 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1882,12 +1882,20 @@ bool Monitor::Analyse() { // Get new score. int motion_score = DetectMotion(*(snap->image), zoneSet); + // lets construct alarm cause. It will contain cause + names of zones alarmed + std::string alarm_cause; snap->zone_stats.reserve(zones.size()); for (const Zone &zone : zones) { const ZoneStats &stats = zone.GetStats(); stats.DumpToLog("After detect motion"); snap->zone_stats.push_back(stats); + if (zone.Alarmed()) { + if (!alarm_cause.empty()) alarm_cause += ","; + alarm_cause += std::string(zone.Label()); + } } + if (!alarm_cause.empty()) + cause = cause+" "+alarm_cause; Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", score, last_motion_score, motion_score); @@ -1913,6 +1921,7 @@ bool Monitor::Analyse() { ); } // end if active and doing motion detection + if (function == RECORD or function == MOCORD) { // If doing record, check to see if we need to close the event or not. if (event) { @@ -1935,75 +1944,9 @@ bool Monitor::Analyse() { } // end if event if (!event) { - Debug(2, "Creating continuous event"); - if (!snap->keyframe and (videowriter == PASSTHROUGH)) { - // Must start on a keyframe so rewind. Only for passthrough though I guess. - // FIXME this iterator is not protected from invalidation - packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - *analysis_it, 0 /* pre_event_count */ - ); + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); - // This gets a lock on the starting packet - - ZMLockedPacket *starting_packet_lock = nullptr; - std::shared_ptr starting_packet = nullptr; - if (*start_it != *analysis_it) { - starting_packet_lock = packetqueue.get_packet(start_it); - if (!starting_packet_lock) { - Warning("Unable to get starting packet lock"); - delete packet_lock; - return false; - } - starting_packet = starting_packet_lock->packet_; - } else { - starting_packet = snap; - } - - event = new Event(this, starting_packet->timestamp, "Continuous", noteSetMap); - // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (starting_packet and ((*start_it) != *analysis_it)) { - event->AddPacket(starting_packet); - // Have added the packet, don't want to unlock it until we have locked the next - - packetqueue.increment_it(start_it); - if ((*start_it) == *analysis_it) { - if (starting_packet_lock) delete starting_packet_lock; - break; - } - ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; - if (!lp) return false; - starting_packet_lock = lp; - starting_packet = lp->packet_; - } - packetqueue.free_it(start_it); - delete start_it; - start_it = nullptr; - } else { - // Create event from current snap - event = new Event(this, timestamp, "Continuous", noteSetMap); - } - shared_data->last_event_id = event->Id(); - - // lets construct alarm cause. It will contain cause + names of zones alarmed - std::string alarm_cause; - for (const Zone &zone : zones) { - if (zone.Alarmed()) { - if (!alarm_cause.empty()) alarm_cause += ","; - alarm_cause += std::string(zone.Label()); - } - } - alarm_cause = cause+" Continuous "+alarm_cause; - strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); - SetVideoWriterStartTime(event->StartTime()); - if (!event_start_command.empty()) { - if (fork() == 0) { - execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); - Error("Error execing %s", event_start_command.c_str()); - } - } - - Info("%s: %03d - Opened new event %" PRIu64 ", section start", + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", name.c_str(), analysis_image_count, event->Id()); /* To prevent cancelling out an existing alert\prealarm\alarm state */ if (state == IDLE) { @@ -2035,70 +1978,12 @@ bool Monitor::Analyse() { (event_close_mode == CLOSE_ALARM)); } if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { - // lets construct alarm cause. It will contain cause + names of zones alarmed - std::string alarm_cause = ""; - for (const Zone &zone : zones) { - if (zone.Alarmed()) { - alarm_cause = alarm_cause + "," + std::string(zone.Label()); - } - } - if (!alarm_cause.empty()) alarm_cause[0] = ' '; - alarm_cause = cause + alarm_cause; - strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", - name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, shared_data->alarm_cause); + name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); if (!event) { - packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - *analysis_it, - (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) - ); - ZMLockedPacket *starting_packet_lock = nullptr; - std::shared_ptr starting_packet = nullptr; - if (*start_it != *analysis_it) { - starting_packet_lock = packetqueue.get_packet(start_it); - if (!starting_packet_lock) return false; - starting_packet = starting_packet_lock->packet_; - } else { - starting_packet = snap; - } - - event = new Event(this, starting_packet->timestamp, cause, noteSetMap); - shared_data->last_event_id = event->Id(); - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - SetVideoWriterStartTime(event->StartTime()); + event = openEvent(snap, cause, noteSetMap); shared_data->state = state = ALARM; - - // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (*start_it != *analysis_it) { - event->AddPacket(starting_packet); - - packetqueue.increment_it(start_it); - if ( (*start_it) == (*analysis_it) ) { - if (starting_packet_lock) delete starting_packet_lock; - break; - } - ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; - if (!lp) { - // Shutting down event will be closed by ~Monitor() - // Perhaps we shouldn't do this. - return false; - } - starting_packet_lock = lp; - starting_packet = lp->packet_; - } - packetqueue.free_it(start_it); - delete start_it; - start_it = nullptr; - - if (!event_start_command.empty()) { - if (fork() == 0) { - execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); - Error("Error execing %s", event_start_command.c_str()); - } - } - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); } else { shared_data->state = state = ALARM; @@ -2208,11 +2093,7 @@ bool Monitor::Analyse() { static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); - event = new Event(this, timestamp, cause, noteSetMap); - shared_data->last_event_id = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - SetVideoWriterStartTime(event->StartTime()); + event = openEvent(snap, cause, noteSetMap); } } else { Error("ALARM but no event"); @@ -2791,6 +2672,67 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const { Debug(2, "done annotating %s", label_text); } // end void Monitor::TimestampImage +Event * Monitor::openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap noteSetMap) { + + // FIXME this iterator is not protected from invalidation + packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( + *analysis_it, + (cause == "Continuous" ? 0 : (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count)) + ); + + // This gets a lock on the starting packet + + ZMLockedPacket *starting_packet_lock = nullptr; + std::shared_ptr starting_packet = nullptr; + if (*start_it != *analysis_it) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) { + Warning("Unable to get starting packet lock"); + return nullptr; + } + starting_packet = starting_packet_lock->packet_; + } else { + starting_packet = snap; + } + + event = new Event(this, starting_packet->timestamp, cause, noteSetMap); + + shared_data->last_event_id = event->Id(); + strncpy(shared_data->alarm_cause, cause.c_str(), sizeof(shared_data->alarm_cause)-1); + + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } + } + + // Write out starting packets, do not modify packetqueue it will garbage collect itself + while (starting_packet and ((*start_it) != *analysis_it)) { + event->AddPacket(starting_packet); + // Have added the packet, don't want to unlock it until we have locked the next + + packetqueue.increment_it(start_it); + if ((*start_it) == *analysis_it) { + if (starting_packet_lock) delete starting_packet_lock; + break; + } + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + delete starting_packet_lock; + if (!lp) return nullptr; // only on terminate FIXME + starting_packet_lock = lp; + starting_packet = lp->packet_; + } + packetqueue.free_it(start_it); + delete start_it; + start_it = nullptr; + + return event; +} + void Monitor::closeEvent() { if (!event) return; From 27e87fc21ff305a062416dd508d6d208146b0809 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 13:25:36 -0500 Subject: [PATCH 145/501] Move all the opening of events code into one function called openEvent. --- src/zm_monitor.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index d670911bb..8d6bbd95e 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -593,6 +593,10 @@ public: bool Decode(); void DumpImage( Image *dump_image ) const; void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const; + Event *openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap noteSetMap); void closeEvent(); void Reload(); From 45559123afc511218f38736ea8356e67b6d50909 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:45:26 -0500 Subject: [PATCH 146/501] Add numCoords, Coords, Area, AlarmRGB to Zone object. Also add Points(), AreaCoords, svg_polygon utility functions to it. --- web/includes/Zone.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/includes/Zone.php b/web/includes/Zone.php index 9f3850bb1..eb216565a 100644 --- a/web/includes/Zone.php +++ b/web/includes/Zone.php @@ -13,6 +13,10 @@ class Zone extends ZM_Object { 'Name' => '', 'Type' => 'Active', 'Units' => 'Pixels', + 'NumCoords' => '0', + 'Coords' => 0, + 'Area' => '0', + 'AlarmRGB' => '0', 'CheckMethod' => 'Blobs', 'MinPixelThreshold' => null, 'MaxPixelThreshold' => null, @@ -46,5 +50,17 @@ class Zone extends ZM_Object { return new Monitor(); } + public function Points() { + return coordsToPoints($this->Coords()); + } + + public function AreaCoords() { + return preg_replace('/\s+/', ',', $this->Coords()); + } + + public function svg_polygon() { + return ''; + } + } # end class Zone ?> From e6a12d20c6ad8279d0a02ce6727b41a4c37eb6a4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:45:54 -0500 Subject: [PATCH 147/501] Add svg syles and rename imageFeed to videoFeed --- web/skins/classic/css/base/views/event.css | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/css/base/views/event.css b/web/skins/classic/css/base/views/event.css index f392cb99c..da59db6aa 100644 --- a/web/skins/classic/css/base/views/event.css +++ b/web/skins/classic/css/base/views/event.css @@ -77,7 +77,7 @@ height: 100%; position: relative; } -#imageFeed { +#videoFeed { display: inline-block; position: relative; text-align: center; @@ -263,3 +263,17 @@ height: 100%; height: 100%; background-color: #999999; } +svg.zones { + position:absolute; + top: 0; + left: 0; + background: none; + width: 100%; + /* + height: 100%; + */ +} +#videoobj { + width: 100%; + height: 100%; +} From 76c4560c25e5d21aee2c17f12ceee2ee84a3a93c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:46:25 -0500 Subject: [PATCH 148/501] put svg zone styles in one files that can be included where needed --- web/skins/classic/css/base/zones.css | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 web/skins/classic/css/base/zones.css diff --git a/web/skins/classic/css/base/zones.css b/web/skins/classic/css/base/zones.css new file mode 100644 index 000000000..82c96e6b2 --- /dev/null +++ b/web/skins/classic/css/base/zones.css @@ -0,0 +1,19 @@ +.zones polygon { + fill-opacity: 0.25; +} +.Active { + stroke: #ff0000; + fill: #ff0000; +} +.Inclusive { + stroke: #FFA500; + fill: #FFA500; +} +.Exclusive { + stroke: #800080; + fill: #800080; +} +.Preclusive { + stroke: #0000FF; + fill: #0000FF; +} From 089c6044f1e3f42382f115bc04b55135489ee50d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:48:49 -0500 Subject: [PATCH 149/501] Add layers toggle button, cleanup code by using ->canView, rename Monitor to monitor, add svg zones layer --- web/skins/classic/views/event.php | 107 ++++++++++++++---------- web/skins/classic/views/js/event.js | 32 +++++-- web/skins/classic/views/js/event.js.php | 10 ++- 3 files changed, 98 insertions(+), 51 deletions(-) diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index e352f305b..b096fda97 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -25,47 +25,57 @@ if ( !canView('Events') ) { require_once('includes/Event.php'); require_once('includes/Filter.php'); +require_once('includes/Zone.php'); $eid = validInt($_REQUEST['eid']); $fid = !empty($_REQUEST['fid']) ? validInt($_REQUEST['fid']) : 1; $Event = new ZM\Event($eid); -if ( $user['MonitorIds'] ) { - $monitor_ids = explode(',', $user['MonitorIds']); - if ( count($monitor_ids) and ! in_array($Event->MonitorId(), $monitor_ids) ) { - $view = 'error'; - return; - } -} -$Monitor = $Event->Monitor(); +$monitor = $Event->Monitor(); -if ( isset($_REQUEST['rate']) ) { +if (!$monitor->canView()) { + $view = 'error'; + return; +} + +zm_session_start(); +if (isset($_REQUEST['rate']) ) { $rate = validInt($_REQUEST['rate']); -} else if ( isset($_COOKIE['zmEventRate']) ) { +} else if (isset($_COOKIE['zmEventRate'])) { $rate = $_COOKIE['zmEventRate']; } else { - $rate = reScale(RATE_BASE, $Monitor->DefaultRate(), ZM_WEB_DEFAULT_RATE); + $rate = reScale(RATE_BASE, $monitor->DefaultRate(), ZM_WEB_DEFAULT_RATE); } -if ( isset($_REQUEST['scale']) ) { +if (isset($_REQUEST['scale'])) { $scale = validInt($_REQUEST['scale']); -} else if ( isset($_COOKIE['zmEventScale'.$Event->MonitorId()]) ) { +} else if (isset($_COOKIE['zmEventScale'.$Event->MonitorId()])) { $scale = $_COOKIE['zmEventScale'.$Event->MonitorId()]; } else { - $scale = $Monitor->DefaultScale(); + $scale = $monitor->DefaultScale(); +} + +$showZones = false; +if (isset($_REQUEST['showZones'])) { + $showZones = $_REQUEST['showZones'] == 1; + $_SESSION['zmEventShowZones'.$monitor->Id()] = $showZones; +} else if (isset($_COOKIE['zmEventShowZones'.$monitor->Id()])) { + $showZones = $_COOKIE['zmEventShowZones'.$monitor->Id()] == 1; +} else if (isset($_SESSION['zmEventShowZones'.$monitor->Id()]) ) { + $showZones = $_SESSION['zmEventShowZones'.$monitor->Id()]; } $codec = 'auto'; -if ( isset($_REQUEST['codec']) ) { +if (isset($_REQUEST['codec'])) { $codec = $_REQUEST['codec']; - zm_session_start(); $_SESSION['zmEventCodec'.$Event->MonitorId()] = $codec; - session_write_close(); } else if ( isset($_SESSION['zmEventCodec'.$Event->MonitorId()]) ) { $codec = $_SESSION['zmEventCodec'.$Event->MonitorId()]; } else { - $codec = $Monitor->DefaultCodec(); + $codec = $monitor->DefaultCodec(); } +session_write_close(); + $codecs = array( 'auto' => translate('Auto'), 'MP4' => translate('MP4'), @@ -79,32 +89,30 @@ $replayModes = array( 'gapless' => translate('ReplayGapless'), ); -if ( isset($_REQUEST['streamMode']) ) +if (isset($_REQUEST['streamMode'])) $streamMode = validHtmlStr($_REQUEST['streamMode']); else $streamMode = 'video'; $replayMode = ''; -if ( isset($_REQUEST['replayMode']) ) +if (isset($_REQUEST['replayMode'])) $replayMode = validHtmlStr($_REQUEST['replayMode']); -if ( isset($_COOKIE['replayMode']) && preg_match('#^[a-z]+$#', $_COOKIE['replayMode']) ) +if (isset($_COOKIE['replayMode']) && preg_match('#^[a-z]+$#', $_COOKIE['replayMode'])) $replayMode = validHtmlStr($_COOKIE['replayMode']); -if ( ( !$replayMode ) or ( !$replayModes[$replayMode] ) ) { +if ((!$replayMode) or !$replayModes[$replayMode]) { $replayMode = 'none'; } -$video_tag = false; -if ( $Event->DefaultVideo() and ( $codec == 'MP4' or $codec == 'auto' ) ) { - $video_tag = true; -} +$video_tag = ($Event->DefaultVideo() and ($codec == 'MP4' or $codec == 'auto')); + // videojs zoomrotate only when direct recording $Zoom = 1; $Rotation = 0; -if ( $Monitor->VideoWriter() == '2' ) { +if ($monitor->VideoWriter() == '2') { # Passthrough $Rotation = $Event->Orientation(); - if ( in_array($Event->Orientation(),array('90','270')) ) + if (in_array($Event->Orientation(),array('90','270'))) $Zoom = $Event->Height()/$Event->Width(); } @@ -143,7 +151,7 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
-Id() ) { ?> +Id()) { ?> @@ -158,7 +166,13 @@ if ( $Event->Id() and !file_exists($Event->Path()) ) -Id ?> + +

Id() ?>

@@ -190,10 +204,10 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
-
+ -
-
-getStreamSrc(array('mode'=>'mpeg', 'scale'=>$scale, 'rate'=>$rate, 'bitrate'=>ZM_WEB_VIDEO_BITRATE, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'format'=>ZM_MPEG_REPLAY_FORMAT, 'replay'=>$replayMode),'&'); outputVideoStream('evtStream', $streamSrc, reScale( $Event->Width(), $scale ).'px', reScale( $Event->Height(), $scale ).'px', ZM_MPEG_LIVE_FORMAT ); } else { $streamSrc = $Event->getStreamSrc(array('mode'=>'jpeg', 'frame'=>$fid, 'scale'=>$scale, 'rate'=>$rate, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>$replayMode),'&'); if ( canStreamNative() ) { - outputImageStream('evtStream', $streamSrc, reScale($Event->Width(), $scale).'px', reScale($Event->Height(), $scale).'px', validHtmlStr($Event->Name())); + outputImageStream('evtStream', $streamSrc, '100%', '100%', validHtmlStr($Event->Name())); } else { - outputHelperStream('evtStream', $streamSrc, reScale($Event->Width(), $scale).'px', reScale($Event->Height(), $scale).'px' ); + outputHelperStream('evtStream', $streamSrc, '100%', '100%'); } } // end if stream method ?> @@ -231,10 +241,18 @@ if ( (ZM_WEB_STREAM_METHOD == 'mpeg') && ZM_MPEG_LIVE_FORMAT ) {
-
+ +$monitor->Id()), array('order'=>'Area DESC')) as $zone) { + echo $zone->svg_polygon(); + } // end foreach zone +?> + Sorry, your browser does not support inline SVG + +

- + diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 94e132cc1..b252c292c 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -177,7 +177,7 @@ function changeScale() { var newWidth; var newHeight; var autoScale; - var eventViewer= $j(vid ? '#videoobj' : '#evtStream'); + var eventViewer= $j(vid ? '#videoobj' : '#videoFeed'); var alarmCue = $j('div.alarmCue'); var bottomEl = $j('#replayStatus'); @@ -910,12 +910,12 @@ function initPage() { progressBarNav(); streamCmdTimer = setTimeout(streamQuery, 500); if (canStreamNative) { - if (!$j('#imageFeed')) { - console.log('No element with id tag imageFeed found.'); + if (!$j('#videoFeed')) { + console.log('No element with id tag videoFeed found.'); } else { - var streamImg = $j('#imageFeed img'); + var streamImg = $j('#videoFeed img'); if (!streamImg) { - streamImg = $j('#imageFeed object'); + streamImg = $j('#videoFeed object'); } $j(streamImg).click(function(event) { handleClick(event); @@ -1071,5 +1071,27 @@ function initPage() { }); } // end initPage +document.getElementById('toggleZonesButton').addEventListener('click', toggleZones); + +function toggleZones(e) { + const zones = $j('#zones'+eventData.MonitorId); + const button = document.getElementById('toggleZonesButton'); + if (zones.length) { + if (zones.is(":visible")) { + zones.hide(); + button.setAttribute('title', showZonesString); + button.innerHTML = 'layers'; + setCookie('zmEventShowZones'+eventData.MonitorId, '0', 3600); + } else { + zones.show(); + button.setAttribute('title', hideZonesString); + button.innerHTML = 'layers_clear'; + setCookie('zmEventShowZones'+eventData.MonitorId, '1', 3600); + } + } else { + console.error("Zones svg not found"); + } +} + // Kick everything off $j(document).ready(initPage); diff --git a/web/skins/classic/views/js/event.js.php b/web/skins/classic/views/js/event.js.php index cdd4e9041..5d4d57f7c 100644 --- a/web/skins/classic/views/js/event.js.php +++ b/web/skins/classic/views/js/event.js.php @@ -1,7 +1,7 @@ Id() ?>', Name: 'Name() ?>', MonitorId: 'MonitorId() ?>', - MonitorName: 'Name()) ?>', + MonitorName: 'Name()) ?>', Cause: 'Cause()) ?>', + Notes: 'Notes()?>', Width: 'Width() ?>', Height: 'Height() ?>', Length: 'Length() ?>', @@ -72,6 +73,7 @@ var eventDataStrings = { MonitorId: '', MonitorName: '', Cause: '', + Notes: '', StartDateTimeShort: '', Length: '', Frames: '', @@ -93,7 +95,7 @@ var sortQuery = '; var rate = ''; // really only used when setting up initial playback rate. var scale = ""; -var LabelFormat = "LabelFormat())?>"; +var LabelFormat = "LabelFormat())?>"; var streamTimeout = ; @@ -105,6 +107,8 @@ var streamMode = ''; // var deleteString = ""; var causeString = ""; +var showZonesString = ""; +var hideZonesString = ""; var WEB_LIST_THUMB_WIDTH = ''; var WEB_LIST_THUMB_HEIGHT = ''; var popup = ''; From 7b66d751d80a9f0722ded2154b1514f72ab24105 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:49:24 -0500 Subject: [PATCH 150/501] cleanup, spacing, use zone object methods to clean up svg zone layers --- web/skins/classic/views/montage.php | 43 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index b0b10f190..deaddcaf3 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -24,6 +24,7 @@ if ( !canView('Stream') ) { } require_once('includes/MontageLayout.php'); +require_once('includes/Zone.php'); $showControl = false; $showZones = false; @@ -49,7 +50,6 @@ $heights = array( '1080' => '1080px', ); - $layouts = ZM\MontageLayout::find(NULL, array('order'=>"lower('Name')")); $layoutsById = array(); foreach ( $layouts as $l ) { @@ -149,7 +149,7 @@ echo getNavBarHTML(); $html .= ' - + diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index 6acce1b5c..3128f7767 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -135,7 +135,11 @@ if (isset($_REQUEST['height'])) { } $connkey = generateConnKey(); -$streamMode = getStreamMode(); +if ( $monitor->JanusEnabled() ) { + $streamMode = 'janus'; +} else { + $streamMode = getStreamMode(); +} noCacheHeaders(); xhtmlHeaders(__FILE__, $monitor->Name().' - '.translate('Feed')); @@ -407,4 +411,6 @@ if ( ZM_WEB_SOUND_ON_ALARM ) { ?> + + From cb46b94ea1c81e1cd62ccc89b5a902d9e8914254 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 00:19:54 -0600 Subject: [PATCH 283/501] Add error handling to libcurl calls to Janus --- src/zm_monitor.cpp | 60 ++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index f38c7b446..774884454 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1115,15 +1115,12 @@ bool Monitor::connect() { //End ONVIF Setup #endif -//janus setup. - if (janus_enabled) { - add_to_janus(); + //janus setup. + if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { + if (add_to_janus() != 0) { + Warning("Failed to add monitor stream to Janus!"); + } } -//ifdef janus, and if url contains rtsp -//look for username and password -//make the initial call, scrape id, then connect to plugin -//add stream using the same id - } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); @@ -3360,16 +3357,21 @@ int Monitor::add_to_janus() { std::string rtsp_username; std::string rtsp_password; std::string rtsp_path = "rtsp://"; + std::string janus_id; std::size_t pos; std::size_t pos2; + CURLcode res; curl = curl_easy_init(); - + if(!curl) return -1; //parse username and password pos = path.find(":", 7); + if (pos == std::string::npos) return -1; rtsp_username = path.substr(7, pos-7); pos2 = path.find("@", pos); + if (pos2 == std::string::npos) return -1; + rtsp_password = path.substr(pos+1, pos2 - pos - 1); rtsp_path += path.substr(pos2 + 1); @@ -3378,9 +3380,12 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + pos = response.find("\"id\": "); - std::string janus_id = response.substr(pos + 6, 16); + if (pos == std::string::npos) return -1; + janus_id = response.substr(pos + 6, 16); response = ""; endpoint += janus_id; postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; @@ -3388,9 +3393,11 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; pos = response.find("\"id\": "); - std::string handle_id = response.substr(pos + 6, 16); + if (pos == std::string::npos) return -1; + std::string handle_id = response.substr(pos + 6, 16); //TODO: This is an assumption that the string is always 16 endpoint += "/"; endpoint += handle_id; @@ -3411,10 +3418,11 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); - Warning(response.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + Debug(1,response.c_str()); curl_easy_cleanup(curl); - + return 0; } int Monitor::remove_from_janus() { //TODO clean this up, add error checking, etc @@ -3422,8 +3430,10 @@ int Monitor::remove_from_janus() { std::string endpoint = "127.0.0.1:8088/janus/"; std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; std::size_t pos; + CURLcode res; curl = curl_easy_init(); + if(!curl) return -1; //Start Janus API init. Need to get a session_id and handle_id @@ -3431,8 +3441,11 @@ int Monitor::remove_from_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + pos = response.find("\"id\": "); + if (pos == std::string::npos) return -1; std::string janus_id = response.substr(pos + 6, 16); response = ""; endpoint += janus_id; @@ -3441,8 +3454,11 @@ int Monitor::remove_from_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + pos = response.find("\"id\": "); + if (pos == std::string::npos) return -1; std::string handle_id = response.substr(pos + 6, 16); endpoint += "/"; endpoint += handle_id; @@ -3457,8 +3473,10 @@ int Monitor::remove_from_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); - Warning(response.c_str()); - curl_easy_cleanup(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + Debug(1, response.c_str()); + curl_easy_cleanup(curl); + return 0; } From 7a5a05fe9428cd95664fc57d2a27bf981e2dbe10 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 01:29:32 -0600 Subject: [PATCH 284/501] Clean up warnings --- src/zm_monitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 774884454..3b8c47a25 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3420,7 +3420,7 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); res = curl_easy_perform(curl); if (res != CURLE_OK) return -1; - Debug(1,response.c_str()); + Debug(1,"Added stream to Janus: %s", response.c_str()); curl_easy_cleanup(curl); return 0; } @@ -3476,7 +3476,7 @@ int Monitor::remove_from_janus() { res = curl_easy_perform(curl); if (res != CURLE_OK) return -1; - Debug(1, response.c_str()); + Debug(1, "Removed stream from Janus: %s", response.c_str()); curl_easy_cleanup(curl); return 0; } From 84a73bb0078dd7c650a13c81a166452140865b98 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 09:37:48 -0600 Subject: [PATCH 285/501] Add check to prevent tight busy loop if ONVIF setup fails --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 3b8c47a25..59dc90ead 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3140,7 +3140,7 @@ int Monitor::PrimeCapture() { #ifdef WITH_GSOAP //For now, just don't run the thread if no ONVIF support. This may change if we add other long polling options. //ONVIF Thread - if (onvif_event_listener) { + if (onvif_event_listener && ONVIF_Healthy) { if (!Poller) { Poller = zm::make_unique(this); } else { From 134d80a91d65f699d54cbefabddd41545b870b49 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 11:32:08 -0500 Subject: [PATCH 286/501] Replace Requires with BindsTo and add comment explaining how to override --- distros/ubuntu2004/zoneminder.service | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/distros/ubuntu2004/zoneminder.service b/distros/ubuntu2004/zoneminder.service index cb2d6791e..6645fea04 100644 --- a/distros/ubuntu2004/zoneminder.service +++ b/distros/ubuntu2004/zoneminder.service @@ -5,7 +5,8 @@ Description=ZoneMinder CCTV recording and surveillance system After=network.target mysql.service # Remarked out so that it will start ZM on machines that don't have mysql installed -#Requires=mysql.service +# Override it by placing an override.conf in /etc/systemd/system/zoneminder.service.d +#BindsTo=mysql.service [Service] #User=www-data From c9db5f44a4a210e81e1da69b3c2db2d4f1b3f2e9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 12:05:29 -0500 Subject: [PATCH 287/501] cherry pick Add check to prevent tight busy loop if ONVIF setup fails --- src/zm_monitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4feea00fe..958410efe 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1077,7 +1077,7 @@ bool Monitor::connect() { //ONVIF Setup #ifdef WITH_GSOAP ONVIF_Trigger_State = FALSE; - if (onvif_event_listener) { //Temporarily using this option to enable the feature + if (onvif_event_listener) { Debug(1, "Starting ONVIF"); ONVIF_Healthy = FALSE; if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message @@ -3130,7 +3130,7 @@ int Monitor::PrimeCapture() { #ifdef WITH_GSOAP //For now, just don't run the thread if no ONVIF support. This may change if we add other long polling options. //ONVIF Thread - if (onvif_event_listener) { + if (onvif_event_listener && ONVIF_Healthy) { if (!Poller) { Poller = zm::make_unique(this); } else { From a9f7b257ea0444f1d5c685bca3188cf3a73d5bd2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 17:16:49 -0500 Subject: [PATCH 288/501] Rough in a queue and a thread into the event to process packets. We do this so that the event creator can get back to analysis as fast as possible so as to avoid the packetqueue filling up. --- src/zm_event.cpp | 205 +++++++++++++++++++++++++++-------------------- src/zm_event.h | 20 +++++ 2 files changed, 138 insertions(+), 87 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 7db8f84f3..f1dce36d3 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -65,7 +65,8 @@ Event::Event( last_db_frame(0), have_video_keyframe(false), //scheme - save_jpegs(0) + save_jpegs(0), + terminate_(false) { std::string notes; createNotes(notes); @@ -133,98 +134,17 @@ Event::Event( ); id = zmDbDoInsert(sql); - if (!SetPath(storage)) { - // Try another - Warning("Failed creating event dir at %s", storage->Path()); - sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id()); - if (monitor->ServerId()) - sql += stringtf(" AND ServerId=%u", monitor->ServerId()); - - storage = nullptr; - - MYSQL_RES *result = zmDbFetch(sql); - if (result) { - for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { - storage = new Storage(atoi(dbrow[0])); - if (SetPath(storage)) - break; - delete storage; - storage = nullptr; - } // end foreach row of Storage - mysql_free_result(result); - result = nullptr; - } - if (!storage) { - Info("No valid local storage area found. Trying all other areas."); - // Try remote - sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL"; - if (monitor->ServerId()) - sql += stringtf(" OR ServerId != %u", monitor->ServerId()); - - result = zmDbFetch(sql); - if (result) { - for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { - storage = new Storage(atoi(dbrow[0])); - if (SetPath(storage)) - break; - delete storage; - storage = nullptr; - } // end foreach row of Storage - mysql_free_result(result); - result = nullptr; - } - } - if (!storage) { - storage = new Storage(); - Warning("Failed to find a storage area to save events."); - } - sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id); - zmDbDo(sql); - } // end if ! setPath(Storage) - Debug(1, "Using storage area at %s", path.c_str()); - - snapshot_file = path + "/snapshot.jpg"; - alarm_file = path + "/alarm.jpg"; - - video_incomplete_path = path + "/" + video_incomplete_file; - - if (monitor->GetOptVideoWriter() != 0) { - /* Save as video */ - - videoStore = new VideoStore( - video_incomplete_path.c_str(), - container.c_str(), - monitor->GetVideoStream(), - monitor->GetVideoCodecContext(), - ( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ), - ( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ), - monitor ); - - if ( !videoStore->open() ) { - Warning("Failed to open videostore, turning on jpegs"); - delete videoStore; - videoStore = nullptr; - if ( ! ( save_jpegs & 1 ) ) { - save_jpegs |= 1; // Turn on jpeg storage - sql = stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id); - zmDbDo(sql); - } - } else { - std::string codec = videoStore->get_codec(); - video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str()); - video_path = path + "/" + video_file; - Debug(1, "Video file is %s", video_file.c_str()); - } - } // end if GetOptVideoWriter - if (storage != monitor->getStorage()) - delete storage; + thread_ = std::thread(&Event::Run, this); } Event::~Event() { - // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. + + Stop(); + if (thread_.joinable()) thread_.join(); /* Close the video file */ + // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. if (videoStore != nullptr) { Debug(4, "Deleting video store"); delete videoStore; @@ -377,6 +297,12 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { } // void Event::updateNotes(const StringSetMap &newNoteSetMap) void Event::AddPacket(const std::shared_ptr&packet) { + std::unique_lock lck(packet_queue_mutex); + packet_queue.push(packet); + packet_queue_condition.notify_one(); +} + +void Event::AddPacket_(const std::shared_ptr&packet) { have_video_keyframe = have_video_keyframe || ( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) && ( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) ); @@ -655,3 +581,108 @@ bool Event::SetPath(Storage *storage) { } // deep storage or not return true; } // end bool Event::SetPath + +void Event::Run() { + Storage *storage = monitor->getStorage(); + if (!SetPath(storage)) { + // Try another + Warning("Failed creating event dir at %s", storage->Path()); + + std::string sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id()); + if (monitor->ServerId()) + sql += stringtf(" AND ServerId=%u", monitor->ServerId()); + + storage = nullptr; + + MYSQL_RES *result = zmDbFetch(sql); + if (result) { + for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { + storage = new Storage(atoi(dbrow[0])); + if (SetPath(storage)) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; + } + if (!storage) { + Info("No valid local storage area found. Trying all other areas."); + // Try remote + sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL"; + if (monitor->ServerId()) + sql += stringtf(" OR ServerId != %u", monitor->ServerId()); + + result = zmDbFetch(sql); + if (result) { + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + storage = new Storage(atoi(dbrow[0])); + if (SetPath(storage)) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; + } + } + if (!storage) { + storage = new Storage(); + Warning("Failed to find a storage area to save events."); + } + sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id); + zmDbDo(sql); + } // end if ! setPath(Storage) + Debug(1, "Using storage area at %s", path.c_str()); + + snapshot_file = path + "/snapshot.jpg"; + alarm_file = path + "/alarm.jpg"; + + video_incomplete_path = path + "/" + video_incomplete_file; + + if (monitor->GetOptVideoWriter() != 0) { + /* Save as video */ + + videoStore = new VideoStore( + video_incomplete_path.c_str(), + container.c_str(), + monitor->GetVideoStream(), + monitor->GetVideoCodecContext(), + ( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ), + ( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ), + monitor ); + + if ( !videoStore->open() ) { + Warning("Failed to open videostore, turning on jpegs"); + delete videoStore; + videoStore = nullptr; + if ( ! ( save_jpegs & 1 ) ) { + save_jpegs |= 1; // Turn on jpeg storage + zmDbDo(stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id)); + } + } else { + std::string codec = videoStore->get_codec(); + video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str()); + video_path = path + "/" + video_file; + Debug(1, "Video file is %s", video_file.c_str()); + } + } // end if GetOptVideoWriter + if (storage != monitor->getStorage()) + delete storage; + + + std::unique_lock lck(packet_queue_mutex); + + // The idea is to process the queue no matter what so that all packets get processed. + // We only break if the queue is empty + while (true) { + if (!packet_queue.empty()) { + this->AddPacket_(packet_queue.front()); + packet_queue.pop(); + } else { + if (terminate_ or zm_terminate) + break; + packet_queue_condition.wait(lck); + } + } +} diff --git a/src/zm_event.h b/src/zm_event.h index 4fb5c7469..447bdeafc 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -22,14 +22,21 @@ #include "zm_config.h" #include "zm_define.h" +#include "zm_packet.h" #include "zm_storage.h" #include "zm_time.h" #include "zm_utils.h" #include "zm_zone.h" +#include +#include #include +#include +#include #include #include +#include + class EventStream; class Frame; @@ -98,6 +105,15 @@ class Event { void createNotes(std::string ¬es); + std::queue> packet_queue; + std::mutex packet_queue_mutex; + std::condition_variable packet_queue_condition; + + void Run(); + + std::atomic terminate_; + std::thread thread_; + public: static bool OpenFrameSocket(int); static bool ValidateFrameSocket(int); @@ -118,6 +134,7 @@ class Event { SystemTimePoint EndTime() const { return end_time; } void AddPacket(const std::shared_ptr &p); + void AddPacket_(const std::shared_ptr &p); bool WritePacket(const std::shared_ptr &p); bool SendFrameImage(const Image *image, bool alarm_frame=false); bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const; @@ -130,6 +147,9 @@ class Event { int score = 0, Image *alarm_image = nullptr); + void Stop() { terminate_ = true; } + bool Stopped() const { return terminate_; } + private: void WriteDbFrames(); bool SetPath(Storage *storage); From 8d061750242b35dc4429ed5cb69771d9cf83d301 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:07:48 -0500 Subject: [PATCH 289/501] Rework to use ZoneMinder::Monitor class. Simplify loadMonitors and get rid of loadMonitor. Add in ServerId change handling. --- scripts/zmtrigger.pl.in | 62 ++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 83fcf29f5..94f59e001 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -42,6 +42,7 @@ use constant SELECT_TIMEOUT => 0.25; @EXTRA_PERL_LIB@ use ZoneMinder; +use ZoneMinder::Monitor; use ZoneMinder::Trigger::Channel::Inet; use ZoneMinder::Trigger::Channel::Unix; use ZoneMinder::Trigger::Channel::Serial; @@ -203,14 +204,10 @@ while (!$zm_terminate) { # Check for alarms that might have happened my @out_messages; foreach my $monitor ( values %monitors ) { - if ($$monitor{Function} eq 'None') { - $monitor_reload_time = 0; - next; - } - - if (!zmMemVerify($monitor)) { + if (!$monitor->connect()) { # Our attempt to verify the memory handle failed. We should reload the monitors. # Don't need to zmMemInvalidate because the monitor reload will do it. + Debug("Failed connect, putting on reloads"); push @needsReload, $monitor; next; } @@ -249,6 +246,7 @@ while (!$zm_terminate) { } $monitor->{LastState} = $state; $monitor->{LastEvent} = $last_event; + $monitor->disconnect(); } # end foreach monitor foreach my $connection ( @out_connections ) { @@ -298,15 +296,22 @@ while (!$zm_terminate) { # Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) { - foreach my $monitor ( values(%monitors) ) { - zmMemInvalidate( $monitor ); # Free up any used memory handle - } loadMonitors(); @needsReload = (); # We just reloaded all monitors so no need reload a specific monitor # If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed - } elsif ( @needsReload ) { - foreach my $monitor ( @needsReload ) { - loadMonitor($monitor); + } elsif (@needsReload) { + foreach my $monitor (@needsReload) { + $monitor = $monitors{$monitor->Id()} = ZoneMinder::Monitor->find_one(Id=>$monitor->Id()); + if ( $$monitor{Function} eq 'None' ) { + delete $monitors{$monitor->Id()}; + } elsif ( $Config{ZM_SERVER_ID} and ($$monitor{ServerId} != $Config{ZM_SERVER_ID})) { + delete $monitors{$monitor->Id()}; + } else { + if ($monitor->connect()) { + $monitor->{LastState} = zmGetMonitorState($monitor); + $monitor->{LastEvent} = zmGetLastEvent($monitor); + } + } } @needsReload = (); } @@ -317,40 +322,21 @@ while (!$zm_terminate) { Info('Trigger daemon exiting'); exit; -sub loadMonitor { - my $monitor = shift; - - Debug('Loading monitor '.$monitor); - zmMemInvalidate($monitor); - - if ( zmMemVerify($monitor) ) { # This will re-init shared memory - $monitor->{LastState} = zmGetMonitorState($monitor); - $monitor->{LastEvent} = zmGetLastEvent($monitor); - } -} # end sub loadMonitor - sub loadMonitors { $monitor_reload_time = time(); - my %new_monitors = (); + %monitors = (); - my $sql = 'SELECT * FROM `Monitors` - WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'. - ( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' ) - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) - or Fatal( "Can't execute: ".$sth->errstr() ); - - while ( my $monitor = $sth->fetchrow_hashref() ) { - if ( zmMemVerify($monitor) ) { # This will re-init shared memory + foreach my $monitor ( ZoneMinder::Monitor->find( + Function=>['Modect','Mocord','Nodect','Record'], + ($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()), + )) { + if ($monitor->connect()) { # This will re-init shared memory $monitor->{LastState} = zmGetMonitorState($monitor); $monitor->{LastEvent} = zmGetLastEvent($monitor); } - $new_monitors{$monitor->{Id}} = $monitor; + $monitors{$monitor->{Id}} = $monitor; } # end while fetchrow - %monitors = %new_monitors; } # end sub loadMonitors sub handleMessage { From e4693c251c598d92be0c54f54e8b41e69893fdb2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:08:29 -0500 Subject: [PATCH 290/501] add backticks around field names because some like Function are reserved --- scripts/ZoneMinder/lib/ZoneMinder/Object.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index e54bb15ce..d64ee7e8a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -639,9 +639,9 @@ $log->debug("Have array for $k $$search{$k}") if DEBUG_ALL; if ( ! ( $db_field =~ /\?/ ) ) { if ( @{$$search{$k}} != 1 ) { - push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; + push @where, '`'.$db_field .'` IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; } # end if } else { $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; @@ -656,10 +656,10 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; foreach my $p_k ( keys %{$$search{$k}} ) { my $v = $$search{$k}{$p_k}; if ( ref $v eq 'ARRAY' ) { - push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')'; + push @where, '`'.$db_field.'` IN ('.join(',', map {'?'} @{$v} ) . ')'; push @values, $p_k, @{$v}; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; push @values, $p_k, $v; } # end if } # end foreach p_k @@ -667,7 +667,7 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; push @where, $db_field.' IS NULL'; } else { if ( ! ( $db_field =~ /\?/ ) ) { - push @where, $db_field .'=?'; + push @where, '`'.$db_field .'`=?'; } else { push @where, $db_field; } From 3d042284857a69496b5404089546458494386b7e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:10:17 -0500 Subject: [PATCH 291/501] rework ::Analyse. The state engine will only go into alarm on actual motion, not on a skipped frame. TRIGGERS and ONVIF events will immediately create events. --- src/zm_monitor.cpp | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 958410efe..587c49f33 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1870,11 +1870,18 @@ bool Monitor::Analyse() { score += 9; Debug(1, "Triggered on ONVIF"); if (!event) { + Event::StringSet noteSet; + noteSet.insert("ONVIF2"); + noteSetMap[MOTION_CAUSE] = noteSet; + + event = openEvent(snap, "ONVIF", noteSetMap); cause += "ONVIF"; + } else { + event->addNote(MOTION_CAUSE, "ONVIF2"); +// Add to cause } - Event::StringSet noteSet; - noteSet.insert("ONVIF2"); - noteSetMap[MOTION_CAUSE] = noteSet; + // Regardless of previous state, we go to ALARM + shared_data->state = state = ALARM; //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. if (!ONVIF_Closes_Event && state == ALARM) ONVIF_Trigger_State = FALSE; @@ -1886,11 +1893,16 @@ bool Monitor::Analyse() { score += trigger_data->trigger_score; Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); if (!event) { - cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; + event = openEvent(snap, trigger_data->trigger_cause, noteSetMap); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } else { + event->addNote(trigger_data->trigger_cause, trigger_data->trigger_text); + // Need to know if we should end the previous and start a new one, or just add the data } - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; + shared_data->state = state = ALARM; } // end if trigger_on // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. @@ -1967,8 +1979,6 @@ bool Monitor::Analyse() { } // end while ! decoded } // end if decoding enabled - SystemTimePoint timestamp = snap->timestamp; - if (Active() and (function == MODECT or function == MOCORD)) { Debug(3, "signal and active and modect"); Event::StringSet zoneSet; @@ -1990,7 +2000,7 @@ bool Monitor::Analyse() { } else if (!(analysis_image_count % (motion_frame_skip+1))) { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. - int motion_score = DetectMotion(*(snap->image), zoneSet); + snap->score = DetectMotion(*(snap->image), zoneSet); // lets construct alarm cause. It will contain cause + names of zones alarmed snap->zone_stats.reserve(zones.size()); @@ -2004,11 +2014,11 @@ bool Monitor::Analyse() { } } Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", - score, last_motion_score, motion_score); + score, last_motion_score, snap->score); motion_frame_count += 1; - last_motion_score = motion_score; + last_motion_score = snap->score; - if (motion_score) { + if (snap->score) { if (cause.length()) cause += ", "; cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause; noteSetMap[MOTION_CAUSE] = zoneSet; @@ -2019,7 +2029,7 @@ bool Monitor::Analyse() { } else { Debug(1, "no image so skipping motion detection"); } // end if has image - score += last_motion_score; +//score += last_motion_score; } else { Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d", Active(), enabled, shared_data->active, @@ -2032,17 +2042,17 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length) + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) - || std::chrono::duration_cast(timestamp.time_since_epoch()) % section_length == Seconds(0))) { + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , name.c_str(), image_count, event->Id(), - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); } // end if section_length @@ -2060,13 +2070,14 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if (score and (function != MONITOR)) { + if ((snap->score > 0) and (function != MONITOR)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() && !event->AlarmFrames() && (event_close_mode == CLOSE_ALARM) - && ((timestamp - event->StartTime()) >= min_section_length) + // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead + && ((snap->timestamp - event->StartTime()) >= min_section_length) && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", name.c_str(), image_count, event->Id()); @@ -2078,7 +2089,7 @@ bool Monitor::Analyse() { Event::PreAlarmCount(), pre_event_count, event->Frames(), event->AlarmFrames(), - static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), static_cast(Seconds(min_section_length).count()), (event_close_mode == CLOSE_ALARM)); } @@ -2088,12 +2099,10 @@ bool Monitor::Analyse() { if (!event) { event = openEvent(snap, cause, noteSetMap); - shared_data->state = state = ALARM; Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } else { - shared_data->state = state = ALARM; } // end if no event, so start it - if ( alarm_frame_count ) { + shared_data->state = state = ALARM; + if (alarm_frame_count) { Debug(1, "alarm frame count so SavePreAlarmFrames"); event->SavePreAlarmFrames(); } @@ -2115,13 +2124,13 @@ bool Monitor::Analyse() { Debug(1, "Was in TAPE, going into ALARM"); } else { Debug(1, "Staying in %s", State_Strings[state].c_str()); - } if (state == ALARM) { last_alarm_count = analysis_image_count; } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else { // no score? + } else if (!score) { // no snap->score? alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count + if (state == ALARM) { Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); shared_data->state = state = ALERT; @@ -2129,7 +2138,7 @@ bool Monitor::Analyse() { if ( ((analysis_image_count - last_alarm_count) > post_event_count) && - ((timestamp - event->StartTime()) >= min_section_length)) { + ((snap->timestamp - event->StartTime()) >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); if ( @@ -2156,7 +2165,7 @@ bool Monitor::Analyse() { analysis_image_count, last_alarm_count, post_event_count, - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), static_cast(Seconds(min_section_length).count())); } @@ -2164,7 +2173,8 @@ bool Monitor::Analyse() { Event::EmptyPreAlarmFrames(); } // end if score or not - snap->score = score; + if (score > snap->score) + snap->score = score; if (state == PREALARM) { // Generate analysis images if necessary @@ -2181,7 +2191,7 @@ bool Monitor::Analyse() { } // end if image. // incremement pre alarm image count - Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr); + Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); } else if (state == ALARM) { if (snap->image) { for (const Zone &zone : zones) { @@ -2198,12 +2208,12 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)) { + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); event = openEvent(snap, cause, noteSetMap); From f764cfbc329612d7dee4a822ab2af8d557f2a911 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:11:19 -0500 Subject: [PATCH 292/501] Fix deadlock on deleting event. Cannot race for the lock, joining will do the job --- src/zm_event.cpp | 19 ++++++++++++++----- src/zm_event.h | 5 ++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index f1dce36d3..e14c1a343 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -134,14 +134,19 @@ Event::Event( ); id = zmDbDoInsert(sql); - thread_ = std::thread(&Event::Run, this); } Event::~Event() { - + Debug(1, "Deleting event, calling stop"); Stop(); - if (thread_.joinable()) thread_.join(); + if (thread_.joinable()) { + // Should be. Issuing the stop and then getting the lock + Debug(1, "Joinable"); + thread_.join(); + } else { + Debug(1, "Not Joinable"); + } /* Close the video file */ // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. @@ -670,19 +675,23 @@ void Event::Run() { if (storage != monitor->getStorage()) delete storage; - std::unique_lock lck(packet_queue_mutex); // The idea is to process the queue no matter what so that all packets get processed. // We only break if the queue is empty while (true) { if (!packet_queue.empty()) { + Debug(1, "adding packet"); this->AddPacket_(packet_queue.front()); packet_queue.pop(); } else { - if (terminate_ or zm_terminate) + if (terminate_ or zm_terminate) { +Debug(1, "terminating"); break; + } +Debug(1, "waiting"); packet_queue_condition.wait(lck); +Debug(1, "wakeing"); } } } diff --git a/src/zm_event.h b/src/zm_event.h index 447bdeafc..be6d922de 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -147,7 +147,10 @@ class Event { int score = 0, Image *alarm_image = nullptr); - void Stop() { terminate_ = true; } + void Stop() { + terminate_ = true; + packet_queue_condition.notify_all(); + } bool Stopped() const { return terminate_; } private: From 2beffeb1b433aa159ca2c20203d55400ab4d46ef Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 22:46:26 -0600 Subject: [PATCH 293/501] Add Janus to Cycle view. Remove debug Alert() messages --- src/zm_monitor.cpp | 6 ++- web/skins/classic/views/cycle.php | 2 + web/skins/classic/views/js/cycle.js | 65 +++++++++++++++++++++++++ web/skins/classic/views/js/cycle.js.php | 3 +- web/skins/classic/views/js/montage.js | 4 +- web/skins/classic/views/js/watch.js | 4 +- 6 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4b136e1aa..a612e84c3 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1117,12 +1117,13 @@ bool Monitor::connect() { //End ONVIF Setup #endif - //janus setup. +#if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { if (add_to_janus() != 0) { Warning("Failed to add monitor stream to Janus!"); } } +#endif } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); @@ -3206,10 +3207,11 @@ int Monitor::Close() { soap = nullptr; } //End ONVIF #endif - //Janus Teardown +#if HAVE_LIBCURL //Janus Teardown if (janus_enabled && (purpose == CAPTURE)) { remove_from_janus(); } +#endif packetqueue.clear(); if (audio_fifo) { diff --git a/web/skins/classic/views/cycle.php b/web/skins/classic/views/cycle.php index b9f779c50..279d9bba5 100644 --- a/web/skins/classic/views/cycle.php +++ b/web/skins/classic/views/cycle.php @@ -192,4 +192,6 @@ xhtmlHeaders(__FILE__, translate('CycleWatch')); + + diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 767fad475..401e36760 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -1,3 +1,6 @@ +var server; +var janus = null; +var streaming2; var intervalId; var pauseBtn = $j('#pauseBtn'); var playBtn = $j('#playBtn'); @@ -46,6 +49,68 @@ function initCycle() { intervalId = setInterval(nextCycleView, cycleRefreshTimeout); var scale = $j('#scale').val(); if ( scale == '0' || scale == 'auto' ) changeScale(); + + if (monitorData[monIdx].janusEnabled) { + server = "http://" + window.location.hostname + ":8088/janus"; + opaqueId = "streamingtest-"+Janus.randomString(12); + Janus.init({debug: "all", callback: function() { + janus = new Janus({ + server: server, + success: function() { + janus.attach({ + plugin: "janus.plugin.streaming", + opaqueId: opaqueId, + success: function(pluginHandle) { + streaming2 = pluginHandle; + var body = { "request": "watch", "id":monitorData[monIdx].id }; + streaming2.send({"message": body}); + }, + error: function(error) { + Janus.error(" -- Error attaching plugin... ", error); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::"); + Janus.debug(msg); + var result = msg["result"]; + if(result !== null && result !== undefined) { + if(result["status"] !== undefined && result["status"] !== null) { + var status = result["status"]; + } + } else if(msg["error"] !== undefined && msg["error"] !== null) { + Janus.debug(msg["error"]); + return; + } + if(jsep !== undefined && jsep !== null) { + Janus.debug("Handling SDP as well..."); + Janus.debug(jsep); + // Offer from the plugin, let's answer + streaming2.createAnswer({ + jsep: jsep, + // We want recvonly audio/video and, if negotiated, datachannels + media: { audioSend: false, videoSend: false, data: true }, + success: function(jsep) { + Janus.debug("Got SDP!"); + Janus.debug(jsep); + var body = { "request": "start"}; + streaming2.send({"message": body, "jsep": jsep}); + }, + error: function(error) { + Janus.error("WebRTC error:", error); + } + }); + } + }, //onmessage function + onremotestream: function(stream) { + Janus.debug(" ::: Got a remote track :::"); + Janus.debug(stream); + Janus.attachMediaStream(document.getElementById("liveStream" + monitorData[monIdx].id), stream); + document.getElementById("liveStream" + monitorData[monIdx].id).play(); + } + });// attach + } //Success functio + }); //new Janus + }}); //janus.init callback + } //if janus } function changeSize() { diff --git a/web/skins/classic/views/js/cycle.js.php b/web/skins/classic/views/js/cycle.js.php index 7bcd4497c..201deea76 100644 --- a/web/skins/classic/views/js/cycle.js.php +++ b/web/skins/classic/views/js/cycle.js.php @@ -20,7 +20,8 @@ monitorData[monitorData.length] = { 'url': 'UrlToIndex() ?>', 'onclick': function(){window.location.assign( '?view=watch&mid=Id() ?>' );}, 'type': 'Type() ?>', - 'refresh': 'Refresh() ?>' + 'refresh': 'Refresh() ?>', + 'janusEnabled': JanusEnabled() ?> }; Date: Thu, 13 Jan 2022 09:37:54 -0500 Subject: [PATCH 294/501] update description to reflect that zmupdate.pl now does all the things it will someday do. Meantion how -c works better. When in interactive mode, check once and print out the result instead of daemonising. Fix formatting osf usage --- scripts/zmupdate.pl.in | 51 ++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 0253fadc2..095218940 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -2,7 +2,7 @@ # # ========================================================================== # -# ZoneMinder Update Script, $Date$, $Revision$ +# ZoneMinder Update Script # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -31,29 +31,29 @@ zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u , --version= - Force upgrade to the current version from --u , --user= - Alternate DB user with privileges to alter DB --p , --pass= - Password of alternate DB user with privileges to alter DB --s, --super - Use system maintenance account on debian based systems instead of unprivileged account --d , --dir= - Directory containing update files if not in default build location --interactive - interact with the user --nointeractive - do not interact with the user + -c, --check - Check for updated versions of ZoneMinder. + If not interactive zmupdate.pl will stay running, checking every hour. + If interactive will try once, print out result and quit. + -f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi + --migrate-events - Update database structures as per USE_DEEP_STORAGE setting. + -v , --version= - Force upgrade to the current version from + -u , --user= - Alternate DB user with privileges to alter DB + -p , --pass= - Password of alternate DB user with privileges to alter DB + -s, --super - Use system maintenance account on debian based systems instead of unprivileged account + -d , --dir= - Directory containing update files if not in default build location + -interactive - interact with the user + -nointeractive - do not interact with the user =cut use strict; use bytes; use version; -use Crypt::Eksblowfish::Bcrypt; -use Data::Entropy::Algorithms qw(rand_bits); # ========================================================================== # @@ -122,9 +122,8 @@ GetOptions( ) or pod2usage(-exitstatus => -1); my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } ); -if ( !$dbh ) { - die "Unable to connect to db\n"; -} +die "Unable to connect to db\n" if !$dbh; + $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; # we escape dbpass with single quotes so that $ in the password has no effect, but dbpass could have a ' in it. @@ -181,6 +180,7 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { Info('Got version: '.$lastVersion); + my $lv_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\''; my $lv_sth = $dbh->prepare_cached($lv_sql) or die("Can't prepare '$lv_sql': ".$dbh->errstr()); my $lv_res = $lv_sth->execute($lastVersion) or die("Can't execute: ".$lv_sth->errstr()); @@ -190,6 +190,11 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { my $lc_sth = $dbh->prepare_cached($lc_sql) or die("Can't prepare '$lc_sql': ".$dbh->errstr()); my $lc_res = $lc_sth->execute($lastCheck) or die("Can't execute: ".$lc_sth->errstr()); $lc_sth->finish(); + if ($interactive) { +print("Hello"); + print("Latest version $lastVersion, our version " . ZM_VERSION."\n"); + exit(0); + } } else { Error('Error check failed: \''.$res->status_line().'\''); } @@ -1044,14 +1049,16 @@ sub patchDB { } # end sub patchDB sub migratePasswords { - print ("Migratings passwords, if any...\n"); + use Crypt::Eksblowfish::Bcrypt; + use Data::Entropy::Algorithms qw(rand_bits); + print("Migratings passwords, if any...\n"); my $sql = 'SELECT * FROM `Users`'; my $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die("Can't execute: ".$sth->errstr()); - while( my $user = $sth->fetchrow_hashref() ) { + while ( my $user = $sth->fetchrow_hashref() ) { my $scheme = substr($user->{Password}, 0, 1); if ($scheme eq '*') { - print ('-->'.$user->{Username}." password will be migrated\n"); + print('-->'.$user->{Username}." password will be migrated\n"); my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8)); my $settings = '$2a$10$'.$salt; my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings); From 3ec6c7e32fc17e2025966ba5efe3655b9630a6cf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 13 Jan 2022 09:54:36 -0500 Subject: [PATCH 295/501] Fix behaviour of update check to support interactive mode. Use zmDbDo functions to simplify code. When interactive print out lastVersion, latestVersion and currentVersion --- scripts/zmupdate.pl.in | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 095218940..9ad5a1a02 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -52,6 +52,7 @@ It can also apply and configure upgrades etc, including on the fly upgrades. =cut use strict; +use warnings; use bytes; use version; @@ -143,8 +144,10 @@ if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) pod2usage(-exitstatus => -1); } -if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { - print('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n"); +if ($check and ($Config{ZM_CHECK_FOR_UPDATES} or $interactive) ) { + if (!$interactive) { + Info('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n"); + } my $currVersion = $Config{ZM_DYN_CURR_VERSION}; my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; @@ -152,16 +155,14 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { if ( !$currVersion ) { $currVersion = $Config{ZM_VERSION}; - - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; - my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute($currVersion) or die("Can't execute: ".$sth->errstr()); - $sth->finish(); + zmDbDo("UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_CURR_VERSION'", $currVersion); } while ( 1 ) { my $now = time(); - if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) { + if ( !$interactive and $lastVersion and $lastCheck and (($now-$lastCheck) <= CHECK_INTERVAL) ) { + Debug("Not checking for updates since we already have less than " . CHECK_INTERVAL . " seconds ago."); + } else { Info('Checking for updates'); use LWP::UserAgent; @@ -174,25 +175,16 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { my $res = $ua->request($req); if ( $res->is_success ) { - $lastVersion = $res->content; - chomp($lastVersion); + my $latestVersion = $res->content; + chomp($latestVersion); $lastCheck = $now; - Info('Got version: '.$lastVersion); + Info('Got version: '.$latestVersion); - - my $lv_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\''; - my $lv_sth = $dbh->prepare_cached($lv_sql) or die("Can't prepare '$lv_sql': ".$dbh->errstr()); - my $lv_res = $lv_sth->execute($lastVersion) or die("Can't execute: ".$lv_sth->errstr()); - $lv_sth->finish(); - - my $lc_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\''; - my $lc_sth = $dbh->prepare_cached($lc_sql) or die("Can't prepare '$lc_sql': ".$dbh->errstr()); - my $lc_res = $lc_sth->execute($lastCheck) or die("Can't execute: ".$lc_sth->errstr()); - $lc_sth->finish(); + zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'', $latestVersion); + zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\'', $lastCheck); if ($interactive) { -print("Hello"); - print("Latest version $lastVersion, our version " . ZM_VERSION."\n"); + print("Last version $lastVersion, Latest version $latestVersion, our version " . ZM_VERSION."\n"); exit(0); } } else { From 58bd09d83d674e5920c3fe781087035bf814fe6a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 13 Jan 2022 09:54:47 -0500 Subject: [PATCH 296/501] Fix debug output from zmDbDo --- dep/RtspServer | 2 +- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dep/RtspServer b/dep/RtspServer index 1b40f1661..cd7fd49be 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit 1b40f1661f93f50fd5805f239d1e466a3bcf888f +Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878 diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 35d2dc6fa..ae1259814 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -273,6 +273,9 @@ sub zmDbDo { if ( ! defined $rows ) { $sql =~ s/\?/'%s'/; Error(sprintf("Failed $sql :", @_).$dbh->errstr()); + } elsif ( ZoneMinder::Logger::logLevel() > INFO ) { + $sql =~ s/\?/'%s'/; + Debug(sprintf("Succeeded $sql : $rows rows affected", @_)); } return $rows; } From 9b8ca69203f9407c5dd13d17212074c9ff3cfa7b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 13 Jan 2022 10:21:32 -0500 Subject: [PATCH 297/501] default interactive to whether we have stdio. So now when running from console it will default to interactive and when running from zmdc.pl will be non-interactive. Do check regardless of ZM_UPDATE_CHECK setting. --- scripts/zmupdate.pl.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 9ad5a1a02..d096c70b5 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -96,7 +96,7 @@ my $use_log = (($> == 0) || ($> == $web_uid)); logInit( toFile=>$use_log?DEBUG:NOLOG ); logSetSignal(); -my $interactive = 1; +my $interactive = -t STDERR; # interactive if we have IO my $check = 0; my $freshen = 0; my $rename = 0; @@ -144,7 +144,7 @@ if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) pod2usage(-exitstatus => -1); } -if ($check and ($Config{ZM_CHECK_FOR_UPDATES} or $interactive) ) { +if ($check) { if (!$interactive) { Info('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n"); } From 7c1ba721aa66b91cfff19193b6b62b81075b9480 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 13 Jan 2022 22:14:14 -0500 Subject: [PATCH 298/501] Preface Debug with ZM --- web/includes/csrf/csrf-magic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/csrf/csrf-magic.php b/web/includes/csrf/csrf-magic.php index b102c565a..10d200240 100644 --- a/web/includes/csrf/csrf-magic.php +++ b/web/includes/csrf/csrf-magic.php @@ -348,7 +348,7 @@ return false; return $value === csrf_hash($_COOKIE[$n], $time); case 'key': if (!$GLOBALS['csrf']['key']) { - Debug("Checking key: no key set" ); + ZM\Debug("Checking key: no key set" ); return false; } #Debug("Checking sid: $value === " . csrf_hash($GLOBALS['csrf']['key'], $time) ); From 9240a93ef501658fb1869433437bdfb4730629fb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:09:18 -0500 Subject: [PATCH 299/501] Revert my idea of having ONVIF and TRIGGER code start/end events. Move code to tst for Ready() up above ONVIF and TRIGGER code. When waiting for decode, junk the idea of locking in the packetqueue. Since waiting releases the lock, we should be ok waiting and notifying in the packet. (I know there was a time when I felt this was necessary and I think it had to do with the deinterlacing case, but I've simplified that code too). --- src/zm_monitor.cpp | 128 ++++++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d5865a842..bc7ee0484 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1839,6 +1839,12 @@ bool Monitor::Analyse() { packetqueue.increment_it(analysis_it); return false; } + // Ready means that we have captured the warmup # of frames + if (!Ready()) { + Debug(3, "Not ready?"); + delete packet_lock; + return false; + } // signal is set by capture bool signal = shared_data->signal; @@ -1853,54 +1859,36 @@ bool Monitor::Analyse() { // if we have been told to be OFF, then we are off and don't do any processing. if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); - // Ready means that we have captured the warmup # of frames - if (!Ready()) { - Debug(3, "Not ready?"); - delete packet_lock; - return false; - } int score = 0; std::string cause; Event::StringSetMap noteSetMap; #ifdef WITH_GSOAP - if (ONVIF_Trigger_State) { - score += 9; - Debug(1, "Triggered on ONVIF"); - if (!event) { + if (onvif_event_listener && ONVIF_Healthy) { + if (ONVIF_Trigger_State) { + score += 9; + Debug(1, "Triggered on ONVIF"); Event::StringSet noteSet; noteSet.insert("ONVIF2"); noteSetMap[MOTION_CAUSE] = noteSet; - - event = openEvent(snap, "ONVIF", noteSetMap); cause += "ONVIF"; - } else { - event->addNote(MOTION_CAUSE, "ONVIF2"); -// Add to cause - } - // Regardless of previous state, we go to ALARM - shared_data->state = state = ALARM; - //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. - if (!ONVIF_Closes_Event && state == ALARM) - ONVIF_Trigger_State = FALSE; - } // end ONVIF_Trigger + //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. + if (!ONVIF_Closes_Event && state == ALARM) + ONVIF_Trigger_State = FALSE; + } // end ONVIF_Trigger + } // end if (onvif_event_listener && ONVIF_Healthy) #endif - // Specifically told to be on. Setting the score here will trigger the alarm. + // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { score += trigger_data->trigger_score; Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); - if (!event) { - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; - event = openEvent(snap, trigger_data->trigger_cause, noteSetMap); - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } else { - event->addNote(trigger_data->trigger_cause, trigger_data->trigger_text); - // Need to know if we should end the previous and start a new one, or just add the data - } + if (!cause.empty()) cause += ", "; + cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; shared_data->state = state = ALARM; } // end if trigger_on @@ -1968,14 +1956,21 @@ bool Monitor::Analyse() { /* try to stay behind the decoder. */ if (decoding_enabled) { - if (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { + while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { // Need to wait for the decoder thread. + // decoder thread might be waiting on the lock for this packet. + // So we need to relinquish the lock and wait. Waiting automatically relinquishes the lock + // So... Debug(1, "Waiting for decode"); - packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all - packetqueue.wait(); - // Everything may have changed, just return and start again. This needs to be more RAII - return true; + packet_lock->wait(); + //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all + //packetqueue.wait(); + ////packet_lock->lock(); } // end while ! decoded + if (zm_terminate or analysis_thread->Stopped()) { + delete packet_lock; + return false; + } } // end if decoding enabled if (Active() and (function == MODECT or function == MOCORD)) { @@ -1990,7 +1985,6 @@ bool Monitor::Analyse() { } if (snap->image) { - // decoder may not have been able to provide an image if (!ref_image.Buffer()) { Debug(1, "Assigning instead of Detecting"); @@ -2024,8 +2018,9 @@ bool Monitor::Analyse() { if (snap->score) { if (cause.length()) cause += ", "; - cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause; + cause += MOTION_CAUSE + std::string(":") + snap->alarm_cause; noteSetMap[MOTION_CAUSE] = zoneSet; + score += snap->score; } // end if motion_score } else { Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); @@ -2069,13 +2064,15 @@ bool Monitor::Analyse() { Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", name.c_str(), analysis_image_count, event->Id()); /* To prevent cancelling out an existing alert\prealarm\alarm state */ + // This ignores current score status. This should all come after the state machine calculations if (state == IDLE) { shared_data->state = state = TAPE; } } // end if ! event } // end if RECORDING - if ((snap->score > 0) and (function != MONITOR)) { + // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score + if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() @@ -2133,8 +2130,8 @@ bool Monitor::Analyse() { if (state == ALARM) { last_alarm_count = analysis_image_count; } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else if (!score and snap->score == 0) { // snap->score means -1 which means didn't do motion detection so don't do state transition - Debug(1, "!score"); + } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition + Debug(1, "!score %s", State_Strings[state].c_str()); alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count if (state == ALARM) { @@ -2238,17 +2235,17 @@ bool Monitor::Analyse() { // In the case where people have pre-alarm frames, the web ui will generate the frame images // from the mp4. So no one will notice anyways. if (snap->image and (videowriter == PASSTHROUGH)) { - if (!savejpegs) { - Debug(1, "Deleting image data for %d", snap->image_index); - // Don't need raw images anymore - delete snap->image; - snap->image = nullptr; - } - if (snap->analysis_image and !(savejpegs & 2)) { - Debug(1, "Deleting analysis image data for %d", snap->image_index); - delete snap->analysis_image; - snap->analysis_image = nullptr; - } + if (!savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; + } + if (snap->analysis_image and !(savejpegs & 2)) { + Debug(1, "Deleting analysis image data for %d", snap->image_index); + delete snap->analysis_image; + snap->analysis_image = nullptr; + } } packetqueue.clearPackets(snap); @@ -2571,7 +2568,8 @@ bool Monitor::Decode() { std::shared_ptr packet = packet_lock->packet_; if (packet->codec_type != AVMEDIA_TYPE_VIDEO) { Debug(4, "Not video"); - packetqueue.unlock(packet_lock); + //packetqueue.unlock(packet_lock); + delete packet_lock; return true; // Don't need decode } @@ -2677,24 +2675,31 @@ bool Monitor::Decode() { capture_image->Deinterlace_Blend(); } else if (deinterlacing_value == 4) { while (!zm_terminate) { + // ICON FIXME SHould we not clone decoder_it? ZMLockedPacket *deinterlace_packet_lock = packetqueue.get_packet(decoder_it); if (!deinterlace_packet_lock) { - packetqueue.unlock(packet_lock); + delete packet_lock; + //packetqueue.unlock(packet_lock); return false; } if (deinterlace_packet_lock->packet_->codec_type == packet->codec_type) { capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); - packetqueue.unlock(deinterlace_packet_lock); + delete deinterlace_packet_lock; + //packetqueue.unlock(deinterlace_packet_lock); break; } - packetqueue.unlock(deinterlace_packet_lock); + delete deinterlace_packet_lock; + //packetqueue.unlock(deinterlace_packet_lock); packetqueue.increment_it(decoder_it); } - if (zm_terminate) return false; + if (zm_terminate) { + delete packet_lock; + return false; + } } else if (deinterlacing_value == 5) { capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff); } - } + } // end if deinterlacing_value if (orientation != ROTATE_0) { Debug(3, "Doing rotation"); @@ -2731,7 +2736,8 @@ bool Monitor::Decode() { shared_data->signal = (capture_image and signal_check_points) ? CheckSignal(capture_image) : true; shared_data->last_write_index = index; shared_data->last_write_time = std::chrono::system_clock::to_time_t(packet->timestamp); - packetqueue.unlock(packet_lock); + delete packet_lock; + //packetqueue.unlock(packet_lock); return true; } // end bool Monitor::Decode() From 8b99b15b41b56e56d66ad37f48fc8616d64d3c77 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:17:07 -0500 Subject: [PATCH 300/501] Use a scope to shorten the lock on event_lock --- src/zm_monitor.cpp | 702 +++++++++++++++++++++++---------------------- 1 file changed, 352 insertions(+), 350 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index bc7ee0484..e8b1bb1c0 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1853,384 +1853,386 @@ bool Monitor::Analyse() { Debug(3, "Motion detection is enabled signal(%d) signal_change(%d) trigger state(%s) image index %d", signal, signal_change, TriggerState_Strings[trigger_data->trigger_state].c_str(), snap->image_index); - // Need to guard around event creation/deletion from Reload() - std::lock_guard lck(event_mutex); + { // scope for event lock + // Need to guard around event creation/deletion from Reload() + std::lock_guard lck(event_mutex); - // if we have been told to be OFF, then we are off and don't do any processing. - if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { - Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); + // if we have been told to be OFF, then we are off and don't do any processing. + if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { + Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); - int score = 0; - std::string cause; - Event::StringSetMap noteSetMap; + int score = 0; + std::string cause; + Event::StringSetMap noteSetMap; #ifdef WITH_GSOAP - if (onvif_event_listener && ONVIF_Healthy) { - if (ONVIF_Trigger_State) { - score += 9; - Debug(1, "Triggered on ONVIF"); - Event::StringSet noteSet; - noteSet.insert("ONVIF2"); - noteSetMap[MOTION_CAUSE] = noteSet; - cause += "ONVIF"; - //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. - if (!ONVIF_Closes_Event && state == ALARM) - ONVIF_Trigger_State = FALSE; - } // end ONVIF_Trigger - } // end if (onvif_event_listener && ONVIF_Healthy) + if (onvif_event_listener && ONVIF_Healthy) { + if (ONVIF_Trigger_State) { + score += 9; + Debug(1, "Triggered on ONVIF"); + Event::StringSet noteSet; + noteSet.insert("ONVIF2"); + noteSetMap[MOTION_CAUSE] = noteSet; + cause += "ONVIF"; + //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. + if (!ONVIF_Closes_Event && state == ALARM) + ONVIF_Trigger_State = FALSE; + } // end ONVIF_Trigger + } // end if (onvif_event_listener && ONVIF_Healthy) #endif - // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM - if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { - score += trigger_data->trigger_score; - Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); - if (!cause.empty()) cause += ", "; - cause += trigger_data->trigger_cause; - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; - shared_data->state = state = ALARM; - } // end if trigger_on + // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM + if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { + score += trigger_data->trigger_score; + Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); + if (!cause.empty()) cause += ", "; + cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; + shared_data->state = state = ALARM; + } // end if trigger_on - // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. - if (signal_change) { - Debug(2, "Signal change, new signal is %d", signal); - if (!signal) { - if (event) { - event->addNote(SIGNAL_CAUSE, "Lost"); - Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name.c_str(), analysis_image_count, event->Id()); - closeEvent(); + // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. + if (signal_change) { + Debug(2, "Signal change, new signal is %d", signal); + if (!signal) { + if (event) { + event->addNote(SIGNAL_CAUSE, "Lost"); + Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name.c_str(), analysis_image_count, event->Id()); + closeEvent(); + } + } else if (function == MOCORD or function == RECORD) { + if (!event) { + if (!cause.empty()) cause += ", "; + cause += SIGNAL_CAUSE + std::string(": Reacquired"); + } else { + event->addNote(SIGNAL_CAUSE, "Reacquired"); + } + if (snap->image) + ref_image.Assign(*(snap->image)); } - } else if (function == MOCORD or function == RECORD) { - if (!event) { - if (!cause.empty()) cause += ", "; - cause += SIGNAL_CAUSE + std::string(": Reacquired"); - } else { - event->addNote(SIGNAL_CAUSE, "Reacquired"); - } - if (snap->image) - ref_image.Assign(*(snap->image)); - } - shared_data->state = state = IDLE; - shared_data->active = signal; - } // end if signal change + shared_data->state = state = IDLE; + shared_data->active = signal; + } // end if signal change - if (signal) { - if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { - // Check to see if linked monitors are triggering. - if (n_linked_monitors > 0) { - Debug(1, "Checking linked monitors"); - // FIXME improve logic here - bool first_link = true; - Event::StringSet noteSet; - for (int i = 0; i < n_linked_monitors; i++) { - // TODO: Shouldn't we try to connect? - if (linked_monitors[i]->isConnected()) { - Debug(1, "Linked monitor %d %s is connected", - linked_monitors[i]->Id(), linked_monitors[i]->Name()); - if (linked_monitors[i]->hasAlarmed()) { - Debug(1, "Linked monitor %d %s is alarmed", + if (signal) { + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { + // Check to see if linked monitors are triggering. + if (n_linked_monitors > 0) { + Debug(1, "Checking linked monitors"); + // FIXME improve logic here + bool first_link = true; + Event::StringSet noteSet; + for (int i = 0; i < n_linked_monitors; i++) { + // TODO: Shouldn't we try to connect? + if (linked_monitors[i]->isConnected()) { + Debug(1, "Linked monitor %d %s is connected", linked_monitors[i]->Id(), linked_monitors[i]->Name()); - if (!event) { - if (first_link) { - if (cause.length()) - cause += ", "; - cause += LINKED_CAUSE; - first_link = false; + if (linked_monitors[i]->hasAlarmed()) { + Debug(1, "Linked monitor %d %s is alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + if (!event) { + if (first_link) { + if (cause.length()) + cause += ", "; + cause += LINKED_CAUSE; + first_link = false; + } + } + noteSet.insert(linked_monitors[i]->Name()); + score += linked_monitors[i]->lastFrameScore(); // 50; + } else { + Debug(1, "Linked monitor %d %s is not alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + } + } else { + Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id()); + linked_monitors[i]->connect(); + } + } // end foreach linked_monitor + if (noteSet.size() > 0) + noteSetMap[LINKED_CAUSE] = noteSet; + } // end if linked_monitors + + /* try to stay behind the decoder. */ + if (decoding_enabled) { + while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { + // Need to wait for the decoder thread. + // decoder thread might be waiting on the lock for this packet. + // So we need to relinquish the lock and wait. Waiting automatically relinquishes the lock + // So... + Debug(1, "Waiting for decode"); + packet_lock->wait(); + //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all + //packetqueue.wait(); + ////packet_lock->lock(); + } // end while ! decoded + if (zm_terminate or analysis_thread->Stopped()) { + delete packet_lock; + return false; + } + } // end if decoding enabled + + if (Active() and (function == MODECT or function == MOCORD)) { + Debug(3, "signal and active and modect"); + Event::StringSet zoneSet; + + if (analysis_fps_limit) { + double capture_fps = get_capture_fps(); + motion_frame_skip = capture_fps / analysis_fps_limit; + Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)", + motion_frame_skip, capture_fps, analysis_fps_limit); + } + + if (snap->image) { + // decoder may not have been able to provide an image + if (!ref_image.Buffer()) { + Debug(1, "Assigning instead of Detecting"); + ref_image.Assign(*(snap->image)); + alarm_image.Assign(*(snap->image)); + } else if (!(analysis_image_count % (motion_frame_skip+1))) { + Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); + // Get new score. + snap->score = DetectMotion(*(snap->image), zoneSet); + + if (!snap->analysis_image) + snap->analysis_image = new Image(*(snap->image)); + // lets construct alarm cause. It will contain cause + names of zones alarmed + snap->zone_stats.reserve(zones.size()); + for (const Zone &zone : zones) { + const ZoneStats &stats = zone.GetStats(); + stats.DumpToLog("After detect motion"); + snap->zone_stats.push_back(stats); + if (zone.Alarmed()) { + if (!snap->alarm_cause.empty()) snap->alarm_cause += ","; + snap->alarm_cause += std::string(zone.Label()); + if (zone.AlarmImage()) + snap->analysis_image->Overlay(*(zone.AlarmImage())); } } - noteSet.insert(linked_monitors[i]->Name()); - score += linked_monitors[i]->lastFrameScore(); // 50; + alarm_image.Assign(*(snap->analysis_image)); + Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", + score, last_motion_score, snap->score); + motion_frame_count += 1; + last_motion_score = snap->score; + + if (snap->score) { + if (cause.length()) cause += ", "; + cause += MOTION_CAUSE + std::string(":") + snap->alarm_cause; + noteSetMap[MOTION_CAUSE] = zoneSet; + score += snap->score; + } // end if motion_score } else { - Debug(1, "Linked monitor %d %s is not alarmed", - linked_monitors[i]->Id(), linked_monitors[i]->Name()); + Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); + alarm_image.Assign(*(snap->image)); } } else { - Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id()); - linked_monitors[i]->connect(); - } - } // end foreach linked_monitor - if (noteSet.size() > 0) - noteSetMap[LINKED_CAUSE] = noteSet; - } // end if linked_monitors - - /* try to stay behind the decoder. */ - if (decoding_enabled) { - while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { - // Need to wait for the decoder thread. - // decoder thread might be waiting on the lock for this packet. - // So we need to relinquish the lock and wait. Waiting automatically relinquishes the lock - // So... - Debug(1, "Waiting for decode"); - packet_lock->wait(); - //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all - //packetqueue.wait(); - ////packet_lock->lock(); - } // end while ! decoded - if (zm_terminate or analysis_thread->Stopped()) { - delete packet_lock; - return false; - } - } // end if decoding enabled - - if (Active() and (function == MODECT or function == MOCORD)) { - Debug(3, "signal and active and modect"); - Event::StringSet zoneSet; - - if (analysis_fps_limit) { - double capture_fps = get_capture_fps(); - motion_frame_skip = capture_fps / analysis_fps_limit; - Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)", - motion_frame_skip, capture_fps, analysis_fps_limit); - } - - if (snap->image) { - // decoder may not have been able to provide an image - if (!ref_image.Buffer()) { - Debug(1, "Assigning instead of Detecting"); - ref_image.Assign(*(snap->image)); - alarm_image.Assign(*(snap->image)); - } else if (!(analysis_image_count % (motion_frame_skip+1))) { - Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); - // Get new score. - snap->score = DetectMotion(*(snap->image), zoneSet); - - if (!snap->analysis_image) - snap->analysis_image = new Image(*(snap->image)); - // lets construct alarm cause. It will contain cause + names of zones alarmed - snap->zone_stats.reserve(zones.size()); - for (const Zone &zone : zones) { - const ZoneStats &stats = zone.GetStats(); - stats.DumpToLog("After detect motion"); - snap->zone_stats.push_back(stats); - if (zone.Alarmed()) { - if (!snap->alarm_cause.empty()) snap->alarm_cause += ","; - snap->alarm_cause += std::string(zone.Label()); - if (zone.AlarmImage()) - snap->analysis_image->Overlay(*(zone.AlarmImage())); - } - } - alarm_image.Assign(*(snap->analysis_image)); - Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", - score, last_motion_score, snap->score); - motion_frame_count += 1; - last_motion_score = snap->score; - - if (snap->score) { - if (cause.length()) cause += ", "; - cause += MOTION_CAUSE + std::string(":") + snap->alarm_cause; - noteSetMap[MOTION_CAUSE] = zoneSet; - score += snap->score; - } // end if motion_score - } else { - Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); - alarm_image.Assign(*(snap->image)); - } + Debug(1, "no image so skipping motion detection"); + } // end if has image + //score += last_motion_score; } else { - Debug(1, "no image so skipping motion detection"); - } // end if has image -//score += last_motion_score; - } else { - Debug(1, "Not Active(%d) enabled %d shared->active %d doing motion detection: %d", - Active(), enabled, shared_data->active, - (function == MODECT or function == MOCORD) - ); - } // end if active and doing motion detection + Debug(1, "Not Active(%d) enabled %d shared->active %d doing motion detection: %d", + Active(), enabled, shared_data->active, + (function == MODECT or function == MOCORD) + ); + } // end if active and doing motion detection - if (function == RECORD or function == MOCORD) { - // If doing record, check to see if we need to close the event or not. - if (event) { - Debug(2, "Have event %" PRIu64 " in record", event->Id()); + if (function == RECORD or function == MOCORD) { + // If doing record, check to see if we need to close the event or not. + if (event) { + Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) - && ((function == MOCORD && event_close_mode != CLOSE_TIME) - || (function == RECORD && event_close_mode == CLOSE_TIME) - || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { - Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , - name.c_str(), - image_count, - event->Id(), - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), - static_cast(Seconds(section_length).count())); - closeEvent(); - } // end if section_length - } // end if event - - if (!event) { - event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); - - Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", - name.c_str(), analysis_image_count, event->Id()); - /* To prevent cancelling out an existing alert\prealarm\alarm state */ - // This ignores current score status. This should all come after the state machine calculations - if (state == IDLE) { - shared_data->state = state = TAPE; - } - } // end if ! event - } // end if RECORDING - - // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score - if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { - if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { - // If we should end then previous continuous event and start a new non-continuous event - if (event && event->Frames() - && !event->AlarmFrames() - && (event_close_mode == CLOSE_ALARM) - // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead - && ((snap->timestamp - event->StartTime()) >= min_section_length) - && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { - Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", - name.c_str(), image_count, event->Id()); - closeEvent(); - } else if (event) { - // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames - Debug(3, - "pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d", - Event::PreAlarmCount(), pre_event_count, - event->Frames(), - event->AlarmFrames(), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), - static_cast(Seconds(min_section_length).count()), - (event_close_mode == CLOSE_ALARM)); - } - if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { - Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", - name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); - - if (!event) { - event = openEvent(snap, cause, noteSetMap); - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } // end if no event, so start it - shared_data->state = state = ALARM; - if (alarm_frame_count) { - Debug(1, "alarm frame count so SavePreAlarmFrames"); - event->SavePreAlarmFrames(); - } - } else if (state != PREALARM) { - Info("%s: %03d - Gone into prealarm state", name.c_str(), analysis_image_count); - shared_data->state = state = PREALARM; - } - } else if (state == ALERT) { - alert_to_alarm_frame_count--; - Info("%s: %03d - Alarmed frame while in alert state. Consecutive alarmed frames left to return to alarm state: %03d", - name.c_str(), analysis_image_count, alert_to_alarm_frame_count); - if (alert_to_alarm_frame_count == 0) { - Info("%s: %03d - Gone back into alarm state", name.c_str(), analysis_image_count); - shared_data->state = state = ALARM; - } - } else if (state == TAPE) { - // Already recording, but IDLE so switch to ALARM - shared_data->state = state = ALARM; - Debug(1, "Was in TAPE, going into ALARM"); - } else { - Debug(1, "Staying in %s", State_Strings[state].c_str()); - } - if (state == ALARM) { - last_alarm_count = analysis_image_count; - } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition - Debug(1, "!score %s", State_Strings[state].c_str()); - alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count - - if (state == ALARM) { - Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); - shared_data->state = state = ALERT; - } else if (state == ALERT) { - if ( - ((analysis_image_count - last_alarm_count) > post_event_count) - && - ((snap->timestamp - event->StartTime()) >= min_section_length)) { - Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", - name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); - if ( - (function != RECORD && function != MOCORD) - || - (event_close_mode == CLOSE_ALARM || event_close_mode==CLOSE_IDLE) - ) { - shared_data->state = state = IDLE; - Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", - name.c_str(), analysis_image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) + && ((function == MOCORD && event_close_mode != CLOSE_TIME) + || (function == RECORD && event_close_mode == CLOSE_TIME) + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , + name.c_str(), + image_count, + event->Id(), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(Seconds(section_length).count())); closeEvent(); - } else { + } // end if section_length + } // end if event + + if (!event) { + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); + + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", + name.c_str(), analysis_image_count, event->Id()); + /* To prevent cancelling out an existing alert\prealarm\alarm state */ + // This ignores current score status. This should all come after the state machine calculations + if (state == IDLE) { shared_data->state = state = TAPE; } + } // end if ! event + } // end if RECORDING + + // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score + if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { + if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { + // If we should end then previous continuous event and start a new non-continuous event + if (event && event->Frames() + && !event->AlarmFrames() + && (event_close_mode == CLOSE_ALARM) + // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead + && ((snap->timestamp - event->StartTime()) >= min_section_length) + && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { + Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", + name.c_str(), image_count, event->Id()); + closeEvent(); + } else if (event) { + // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames + Debug(3, + "pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d", + Event::PreAlarmCount(), pre_event_count, + event->Frames(), + event->AlarmFrames(), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(Seconds(min_section_length).count()), + (event_close_mode == CLOSE_ALARM)); + } + if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { + Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", + name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); + + if (!event) { + event = openEvent(snap, cause, noteSetMap); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } // end if no event, so start it + shared_data->state = state = ALARM; + if (alarm_frame_count) { + Debug(1, "alarm frame count so SavePreAlarmFrames"); + event->SavePreAlarmFrames(); + } + } else if (state != PREALARM) { + Info("%s: %03d - Gone into prealarm state", name.c_str(), analysis_image_count); + shared_data->state = state = PREALARM; + } + } else if (state == ALERT) { + alert_to_alarm_frame_count--; + Info("%s: %03d - Alarmed frame while in alert state. Consecutive alarmed frames left to return to alarm state: %03d", + name.c_str(), analysis_image_count, alert_to_alarm_frame_count); + if (alert_to_alarm_frame_count == 0) { + Info("%s: %03d - Gone back into alarm state", name.c_str(), analysis_image_count); + shared_data->state = state = ALARM; + } + } else if (state == TAPE) { + // Already recording, but IDLE so switch to ALARM + shared_data->state = state = ALARM; + Debug(1, "Was in TAPE, going into ALARM"); + } else { + Debug(1, "Staying in %s", State_Strings[state].c_str()); } - } else if (state == PREALARM) { - // Back to IDLE - shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); - } else { - Debug(1, - "State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", - state, - State_Strings[state].c_str(), - analysis_image_count, - last_alarm_count, - post_event_count, - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(Seconds(min_section_length).count())); - } - if (Event::PreAlarmCount()) - Event::EmptyPreAlarmFrames(); - } // end if score or not + if (state == ALARM) { + last_alarm_count = analysis_image_count; + } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT + } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition + Debug(1, "!score %s", State_Strings[state].c_str()); + alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count - if (score > snap->score) - snap->score = score; + if (state == ALARM) { + Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); + shared_data->state = state = ALERT; + } else if (state == ALERT) { + if ( + ((analysis_image_count - last_alarm_count) > post_event_count) + && + ((snap->timestamp - event->StartTime()) >= min_section_length)) { + Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", + name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); + if ( + (function != RECORD && function != MOCORD) + || + (event_close_mode == CLOSE_ALARM || event_close_mode==CLOSE_IDLE) + ) { + shared_data->state = state = IDLE; + Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", + name.c_str(), analysis_image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); + closeEvent(); + } else { + shared_data->state = state = TAPE; + } + } + } else if (state == PREALARM) { + // Back to IDLE + shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); + } else { + Debug(1, + "State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", + state, + State_Strings[state].c_str(), + analysis_image_count, + last_alarm_count, + post_event_count, + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(Seconds(min_section_length).count())); + } + if (Event::PreAlarmCount()) + Event::EmptyPreAlarmFrames(); + } // end if score or not - if (state == PREALARM) { - // incremement pre alarm image count - Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); - } else if (state == ALARM) { - if (event) { - if (noteSetMap.size() > 0) + if (score > snap->score) + snap->score = score; + + if (state == PREALARM) { + // incremement pre alarm image count + Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); + } else if (state == ALARM) { + if (event) { + if (noteSetMap.size() > 0) + event->updateNotes(noteSetMap); + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { + Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, + name.c_str(), analysis_image_count, event->Id(), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), + static_cast(Seconds(section_length).count())); + closeEvent(); + event = openEvent(snap, cause, noteSetMap); + } + } else { + Error("ALARM but no event"); + } + } else if (state == ALERT) { + // Alert means this frame has no motion, but we were alarmed and are still recording. + if ((noteSetMap.size() > 0) and event) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { - Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, - name.c_str(), analysis_image_count, event->Id(), - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), - static_cast(Seconds(section_length).count())); - closeEvent(); - event = openEvent(snap, cause, noteSetMap); + } else if (state == TAPE) { + // bulk frame code moved to event. + } // end if state machine + + if ((function == MODECT or function == MOCORD) and snap->image) { + if (!ref_image.Buffer()) { + Debug(1, "Assigning"); + ref_image.Assign(*(snap->image)); + } else { + Debug(1, "Blending"); + ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + Debug(1, "Done Blending"); } - } else { - Error("ALARM but no event"); } - } else if (state == ALERT) { - // Alert means this frame has no motion, but we were alarmed and are still recording. - if ((noteSetMap.size() > 0) and event) - event->updateNotes(noteSetMap); - } else if (state == TAPE) { - // bulk frame code moved to event. - } // end if state machine + last_signal = signal; + } // end if videostream + } // end if signal + shared_data->last_frame_score = score; + } else { + Debug(3, "trigger == off"); + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name.c_str(), analysis_image_count, event->Id()); + closeEvent(); + } + shared_data->state = state = IDLE; + } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - if ((function == MODECT or function == MOCORD) and snap->image) { - if (!ref_image.Buffer()) { - Debug(1, "Assigning"); - ref_image.Assign(*(snap->image)); - } else { - Debug(1, "Blending"); - ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); - Debug(1, "Done Blending"); - } - } - last_signal = signal; - } // end if videostream - } // end if signal - shared_data->last_frame_score = score; - } else { - Debug(3, "trigger == off"); - if (event) { - Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name.c_str(), analysis_image_count, event->Id()); - closeEvent(); - } - shared_data->state = state = IDLE; - } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - - if (event) event->AddPacket(snap); + if (event) event->AddPacket(snap); + } // end scope for event_lock // In the case where people have pre-alarm frames, the web ui will generate the frame images // from the mp4. So no one will notice anyways. From eb59b4de7f920236520724598afd26e8eb503415 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:20:17 -0500 Subject: [PATCH 301/501] Clear out dead code --- src/zm_event.h | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/zm_event.h b/src/zm_event.h index be6d922de..74955d23f 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -178,18 +178,6 @@ class Event { return pre_alarm_count; } static void EmptyPreAlarmFrames() { -#if 0 - while ( pre_alarm_count > 0 ) { - int i = pre_alarm_count - 1; - delete pre_alarm_data[i].image; - pre_alarm_data[i].image = nullptr; - if ( pre_alarm_data[i].alarm_frame ) { - delete pre_alarm_data[i].alarm_frame; - pre_alarm_data[i].alarm_frame = nullptr; - } - pre_alarm_count--; - } -#endif pre_alarm_count = 0; } static void AddPreAlarmFrame( @@ -198,28 +186,10 @@ class Event { int score=0, Image *alarm_frame=nullptr ) { -#if 0 - pre_alarm_data[pre_alarm_count].image = new Image(*image); - pre_alarm_data[pre_alarm_count].timestamp = timestamp; - pre_alarm_data[pre_alarm_count].score = score; - if ( alarm_frame ) { - pre_alarm_data[pre_alarm_count].alarm_frame = new Image(*alarm_frame); - } -#endif pre_alarm_count++; } void SavePreAlarmFrames() { -#if 0 - for ( int i = 0; i < pre_alarm_count; i++ ) { - AddFrame( - pre_alarm_data[i].image, - pre_alarm_data[i].timestamp, - pre_alarm_data[i].score, - pre_alarm_data[i].alarm_frame); - } -#endif EmptyPreAlarmFrames(); } }; - #endif // ZM_EVENT_H From 1fe4bbedc0e2e810ec5d036e73d00e9766975636 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:35:16 -0500 Subject: [PATCH 302/501] use min_section_length instead of 0 when testing minimumm section length. Use event->Duration instead of snap-event->StartTime because this snap will not be included in the event. This results in events of 29.59 seconds instead of 30 seconds. --- src/zm_monitor.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index e8b1bb1c0..4a707889f 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2043,7 +2043,7 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) + if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { @@ -2080,7 +2080,7 @@ bool Monitor::Analyse() { && !event->AlarmFrames() && (event_close_mode == CLOSE_ALARM) // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead - && ((snap->timestamp - event->StartTime()) >= min_section_length) + && (event->Duration() >= min_section_length) && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", name.c_str(), image_count, event->Id()); @@ -2092,7 +2092,7 @@ bool Monitor::Analyse() { Event::PreAlarmCount(), pre_event_count, event->Frames(), event->AlarmFrames(), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(event->Duration()).count()), static_cast(Seconds(min_section_length).count()), (event_close_mode == CLOSE_ALARM)); } @@ -2142,7 +2142,7 @@ bool Monitor::Analyse() { if ( ((analysis_image_count - last_alarm_count) > post_event_count) && - ((snap->timestamp - event->StartTime()) >= min_section_length)) { + (event->Duration() >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); if ( @@ -2187,12 +2187,12 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { + if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->Duration()).count()), static_cast(Seconds(section_length).count())); closeEvent(); event = openEvent(snap, cause, noteSetMap); From a4b0aa442a655337c808bde8d0dcabcc0b82851f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:35:26 -0500 Subject: [PATCH 303/501] Introduce event->Duration() --- src/zm_event.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_event.h b/src/zm_event.h index 74955d23f..31a23fad4 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -132,6 +132,7 @@ class Event { SystemTimePoint StartTime() const { return start_time; } SystemTimePoint EndTime() const { return end_time; } + TimePoint::duration Duration() const { return end_time - start_time; }; void AddPacket(const std::shared_ptr &p); void AddPacket_(const std::shared_ptr &p); From 3296e142644e6d6aae0dde02b18a6576088d6622 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 15:19:28 -0500 Subject: [PATCH 304/501] Move RECORD/MOCORD length close code after the state machine. If the state machine has taken action, this code likely won't trigger, but the way it was before it could ignore current state. Plus I think it logically keeps relevant logic closer together. Must use packetqueue.unlock after decoding as analysis might be waiting in the packetqueue. Doing this will do both notifies. Disable de-interlacing for decoding style monitors. --- src/zm_monitor.cpp | 76 ++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4a707889f..96d082f71 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2038,39 +2038,6 @@ bool Monitor::Analyse() { ); } // end if active and doing motion detection - if (function == RECORD or function == MOCORD) { - // If doing record, check to see if we need to close the event or not. - if (event) { - Debug(2, "Have event %" PRIu64 " in record", event->Id()); - - if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) - && ((function == MOCORD && event_close_mode != CLOSE_TIME) - || (function == RECORD && event_close_mode == CLOSE_TIME) - || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { - Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , - name.c_str(), - image_count, - event->Id(), - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), - static_cast(Seconds(section_length).count())); - closeEvent(); - } // end if section_length - } // end if event - - if (!event) { - event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); - - Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", - name.c_str(), analysis_image_count, event->Id()); - /* To prevent cancelling out an existing alert\prealarm\alarm state */ - // This ignores current score status. This should all come after the state machine calculations - if (state == IDLE) { - shared_data->state = state = TAPE; - } - } // end if ! event - } // end if RECORDING // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { @@ -2208,6 +2175,40 @@ bool Monitor::Analyse() { // bulk frame code moved to event. } // end if state machine + if (function == RECORD or function == MOCORD) { + // If doing record, check to see if we need to close the event or not. + if (event) { + Debug(2, "Have event %" PRIu64 " in record", event->Id()); + + if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) + && ((function == MOCORD && event_close_mode != CLOSE_TIME) + || (function == RECORD && event_close_mode == CLOSE_TIME) + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , + name.c_str(), + image_count, + event->Id(), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(Seconds(section_length).count())); + closeEvent(); + } // end if section_length + } // end if event + + if (!event) { + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); + + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", + name.c_str(), analysis_image_count, event->Id()); + /* To prevent cancelling out an existing alert\prealarm\alarm state */ + // This ignores current score status. This should all come after the state machine calculations + if (state == IDLE) { + shared_data->state = state = TAPE; + } + } // end if ! event + } // end if RECORDING + if ((function == MODECT or function == MOCORD) and snap->image) { if (!ref_image.Buffer()) { Debug(1, "Assigning"); @@ -2685,7 +2686,11 @@ bool Monitor::Decode() { return false; } if (deinterlace_packet_lock->packet_->codec_type == packet->codec_type) { - capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); + if (!deinterlace_packet_lock->packet_->image) { + Error("Can't de-interlace when we have to decode. De-Interlacing should only be useful on old low res local cams"); + } else { + capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); + } delete deinterlace_packet_lock; //packetqueue.unlock(deinterlace_packet_lock); break; @@ -2738,8 +2743,7 @@ bool Monitor::Decode() { shared_data->signal = (capture_image and signal_check_points) ? CheckSignal(capture_image) : true; shared_data->last_write_index = index; shared_data->last_write_time = std::chrono::system_clock::to_time_t(packet->timestamp); - delete packet_lock; - //packetqueue.unlock(packet_lock); + packetqueue.unlock(packet_lock); return true; } // end bool Monitor::Decode() From aba387f20255cbc8766056d793899c3cafb9a5fa Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 16:31:52 -0500 Subject: [PATCH 305/501] Fix !== which should be >= --- src/zm_monitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 96d082f71..18f72f6b3 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2154,7 +2154,7 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length)) { + if (section_length >= Seconds(min_section_length) && (event->Duration() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), @@ -2180,7 +2180,7 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) + if (section_length >= Seconds(min_section_length) && (event->Duration() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { From 8d757d37a6a85f6b8eeef4b5fa47d02bdc927f0e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 16:43:59 -0500 Subject: [PATCH 306/501] fix eslint --- web/skins/classic/views/js/cycle.js | 17 ++-- web/skins/classic/views/js/montage.js | 102 +++++++++++------------ web/skins/classic/views/js/watch.js | 115 +++++++++++++------------- 3 files changed, 118 insertions(+), 116 deletions(-) diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 401e36760..670671faa 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -62,7 +62,7 @@ function initCycle() { opaqueId: opaqueId, success: function(pluginHandle) { streaming2 = pluginHandle; - var body = { "request": "watch", "id":monitorData[monIdx].id }; + var body = {"request": "watch", "id": monitorData[monIdx].id}; streaming2.send({"message": body}); }, error: function(error) { @@ -72,26 +72,27 @@ function initCycle() { Janus.debug(" ::: Got a message :::"); Janus.debug(msg); var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { - var status = result["status"]; + if (result !== null && result !== undefined) { + if (result["status"] !== undefined && result["status"] !== null) { + const status = result["status"]; + console.log(status); } - } else if(msg["error"] !== undefined && msg["error"] !== null) { + } else if (msg["error"] !== undefined && msg["error"] !== null) { Janus.debug(msg["error"]); return; } - if(jsep !== undefined && jsep !== null) { + if (jsep !== undefined && jsep !== null) { Janus.debug("Handling SDP as well..."); Janus.debug(jsep); // Offer from the plugin, let's answer streaming2.createAnswer({ jsep: jsep, // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, + media: {audioSend: false, videoSend: false, data: true}, success: function(jsep) { Janus.debug("Got SDP!"); Janus.debug(jsep); - var body = { "request": "start"}; + var body = {"request": "start"}; streaming2.send({"message": body, "jsep": jsep}); }, error: function(error) { diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index d02fb63f8..369f23e8f 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -307,7 +307,7 @@ function initPage() { $j("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up'); } var initJanus = false; - var streamingMonitors = []; + //var streamingMonitors = []; for ( var i = 0, length = monitorData.length; i < length; i++ ) { if (monitorData[i].janusEnabled) { initJanus = true; @@ -353,57 +353,57 @@ function watchFullscreen() { } function attachVideo(janus, i) { - janus.attach({ - plugin: "janus.plugin.streaming", - opaqueId:"streamingtest-"+Janus.randomString(12), - success: function(pluginHandle) { - janusMonitors[i].streaming = pluginHandle; - var body = { "request": "watch", "id":parseInt(janusMonitors[i].id) }; - janusMonitors[i].streaming.send({"message": body}); - }, - error: function(error) { - Janus.error(" -- Error attaching plugin... ", error); - }, - onmessage: function(msg, jsep) { - Janus.debug(" ::: Got a message :::"); - Janus.debug(msg); - var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { - var status = result["status"]; - } - } else if(msg["error"] !== undefined && msg["error"] !== null) { - Janus.error(msg["error"]); - return; - } - if(jsep !== undefined && jsep !== null) { - Janus.debug("Handling SDP as well..."); - Janus.debug(jsep); - // Offer from the plugin, let's answer - janusMonitors[i].streaming.createAnswer({ - jsep: jsep, - // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, - success: function(jsep) { - Janus.debug("Got SDP!"); - Janus.debug(jsep); - var body = { "request": "start"}; - janusMonitors[i].streaming.send({"message": body, "jsep": jsep}); - }, - error: function(error) { - Janus.error("WebRTC error:", error); - } - }); - } - }, //onmessage function - onremotestream: function(ourstream) { - Janus.debug(" ::: Got a remote track :::"); - Janus.debug(ourstream); - Janus.attachMediaStream(document.getElementById("liveStream" + janusMonitors[i].id), ourstream); - document.getElementById("liveStream" + janusMonitors[i].id).play() + janus.attach({ + plugin: "janus.plugin.streaming", + opaqueId: "streamingtest-"+Janus.randomString(12), + success: function(pluginHandle) { + janusMonitors[i].streaming = pluginHandle; + var body = {"request": "watch", "id": parseInt(janusMonitors[i].id)}; + janusMonitors[i].streaming.send({"message": body}); + }, + error: function(error) { + Janus.error(" -- Error attaching plugin... ", error); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::"); + Janus.debug(msg); + var result = msg["result"]; + if (result !== null && result !== undefined) { + if (result["status"] !== undefined && result["status"] !== null) { + const status = result["status"]; + console.log(status); } - });// attach - + } else if (msg["error"] !== undefined && msg["error"] !== null) { + Janus.error(msg["error"]); + return; + } + if (jsep !== undefined && jsep !== null) { + Janus.debug("Handling SDP as well..."); + Janus.debug(jsep); + // Offer from the plugin, let's answer + janusMonitors[i].streaming.createAnswer({ + jsep: jsep, + // We want recvonly audio/video and, if negotiated, datachannels + media: {audioSend: false, videoSend: false, data: true}, + success: function(jsep) { + Janus.debug("Got SDP!"); + Janus.debug(jsep); + var body = {"request": "start"}; + janusMonitors[i].streaming.send({"message": body, "jsep": jsep}); + }, + error: function(error) { + Janus.error("WebRTC error:", error); + } + }); + } + }, //onmessage function + onremotestream: function(ourstream) { + Janus.debug(" ::: Got a remote track :::"); + Janus.debug(ourstream); + Janus.attachMediaStream(document.getElementById("liveStream" + janusMonitors[i].id), ourstream); + document.getElementById("liveStream" + janusMonitors[i].id).play(); + } + });// attach } // Kick everything off $j(document).ready(initPage); diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index f3ea6385f..13d54928a 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -911,65 +911,66 @@ function initPage() { } } if (streamMode == 'janus') { - server = "http://" + window.location.hostname + ":8088/janus"; - opaqueId = "streamingtest-"+Janus.randomString(12); - Janus.init({debug: "all", callback: function() { - janus = new Janus({ - server: server, - success: function() { - janus.attach({ - plugin: "janus.plugin.streaming", - opaqueId: opaqueId, - success: function(pluginHandle) { - streaming2 = pluginHandle; - var body = { "request": "watch", "id":monitorId }; - streaming2.send({"message": body}); - }, - error: function(error) { - Janus.error(" -- Error attaching plugin... ", error); - }, - onmessage: function(msg, jsep) { - Janus.debug(" ::: Got a message :::"); - Janus.debug(msg); - var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { - var status = result["status"]; - } - } else if(msg["error"] !== undefined && msg["error"] !== null) { - Janus.error(msg["error"]); - return; - } - if(jsep !== undefined && jsep !== null) { - Janus.debug("Handling SDP as well..."); - Janus.debug(jsep); - // Offer from the plugin, let's answer - streaming2.createAnswer({ - jsep: jsep, - // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, - success: function(jsep) { - Janus.debug("Got SDP!"); - Janus.debug(jsep); - var body = { "request": "start"}; - streaming2.send({"message": body, "jsep": jsep}); - }, - error: function(error) { - Janus.error("WebRTC error:", error); + server = "http://" + window.location.hostname + ":8088/janus"; + opaqueId = "streamingtest-"+Janus.randomString(12); + Janus.init({debug: "all", callback: function() { + janus = new Janus({ + server: server, + success: function() { + janus.attach({ + plugin: "janus.plugin.streaming", + opaqueId: opaqueId, + success: function(pluginHandle) { + streaming2 = pluginHandle; + var body = {"request": "watch", "id": monitorId}; + streaming2.send({"message": body}); + }, + error: function(error) { + Janus.error(" -- Error attaching plugin... ", error); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::"); + Janus.debug(msg); + var result = msg["result"]; + if (result !== null && result !== undefined) { + if (result["status"] !== undefined && result["status"] !== null) { + var status = result["status"]; + console.log(status); } - }); + } else if (msg["error"] !== undefined && msg["error"] !== null) { + Janus.error(msg["error"]); + return; + } + if (jsep !== undefined && jsep !== null) { + Janus.debug("Handling SDP as well..."); + Janus.debug(jsep); + // Offer from the plugin, let's answer + streaming2.createAnswer({ + jsep: jsep, + // We want recvonly audio/video and, if negotiated, datachannels + media: {audioSend: false, videoSend: false, data: true}, + success: function(jsep) { + Janus.debug("Got SDP!"); + Janus.debug(jsep); + var body = {"request": "start"}; + streaming2.send({"message": body, "jsep": jsep}); + }, + error: function(error) { + Janus.error("WebRTC error:", error); + } + }); + } + }, //onmessage function + onremotestream: function(stream) { + Janus.debug(" ::: Got a remote track :::"); + Janus.debug(stream); + Janus.attachMediaStream(document.getElementById("liveStream" + monitorId), stream); + document.getElementById("liveStream" + monitorId).play(); } - }, //onmessage function - onremotestream: function(stream) { - Janus.debug(" ::: Got a remote track :::"); - Janus.debug(stream); - Janus.attachMediaStream(document.getElementById("liveStream" + monitorId), stream); - document.getElementById("liveStream" + monitorId).play(); - } - });// attach - } //Success functio - }); //new Janus - }}); //janus.init callback + }); // attach + } //Success functio + }); //new Janus + }}); //janus.init callback } else if (canStreamNative || (streamMode == 'single')) { var streamImg = $j('#imageFeed img'); if (!streamImg) streamImg = $j('#imageFeed object'); From 59056b41038aaf2f3bbe2445ec54ae232e1b22ad Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 14 Jan 2022 23:14:43 -0600 Subject: [PATCH 307/501] Refactor Montage view js -- Janus in MonitorStream --- web/js/MonitorStream.js | 79 ++++++++++++++++++++ web/skins/classic/views/js/montage.js | 101 +++----------------------- 2 files changed, 88 insertions(+), 92 deletions(-) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index ef4d6db41..aaf5ac8fb 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -1,3 +1,5 @@ +var janus = null; +var streaming = []; function MonitorStream(monitorData) { this.id = monitorData.id; @@ -8,6 +10,7 @@ function MonitorStream(monitorData) { this.url_to_zms = monitorData.url_to_zms; this.width = monitorData.width; this.height = monitorData.height; + this.janusEnabled = monitorData.janusEnabled; this.scale = 100; this.status = null; this.alarmState = STATE_IDLE; @@ -80,6 +83,17 @@ function MonitorStream(monitorData) { const stream = this.getElement(); if (!stream) return; + if (this.janusEnabled) { + var id = parseInt(this.id); + var server = "http://" + window.location.hostname + ":8088/janus"; + if (janus == null) { + Janus.init({debug: "all", callback: function() { + janus = new Janus({server: server}); //new Janus + }}); + } + attachVideo(id); + return; + } if (!stream.src) { // Website Monitors won't have an img tag console.log('No src for #liveStream'+this.id); @@ -287,3 +301,68 @@ function MonitorStream(monitorData) { this.streamCmdReq(this.streamCmdParms); }; } // end function MonitorStream + +async function attachVideo(id) { + await waitUntil(() => janus.isConnected() ) + janus.attach({ + plugin: "janus.plugin.streaming", + opaqueId: "streamingtest-"+Janus.randomString(12), + success: function(pluginHandle) { + streaming[id] = pluginHandle; + var body = { "request": "watch", "id": id}; + streaming[id].send({"message": body}); + }, + error: function(error) { + Janus.error(" -- Error attaching plugin... ", error); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::"); + Janus.debug(msg); + var result = msg["result"]; + if(result !== null && result !== undefined) { + if(result["status"] !== undefined && result["status"] !== null) { + var status = result["status"]; + Janus.debug(status); + } + } else if(msg["error"] !== undefined && msg["error"] !== null) { + return; + } + if(jsep !== undefined && jsep !== null) { + Janus.debug("Handling SDP as well..."); + Janus.debug(jsep); + // Offer from the plugin, let's answer + streaming[id].createAnswer({ + jsep: jsep, + // We want recvonly audio/video and, if negotiated, datachannels + media: { audioSend: false, videoSend: false, data: true }, + success: function(jsep) { + Janus.debug("Got SDP!"); + Janus.debug(jsep); + var body = { "request": "start"}; + streaming[id].send({"message": body, "jsep": jsep}); + }, + error: function(error) { + Janus.error("WebRTC error:", error); + } + }); + } + }, //onmessage function + onremotestream: function(ourstream) { + Janus.debug(" ::: Got a remote track :::"); + Janus.debug(ourstream); + Janus.attachMediaStream(document.getElementById("liveStream" + id), ourstream); + } + }); // janus.attach +} //function attachVideo + +const waitUntil = (condition) => { + return new Promise((resolve) => { + let interval = setInterval(() => { + if (!condition()) { + return; + } + clearInterval(interval); + resolve(); + }, 100); + }); +} diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 369f23e8f..f6da7fbac 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -1,9 +1,3 @@ -var server; -var janus = null; -var opaqueId; -var globalCount = 0; -var streamingList = []; -var janusMonitors = []; /** * called when the layoutControl select element is changed, or the page * is rendered @@ -306,43 +300,19 @@ function initPage() { $j("#flipMontageHeader").slideToggle("fast"); $j("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up'); } - var initJanus = false; - //var streamingMonitors = []; for ( var i = 0, length = monitorData.length; i < length; i++ ) { - if (monitorData[i].janusEnabled) { - initJanus = true; - janusMonitors.push(monitorData[i]); - } - } - if (initJanus) { - server = "http://" + window.location.hostname + ":8088/janus"; - opaqueId = "streamingtest-"+Janus.randomString(12); - Janus.init({debug: "all", callback: function() { - janus = new Janus({ - server: server, - success: function() { - for ( var i = 0, length = janusMonitors.length; i < length; i++ ) { - attachVideo(janus, i); - } - } - }); - }}); - } - for ( var i = 0, length = monitorData.length; i < length; i++ ) { - if (!monitorData[i].janusEnabled) { - monitors[i] = new MonitorStream(monitorData[i]); + monitors[i] = new MonitorStream(monitorData[i]); - // Start the fps and status updates. give a random delay so that we don't assault the server - var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); - console.log("delay: " + delay); - monitors[i].start(delay); + // Start the fps and status updates. give a random delay so that we don't assault the server + var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); + console.log("delay: " + delay); + monitors[i].start(delay); - var interval = monitors[i].refresh; - if ( monitors[i].type == 'WebSite' && interval > 0 ) { - setInterval(reloadWebSite, interval*1000, i); - } - monitors[i].setup_onclick(); + var interval = monitors[i].refresh; + if ( monitors[i].type == 'WebSite' && interval > 0 ) { + setInterval(reloadWebSite, interval*1000, i); } + monitors[i].setup_onclick(); } selectLayout('#zmMontageLayout'); } @@ -352,58 +322,5 @@ function watchFullscreen() { openFullscreen(content); } -function attachVideo(janus, i) { - janus.attach({ - plugin: "janus.plugin.streaming", - opaqueId: "streamingtest-"+Janus.randomString(12), - success: function(pluginHandle) { - janusMonitors[i].streaming = pluginHandle; - var body = {"request": "watch", "id": parseInt(janusMonitors[i].id)}; - janusMonitors[i].streaming.send({"message": body}); - }, - error: function(error) { - Janus.error(" -- Error attaching plugin... ", error); - }, - onmessage: function(msg, jsep) { - Janus.debug(" ::: Got a message :::"); - Janus.debug(msg); - var result = msg["result"]; - if (result !== null && result !== undefined) { - if (result["status"] !== undefined && result["status"] !== null) { - const status = result["status"]; - console.log(status); - } - } else if (msg["error"] !== undefined && msg["error"] !== null) { - Janus.error(msg["error"]); - return; - } - if (jsep !== undefined && jsep !== null) { - Janus.debug("Handling SDP as well..."); - Janus.debug(jsep); - // Offer from the plugin, let's answer - janusMonitors[i].streaming.createAnswer({ - jsep: jsep, - // We want recvonly audio/video and, if negotiated, datachannels - media: {audioSend: false, videoSend: false, data: true}, - success: function(jsep) { - Janus.debug("Got SDP!"); - Janus.debug(jsep); - var body = {"request": "start"}; - janusMonitors[i].streaming.send({"message": body, "jsep": jsep}); - }, - error: function(error) { - Janus.error("WebRTC error:", error); - } - }); - } - }, //onmessage function - onremotestream: function(ourstream) { - Janus.debug(" ::: Got a remote track :::"); - Janus.debug(ourstream); - Janus.attachMediaStream(document.getElementById("liveStream" + janusMonitors[i].id), ourstream); - document.getElementById("liveStream" + janusMonitors[i].id).play(); - } - });// attach -} // Kick everything off $j(document).ready(initPage); From 4cd085e0852d29a5606bda6215665b5ca6df8f26 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 15 Jan 2022 17:18:55 -0500 Subject: [PATCH 308/501] Bump eslint to ECMA2017 --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0bb050ad1..16cbb37ba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { "env": { "browser": true, - "es6": true, + "es2017": true, }, "extends": ["google"], "overrides": [{ From 1891002c65e6e19a59604e06b11c7573901a8672 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 15 Jan 2022 17:19:10 -0500 Subject: [PATCH 309/501] Remove Freenode from the badges. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2249f1752..da96b3e60 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ ZoneMinder [![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bounty Source](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) [![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE) -[![IRC Network](https://img.shields.io/badge/irc-%23zoneminder-blue.svg "IRC Freenode")](https://webchat.freenode.net/?channels=zoneminder) All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org From 5f17cb6e9a8c65611eb1b3589a1d9b43f51be26e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 15 Jan 2022 17:19:30 -0500 Subject: [PATCH 310/501] Debug the size returned as compared to the file size --- src/zm_eventstream.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 927c3b3aa..54f6b60d8 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -1126,7 +1126,8 @@ bool EventStream::send_file(const std::string &filepath) { } else { Debug(1, "Failed to sendfile?"); } - Warning("Unable to send raw frame %ld: %s rc %d", curr_frame_id, strerror(errno), rc); + Warning("Unable to send raw frame %ld: %s rc %d != %d", + curr_frame_id, strerror(errno), rc, (int)filestat.st_size); #endif static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; From 688f505f28b218a071a7534813a6a447eba2da27 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 16 Jan 2022 19:44:52 -0600 Subject: [PATCH 311/501] Adds better error handling, changes expected location of janus.js --- src/zm_monitor.cpp | 29 ++++++++++++++++++++--------- web/skins/classic/views/cycle.php | 2 +- web/skins/classic/views/montage.php | 2 +- web/skins/classic/views/watch.php | 2 +- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index e638ed8c8..15a812f83 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1100,12 +1100,13 @@ bool Monitor::connect() { set_credentials(soap); Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { - Warning("Couldn't create subscription!"); + Error("Couldn't create subscription! %s, %s", soap_fault_string(soap), soap_fault_detail(soap)); } else { //Empty the stored messages set_credentials(soap); - if (proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) { - Warning("Couldn't do initial event pull! %s", response.SubscriptionReference.Address); + if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) && + ( soap->error != SOAP_EOF)) { //SOAP_EOF could indicate no messages to pull. + Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap)); } else { Debug(1, "Good Initial ONVIF Pull"); ONVIF_Healthy = TRUE; @@ -1120,7 +1121,9 @@ bool Monitor::connect() { #if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { if (add_to_janus() != 0) { - Warning("Failed to add monitor stream to Janus!"); + if (add_to_janus() != 0) { + Warning("Failed to add monitor stream to Janus!"); + } } } #endif @@ -1794,8 +1797,10 @@ bool Monitor::Poll() { set_credentials(soap); int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse); if (result != SOAP_OK) { - if (result != -1) //Ignore the timeout error - Warning("Failed to get ONVIF messages! %i", result); + if (result != SOAP_EOF) { //Ignore the timeout error + Error("Failed to get ONVIF messages! %s", soap_fault_string(soap)); + ONVIF_Healthy = FALSE; + } } else { Debug(1, "Got Good Response! %i", result); for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) { @@ -1814,6 +1819,7 @@ bool Monitor::Poll() { if (!ONVIF_Trigger_State) { Debug(1,"Triggered Event"); ONVIF_Trigger_State = TRUE; + std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep } } else { Debug(1, "Triggered off ONVIF"); @@ -1826,6 +1832,8 @@ bool Monitor::Poll() { } } } + } else { + std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep to avoid the busy loop. } #endif return TRUE; @@ -2109,11 +2117,14 @@ bool Monitor::Analyse() { Debug(1, "Staying in %s", State_Strings[state].c_str()); } if (state == ALARM) { - last_alarm_count = analysis_image_count; + last_alarm_count = analysis_image_count; } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition + + // snap->score -1 means didn't do motion detection so don't do state transition + // In Nodect, we may still have a triggered event, so need this code to run to end the event. + } else if (!score and ((snap->score == 0) or (function == NODECT))) { Debug(1, "!score %s", State_Strings[state].c_str()); - alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count + alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count if (state == ALARM) { Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); diff --git a/web/skins/classic/views/cycle.php b/web/skins/classic/views/cycle.php index 279d9bba5..ee9e03d4f 100644 --- a/web/skins/classic/views/cycle.php +++ b/web/skins/classic/views/cycle.php @@ -193,5 +193,5 @@ xhtmlHeaders(__FILE__, translate('CycleWatch')); - + diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index ed1869fab..bb99916dd 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -326,6 +326,6 @@ foreach (array_reverse($zones) as $zone) { - + diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index 3128f7767..f47bd1ccb 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -412,5 +412,5 @@ if ( ZM_WEB_SOUND_ON_ALARM ) { - + From 5026008482aecf59b6f1760bff4e7641fa72f4e7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 16 Jan 2022 19:55:53 -0600 Subject: [PATCH 312/501] Changes due to janus packaging --- misc/zoneminder.service.in | 4 +- web/js/janus.js | 3649 ------------------------------------ 2 files changed, 2 insertions(+), 3651 deletions(-) delete mode 100644 web/js/janus.js diff --git a/misc/zoneminder.service.in b/misc/zoneminder.service.in index d1cfb36a0..21be2e433 100644 --- a/misc/zoneminder.service.in +++ b/misc/zoneminder.service.in @@ -3,8 +3,8 @@ [Unit] Description=ZoneMinder CCTV recording and security system -After=network.target mysqld.service httpd.service -Requires=mysqld.service httpd.service +After=network.target mysqld.service httpd.service janus.service +Requires=mysqld.service httpd.service janus.service [Service] User=@WEB_USER@ diff --git a/web/js/janus.js b/web/js/janus.js deleted file mode 100644 index 177bcf2f7..000000000 --- a/web/js/janus.js +++ /dev/null @@ -1,3649 +0,0 @@ -"use strict"; - -/* - The MIT License (MIT) - - Copyright (c) 2016 Meetecho - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - */ - -// List of sessions -Janus.sessions = {}; - -Janus.isExtensionEnabled = function() { - if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { - // No need for the extension, getDisplayMedia is supported - return true; - } - if(window.navigator.userAgent.match('Chrome')) { - var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10); - var maxver = 33; - if(window.navigator.userAgent.match('Linux')) - maxver = 35; // "known" crash in chrome 34 and 35 on linux - if(chromever >= 26 && chromever <= maxver) { - // Older versions of Chrome don't support this extension-based approach, so lie - return true; - } - return Janus.extension.isInstalled(); - } else { - // Firefox and others, no need for the extension (but this doesn't mean it will work) - return true; - } -}; - -var defaultExtension = { - // Screensharing Chrome Extension ID - extensionId: 'hapfgfdkleiggjjpfpenajgdnfckjpaj', - isInstalled: function() { return document.querySelector('#janus-extension-installed') !== null; }, - getScreen: function (callback) { - var pending = window.setTimeout(function () { - var error = new Error('NavigatorUserMediaError'); - error.name = 'The required Chrome extension is not installed: click here to install it. (NOTE: this will need you to refresh the page)'; - return callback(error); - }, 1000); - this.cache[pending] = callback; - window.postMessage({ type: 'janusGetScreen', id: pending }, '*'); - }, - init: function () { - var cache = {}; - this.cache = cache; - // Wait for events from the Chrome Extension - window.addEventListener('message', function (event) { - if(event.origin != window.location.origin) - return; - if(event.data.type == 'janusGotScreen' && cache[event.data.id]) { - var callback = cache[event.data.id]; - delete cache[event.data.id]; - - if (event.data.sourceId === '') { - // user canceled - var error = new Error('NavigatorUserMediaError'); - error.name = 'You cancelled the request for permission, giving up...'; - callback(error); - } else { - callback(null, event.data.sourceId); - } - } else if (event.data.type == 'janusGetScreenPending') { - console.log('clearing ', event.data.id); - window.clearTimeout(event.data.id); - } - }); - } -}; - -Janus.useDefaultDependencies = function (deps) { - var f = (deps && deps.fetch) || fetch; - var p = (deps && deps.Promise) || Promise; - var socketCls = (deps && deps.WebSocket) || WebSocket; - - return { - newWebSocket: function(server, proto) { return new socketCls(server, proto); }, - extension: (deps && deps.extension) || defaultExtension, - isArray: function(arr) { return Array.isArray(arr); }, - webRTCAdapter: (deps && deps.adapter) || adapter, - httpAPICall: function(url, options) { - var fetchOptions = { - method: options.verb, - headers: { - 'Accept': 'application/json, text/plain, */*' - }, - cache: 'no-cache' - }; - if(options.verb === "POST") { - fetchOptions.headers['Content-Type'] = 'application/json'; - } - if(options.withCredentials !== undefined) { - fetchOptions.credentials = options.withCredentials === true ? 'include' : (options.withCredentials ? options.withCredentials : 'omit'); - } - if(options.body) { - fetchOptions.body = JSON.stringify(options.body); - } - - var fetching = f(url, fetchOptions).catch(function(error) { - return p.reject({message: 'Probably a network error, is the server down?', error: error}); - }); - - /* - * fetch() does not natively support timeouts. - * Work around this by starting a timeout manually, and racing it agains the fetch() to see which thing resolves first. - */ - - if(options.timeout) { - var timeout = new p(function(resolve, reject) { - var timerId = setTimeout(function() { - clearTimeout(timerId); - return reject({message: 'Request timed out', timeout: options.timeout}); - }, options.timeout); - }); - fetching = p.race([fetching, timeout]); - } - - fetching.then(function(response) { - if(response.ok) { - if(typeof(options.success) === typeof(Janus.noop)) { - return response.json().then(function(parsed) { - try { - options.success(parsed); - } catch(error) { - Janus.error('Unhandled httpAPICall success callback error', error); - } - }, function(error) { - return p.reject({message: 'Failed to parse response body', error: error, response: response}); - }); - } - } - else { - return p.reject({message: 'API call failed', response: response}); - } - }).catch(function(error) { - if(typeof(options.error) === typeof(Janus.noop)) { - options.error(error.message || '<< internal error >>', error); - } - }); - - return fetching; - } - } -}; - -Janus.useOldDependencies = function (deps) { - var jq = (deps && deps.jQuery) || jQuery; - var socketCls = (deps && deps.WebSocket) || WebSocket; - return { - newWebSocket: function(server, proto) { return new socketCls(server, proto); }, - isArray: function(arr) { return jq.isArray(arr); }, - extension: (deps && deps.extension) || defaultExtension, - webRTCAdapter: (deps && deps.adapter) || adapter, - httpAPICall: function(url, options) { - var payload = options.body !== undefined ? { - contentType: 'application/json', - data: JSON.stringify(options.body) - } : {}; - var credentials = options.withCredentials !== undefined ? {xhrFields: {withCredentials: options.withCredentials}} : {}; - - return jq.ajax(jq.extend(payload, credentials, { - url: url, - type: options.verb, - cache: false, - dataType: 'json', - async: options.async, - timeout: options.timeout, - success: function(result) { - if(typeof(options.success) === typeof(Janus.noop)) { - options.success(result); - } - }, - error: function(xhr, status, err) { - if(typeof(options.error) === typeof(Janus.noop)) { - options.error(status, err); - } - } - })); - } - }; -}; - -Janus.noop = function() {}; - -Janus.dataChanDefaultLabel = "JanusDataChannel"; - -// Note: in the future we may want to change this, e.g., as was -// attempted in https://github.com/meetecho/janus-gateway/issues/1670 -Janus.endOfCandidates = null; - -// Stop all tracks from a given stream -Janus.stopAllTracks = function(stream) { - try { - // Try a MediaStreamTrack.stop() for each track - var tracks = stream.getTracks(); - for(var mst of tracks) { - Janus.log(mst); - if(mst) { - mst.stop(); - } - } - } catch(e) { - // Do nothing if this fails - } -} - -// Initialization -Janus.init = function(options) { - options = options || {}; - options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop; - if(Janus.initDone) { - // Already initialized - options.callback(); - } else { - if(typeof console.log == "undefined") { - console.log = function() {}; - } - // Console logging (all debugging disabled by default) - Janus.trace = Janus.noop; - Janus.debug = Janus.noop; - Janus.vdebug = Janus.noop; - Janus.log = Janus.noop; - Janus.warn = Janus.noop; - Janus.error = Janus.noop; - if(options.debug === true || options.debug === "all") { - // Enable all debugging levels - Janus.trace = console.trace.bind(console); - Janus.debug = console.debug.bind(console); - Janus.vdebug = console.debug.bind(console); - Janus.log = console.log.bind(console); - Janus.warn = console.warn.bind(console); - Janus.error = console.error.bind(console); - } else if(Array.isArray(options.debug)) { - for(var d of options.debug) { - switch(d) { - case "trace": - Janus.trace = console.trace.bind(console); - break; - case "debug": - Janus.debug = console.debug.bind(console); - break; - case "vdebug": - Janus.vdebug = console.debug.bind(console); - break; - case "log": - Janus.log = console.log.bind(console); - break; - case "warn": - Janus.warn = console.warn.bind(console); - break; - case "error": - Janus.error = console.error.bind(console); - break; - default: - console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')"); - break; - } - } - } - Janus.log("Initializing library"); - - var usedDependencies = options.dependencies || Janus.useDefaultDependencies(); - Janus.isArray = usedDependencies.isArray; - Janus.webRTCAdapter = usedDependencies.webRTCAdapter; - Janus.httpAPICall = usedDependencies.httpAPICall; - Janus.newWebSocket = usedDependencies.newWebSocket; - Janus.extension = usedDependencies.extension; - Janus.extension.init(); - - // Helper method to enumerate devices - Janus.listDevices = function(callback, config) { - callback = (typeof callback == "function") ? callback : Janus.noop; - if (config == null) config = { audio: true, video: true }; - if(Janus.isGetUserMediaAvailable()) { - navigator.mediaDevices.getUserMedia(config) - .then(function(stream) { - navigator.mediaDevices.enumerateDevices().then(function(devices) { - Janus.debug(devices); - callback(devices); - // Get rid of the now useless stream - Janus.stopAllTracks(stream) - }); - }) - .catch(function(err) { - Janus.error(err); - callback([]); - }); - } else { - Janus.warn("navigator.mediaDevices unavailable"); - callback([]); - } - }; - // Helper methods to attach/reattach a stream to a video element (previously part of adapter.js) - Janus.attachMediaStream = function(element, stream) { - try { - element.srcObject = stream; - } catch (e) { - try { - element.src = URL.createObjectURL(stream); - } catch (e) { - Janus.error("Error attaching stream to element"); - } - } - }; - Janus.reattachMediaStream = function(to, from) { - try { - to.srcObject = from.srcObject; - } catch (e) { - try { - to.src = from.src; - } catch (e) { - Janus.error("Error reattaching stream to element"); - } - } - }; - // Detect tab close: make sure we don't loose existing onbeforeunload handlers - // (note: for iOS we need to subscribe to a different event, 'pagehide', see - // https://gist.github.com/thehunmonkgroup/6bee8941a49b86be31a787fe8f4b8cfe) - var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; - var eventName = iOS ? 'pagehide' : 'beforeunload'; - var oldOBF = window["on" + eventName]; - window.addEventListener(eventName, function() { - Janus.log("Closing window"); - for(var s in Janus.sessions) { - if(Janus.sessions[s] && Janus.sessions[s].destroyOnUnload) { - Janus.log("Destroying session " + s); - Janus.sessions[s].destroy({unload: true, notifyDestroyed: false}); - } - } - if(oldOBF && typeof oldOBF == "function") { - oldOBF(); - } - }); - // If this is a Safari Technology Preview, check if VP8 is supported - Janus.safariVp8 = false; - if(Janus.webRTCAdapter.browserDetails.browser === 'safari' && - Janus.webRTCAdapter.browserDetails.version >= 605) { - // Let's see if RTCRtpSender.getCapabilities() is there - if(RTCRtpSender && RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities("video") && - RTCRtpSender.getCapabilities("video").codecs && RTCRtpSender.getCapabilities("video").codecs.length) { - for(var codec of RTCRtpSender.getCapabilities("video").codecs) { - if(codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp8") { - Janus.safariVp8 = true; - break; - } - } - if(Janus.safariVp8) { - Janus.log("This version of Safari supports VP8"); - } else { - Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " + - "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu"); - } - } else { - // We do it in a very ugly way, as there's no alternative... - // We create a PeerConnection to see if VP8 is in an offer - var testpc = new RTCPeerConnection({}); - testpc.createOffer({offerToReceiveVideo: true}).then(function(offer) { - Janus.safariVp8 = offer.sdp.indexOf("VP8") !== -1; - if(Janus.safariVp8) { - Janus.log("This version of Safari supports VP8"); - } else { - Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " + - "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu"); - } - testpc.close(); - testpc = null; - }); - } - } - // Check if this browser supports Unified Plan and transceivers - // Based on https://codepen.io/anon/pen/ZqLwWV?editors=0010 - Janus.unifiedPlan = false; - if(Janus.webRTCAdapter.browserDetails.browser === 'firefox' && - Janus.webRTCAdapter.browserDetails.version >= 59) { - // Firefox definitely does, starting from version 59 - Janus.unifiedPlan = true; - } else if(Janus.webRTCAdapter.browserDetails.browser === 'chrome' && - Janus.webRTCAdapter.browserDetails.version >= 72) { - // Chrome does, but it's only usable from version 72 on - Janus.unifiedPlan = true; - } else if(!window.RTCRtpTransceiver || !('currentDirection' in RTCRtpTransceiver.prototype)) { - // Safari supports addTransceiver() but not Unified Plan when - // currentDirection is not defined (see codepen above). - Janus.unifiedPlan = false; - } else { - // Check if addTransceiver() throws an exception - var tempPc = new RTCPeerConnection(); - try { - tempPc.addTransceiver('audio'); - Janus.unifiedPlan = true; - } catch (e) {} - tempPc.close(); - } - Janus.initDone = true; - options.callback(); - } -}; - -// Helper method to check whether WebRTC is supported by this browser -Janus.isWebrtcSupported = function() { - return !!window.RTCPeerConnection; -}; -// Helper method to check whether devices can be accessed by this browser (e.g., not possible via plain HTTP) -Janus.isGetUserMediaAvailable = function() { - return navigator.mediaDevices && navigator.mediaDevices.getUserMedia; -}; - -// Helper method to create random identifiers (e.g., transaction) -Janus.randomString = function(len) { - var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - var randomString = ''; - for (var i = 0; i < len; i++) { - var randomPoz = Math.floor(Math.random() * charSet.length); - randomString += charSet.substring(randomPoz,randomPoz+1); - } - return randomString; -}; - -function Janus(gatewayCallbacks) { - gatewayCallbacks = gatewayCallbacks || {}; - gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop; - gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop; - gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop; - if(!Janus.initDone) { - gatewayCallbacks.error("Library not initialized"); - return {}; - } - if(!Janus.isWebrtcSupported()) { - gatewayCallbacks.error("WebRTC not supported by this browser"); - return {}; - } - Janus.log("Library initialized: " + Janus.initDone); - if(!gatewayCallbacks.server) { - gatewayCallbacks.error("Invalid server url"); - return {}; - } - var websockets = false; - var ws = null; - var wsHandlers = {}; - var wsKeepaliveTimeoutId = null; - var servers = null; - var serversIndex = 0; - var server = gatewayCallbacks.server; - if(Janus.isArray(server)) { - Janus.log("Multiple servers provided (" + server.length + "), will use the first that works"); - server = null; - servers = gatewayCallbacks.server; - Janus.debug(servers); - } else { - if(server.indexOf("ws") === 0) { - websockets = true; - Janus.log("Using WebSockets to contact Janus: " + server); - } else { - websockets = false; - Janus.log("Using REST API to contact Janus: " + server); - } - } - var iceServers = gatewayCallbacks.iceServers || [{urls: "stun:stun.l.google.com:19302"}]; - var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy; - var bundlePolicy = gatewayCallbacks.bundlePolicy; - // Whether IPv6 candidates should be gathered - var ipv6Support = (gatewayCallbacks.ipv6 === true); - // Whether we should enable the withCredentials flag for XHR requests - var withCredentials = false; - if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null) - withCredentials = gatewayCallbacks.withCredentials === true; - // Optional max events - var maxev = 10; - if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null) - maxev = gatewayCallbacks.max_poll_events; - if(maxev < 1) - maxev = 1; - // Token to use (only if the token based authentication mechanism is enabled) - var token = null; - if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null) - token = gatewayCallbacks.token; - // API secret to use (only if the shared API secret is enabled) - var apisecret = null; - if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null) - apisecret = gatewayCallbacks.apisecret; - // Whether we should destroy this session when onbeforeunload is called - this.destroyOnUnload = true; - if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null) - this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true); - // Some timeout-related values - var keepAlivePeriod = 25000; - if(gatewayCallbacks.keepAlivePeriod !== undefined && gatewayCallbacks.keepAlivePeriod !== null) - keepAlivePeriod = gatewayCallbacks.keepAlivePeriod; - if(isNaN(keepAlivePeriod)) - keepAlivePeriod = 25000; - var longPollTimeout = 60000; - if(gatewayCallbacks.longPollTimeout !== undefined && gatewayCallbacks.longPollTimeout !== null) - longPollTimeout = gatewayCallbacks.longPollTimeout; - if(isNaN(longPollTimeout)) - longPollTimeout = 60000; - - // overrides for default maxBitrate values for simulcasting - function getMaxBitrates(simulcastMaxBitrates) { - var maxBitrates = { - high: 900000, - medium: 300000, - low: 100000, - }; - - if (simulcastMaxBitrates !== undefined && simulcastMaxBitrates !== null) { - if (simulcastMaxBitrates.high) - maxBitrates.high = simulcastMaxBitrates.high; - if (simulcastMaxBitrates.medium) - maxBitrates.medium = simulcastMaxBitrates.medium; - if (simulcastMaxBitrates.low) - maxBitrates.low = simulcastMaxBitrates.low; - } - - return maxBitrates; - } - - var connected = false; - var sessionId = null; - var pluginHandles = {}; - var that = this; - var retries = 0; - var transactions = {}; - createSession(gatewayCallbacks); - - // Public methods - this.getServer = function() { return server; }; - this.isConnected = function() { return connected; }; - this.reconnect = function(callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - callbacks["reconnect"] = true; - createSession(callbacks); - }; - this.getSessionId = function() { return sessionId; }; - this.getInfo = function(callbacks) { getInfo(callbacks); }; - this.destroy = function(callbacks) { destroySession(callbacks); }; - this.attach = function(callbacks) { createHandle(callbacks); }; - - function eventHandler() { - if(sessionId == null) - return; - Janus.debug('Long poll...'); - if(!connected) { - Janus.warn("Is the server down? (connected=false)"); - return; - } - var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime(); - if(maxev) - longpoll = longpoll + "&maxev=" + maxev; - if(token) - longpoll = longpoll + "&token=" + encodeURIComponent(token); - if(apisecret) - longpoll = longpoll + "&apisecret=" + encodeURIComponent(apisecret); - Janus.httpAPICall(longpoll, { - verb: 'GET', - withCredentials: withCredentials, - success: handleEvent, - timeout: longPollTimeout, - error: function(textStatus, errorThrown) { - Janus.error(textStatus + ":", errorThrown); - retries++; - if(retries > 3) { - // Did we just lose the server? :-( - connected = false; - gatewayCallbacks.error("Lost connection to the server (is it down?)"); - return; - } - eventHandler(); - } - }); - } - - // Private event handler: this will trigger plugin callbacks, if set - function handleEvent(json, skipTimeout) { - retries = 0; - if(!websockets && sessionId !== undefined && sessionId !== null && skipTimeout !== true) - eventHandler(); - if(!websockets && Janus.isArray(json)) { - // We got an array: it means we passed a maxev > 1, iterate on all objects - for(var i=0; i data channel: ' + dcState); - if(dcState === 'open') { - // Any pending messages to send? - if(config.dataChannel[label].pending && config.dataChannel[label].pending.length > 0) { - Janus.log("Sending pending messages on <" + label + ">:", config.dataChannel[label].pending.length); - for(var data of config.dataChannel[label].pending) { - Janus.log("Sending data on data channel <" + label + ">"); - Janus.debug(data); - config.dataChannel[label].send(data); - } - config.dataChannel[label].pending = []; - } - // Notify the open data channel - pluginHandle.ondataopen(label, protocol); - } - }; - var onDataChannelError = function(error) { - Janus.error('Got error on data channel:', error); - // TODO - }; - if(!incoming) { - // FIXME Add options (ordered, maxRetransmits, etc.) - var dcoptions = config.dataChannelOptions; - if(dcprotocol) - dcoptions.protocol = dcprotocol; - config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, dcoptions); - } else { - // The channel was created by Janus - config.dataChannel[dclabel] = incoming; - } - config.dataChannel[dclabel].onmessage = onDataChannelMessage; - config.dataChannel[dclabel].onopen = onDataChannelStateChange; - config.dataChannel[dclabel].onclose = onDataChannelStateChange; - config.dataChannel[dclabel].onerror = onDataChannelError; - config.dataChannel[dclabel].pending = []; - if(pendingData) - config.dataChannel[dclabel].pending.push(pendingData); - } - - // Private method to send a data channel message - function sendData(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - var data = callbacks.text || callbacks.data; - if(!data) { - Janus.warn("Invalid data"); - callbacks.error("Invalid data"); - return; - } - var label = callbacks.label ? callbacks.label : Janus.dataChanDefaultLabel; - if(!config.dataChannel[label]) { - // Create new data channel and wait for it to open - createDataChannel(handleId, label, callbacks.protocol, false, data, callbacks.protocol); - callbacks.success(); - return; - } - if(config.dataChannel[label].readyState !== "open") { - config.dataChannel[label].pending.push(data); - callbacks.success(); - return; - } - Janus.log("Sending data on data channel <" + label + ">"); - Janus.debug(data); - config.dataChannel[label].send(data); - callbacks.success(); - } - - // Private method to send a DTMF tone - function sendDtmf(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - if(!config.dtmfSender) { - // Create the DTMF sender the proper way, if possible - if(config.pc) { - var senders = config.pc.getSenders(); - var audioSender = senders.find(function(sender) { - return sender.track && sender.track.kind === 'audio'; - }); - if(!audioSender) { - Janus.warn("Invalid DTMF configuration (no audio track)"); - callbacks.error("Invalid DTMF configuration (no audio track)"); - return; - } - config.dtmfSender = audioSender.dtmf; - if(config.dtmfSender) { - Janus.log("Created DTMF Sender"); - config.dtmfSender.ontonechange = function(tone) { Janus.debug("Sent DTMF tone: " + tone.tone); }; - } - } - if(!config.dtmfSender) { - Janus.warn("Invalid DTMF configuration"); - callbacks.error("Invalid DTMF configuration"); - return; - } - } - var dtmf = callbacks.dtmf; - if(!dtmf) { - Janus.warn("Invalid DTMF parameters"); - callbacks.error("Invalid DTMF parameters"); - return; - } - var tones = dtmf.tones; - if(!tones) { - Janus.warn("Invalid DTMF string"); - callbacks.error("Invalid DTMF string"); - return; - } - var duration = (typeof dtmf.duration === 'number') ? dtmf.duration : 500; // We choose 500ms as the default duration for a tone - var gap = (typeof dtmf.gap === 'number') ? dtmf.gap : 50; // We choose 50ms as the default gap between tones - Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms)"); - config.dtmfSender.insertDTMF(tones, duration, gap); - callbacks.success(); - } - - // Private method to destroy a plugin handle - function destroyHandle(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var noRequest = (callbacks.noRequest === true); - Janus.log("Destroying handle " + handleId + " (only-locally=" + noRequest + ")"); - cleanupWebrtc(handleId); - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || pluginHandle.detached) { - // Plugin was already detached by Janus, calling detach again will return a handle not found error, so just exit here - delete pluginHandles[handleId]; - callbacks.success(); - return; - } - pluginHandle.detached = true; - if(noRequest) { - // We're only removing the handle locally - delete pluginHandles[handleId]; - callbacks.success(); - return; - } - if(!connected) { - Janus.warn("Is the server down? (connected=false)"); - callbacks.error("Is the server down? (connected=false)"); - return; - } - var request = { "janus": "detach", "transaction": Janus.randomString(12) }; - if(pluginHandle.token) - request["token"] = pluginHandle.token; - if(apisecret) - request["apisecret"] = apisecret; - if(websockets) { - request["session_id"] = sessionId; - request["handle_id"] = handleId; - ws.send(JSON.stringify(request)); - delete pluginHandles[handleId]; - callbacks.success(); - return; - } - Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, { - verb: 'POST', - withCredentials: withCredentials, - body: request, - success: function(json) { - Janus.log("Destroyed handle:"); - Janus.debug(json); - if(json["janus"] !== "success") { - Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME - } - delete pluginHandles[handleId]; - callbacks.success(); - }, - error: function(textStatus, errorThrown) { - Janus.error(textStatus + ":", errorThrown); // FIXME - // We cleanup anyway - delete pluginHandles[handleId]; - callbacks.success(); - } - }); - } - - // WebRTC stuff - function streamsDone(handleId, jsep, media, callbacks, stream) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - // Close all tracks if the given stream has been created internally - if(!callbacks.stream) { - Janus.stopAllTracks(stream); - } - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - Janus.debug("streamsDone:", stream); - if(stream) { - Janus.debug(" -- Audio tracks:", stream.getAudioTracks()); - Janus.debug(" -- Video tracks:", stream.getVideoTracks()); - } - // We're now capturing the new stream: check if we're updating or if it's a new thing - var addTracks = false; - if(!config.myStream || !media.update || (config.streamExternal && !media.replaceAudio && !media.replaceVideo)) { - config.myStream = stream; - addTracks = true; - } else { - // We only need to update the existing stream - if(((!media.update && isAudioSendEnabled(media)) || (media.update && (media.addAudio || media.replaceAudio))) && - stream.getAudioTracks() && stream.getAudioTracks().length) { - config.myStream.addTrack(stream.getAudioTracks()[0]); - if(Janus.unifiedPlan) { - // Use Transceivers - Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]); - var audioTransceiver = null; - const transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(const t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "audio") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) { - audioTransceiver = t; - break; - } - } - } - if(audioTransceiver && audioTransceiver.sender) { - audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]); - } else { - config.pc.addTrack(stream.getAudioTracks()[0], stream); - } - } else { - Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]); - config.pc.addTrack(stream.getAudioTracks()[0], stream); - } - } - if(((!media.update && isVideoSendEnabled(media)) || (media.update && (media.addVideo || media.replaceVideo))) && - stream.getVideoTracks() && stream.getVideoTracks().length) { - config.myStream.addTrack(stream.getVideoTracks()[0]); - if(Janus.unifiedPlan) { - // Use Transceivers - Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]); - var videoTransceiver = null; - const transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(const t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "video") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) { - videoTransceiver = t; - break; - } - } - } - if(videoTransceiver && videoTransceiver.sender) { - videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]); - } else { - config.pc.addTrack(stream.getVideoTracks()[0], stream); - } - } else { - Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]); - config.pc.addTrack(stream.getVideoTracks()[0], stream); - } - } - } - // If we still need to create a PeerConnection, let's do that - if(!config.pc) { - var pc_config = {"iceServers": iceServers, "iceTransportPolicy": iceTransportPolicy, "bundlePolicy": bundlePolicy}; - if(Janus.webRTCAdapter.browserDetails.browser === "chrome") { - // For Chrome versions before 72, we force a plan-b semantic, and unified-plan otherwise - pc_config["sdpSemantics"] = (Janus.webRTCAdapter.browserDetails.version < 72) ? "plan-b" : "unified-plan"; - } - var pc_constraints = { - "optional": [{"DtlsSrtpKeyAgreement": true}] - }; - if(ipv6Support) { - pc_constraints.optional.push({"googIPv6":true}); - } - // Any custom constraint to add? - if(callbacks.rtcConstraints && typeof callbacks.rtcConstraints === 'object') { - Janus.debug("Adding custom PeerConnection constraints:", callbacks.rtcConstraints); - for(var i in callbacks.rtcConstraints) { - pc_constraints.optional.push(callbacks.rtcConstraints[i]); - } - } - if(Janus.webRTCAdapter.browserDetails.browser === "edge") { - // This is Edge, enable BUNDLE explicitly - pc_config.bundlePolicy = "max-bundle"; - } - // Check if a sender or receiver transform has been provided - if(RTCRtpSender && (RTCRtpSender.prototype.createEncodedStreams || - (RTCRtpSender.prototype.createEncodedAudioStreams && - RTCRtpSender.prototype.createEncodedVideoStreams)) && - (callbacks.senderTransforms || callbacks.receiverTransforms)) { - config.senderTransforms = callbacks.senderTransforms; - config.receiverTransforms = callbacks.receiverTransforms; - pc_config["forceEncodedAudioInsertableStreams"] = true; - pc_config["forceEncodedVideoInsertableStreams"] = true; - pc_config["encodedInsertableStreams"] = true; - } - Janus.log("Creating PeerConnection"); - Janus.debug(pc_constraints); - config.pc = new RTCPeerConnection(pc_config, pc_constraints); - Janus.debug(config.pc); - if(config.pc.getStats) { // FIXME - config.volume = {}; - config.bitrate.value = "0 kbits/sec"; - } - Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")"); - config.pc.oniceconnectionstatechange = function() { - if(config.pc) - pluginHandle.iceState(config.pc.iceConnectionState); - }; - config.pc.onicecandidate = function(event) { - if (!event.candidate || - (Janus.webRTCAdapter.browserDetails.browser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) { - Janus.log("End of candidates."); - config.iceDone = true; - if(config.trickle === true) { - // Notify end of candidates - sendTrickleCandidate(handleId, {"completed": true}); - } else { - // No trickle, time to send the complete SDP (including all candidates) - sendSDP(handleId, callbacks); - } - } else { - // JSON.stringify doesn't work on some WebRTC objects anymore - // See https://code.google.com/p/chromium/issues/detail?id=467366 - var candidate = { - "candidate": event.candidate.candidate, - "sdpMid": event.candidate.sdpMid, - "sdpMLineIndex": event.candidate.sdpMLineIndex - }; - if(config.trickle === true) { - // Send candidate - sendTrickleCandidate(handleId, candidate); - } - } - }; - config.pc.ontrack = function(event) { - Janus.log("Handling Remote Track"); - Janus.debug(event); - if(!event.streams) - return; - config.remoteStream = event.streams[0]; - pluginHandle.onremotestream(config.remoteStream); - if(event.track.onended) - return; - if(config.receiverTransforms) { - var receiverStreams = null; - if(RTCRtpSender.prototype.createEncodedStreams) { - receiverStreams = event.receiver.createEncodedStreams(); - } else if(RTCRtpSender.prototype.createAudioEncodedStreams || RTCRtpSender.prototype.createEncodedVideoStreams) { - if(event.track.kind === "audio" && config.receiverTransforms["audio"]) { - receiverStreams = event.receiver.createEncodedAudioStreams(); - } else if(event.track.kind === "video" && config.receiverTransforms["video"]) { - receiverStreams = event.receiver.createEncodedVideoStreams(); - } - } - if(receiverStreams) { - console.log(receiverStreams); - if(receiverStreams.readableStream && receiverStreams.writableStream) { - receiverStreams.readableStream - .pipeThrough(config.receiverTransforms[event.track.kind]) - .pipeTo(receiverStreams.writableStream); - } else if(receiverStreams.readable && receiverStreams.writable) { - receiverStreams.readable - .pipeThrough(config.receiverTransforms[event.track.kind]) - .pipeTo(receiverStreams.writable); - } - } - } - var trackMutedTimeoutId = null; - Janus.log("Adding onended callback to track:", event.track); - event.track.onended = function(ev) { - Janus.log("Remote track removed:", ev); - if(config.remoteStream) { - clearTimeout(trackMutedTimeoutId); - config.remoteStream.removeTrack(ev.target); - pluginHandle.onremotestream(config.remoteStream); - } - }; - event.track.onmute = function(ev) { - Janus.log("Remote track muted:", ev); - if(config.remoteStream && trackMutedTimeoutId == null) { - trackMutedTimeoutId = setTimeout(function() { - Janus.log("Removing remote track"); - if (config.remoteStream) { - config.remoteStream.removeTrack(ev.target); - pluginHandle.onremotestream(config.remoteStream); - } - trackMutedTimeoutId = null; - // Chrome seems to raise mute events only at multiples of 834ms; - // we set the timeout to three times this value (rounded to 840ms) - }, 3 * 840); - } - }; - event.track.onunmute = function(ev) { - Janus.log("Remote track flowing again:", ev); - if(trackMutedTimeoutId != null) { - clearTimeout(trackMutedTimeoutId); - trackMutedTimeoutId = null; - } else { - try { - config.remoteStream.addTrack(ev.target); - pluginHandle.onremotestream(config.remoteStream); - } catch(e) { - Janus.error(e); - } - } - }; - }; - } - if(addTracks && stream) { - Janus.log('Adding local stream'); - var simulcast2 = (callbacks.simulcast2 === true); - stream.getTracks().forEach(function(track) { - Janus.log('Adding local track:', track); - var sender = null; - if(!simulcast2 || track.kind === 'audio') { - sender = config.pc.addTrack(track, stream); - } else { - Janus.log('Enabling rid-based simulcasting:', track); - var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates); - var tr = config.pc.addTransceiver(track, { - direction: "sendrecv", - streams: [stream], - sendEncodings: callbacks.sendEncodings || [ - { rid: "h", active: true, maxBitrate: maxBitrates.high }, - { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 }, - { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 } - ] - }); - if(tr) - sender = tr.sender; - } - // Check if insertable streams are involved - if(sender && config.senderTransforms) { - var senderStreams = null; - if(RTCRtpSender.prototype.createEncodedStreams) { - senderStreams = sender.createEncodedStreams(); - } else if(RTCRtpSender.prototype.createAudioEncodedStreams || RTCRtpSender.prototype.createEncodedVideoStreams) { - if(sender.track.kind === "audio" && config.senderTransforms["audio"]) { - senderStreams = sender.createEncodedAudioStreams(); - } else if(sender.track.kind === "video" && config.senderTransforms["video"]) { - senderStreams = sender.createEncodedVideoStreams(); - } - } - if(senderStreams) { - console.log(senderStreams); - if(senderStreams.readableStream && senderStreams.writableStream) { - senderStreams.readableStream - .pipeThrough(config.senderTransforms[sender.track.kind]) - .pipeTo(senderStreams.writableStream); - } else if(senderStreams.readable && senderStreams.writable) { - senderStreams.readable - .pipeThrough(config.senderTransforms[sender.track.kind]) - .pipeTo(senderStreams.writable); - } - } - } - }); - } - // Any data channel to create? - if(isDataEnabled(media) && !config.dataChannel[Janus.dataChanDefaultLabel]) { - Janus.log("Creating default data channel"); - createDataChannel(handleId, Janus.dataChanDefaultLabel, null, false); - config.pc.ondatachannel = function(event) { - Janus.log("Data channel created by Janus:", event); - createDataChannel(handleId, event.channel.label, event.channel.protocol, event.channel); - }; - } - // If there's a new local stream, let's notify the application - if(config.myStream) { - pluginHandle.onlocalstream(config.myStream); - } - // Create offer/answer now - if(!jsep) { - createOffer(handleId, media, callbacks); - } else { - config.pc.setRemoteDescription(jsep) - .then(function() { - Janus.log("Remote description accepted!"); - config.remoteSdp = jsep.sdp; - // Any trickle candidate we cached? - if(config.candidates && config.candidates.length > 0) { - for(var i = 0; i< config.candidates.length; i++) { - var candidate = config.candidates[i]; - Janus.debug("Adding remote candidate:", candidate); - if(!candidate || candidate.completed === true) { - // end-of-candidates - config.pc.addIceCandidate(Janus.endOfCandidates); - } else { - // New candidate - config.pc.addIceCandidate(candidate); - } - } - config.candidates = []; - } - // Create the answer now - createAnswer(handleId, media, callbacks); - }, callbacks.error); - } - } - - function prepareWebrtc(handleId, offer, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError; - var jsep = callbacks.jsep; - if(offer && jsep) { - Janus.error("Provided a JSEP to a createOffer"); - callbacks.error("Provided a JSEP to a createOffer"); - return; - } else if(!offer && (!jsep || !jsep.type || !jsep.sdp)) { - Janus.error("A valid JSEP is required for createAnswer"); - callbacks.error("A valid JSEP is required for createAnswer"); - return; - } - /* Check that callbacks.media is a (not null) Object */ - callbacks.media = (typeof callbacks.media === 'object' && callbacks.media) ? callbacks.media : { audio: true, video: true }; - var media = callbacks.media; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - config.trickle = isTrickleEnabled(callbacks.trickle); - // Are we updating a session? - if(!config.pc) { - // Nope, new PeerConnection - media.update = false; - media.keepAudio = false; - media.keepVideo = false; - } else { - Janus.log("Updating existing media session"); - media.update = true; - // Check if there's anything to add/remove/replace, or if we - // can go directly to preparing the new SDP offer or answer - if(callbacks.stream) { - // External stream: is this the same as the one we were using before? - if(callbacks.stream !== config.myStream) { - Janus.log("Renegotiation involves a new external stream"); - } - } else { - // Check if there are changes on audio - if(media.addAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.removeAudio = false; - media.audioSend = true; - if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) { - Janus.error("Can't add audio stream, there already is one"); - callbacks.error("Can't add audio stream, there already is one"); - return; - } - } else if(media.removeAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.addAudio = false; - media.audioSend = false; - } else if(media.replaceAudio) { - media.keepAudio = false; - media.addAudio = false; - media.removeAudio = false; - media.audioSend = true; - } - if(!config.myStream) { - // No media stream: if we were asked to replace, it's actually an "add" - if(media.replaceAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.addAudio = true; - media.audioSend = true; - } - if(isAudioSendEnabled(media)) { - media.keepAudio = false; - media.addAudio = true; - } - } else { - if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) { - // No audio track: if we were asked to replace, it's actually an "add" - if(media.replaceAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.addAudio = true; - media.audioSend = true; - } - if(isAudioSendEnabled(media)) { - media.keepAudio = false; - media.addAudio = true; - } - } else { - // We have an audio track: should we keep it as it is? - if(isAudioSendEnabled(media) && - !media.removeAudio && !media.replaceAudio) { - media.keepAudio = true; - } - } - } - // Check if there are changes on video - if(media.addVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.removeVideo = false; - media.videoSend = true; - if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) { - Janus.error("Can't add video stream, there already is one"); - callbacks.error("Can't add video stream, there already is one"); - return; - } - } else if(media.removeVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.addVideo = false; - media.videoSend = false; - } else if(media.replaceVideo) { - media.keepVideo = false; - media.addVideo = false; - media.removeVideo = false; - media.videoSend = true; - } - if(!config.myStream) { - // No media stream: if we were asked to replace, it's actually an "add" - if(media.replaceVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.addVideo = true; - media.videoSend = true; - } - if(isVideoSendEnabled(media)) { - media.keepVideo = false; - media.addVideo = true; - } - } else { - if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) { - // No video track: if we were asked to replace, it's actually an "add" - if(media.replaceVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.addVideo = true; - media.videoSend = true; - } - if(isVideoSendEnabled(media)) { - media.keepVideo = false; - media.addVideo = true; - } - } else { - // We have a video track: should we keep it as it is? - if(isVideoSendEnabled(media) && !media.removeVideo && !media.replaceVideo) { - media.keepVideo = true; - } - } - } - // Data channels can only be added - if(media.addData) { - media.data = true; - } - } - // If we're updating and keeping all tracks, let's skip the getUserMedia part - if((isAudioSendEnabled(media) && media.keepAudio) && - (isVideoSendEnabled(media) && media.keepVideo)) { - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, config.myStream); - return; - } - } - // If we're updating, check if we need to remove/replace one of the tracks - if(media.update && (!config.streamExternal || (config.streamExternal && (media.replaceAudio || media.replaceVideo)))) { - if(media.removeAudio || media.replaceAudio) { - if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) { - var at = config.myStream.getAudioTracks()[0]; - Janus.log("Removing audio track:", at); - config.myStream.removeTrack(at); - try { - at.stop(); - } catch(e) {} - } - if(config.pc.getSenders() && config.pc.getSenders().length) { - var ra = true; - if(media.replaceAudio && Janus.unifiedPlan) { - // We can use replaceTrack - ra = false; - } - if(ra) { - for(var asnd of config.pc.getSenders()) { - if(asnd && asnd.track && asnd.track.kind === "audio") { - Janus.log("Removing audio sender:", asnd); - config.pc.removeTrack(asnd); - } - } - } - } - } - if(media.removeVideo || media.replaceVideo) { - if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) { - var vt = config.myStream.getVideoTracks()[0]; - Janus.log("Removing video track:", vt); - config.myStream.removeTrack(vt); - try { - vt.stop(); - } catch(e) {} - } - if(config.pc.getSenders() && config.pc.getSenders().length) { - var rv = true; - if(media.replaceVideo && Janus.unifiedPlan) { - // We can use replaceTrack - rv = false; - } - if(rv) { - for(var vsnd of config.pc.getSenders()) { - if(vsnd && vsnd.track && vsnd.track.kind === "video") { - Janus.log("Removing video sender:", vsnd); - config.pc.removeTrack(vsnd); - } - } - } - } - } - } - // Was a MediaStream object passed, or do we need to take care of that? - if(callbacks.stream) { - var stream = callbacks.stream; - Janus.log("MediaStream provided by the application"); - Janus.debug(stream); - // If this is an update, let's check if we need to release the previous stream - if(media.update && config.myStream && config.myStream !== callbacks.stream && !config.streamExternal && !media.replaceAudio && !media.replaceVideo) { - // We're replacing a stream we captured ourselves with an external one - Janus.stopAllTracks(config.myStream); - config.myStream = null; - } - // Skip the getUserMedia part - config.streamExternal = true; - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, stream); - return; - } - if(isAudioSendEnabled(media) || isVideoSendEnabled(media)) { - if(!Janus.isGetUserMediaAvailable()) { - callbacks.error("getUserMedia not available"); - return; - } - var constraints = { mandatory: {}, optional: []}; - pluginHandle.consentDialog(true); - var audioSupport = isAudioSendEnabled(media); - if(audioSupport && media && typeof media.audio === 'object') - audioSupport = media.audio; - var videoSupport = isVideoSendEnabled(media); - if(videoSupport && media) { - var simulcast = (callbacks.simulcast === true); - var simulcast2 = (callbacks.simulcast2 === true); - if((simulcast || simulcast2) && !jsep && !media.video) - media.video = "hires"; - if(media.video && media.video != 'screen' && media.video != 'window') { - if(typeof media.video === 'object') { - videoSupport = media.video; - } else { - var width = 0; - var height = 0; - if(media.video === 'lowres') { - // Small resolution, 4:3 - height = 240; - width = 320; - } else if(media.video === 'lowres-16:9') { - // Small resolution, 16:9 - height = 180; - width = 320; - } else if(media.video === 'hires' || media.video === 'hires-16:9' || media.video === 'hdres') { - // High(HD) resolution is only 16:9 - height = 720; - width = 1280; - } else if(media.video === 'fhdres') { - // Full HD resolution is only 16:9 - height = 1080; - width = 1920; - } else if(media.video === '4kres') { - // 4K resolution is only 16:9 - height = 2160; - width = 3840; - } else if(media.video === 'stdres') { - // Normal resolution, 4:3 - height = 480; - width = 640; - } else if(media.video === 'stdres-16:9') { - // Normal resolution, 16:9 - height = 360; - width = 640; - } else { - Janus.log("Default video setting is stdres 4:3"); - height = 480; - width = 640; - } - Janus.log("Adding media constraint:", media.video); - videoSupport = { - 'height': {'ideal': height}, - 'width': {'ideal': width} - }; - Janus.log("Adding video constraint:", videoSupport); - } - } else if(media.video === 'screen' || media.video === 'window') { - if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { - // The new experimental getDisplayMedia API is available, let's use that - // https://groups.google.com/forum/#!topic/discuss-webrtc/Uf0SrR4uxzk - // https://webrtchacks.com/chrome-screensharing-getdisplaymedia/ - constraints.video = {}; - if(media.screenshareFrameRate) { - constraints.video.frameRate = media.screenshareFrameRate; - } - if(media.screenshareHeight) { - constraints.video.height = media.screenshareHeight; - } - if(media.screenshareWidth) { - constraints.video.width = media.screenshareWidth; - } - constraints.audio = media.captureDesktopAudio; - navigator.mediaDevices.getDisplayMedia(constraints) - .then(function(stream) { - pluginHandle.consentDialog(false); - if(isAudioSendEnabled(media) && !media.keepAudio) { - navigator.mediaDevices.getUserMedia({ audio: true, video: false }) - .then(function (audioStream) { - stream.addTrack(audioStream.getAudioTracks()[0]); - streamsDone(handleId, jsep, media, callbacks, stream); - }) - } else { - streamsDone(handleId, jsep, media, callbacks, stream); - } - }, function (error) { - pluginHandle.consentDialog(false); - callbacks.error(error); - }); - return; - } - // We're going to try and use the extension for Chrome 34+, the old approach - // for older versions of Chrome, or the experimental support in Firefox 33+ - const callbackUserMedia = function(error, stream) { - pluginHandle.consentDialog(false); - if(error) { - callbacks.error(error); - } else { - streamsDone(handleId, jsep, media, callbacks, stream); - } - } - const getScreenMedia = function(constraints, gsmCallback, useAudio) { - Janus.log("Adding media constraint (screen capture)"); - Janus.debug(constraints); - navigator.mediaDevices.getUserMedia(constraints) - .then(function(stream) { - if(useAudio) { - navigator.mediaDevices.getUserMedia({ audio: true, video: false }) - .then(function (audioStream) { - stream.addTrack(audioStream.getAudioTracks()[0]); - gsmCallback(null, stream); - }) - } else { - gsmCallback(null, stream); - } - }) - .catch(function(error) { pluginHandle.consentDialog(false); gsmCallback(error); }); - } - if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') { - var chromever = Janus.webRTCAdapter.browserDetails.version; - var maxver = 33; - if(window.navigator.userAgent.match('Linux')) - maxver = 35; // "known" crash in chrome 34 and 35 on linux - if(chromever >= 26 && chromever <= maxver) { - // Chrome 26->33 requires some awkward chrome://flags manipulation - constraints = { - video: { - mandatory: { - googLeakyBucket: true, - maxWidth: window.screen.width, - maxHeight: window.screen.height, - minFrameRate: media.screenshareFrameRate, - maxFrameRate: media.screenshareFrameRate, - chromeMediaSource: 'screen' - } - }, - audio: isAudioSendEnabled(media) && !media.keepAudio - }; - getScreenMedia(constraints, callbackUserMedia); - } else { - // Chrome 34+ requires an extension - Janus.extension.getScreen(function (error, sourceId) { - if (error) { - pluginHandle.consentDialog(false); - return callbacks.error(error); - } - constraints = { - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - maxWidth: window.screen.width, - maxHeight: window.screen.height, - minFrameRate: media.screenshareFrameRate, - maxFrameRate: media.screenshareFrameRate, - }, - optional: [ - {googLeakyBucket: true}, - {googTemporalLayeredScreencast: true} - ] - } - }; - constraints.video.mandatory.chromeMediaSourceId = sourceId; - getScreenMedia(constraints, callbackUserMedia, - isAudioSendEnabled(media) && !media.keepAudio); - }); - } - } else if(Janus.webRTCAdapter.browserDetails.browser === 'firefox') { - if(Janus.webRTCAdapter.browserDetails.version >= 33) { - // Firefox 33+ has experimental support for screen sharing - constraints = { - video: { - mozMediaSource: media.video, - mediaSource: media.video - }, - audio: isAudioSendEnabled(media) && !media.keepAudio - }; - getScreenMedia(constraints, function (err, stream) { - callbackUserMedia(err, stream); - // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 - if (!err) { - var lastTime = stream.currentTime; - var polly = window.setInterval(function () { - if(!stream) - window.clearInterval(polly); - if(stream.currentTime == lastTime) { - window.clearInterval(polly); - if(stream.onended) { - stream.onended(); - } - } - lastTime = stream.currentTime; - }, 500); - } - }); - } else { - var error = new Error('NavigatorUserMediaError'); - error.name = 'Your version of Firefox does not support screen sharing, please install Firefox 33 (or more recent versions)'; - pluginHandle.consentDialog(false); - callbacks.error(error); - return; - } - } - return; - } - } - // If we got here, we're not screensharing - if(!media || media.video !== 'screen') { - // Check whether all media sources are actually available or not - navigator.mediaDevices.enumerateDevices().then(function(devices) { - var audioExist = devices.some(function(device) { - return device.kind === 'audioinput'; - }), - videoExist = isScreenSendEnabled(media) || devices.some(function(device) { - return device.kind === 'videoinput'; - }); - - // Check whether a missing device is really a problem - var audioSend = isAudioSendEnabled(media); - var videoSend = isVideoSendEnabled(media); - var needAudioDevice = isAudioSendRequired(media); - var needVideoDevice = isVideoSendRequired(media); - if(audioSend || videoSend || needAudioDevice || needVideoDevice) { - // We need to send either audio or video - var haveAudioDevice = audioSend ? audioExist : false; - var haveVideoDevice = videoSend ? videoExist : false; - if(!haveAudioDevice && !haveVideoDevice) { - // FIXME Should we really give up, or just assume recvonly for both? - pluginHandle.consentDialog(false); - callbacks.error('No capture device found'); - return false; - } else if(!haveAudioDevice && needAudioDevice) { - pluginHandle.consentDialog(false); - callbacks.error('Audio capture is required, but no capture device found'); - return false; - } else if(!haveVideoDevice && needVideoDevice) { - pluginHandle.consentDialog(false); - callbacks.error('Video capture is required, but no capture device found'); - return false; - } - } - - var gumConstraints = { - audio: (audioExist && !media.keepAudio) ? audioSupport : false, - video: (videoExist && !media.keepVideo) ? videoSupport : false - }; - Janus.debug("getUserMedia constraints", gumConstraints); - if (!gumConstraints.audio && !gumConstraints.video) { - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, stream); - } else { - navigator.mediaDevices.getUserMedia(gumConstraints) - .then(function(stream) { - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, stream); - }).catch(function(error) { - pluginHandle.consentDialog(false); - callbacks.error({code: error.code, name: error.name, message: error.message}); - }); - } - }) - .catch(function(error) { - pluginHandle.consentDialog(false); - callbacks.error(error); - }); - } - } else { - // No need to do a getUserMedia, create offer/answer right away - streamsDone(handleId, jsep, media, callbacks); - } - } - - function prepareWebrtcPeer(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError; - callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop; - var jsep = callbacks.jsep; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - if(jsep) { - if(!config.pc) { - Janus.warn("Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep"); - callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep"); - return; - } - callbacks.customizeSdp(jsep); - config.pc.setRemoteDescription(jsep) - .then(function() { - Janus.log("Remote description accepted!"); - config.remoteSdp = jsep.sdp; - // Any trickle candidate we cached? - if(config.candidates && config.candidates.length > 0) { - for(var i = 0; i< config.candidates.length; i++) { - var candidate = config.candidates[i]; - Janus.debug("Adding remote candidate:", candidate); - if(!candidate || candidate.completed === true) { - // end-of-candidates - config.pc.addIceCandidate(Janus.endOfCandidates); - } else { - // New candidate - config.pc.addIceCandidate(candidate); - } - } - config.candidates = []; - } - // Done - callbacks.success(); - }, callbacks.error); - } else { - callbacks.error("Invalid JSEP"); - } - } - - function createOffer(handleId, media, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - var simulcast = (callbacks.simulcast === true); - if(!simulcast) { - Janus.log("Creating offer (iceDone=" + config.iceDone + ")"); - } else { - Janus.log("Creating offer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")"); - } - // https://code.google.com/p/webrtc/issues/detail?id=3508 - var mediaConstraints = {}; - if(Janus.unifiedPlan) { - // We can use Transceivers - var audioTransceiver = null, videoTransceiver = null; - var transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(var t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "audio") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) { - if(!audioTransceiver) { - audioTransceiver = t; - } - continue; - } - if((t.sender && t.sender.track && t.sender.track.kind === "video") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) { - if(!videoTransceiver) { - videoTransceiver = t; - } - continue; - } - } - } - // Handle audio (and related changes, if any) - var audioSend = isAudioSendEnabled(media); - var audioRecv = isAudioRecvEnabled(media); - if(!audioSend && !audioRecv) { - // Audio disabled: have we removed it? - if(media.removeAudio && audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("inactive"); - } else { - audioTransceiver.direction = "inactive"; - } - Janus.log("Setting audio transceiver to inactive:", audioTransceiver); - } - } else { - // Take care of audio m-line - if(audioSend && audioRecv) { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendrecv"); - } else { - audioTransceiver.direction = "sendrecv"; - } - Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver); - } - } else if(audioSend && !audioRecv) { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendonly"); - } else { - audioTransceiver.direction = "sendonly"; - } - Janus.log("Setting audio transceiver to sendonly:", audioTransceiver); - } - } else if(!audioSend && audioRecv) { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("recvonly"); - } else { - audioTransceiver.direction = "recvonly"; - } - Janus.log("Setting audio transceiver to recvonly:", audioTransceiver); - } else { - // In theory, this is the only case where we might not have a transceiver yet - audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" }); - Janus.log("Adding recvonly audio transceiver:", audioTransceiver); - } - } - } - // Handle video (and related changes, if any) - var videoSend = isVideoSendEnabled(media); - var videoRecv = isVideoRecvEnabled(media); - if(!videoSend && !videoRecv) { - // Video disabled: have we removed it? - if(media.removeVideo && videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("inactive"); - } else { - videoTransceiver.direction = "inactive"; - } - Janus.log("Setting video transceiver to inactive:", videoTransceiver); - } - } else { - // Take care of video m-line - if(videoSend && videoRecv) { - if(videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendrecv"); - } else { - videoTransceiver.direction = "sendrecv"; - } - Janus.log("Setting video transceiver to sendrecv:", videoTransceiver); - } - } else if(videoSend && !videoRecv) { - if(videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendonly"); - } else { - videoTransceiver.direction = "sendonly"; - } - Janus.log("Setting video transceiver to sendonly:", videoTransceiver); - } - } else if(!videoSend && videoRecv) { - if(videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("recvonly"); - } else { - videoTransceiver.direction = "recvonly"; - } - Janus.log("Setting video transceiver to recvonly:", videoTransceiver); - } else { - // In theory, this is the only case where we might not have a transceiver yet - videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" }); - Janus.log("Adding recvonly video transceiver:", videoTransceiver); - } - } - } - } else { - mediaConstraints["offerToReceiveAudio"] = isAudioRecvEnabled(media); - mediaConstraints["offerToReceiveVideo"] = isVideoRecvEnabled(media); - } - var iceRestart = (callbacks.iceRestart === true); - if(iceRestart) { - mediaConstraints["iceRestart"] = true; - } - Janus.debug(mediaConstraints); - // Check if this is Firefox and we've been asked to do simulcasting - var sendVideo = isVideoSendEnabled(media); - if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") { - // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b - Janus.log("Enabling Simulcasting for Firefox (RID)"); - var sender = config.pc.getSenders().find(function(s) {return s.track && s.track.kind === "video"}); - if(sender) { - var parameters = sender.getParameters(); - if(!parameters) { - parameters = {}; - } - var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates); - parameters.encodings = callbacks.sendEncodings || [ - { rid: "h", active: true, maxBitrate: maxBitrates.high }, - { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 }, - { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 } - ]; - sender.setParameters(parameters); - } - } - config.pc.createOffer(mediaConstraints) - .then(function(offer) { - Janus.debug(offer); - // JSON.stringify doesn't work on some WebRTC objects anymore - // See https://code.google.com/p/chromium/issues/detail?id=467366 - var jsep = { - "type": offer.type, - "sdp": offer.sdp - }; - callbacks.customizeSdp(jsep); - offer.sdp = jsep.sdp; - Janus.log("Setting local description"); - if(sendVideo && simulcast) { - // This SDP munging only works with Chrome (Safari STP may support it too) - if(Janus.webRTCAdapter.browserDetails.browser === "chrome" || - Janus.webRTCAdapter.browserDetails.browser === "safari") { - Janus.log("Enabling Simulcasting for Chrome (SDP munging)"); - offer.sdp = mungeSdpForSimulcasting(offer.sdp); - } else if(Janus.webRTCAdapter.browserDetails.browser !== "firefox") { - Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring"); - } - } - config.mySdp = { - type: "offer", - sdp: offer.sdp - }; - config.pc.setLocalDescription(offer) - .catch(callbacks.error); - config.mediaConstraints = mediaConstraints; - if(!config.iceDone && !config.trickle) { - // Don't do anything until we have all candidates - Janus.log("Waiting for all candidates..."); - return; - } - // If transforms are present, notify Janus that the media is end-to-end encrypted - if(config.senderTransforms || config.receiverTransforms) { - offer["e2ee"] = true; - } - callbacks.success(offer); - }, callbacks.error); - } - - function createAnswer(handleId, media, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - var simulcast = (callbacks.simulcast === true); - if(!simulcast) { - Janus.log("Creating answer (iceDone=" + config.iceDone + ")"); - } else { - Janus.log("Creating answer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")"); - } - var mediaConstraints = null; - if(Janus.unifiedPlan) { - // We can use Transceivers - mediaConstraints = {}; - var audioTransceiver = null, videoTransceiver = null; - var transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(var t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "audio") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) { - if(!audioTransceiver) - audioTransceiver = t; - continue; - } - if((t.sender && t.sender.track && t.sender.track.kind === "video") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) { - if(!videoTransceiver) - videoTransceiver = t; - continue; - } - } - } - // Handle audio (and related changes, if any) - var audioSend = isAudioSendEnabled(media); - var audioRecv = isAudioRecvEnabled(media); - if(!audioSend && !audioRecv) { - // Audio disabled: have we removed it? - if(media.removeAudio && audioTransceiver) { - try { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("inactive"); - } else { - audioTransceiver.direction = "inactive"; - } - Janus.log("Setting audio transceiver to inactive:", audioTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else { - // Take care of audio m-line - if(audioSend && audioRecv) { - if(audioTransceiver) { - try { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendrecv"); - } else { - audioTransceiver.direction = "sendrecv"; - } - Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else if(audioSend && !audioRecv) { - try { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendonly"); - } else { - audioTransceiver.direction = "sendonly"; - } - Janus.log("Setting audio transceiver to sendonly:", audioTransceiver); - } - } catch(e) { - Janus.error(e); - } - } else if(!audioSend && audioRecv) { - if(audioTransceiver) { - try { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("recvonly"); - } else { - audioTransceiver.direction = "recvonly"; - } - Janus.log("Setting audio transceiver to recvonly:", audioTransceiver); - } catch(e) { - Janus.error(e); - } - } else { - // In theory, this is the only case where we might not have a transceiver yet - audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" }); - Janus.log("Adding recvonly audio transceiver:", audioTransceiver); - } - } - } - // Handle video (and related changes, if any) - var videoSend = isVideoSendEnabled(media); - var videoRecv = isVideoRecvEnabled(media); - if(!videoSend && !videoRecv) { - // Video disabled: have we removed it? - if(media.removeVideo && videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("inactive"); - } else { - videoTransceiver.direction = "inactive"; - } - Janus.log("Setting video transceiver to inactive:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else { - // Take care of video m-line - if(videoSend && videoRecv) { - if(videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendrecv"); - } else { - videoTransceiver.direction = "sendrecv"; - } - Janus.log("Setting video transceiver to sendrecv:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else if(videoSend && !videoRecv) { - if(videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendonly"); - } else { - videoTransceiver.direction = "sendonly"; - } - Janus.log("Setting video transceiver to sendonly:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else if(!videoSend && videoRecv) { - if(videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("recvonly"); - } else { - videoTransceiver.direction = "recvonly"; - } - Janus.log("Setting video transceiver to recvonly:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } else { - // In theory, this is the only case where we might not have a transceiver yet - videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" }); - Janus.log("Adding recvonly video transceiver:", videoTransceiver); - } - } - } - } else { - if(Janus.webRTCAdapter.browserDetails.browser === "firefox" || Janus.webRTCAdapter.browserDetails.browser === "edge") { - mediaConstraints = { - offerToReceiveAudio: isAudioRecvEnabled(media), - offerToReceiveVideo: isVideoRecvEnabled(media) - }; - } else { - mediaConstraints = { - mandatory: { - OfferToReceiveAudio: isAudioRecvEnabled(media), - OfferToReceiveVideo: isVideoRecvEnabled(media) - } - }; - } - } - Janus.debug(mediaConstraints); - // Check if this is Firefox and we've been asked to do simulcasting - var sendVideo = isVideoSendEnabled(media); - if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") { - // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b - Janus.log("Enabling Simulcasting for Firefox (RID)"); - var sender = config.pc.getSenders()[1]; - Janus.log(sender); - var parameters = sender.getParameters(); - Janus.log(parameters); - - var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates); - sender.setParameters({encodings: callbacks.sendEncodings || [ - { rid: "h", active: true, maxBitrate: maxBitrates.high }, - { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2}, - { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4} - ]}); - } - config.pc.createAnswer(mediaConstraints) - .then(function(answer) { - Janus.debug(answer); - // JSON.stringify doesn't work on some WebRTC objects anymore - // See https://code.google.com/p/chromium/issues/detail?id=467366 - var jsep = { - "type": answer.type, - "sdp": answer.sdp - }; - callbacks.customizeSdp(jsep); - answer.sdp = jsep.sdp; - Janus.log("Setting local description"); - if(sendVideo && simulcast) { - // This SDP munging only works with Chrome - if(Janus.webRTCAdapter.browserDetails.browser === "chrome") { - // FIXME Apparently trying to simulcast when answering breaks video in Chrome... - //~ Janus.log("Enabling Simulcasting for Chrome (SDP munging)"); - //~ answer.sdp = mungeSdpForSimulcasting(answer.sdp); - Janus.warn("simulcast=true, but this is an answer, and video breaks in Chrome if we enable it"); - } else if(Janus.webRTCAdapter.browserDetails.browser !== "firefox") { - Janus.warn("simulcast=true, but this is not Chrome nor Firefox, ignoring"); - } - } - config.mySdp = { - type: "answer", - sdp: answer.sdp - }; - config.pc.setLocalDescription(answer) - .catch(callbacks.error); - config.mediaConstraints = mediaConstraints; - if(!config.iceDone && !config.trickle) { - // Don't do anything until we have all candidates - Janus.log("Waiting for all candidates..."); - return; - } - // If transforms are present, notify Janus that the media is end-to-end encrypted - if(config.senderTransforms || config.receiverTransforms) { - answer["e2ee"] = true; - } - callbacks.success(answer); - }, callbacks.error); - } - - function sendSDP(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle, not sending anything"); - return; - } - var config = pluginHandle.webrtcStuff; - Janus.log("Sending offer/answer SDP..."); - if(!config.mySdp) { - Janus.warn("Local SDP instance is invalid, not sending anything..."); - return; - } - config.mySdp = { - "type": config.pc.localDescription.type, - "sdp": config.pc.localDescription.sdp - }; - if(config.trickle === false) - config.mySdp["trickle"] = false; - Janus.debug(callbacks); - config.sdpSent = true; - callbacks.success(config.mySdp); - } - - function getVolume(handleId, remote) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return 0; - } - var stream = remote ? "remote" : "local"; - var config = pluginHandle.webrtcStuff; - if(!config.volume[stream]) - config.volume[stream] = { value: 0 }; - // Start getting the volume, if audioLevel in getStats is supported (apparently - // they're only available in Chrome/Safari right now: https://webrtc-stats.callstats.io/) - if(config.pc.getStats && (Janus.webRTCAdapter.browserDetails.browser === "chrome" || - Janus.webRTCAdapter.browserDetails.browser === "safari")) { - if(remote && !config.remoteStream) { - Janus.warn("Remote stream unavailable"); - return 0; - } else if(!remote && !config.myStream) { - Janus.warn("Local stream unavailable"); - return 0; - } - if(!config.volume[stream].timer) { - Janus.log("Starting " + stream + " volume monitor"); - config.volume[stream].timer = setInterval(function() { - config.pc.getStats() - .then(function(stats) { - stats.forEach(function (res) { - if(!res || res.kind !== "audio") - return; - if((remote && !res.remoteSource) || (!remote && res.type !== "media-source")) - return; - config.volume[stream].value = (res.audioLevel ? res.audioLevel : 0); - }); - }); - }, 200); - return 0; // We don't have a volume to return yet - } - return config.volume[stream].value; - } else { - // audioInputLevel and audioOutputLevel seem only available in Chrome? audioLevel - // seems to be available on Chrome and Firefox, but they don't seem to work - Janus.warn("Getting the " + stream + " volume unsupported by browser"); - return 0; - } - } - - function isMuted(handleId, video) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return true; - } - var config = pluginHandle.webrtcStuff; - if(!config.pc) { - Janus.warn("Invalid PeerConnection"); - return true; - } - if(!config.myStream) { - Janus.warn("Invalid local MediaStream"); - return true; - } - if(video) { - // Check video track - if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) { - Janus.warn("No video track"); - return true; - } - return !config.myStream.getVideoTracks()[0].enabled; - } else { - // Check audio track - if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) { - Janus.warn("No audio track"); - return true; - } - return !config.myStream.getAudioTracks()[0].enabled; - } - } - - function mute(handleId, video, mute) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return false; - } - var config = pluginHandle.webrtcStuff; - if(!config.pc) { - Janus.warn("Invalid PeerConnection"); - return false; - } - if(!config.myStream) { - Janus.warn("Invalid local MediaStream"); - return false; - } - if(video) { - // Mute/unmute video track - if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) { - Janus.warn("No video track"); - return false; - } - config.myStream.getVideoTracks()[0].enabled = !mute; - return true; - } else { - // Mute/unmute audio track - if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) { - Janus.warn("No audio track"); - return false; - } - config.myStream.getAudioTracks()[0].enabled = !mute; - return true; - } - } - - function getBitrate(handleId) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return "Invalid handle"; - } - var config = pluginHandle.webrtcStuff; - if(!config.pc) - return "Invalid PeerConnection"; - // Start getting the bitrate, if getStats is supported - if(config.pc.getStats) { - if(!config.bitrate.timer) { - Janus.log("Starting bitrate timer (via getStats)"); - config.bitrate.timer = setInterval(function() { - config.pc.getStats() - .then(function(stats) { - stats.forEach(function (res) { - if(!res) - return; - var inStats = false; - // Check if these are statistics on incoming media - if((res.mediaType === "video" || res.id.toLowerCase().indexOf("video") > -1) && - res.type === "inbound-rtp" && res.id.indexOf("rtcp") < 0) { - // New stats - inStats = true; - } else if(res.type == 'ssrc' && res.bytesReceived && - (res.googCodecName === "VP8" || res.googCodecName === "")) { - // Older Chromer versions - inStats = true; - } - // Parse stats now - if(inStats) { - config.bitrate.bsnow = res.bytesReceived; - config.bitrate.tsnow = res.timestamp; - if(config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) { - // Skip this round - config.bitrate.bsbefore = config.bitrate.bsnow; - config.bitrate.tsbefore = config.bitrate.tsnow; - } else { - // Calculate bitrate - var timePassed = config.bitrate.tsnow - config.bitrate.tsbefore; - if(Janus.webRTCAdapter.browserDetails.browser === "safari") - timePassed = timePassed/1000; // Apparently the timestamp is in microseconds, in Safari - var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 / timePassed); - if(Janus.webRTCAdapter.browserDetails.browser === "safari") - bitRate = parseInt(bitRate/1000); - config.bitrate.value = bitRate + ' kbits/sec'; - //~ Janus.log("Estimated bitrate is " + config.bitrate.value); - config.bitrate.bsbefore = config.bitrate.bsnow; - config.bitrate.tsbefore = config.bitrate.tsnow; - } - } - }); - }); - }, 1000); - return "0 kbits/sec"; // We don't have a bitrate value yet - } - return config.bitrate.value; - } else { - Janus.warn("Getting the video bitrate unsupported by browser"); - return "Feature unsupported by browser"; - } - } - - function webrtcError(error) { - Janus.error("WebRTC error:", error); - } - - function cleanupWebrtc(handleId, hangupRequest) { - Janus.log("Cleaning WebRTC stuff"); - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle) { - // Nothing to clean - return; - } - var config = pluginHandle.webrtcStuff; - if(config) { - if(hangupRequest === true) { - // Send a hangup request (we don't really care about the response) - var request = { "janus": "hangup", "transaction": Janus.randomString(12) }; - if(pluginHandle.token) - request["token"] = pluginHandle.token; - if(apisecret) - request["apisecret"] = apisecret; - Janus.debug("Sending hangup request (handle=" + handleId + "):"); - Janus.debug(request); - if(websockets) { - request["session_id"] = sessionId; - request["handle_id"] = handleId; - ws.send(JSON.stringify(request)); - } else { - Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, { - verb: 'POST', - withCredentials: withCredentials, - body: request - }); - } - } - // Cleanup stack - config.remoteStream = null; - if(config.volume) { - if(config.volume["local"] && config.volume["local"].timer) - clearInterval(config.volume["local"].timer); - if(config.volume["remote"] && config.volume["remote"].timer) - clearInterval(config.volume["remote"].timer); - } - config.volume = {}; - if(config.bitrate.timer) - clearInterval(config.bitrate.timer); - config.bitrate.timer = null; - config.bitrate.bsnow = null; - config.bitrate.bsbefore = null; - config.bitrate.tsnow = null; - config.bitrate.tsbefore = null; - config.bitrate.value = null; - if(!config.streamExternal && config.myStream) { - Janus.log("Stopping local stream tracks"); - Janus.stopAllTracks(config.myStream); - } - config.streamExternal = false; - config.myStream = null; - // Close PeerConnection - try { - config.pc.close(); - } catch(e) { - // Do nothing - } - config.pc = null; - config.candidates = null; - config.mySdp = null; - config.remoteSdp = null; - config.iceDone = false; - config.dataChannel = {}; - config.dtmfSender = null; - config.senderTransforms = null; - config.receiverTransforms = null; - } - pluginHandle.oncleanup(); - } - - // Helper method to munge an SDP to enable simulcasting (Chrome only) - function mungeSdpForSimulcasting(sdp) { - // Let's munge the SDP to add the attributes for enabling simulcasting - // (based on https://gist.github.com/ggarber/a19b4c33510028b9c657) - var lines = sdp.split("\r\n"); - var video = false; - var ssrc = [ -1 ], ssrc_fid = [ -1 ]; - var cname = null, msid = null, mslabel = null, label = null; - var insertAt = -1; - for(let i=0; i -1) { - // We're done, let's add the new attributes here - insertAt = i; - break; - } - } - continue; - } - if(!video) - continue; - var sim = lines[i].match(/a=ssrc-group:SIM (\d+) (\d+) (\d+)/); - if(sim) { - Janus.warn("The SDP already contains a SIM attribute, munging will be skipped"); - return sdp; - } - var fid = lines[i].match(/a=ssrc-group:FID (\d+) (\d+)/); - if(fid) { - ssrc[0] = fid[1]; - ssrc_fid[0] = fid[2]; - lines.splice(i, 1); i--; - continue; - } - if(ssrc[0]) { - var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)') - if(match) { - cname = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)') - if(match) { - msid = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)') - if(match) { - mslabel = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)') - if(match) { - label = match[1]; - } - if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - } - if(lines[i].length == 0) { - lines.splice(i, 1); i--; - continue; - } - } - if(ssrc[0] < 0) { - // Couldn't find a FID attribute, let's just take the first video SSRC we find - insertAt = -1; - video = false; - for(let i=0; i -1) { - // We're done, let's add the new attributes here - insertAt = i; - break; - } - } - continue; - } - if(!video) - continue; - if(ssrc[0] < 0) { - var value = lines[i].match(/a=ssrc:(\d+)/); - if(value) { - ssrc[0] = value[1]; - lines.splice(i, 1); i--; - continue; - } - } else { - let match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)') - if(match) { - cname = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)') - if(match) { - msid = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)') - if(match) { - mslabel = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)') - if(match) { - label = match[1]; - } - if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - } - if(lines[i].length === 0) { - lines.splice(i, 1); i--; - continue; - } - } - } - if(ssrc[0] < 0) { - // Still nothing, let's just return the SDP we were asked to munge - Janus.warn("Couldn't find the video SSRC, simulcasting NOT enabled"); - return sdp; - } - if(insertAt < 0) { - // Append at the end - insertAt = lines.length; - } - // Generate a couple of SSRCs (for retransmissions too) - // Note: should we check if there are conflicts, here? - ssrc[1] = Math.floor(Math.random()*0xFFFFFFFF); - ssrc[2] = Math.floor(Math.random()*0xFFFFFFFF); - ssrc_fid[1] = Math.floor(Math.random()*0xFFFFFFFF); - ssrc_fid[2] = Math.floor(Math.random()*0xFFFFFFFF); - // Add attributes to the SDP - for(var i=0; i Date: Mon, 17 Jan 2022 14:09:27 -0500 Subject: [PATCH 313/501] bump version for janus --- distros/redhat/zoneminder.spec | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 0d189dc9c..33996f755 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.7 +Version: 1.37.8 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index 199e8f7a3..21b6230a1 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.7 +1.37.8 From 4374a6e70ee0607fc22d997ccdeb12d9e813f3db Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 17 Jan 2022 14:35:59 -0500 Subject: [PATCH 314/501] Bump versions of eslint to latest --- .github/workflows/ci-eslint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-eslint.yml b/.github/workflows/ci-eslint.yml index 7a5c4337f..dce24c367 100644 --- a/.github/workflows/ci-eslint.yml +++ b/.github/workflows/ci-eslint.yml @@ -16,6 +16,6 @@ jobs: with: submodules: recursive - name: Install ESLint - run: npm install eslint@5.12.0 eslint-config-google@0.11.0 eslint-plugin-html@5.0.0 eslint-plugin-php-markup@0.2.5 + run: npm install eslint@8.7.0 eslint-config-google@0.14.0 eslint-plugin-html@6.2.0 eslint-plugin-php-markup@6.0.0 - name: Run ESLint run: npx eslint --ext .php,.js . From 32a5dcbda995c432d3decd2d77470a16ba9853f6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 17 Jan 2022 14:52:43 -0500 Subject: [PATCH 315/501] ignore janus for eslint --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index 91b0fd196..2e2fdd39a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,6 +13,7 @@ web/skins/classic/js/jquery.js web/skins/classic/js/moment.js web/skins/classic/js/video.js web/tools/mootools +web/js/janus.js # Cannot be parsed as JS web/skins/classic/includes/export_functions.php From 24c2efeb00005d06492bffe34e296cc18bbbaa62 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 17 Jan 2022 16:19:27 -0500 Subject: [PATCH 316/501] fix eslint --- web/js/MonitorStream.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index aaf5ac8fb..5a0dd7e25 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -303,13 +303,13 @@ function MonitorStream(monitorData) { } // end function MonitorStream async function attachVideo(id) { - await waitUntil(() => janus.isConnected() ) + await waitUntil(() => janus.isConnected() ); janus.attach({ plugin: "janus.plugin.streaming", opaqueId: "streamingtest-"+Janus.randomString(12), success: function(pluginHandle) { streaming[id] = pluginHandle; - var body = { "request": "watch", "id": id}; + var body = {"request": "watch", "id": id}; streaming[id].send({"message": body}); }, error: function(error) { @@ -319,26 +319,26 @@ async function attachVideo(id) { Janus.debug(" ::: Got a message :::"); Janus.debug(msg); var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { + if (result !== null && result !== undefined) { + if (result["status"] !== undefined && result["status"] !== null) { var status = result["status"]; Janus.debug(status); } - } else if(msg["error"] !== undefined && msg["error"] !== null) { + } else if (msg["error"] !== undefined && msg["error"] !== null) { return; } - if(jsep !== undefined && jsep !== null) { + if (jsep !== undefined && jsep !== null) { Janus.debug("Handling SDP as well..."); Janus.debug(jsep); // Offer from the plugin, let's answer streaming[id].createAnswer({ jsep: jsep, // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, + media: {audioSend: false, videoSend: false, data: true}, success: function(jsep) { Janus.debug("Got SDP!"); Janus.debug(jsep); - var body = { "request": "start"}; + var body = {"request": "start"}; streaming[id].send({"message": body, "jsep": jsep}); }, error: function(error) { @@ -357,7 +357,7 @@ async function attachVideo(id) { const waitUntil = (condition) => { return new Promise((resolve) => { - let interval = setInterval(() => { + const interval = setInterval(() => { if (!condition()) { return; } @@ -365,4 +365,4 @@ const waitUntil = (condition) => { resolve(); }, 100); }); -} +}; From 90492d332c57df7345013eefcc1ac8d008fbd5e5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Jan 2022 10:39:24 -0500 Subject: [PATCH 317/501] Put back to brand 3.0 of upstream --- web/api/app/Plugin/Crud | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/api/app/Plugin/Crud b/web/api/app/Plugin/Crud index 0bd63fb46..14292374c 160000 --- a/web/api/app/Plugin/Crud +++ b/web/api/app/Plugin/Crud @@ -1 +1 @@ -Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef +Subproject commit 14292374ccf1328f2d5db20897bd06f99ba4d938 From 0c055edb5e3dbe430bb77e68d9fd646df7721cd0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Jan 2022 12:20:19 -0500 Subject: [PATCH 318/501] I think in all my changes I got rid of the code that opens events on Alarm. --- src/zm_monitor.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 15a812f83..aea0c0000 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2087,16 +2087,8 @@ bool Monitor::Analyse() { if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); - - if (!event) { - event = openEvent(snap, cause, noteSetMap); - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } // end if no event, so start it shared_data->state = state = ALARM; - if (alarm_frame_count) { - Debug(1, "alarm frame count so SavePreAlarmFrames"); - event->SavePreAlarmFrames(); - } + } else if (state != PREALARM) { Info("%s: %03d - Gone into prealarm state", name.c_str(), analysis_image_count); shared_data->state = state = PREALARM; @@ -2189,7 +2181,14 @@ bool Monitor::Analyse() { event = openEvent(snap, cause, noteSetMap); } } else { - Error("ALARM but no event"); + if (!event) { + event = openEvent(snap, cause, noteSetMap); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } // end if no event, so start it + if (alarm_frame_count) { + Debug(1, "alarm frame count so SavePreAlarmFrames"); + event->SavePreAlarmFrames(); + } } } else if (state == ALERT) { // Alert means this frame has no motion, but we were alarmed and are still recording. From 3241fa59c5eb3834add68e537e90b69d985ef9d4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Jan 2022 12:52:21 -0500 Subject: [PATCH 319/501] Don't redirect if there was an error so that we display it --- web/includes/actions/monitor.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/includes/actions/monitor.php b/web/includes/actions/monitor.php index 2d900433a..7ec354179 100644 --- a/web/includes/actions/monitor.php +++ b/web/includes/actions/monitor.php @@ -324,7 +324,8 @@ if ($action == 'save') { // really should thump zmwatch and maybe zmtrigger too. //daemonControl( 'restart', 'zmwatch.pl' ); } // end if restart - $redirect = '?view=console'; + if (!$error_message) + $redirect = '?view=console'; } else { ZM\Warning("Unknown action $action in Monitor"); } // end if action == Delete From d2d9721c4e6ead2edbfab8c8acdbf5a87c2d11a1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Jan 2022 13:43:56 -0500 Subject: [PATCH 320/501] spacing, remove redundant debug --- src/zm_eventstream.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 54f6b60d8..28a0e50d6 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -1096,35 +1096,33 @@ void EventStream::runStream() { bool EventStream::send_file(const std::string &filepath) { FILE *fdj = nullptr; fdj = fopen(filepath.c_str(), "rb"); - if ( !fdj ) { + if (!fdj) { Error("Can't open %s: %s", filepath.c_str(), strerror(errno)); std::string error_message = stringtf("Can't open %s: %s", filepath.c_str(), strerror(errno)); return sendTextFrame(error_message.c_str()); } #if HAVE_SENDFILE static struct stat filestat; - if ( fstat(fileno(fdj), &filestat) < 0 ) { + if (fstat(fileno(fdj), &filestat) < 0) { fclose(fdj); /* Close the file handle */ Error("Failed getting information about file %s: %s", filepath.c_str(), strerror(errno)); return false; } - if ( !filestat.st_size ) { + if (!filestat.st_size) { fclose(fdj); /* Close the file handle */ Info("File size is zero. Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); return false; } - if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size) ) { + if (0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size)) { fclose(fdj); /* Close the file handle */ Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); return false; } int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size); - if ( rc == (int)filestat.st_size ) { + if (rc == (int)filestat.st_size) { // Success fclose(fdj); /* Close the file handle */ return true; - } else { - Debug(1, "Failed to sendfile?"); } Warning("Unable to send raw frame %ld: %s rc %d != %d", curr_frame_id, strerror(errno), rc, (int)filestat.st_size); From 13ee39b1e9fb8b4103acb0a71e71d71b13031c4b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Jan 2022 13:44:21 -0500 Subject: [PATCH 321/501] Log errors talking to Janus. Log if janus is turned on but not compiled in --- src/zm_monitor.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index aea0c0000..454e7068c 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1126,6 +1126,9 @@ bool Monitor::connect() { } } } +#else + if (janus_enabled) + Error("zmc not compiled with LIBCURL. Janus support not built in!"); #endif } else if (!shared_data->valid) { @@ -3390,7 +3393,10 @@ int Monitor::add_to_janus() { CURLcode res; curl = curl_easy_init(); - if(!curl) return -1; + if (!curl) { + Error("Failed to init curl"); + return -1; + } //parse username and password pos = path.find(":", 7); if (pos == std::string::npos) return -1; @@ -3408,7 +3414,11 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; + if (res != CURLE_OK) { + Error("Failed to curl_easy_perform getting session/handle id"); + curl_easy_cleanup(curl); + return -1; + } pos = response.find("\"id\": "); if (pos == std::string::npos) return -1; @@ -3421,7 +3431,11 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; + if (res != CURLE_OK) { + Error("Failed to curl_easy_perform attaching"); + curl_easy_cleanup(curl); + return -1; + } pos = response.find("\"id\": "); if (pos == std::string::npos) return -1; std::string handle_id = response.substr(pos + 6, 16); //TODO: This is an assumption that the string is always 16 @@ -3446,7 +3460,11 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; + if (res != CURLE_OK) { + Error("Failed to curl_easy_perform adding rtsp stream"); + curl_easy_cleanup(curl); + return -1; + } Debug(1,"Added stream to Janus: %s", response.c_str()); curl_easy_cleanup(curl); return 0; From 388735e94288c65914f8ed60eb007fab987b759b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Jan 2022 14:30:10 -0500 Subject: [PATCH 322/501] Fix relating auth_hash causing repeated reloads. If https, then assume a reverse proxy setup to janus. If video feed is not an img, log it and return --- web/js/MonitorStream.js | 47 +++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index 5a0dd7e25..54669a964 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -47,6 +47,11 @@ function MonitorStream(monitorData) { this.scale = newscale; const oldSrc = img.getAttribute('src'); + if (!oldSrc) { + console.log("No src on img?!"); + console.log(img); + return; + } let newSrc = ''; img.setAttribute('src', ''); @@ -85,7 +90,14 @@ function MonitorStream(monitorData) { if (this.janusEnabled) { var id = parseInt(this.id); - var server = "http://" + window.location.hostname + ":8088/janus"; + var server; + if (window.location.protocol=='https:') { + // Assume reverse proxy setup for now + server = "https://" + window.location.hostname + "/janus"; + } else { + server = "http://" + window.location.hostname + ":8088/janus"; + } + if (janus == null) { Janus.init({debug: "all", callback: function() { janus = new Janus({server: server}); //new Janus @@ -178,11 +190,16 @@ function MonitorStream(monitorData) { this.getStreamCmdResponse = function(respObj, respText) { var stream = $j('#liveStream'+this.id)[0]; - if ( ! stream ) { + if (!stream) { console.log('No live stream'); return; } + //watchdogOk('stream'); + if (streamCmdTimer) { + streamCmdTimer = clearTimeout(streamCmdTimer); + } + if ( respObj.result == 'Ok' ) { if ( respObj.status ) { this.status = respObj.status; @@ -202,13 +219,25 @@ function MonitorStream(monitorData) { !COMPACT_MONTAGE) && (this.type != 'WebSite') ) { - var fpsValue = $j('#fpsValue'+this.id); - var stateValue = $j('#stateValue'+this.id); - var monitorState = $j('#monitorState'+this.id); + const viewingFPSValue = $j('#vewingFPSValue'+this.id); + const captureFPSValue = $j('#captureFPSValue'+this.id); + const analysisFPSValue = $j('#analysisFPSValue'+this.id); - if ( fpsValue.length ) fpsValue.text(this.status.fps); - if ( stateValue.length ) stateValue.text(stateStrings[this.alarmState]); - if ( monitorState.length ) this.setStateClass(monitorState, stateClass); + const stateValue = $j('#stateValue'+this.id); + const monitorState = $j('#monitorState'+this.id); + + if (viewingFPSValue.length && (viewingFPSValue.text != this.status.fps)) { + viewingFPSValue.text(this.status.fps); + } + if (analysisFPSValue.length && (analysisFPSValue.text != this.status.analysisfps)) { + analysisFPSValue.text(this.status.analysisfps); + } + if (captureFPSValue.length && (captureFPSValue.text != this.status.capturefps)) { + captureFPSValue.text(this.status.capturefps); + } + + if (stateValue.length) stateValue.text(stateStrings[this.alarmState]); + if (monitorState.length) this.setStateClass(monitorState, stateClass); } this.setStateClass($j('#monitor'+this.id), stateClass); @@ -238,7 +267,7 @@ function MonitorStream(monitorData) { } } if (this.status.auth) { - if (this.status.auth != auth_hash) { + if (this.status.auth != this.auth_hash) { // Try to reload the image stream. if (stream) { const oldsrc = stream.src; From d00aaa11e9231e0d6d96adde5e327307e61ab6f2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Jan 2022 23:01:24 -0500 Subject: [PATCH 323/501] default JanusEnabled to 0 so that we can turn it off --- web/includes/actions/monitor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/includes/actions/monitor.php b/web/includes/actions/monitor.php index 7ec354179..e0f695d44 100644 --- a/web/includes/actions/monitor.php +++ b/web/includes/actions/monitor.php @@ -89,6 +89,7 @@ if ($action == 'save') { 'ModectDuringPTZ' => 0, 'Enabled' => 0, 'DecodingEnabled' => 0, + 'JanusEnabled' => 0, 'Exif' => 0, 'RTSPDescribe' => 0, 'V4LMultiBuffer' => '', From fb832e7d1bd13b645fed9bd26c994d2fa5278788 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 19 Jan 2022 00:14:52 -0600 Subject: [PATCH 324/501] Adds janus_enable_audio, a switch to try to enable audio in live stream viewing. --- db/zm_create.sql.in | 1 + db/zm_update-1.37.9.sql | 18 ++++++++++++++++++ src/zm_monitor.cpp | 9 ++++++--- src/zm_monitor.h | 13 +++++++++++++ web/ajax/modals/function.php | 10 ++++++++++ web/includes/Monitor.php | 1 + web/includes/actions/monitor.php | 1 + web/lang/en_gb.php | 5 +++++ web/skins/classic/views/js/monitor.js | 15 +++++++++++++++ web/skins/classic/views/monitor.php | 10 ++++++++++ 10 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 db/zm_update-1.37.9.sql diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 06d2ab385..db72cb58d 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -456,6 +456,7 @@ CREATE TABLE `Monitors` ( `Enabled` tinyint(3) unsigned NOT NULL default '1', `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1', `JanusEnabled` BOOLEAN NOT NULL default false, + `JanusAudioEnabled` BOOLEAN NOT NULL default false, `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '', diff --git a/db/zm_update-1.37.9.sql b/db/zm_update-1.37.9.sql new file mode 100644 index 000000000..be7d2a9ec --- /dev/null +++ b/db/zm_update-1.37.9.sql @@ -0,0 +1,18 @@ +-- +-- Update Monitors table to have JanusEnabled +-- + +SELECT 'Checking for JanusAudioEnabled in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'JanusAudioEnabled' + ) > 0, +"SELECT 'Column JanusAudioEnabled already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `JanusAudioEnabled` BOOLEAN NOT NULL default false AFTER `JanusEnabled`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 454e7068c..1733dd24d 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -77,7 +77,7 @@ struct Namespace namespaces[] = // This is the official SQL (and ordering of the fields) to load a Monitor. // It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended std::string load_monitor_sql = -"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, `JanusEnabled`," +"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, `JanusEnabled`, `JanusAudioEnabled`," "`LinkedMonitors`, `EventStartCommand`, `EventEndCommand`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," "`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings "`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `SecondPath`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " @@ -307,6 +307,7 @@ Monitor::Monitor() enabled(false), decoding_enabled(false), janus_enabled(false), + janus_audio_enabled(false), //protocol //method //options @@ -447,7 +448,7 @@ Monitor::Monitor() /* std::string load_monitor_sql = - "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, JanusEnabled, LinkedMonitors, `EventStartCommand`, `EventEndCommand`, " + "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, JanusEnabled, JanusAudioEnabled, LinkedMonitors, `EventStartCommand`, `EventEndCommand`, " "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " @@ -501,6 +502,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; // See below after save_jpegs for a recalculation of decoding_enabled janus_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + janus_audio_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; ReloadLinkedMonitors(dbrow[col]); col++; event_start_command = dbrow[col] ? dbrow[col] : ""; col++; @@ -1121,7 +1123,7 @@ bool Monitor::connect() { #if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { if (add_to_janus() != 0) { - if (add_to_janus() != 0) { + if (add_to_janus() != 0) { //The initial attempt may fail. This is a temporary workaround. Warning("Failed to add monitor stream to Janus!"); } } @@ -3453,6 +3455,7 @@ int Monitor::add_to_janus() { postData += rtsp_password; postData += "\", \"id\" : "; postData += std::to_string(id); + if (janus_audio_enabled) postData += ", \"audio\" : true"; postData += ", \"video\" : true}}"; curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 0d63816e9..94c37affb 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -269,6 +269,7 @@ protected: bool enabled; // Whether the monitor is enabled or asleep bool decoding_enabled; // Whether the monitor will decode h264/h265 packets bool janus_enabled; // Whether we set the h264/h265 stream up on janus + bool janus_audio_enabled; // Whether we tell Janus to try to include audio. std::string protocol; std::string method; @@ -511,6 +512,18 @@ public: inline bool DecodingEnabled() const { return decoding_enabled; } + bool JanusEnabled() { + return janus_enabled; + } + bool JanusAudioEnabled() { + return janus_audio_enabled; + } + bool OnvifEnabled() { + return onvif_event_listener; + } + bool OnvifHealthy() { + return ONVIF_Healthy; + } inline const char *EventPrefix() const { return event_prefix.c_str(); } inline bool Ready() const { if ( image_count >= ready_count ) { diff --git a/web/ajax/modals/function.php b/web/ajax/modals/function.php index b23bbb649..c47664c4b 100644 --- a/web/ajax/modals/function.php +++ b/web/ajax/modals/function.php @@ -79,6 +79,16 @@ if ( !canEdit('Monitors') ) return; } ?> + +
+ + +'.$OLANG['FUNCTION_JANUS_AUDIO_ENABLED']['Help'].'
'; + } +?> + '; + } ?> From 04cfe372f83fbb70c56d7ff68674f327b80fda17 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 19 Jan 2022 00:56:36 -0600 Subject: [PATCH 325/501] Wrap public access function in Gsoap ifdef to remove build warning --- src/zm_monitor.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 94c37affb..898349eb3 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -521,9 +521,11 @@ public: bool OnvifEnabled() { return onvif_event_listener; } +#ifdef WITH_GSOAP bool OnvifHealthy() { return ONVIF_Healthy; } +#endif inline const char *EventPrefix() const { return event_prefix.c_str(); } inline bool Ready() const { if ( image_count >= ready_count ) { From ebcb3abf2efaca3e65cffa6acb5224f43cfa7da0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 19 Jan 2022 09:33:30 -0500 Subject: [PATCH 326/501] image_count is only relevant to capture. decode and analysis should use packet->image_index for indexing into image_buffer, etc. --- src/zm_monitor.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 1733dd24d..f5d225d66 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2076,7 +2076,7 @@ bool Monitor::Analyse() { && (event->Duration() >= min_section_length) && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", - name.c_str(), image_count, event->Id()); + name.c_str(), snap->image_index, event->Id()); closeEvent(); } else if (event) { // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames @@ -2091,7 +2091,7 @@ bool Monitor::Analyse() { } if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", - name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); + name.c_str(), snap->image_index, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); shared_data->state = state = ALARM; } else if (state != PREALARM) { @@ -2214,7 +2214,7 @@ bool Monitor::Analyse() { || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , name.c_str(), - image_count, + snap->image_index, event->Id(), static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), @@ -2558,8 +2558,6 @@ int Monitor::Capture() { } else { Debug(4, "Not Queueing audio packet"); } - // Don't update last_write_index because that is used for live streaming - //shared_data->last_write_time = image_buffer[index].timestamp->tv_sec; return 1; } else { Debug(1, "Unknown codec type %d", packet->codec_type); @@ -2690,7 +2688,7 @@ bool Monitor::Decode() { } // end if need_decoding Image* capture_image = nullptr; - unsigned int index = image_count % image_buffer_count; + unsigned int index = packet->image_index % image_buffer_count; if (packet->image) { capture_image = packet->image; From 062cde54a0c8139832aa4368eb75ddfae7aaa151 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 19 Jan 2022 10:45:38 -0500 Subject: [PATCH 327/501] Bump version for janus audio --- distros/redhat/zoneminder.spec | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 33996f755..16b8efb7e 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.8 +Version: 1.37.9 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index 21b6230a1..938adc6f6 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.8 +1.37.9 From 05043a37b1a35bc7f2a6ca34c58b54148fcc76bf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 19 Jan 2022 12:27:16 -0500 Subject: [PATCH 328/501] include image index in debug --- src/zm_monitorstream.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index f9e91d94b..a6232a23e 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -727,12 +727,12 @@ void MonitorStream::runStream() { (monitor->GetFunction() == Monitor::MOCORD || monitor->GetFunction() == Monitor::MODECT)) { Debug(1, "Sending analysis image"); send_image = monitor->GetAlarmImage(); - if ( !send_image ) { + if (!send_image) { Debug(1, "Falling back"); send_image = monitor->image_buffer[index]; } } else { - Debug(1, "Sending regular image"); + Debug(1, "Sending regular image index %d", index); send_image = monitor->image_buffer[index]; } From dec440ead1ba8b4b3c3baf17543e7ccd56e901d5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 19 Jan 2022 15:01:37 -0500 Subject: [PATCH 329/501] Unset holdbuffer so that when we connect, we reset the shm buffer pointer. cleanup initializers in Image --- src/zm_image.cpp | 37 +++++++++++++++++++------------------ src/zm_image.h | 4 +++- src/zm_monitor.cpp | 1 + 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 1c58130cc..a4aad8b61 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -114,22 +114,23 @@ Image::Image() : delta8_argb(&std_delta8_argb), delta8_abgr(&std_delta8_abgr), delta8_gray8(&std_delta8_gray8), - blend(&std_blend) + blend(&std_blend), + width(0), + linesize(0), + height(0), + pixels(0), + colours(0), + padding(0), + size(0), + subpixelorder(0), + allocation(0), + buffer(nullptr), + buffertype(ZM_BUFTYPE_DONTFREE), + holdbuffer(0) { - if ( !initialised ) + if (!initialised) Initialise(); - width = 0; - linesize = 0; - height = 0; - padding = 0; - pixels = 0; - colours = 0; - subpixelorder = 0; - size = 0; - allocation = 0; - buffer = 0; - buffertype = ZM_BUFTYPE_DONTFREE; - holdbuffer = 0; + // Update blend to fast function determined by Initialise, I'm sure this can be improve. blend = fptr_blend; } @@ -158,15 +159,15 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint colours(p_colours), padding(p_padding), subpixelorder(p_subpixelorder), - buffer(p_buffer) { + buffer(p_buffer), + holdbuffer(0) +{ if (!initialised) Initialise(); pixels = width * height; linesize = p_width * p_colours; size = linesize * height + padding; - buffer = nullptr; - holdbuffer = 0; if (p_buffer) { allocation = size; buffertype = ZM_BUFTYPE_DONTFREE; @@ -174,7 +175,7 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint } else { AllocImgBuffer(size); } - if (!subpixelorder and colours>1) { + if (!subpixelorder and (colours>1)) { // Default to RGBA when no subpixelorder is specified. subpixelorder = ZM_SUBPIX_ORDER_RGBA; } diff --git a/src/zm_image.h b/src/zm_image.h index 74e5931eb..8bcb92c4c 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -145,11 +145,13 @@ class Image { explicit Image(const AVFrame *frame); ~Image(); + static void Initialise(); static void Deinitialise(); inline void DumpImgBuffer() { - DumpBuffer(buffer, buffertype); + if (buffertype != ZM_BUFTYPE_DONTFREE) + DumpBuffer(buffer, buffertype); buffertype = ZM_BUFTYPE_DONTFREE; buffer = nullptr; allocation = 0; diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index f5d225d66..2179197e8 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1153,6 +1153,7 @@ bool Monitor::disconnect() { } if (purpose == CAPTURE) { + alarm_image.HoldBuffer(false); /* Allow to reset buffer */ if (unlink(mem_file.c_str()) < 0) { Warning("Can't unlink '%s': %s", mem_file.c_str(), strerror(errno)); } From db866fa668c19e6ecaf6a4ee06200deefef25a15 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 09:46:38 -0500 Subject: [PATCH 330/501] Implement zm_setcookie to simplify setting cookies, set samesite, deal with older php etc. Use it. --- web/includes/Group.php | 2 +- web/includes/session.php | 17 +++++++++++++++++ web/index.php | 17 ++--------------- web/skins/classic/views/montage.php | 9 +-------- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/web/includes/Group.php b/web/includes/Group.php index 34db0e869..93c20fc70 100644 --- a/web/includes/Group.php +++ b/web/includes/Group.php @@ -25,7 +25,7 @@ class Group extends ZM_Object { if ( isset($_COOKIE['zmGroup']) ) { if ( $this->{'Id'} == $_COOKIE['zmGroup'] ) { unset($_COOKIE['zmGroup']); - setcookie('zmGroup', '', time()-3600*24*2); + zm_setcookie('zmGroup', ''); } } } diff --git a/web/includes/session.php b/web/includes/session.php index 0190f9897..6e9c17670 100644 --- a/web/includes/session.php +++ b/web/includes/session.php @@ -1,4 +1,21 @@ =')) { + setcookie($cookie, $value, $options); + } else { + setcookie($cookie, $value, $options['expires'], '/; samesite=strict'); + } +} + // ZM session start function support timestamp management function zm_session_start() { diff --git a/web/index.php b/web/index.php index b3df502f6..5ace8e9b6 100644 --- a/web/index.php +++ b/web/index.php @@ -139,11 +139,6 @@ $skinBase[] = $skin; zm_session_start(); -$cookie_options = array( - 'expires'=>time()+3600*24*30*12*10, - 'samesite' => 'Strict', -); - if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || @@ -151,11 +146,7 @@ if ( ($_COOKIE['zmSkin'] != $skin) ) { $_SESSION['skin'] = $skin; - if (version_compare(phpversion(), '7.3.0', '>=')) { - setcookie('zmSkin', $skin, $cookie_options); - } else { - setcookie('zmSkin', $skin, $cookie_options['expires'], '/; samesite=strict'); - } + zm_setcookie('zmSkin', $skin); } if ( @@ -165,11 +156,7 @@ if ( ($_COOKIE['zmCSS'] != $css) ) { $_SESSION['css'] = $css; - if (version_compare(phpversion(), '7.3.0', '>=')) { - setcookie('zmCSS', $css, $cookie_options); - } else { - setcookie('zmCSS', $css, $cookie_options['expires'], '/; samesite=strict'); - } + zm_setcookie('zmCSS', $css); } # Running is global but only do the daemonCheck if it is actually needed diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index bb99916dd..a96e8a88e 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -307,14 +307,7 @@ foreach (array_reverse($zones) as $zone) { Type() != 'WebSite')) { -?> -
- : - -  -  -  fps -
-getMonitorStateHTML(); } ?> From a57206ef5493b5cfabbaf09caecfe9ebb333b8ea Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 09:47:36 -0500 Subject: [PATCH 331/501] Implement getMonitorStateHTML to synchronize it between montage and live view, cycle etc. Reuseable code. Also the structure is required by MonitorStream.js --- web/includes/Monitor.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index c8fa6ebb1..09d4657a8 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -719,5 +719,27 @@ class Monitor extends ZM_Object { } return $this->{'Manufacturer'}; } + function getMonitorStateHTML() { + $html = ' +
+
+ '.translate('State').': + fps + fps +'; + if ( $this->Function() == 'Modect' or $this->Function() == 'Mocord' ) { + $html .= ' fps + '; + } + $html .= ' + + + + '. translate('Zoom').': x +
+
+'; + return $html; + } } // end class Monitor ?> From 9395b7e47c27199d2bc4c88fecff8756af110747 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 09:47:53 -0500 Subject: [PATCH 332/501] Use net zm_setcookie --- web/includes/actions/bandwidth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/actions/bandwidth.php b/web/includes/actions/bandwidth.php index c5fafa1c9..ac6bd407f 100644 --- a/web/includes/actions/bandwidth.php +++ b/web/includes/actions/bandwidth.php @@ -21,7 +21,7 @@ if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); - setcookie('zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10); + zm_setcookie('zmBandwidth', validStr($_REQUEST['newBandwidth'])); $refreshParent = true; $view = 'none'; $closePopup = true; From 3cd6fbdc12d0ec056683e8aaf2350c0bde495418 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 09:48:02 -0500 Subject: [PATCH 333/501] Use net zm_setcookie --- web/includes/actions/groups.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/includes/actions/groups.php b/web/includes/actions/groups.php index 8e4522187..bc431998e 100644 --- a/web/includes/actions/groups.php +++ b/web/includes/actions/groups.php @@ -21,9 +21,9 @@ // Group view actions if ( ($action == 'setgroup') && canView('Groups')) { if ( !empty($_REQUEST['gid']) ) { - setcookie('zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10); + zm_setcookie('zmGroup', validInt($_REQUEST['gid'])); } else { - setcookie('zmGroup', '', time()-3600*24*2); + zm_setcookie('zmGroup', '', time()-3600*24*2); } $refreshParent = true; return; From 4bf55b1af196fc777d95c4d6c42976c9005c46c7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 09:48:09 -0500 Subject: [PATCH 334/501] Use net zm_setcookie --- web/includes/actions/montage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/actions/montage.php b/web/includes/actions/montage.php index cd9a41aaf..a5306a33b 100644 --- a/web/includes/actions/montage.php +++ b/web/includes/actions/montage.php @@ -38,7 +38,7 @@ if ( isset($_REQUEST['object']) ) { $Layout->save(); zm_session_start(); $_SESSION['zmMontageLayout'] = $Layout->Id(); - setcookie('zmMontageLayout', $Layout->Id(), 1); + zm_setcookie('zmMontageLayout', $Layout->Id()); session_write_close(); $redirect = '?view=montage'; } // end if save From 22b63377528404c7115f27c7e18a3a042b9fa9db Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 11:47:28 -0500 Subject: [PATCH 335/501] Merge code from watch.js. Add an ajaxQueue. Link up buttons from UI --- web/js/MonitorStream.js | 368 ++++++++++++++++++++++++++++------------ 1 file changed, 264 insertions(+), 104 deletions(-) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index 54669a964..17f43c4b9 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -13,15 +13,22 @@ function MonitorStream(monitorData) { this.janusEnabled = monitorData.janusEnabled; this.scale = 100; this.status = null; - this.alarmState = STATE_IDLE; this.lastAlarmState = STATE_IDLE; + this.streamCmdTimer = null; this.streamCmdParms = { view: 'request', request: 'stream', connkey: this.connKey }; + this.ajaxQueue = null; this.type = monitorData.type; this.refresh = monitorData.refresh; + + this.buttons = {}; // index by name + this.setButton = function(name, element) { + this.buttons.name = element; + }; + this.element = null; this.getElement = function() { if (this.element) return this.element; @@ -36,7 +43,7 @@ function MonitorStream(monitorData) { this.show = function() { const stream = this.getElement(); if (!stream.src) { - stream.src = this.url_to_zms+"&mode=single&scale=100&connkey="+this.connKey; + stream.src = this.url_to_zms+"&mode=single&scale=100&connkey="+this.connKey+this.auth_relay; } }; @@ -48,7 +55,7 @@ function MonitorStream(monitorData) { const oldSrc = img.getAttribute('src'); if (!oldSrc) { - console.log("No src on img?!"); + console.log('No src on img?!'); console.log(img); return; } @@ -58,13 +65,10 @@ function MonitorStream(monitorData) { console.log("Scaling to: " + newscale); if (newscale == '0' || newscale == 'auto') { - let bottomElement = document.getElementById('replayStatus'); - if (!bottomElement) { - bottomElement = document.getElementById('monitorState'); - } + const bottomElement = document.getElementById('monitorState'+this.id); var newSize = scaleToFit(this.width, this.height, $j(img), $j(bottomElement)); - console.log(newSize); + //console.log(newSize); newWidth = newSize.width; newHeight = newSize.height; autoScale = parseInt(newSize.autoScale); @@ -83,11 +87,8 @@ function MonitorStream(monitorData) { } img.setAttribute('src', newSrc); }; - this.start = function(delay) { - // Step 1 make sure we are streaming instead of a static image - const stream = this.getElement(); - if (!stream) return; + this.start = function(delay) { if (this.janusEnabled) { var id = parseInt(this.id); var server; @@ -106,47 +107,46 @@ function MonitorStream(monitorData) { attachVideo(id); return; } + + const stream = this.getElement(); + if (!stream) return; if (!stream.src) { - // Website Monitors won't have an img tag + // Website Monitors won't have an img tag, neither will video console.log('No src for #liveStream'+this.id); console.log(stream); return; } + // Step 1 make sure we are streaming instead of a static image src = stream.src.replace(/mode=single/i, 'mode=jpeg'); - if ( -1 == src.search('connkey') ) { + if (-1 == src.search('connkey')) { src += '&connkey='+this.connKey; } - if ( stream.src != src ) { + if (stream.src != src) { console.log("Setting to streaming: " + src); stream.src = ''; stream.src = src; } - setTimeout(this.streamCmdQuery.bind(this), delay); + setTimeout(this.statusQuery.bind(this), delay); }; + this.stop = function() { if ( 0 ) { - var stream = $j('#liveStream'+this.id)[0]; - if ( ! stream ) { - console.log('No live stream'); - return; - } + const stream = this.getElement(); + if (!stream) return; src = stream.src.replace(/mode=jpeg/i, 'mode=single'); - if ( stream.src != src ) { + if (stream.src != src) { console.log("Setting to stopped"); stream.src = ''; stream.src = src; } } - this.streamCmdParms.command = CMD_STOP; - this.streamCmdReq(this.streamCmdParms); + this.streamCommand(CMD_STOP); }; this.pause = function() { - this.streamCmdParms.command = CMD_PAUSE; - this.streamCmdReq(this.streamCmdParms); + this.streamCommand(CMD_PAUSE); }; this.play = function() { - this.streamCmdParms.command = CMD_PLAY; - this.streamCmdReq(this.streamCmdParms); + this.streamCommand(CMD_PLAY); }; this.eventHandler = function(event) { @@ -154,77 +154,142 @@ function MonitorStream(monitorData) { }; this.onclick = function(evt) { - var el = evt.currentTarget; - var id = el.getAttribute("data-monitor-id"); - var url = '?view=watch&mid='+id; - evt.preventDefault(); - window.location.assign(url); + console.log('onclick'); }; - this.setup_onclick = function() { - var el = document.getElementById('imageFeed'+this.id); - if ( el ) el.addEventListener('click', this.onclick, false); + this.setup_onclick = function(func) { + this.onclick = func; + const el = this.getElement(); + if (!el) return; + el.addEventListener('click', this.onclick, false); }; + this.disable_onclick = function() { - document.getElementById('imageFeed'+this.id).removeEventListener('click', this.onclick ); + const el = this.getElement(); + if (!el) return; + el.removeEventListener('click', this.onclick); + }; + + this.onpause = function() { + console.log('onpause'); + }; + this.setup_onpause = function(func) { + this.onpause = func; + }; + this.onplay = null; + this.setup_onplay = function(func) { + this.onplay = func; }; this.setStateClass = function(jobj, stateClass) { - if ( !jobj ) { + if (!jobj) { + console.log("No obj in setStateClass"); return; } - if ( !jobj.hasClass( stateClass ) ) { - if ( stateClass != 'alarm' ) jobj.removeClass('alarm'); - if ( stateClass != 'alert' ) jobj.removeClass('alert'); - if ( stateClass != 'idle' ) jobj.removeClass('idle'); + if (!jobj.hasClass(stateClass)) { + if (stateClass != 'alarm') jobj.removeClass('alarm'); + if (stateClass != 'alert') jobj.removeClass('alert'); + if (stateClass != 'idle') jobj.removeClass('idle'); jobj.addClass(stateClass); } }; + this.setAlarmState = function(alarmState) { + var stateClass = ''; + if (alarmState == STATE_ALARM) { + stateClass = 'alarm'; + } else if (alarmState == STATE_ALERT) { + stateClass = 'alert'; + } + + const stateValue = $j('#stateValue'+this.id); + if (stateValue.length) { + stateValue.text(stateStrings[alarmState]); + if (stateClass) { + stateValue.addClass(stateClass); + } else { + stateValue.removeClass(); + } + } else { + console.log("No statevalue"); + } + //const monitorState = $j('#monitorState'+this.id); + //if (monitorState.length) this.setStateClass(monitorState, stateClass); + + const isAlarmed = ( alarmState == STATE_ALARM || alarmState == STATE_ALERT ); + const wasAlarmed = ( this.lastAlarmState == STATE_ALARM || this.lastAlarmState == STATE_ALERT ); + + const newAlarm = ( isAlarmed && !wasAlarmed ); + const oldAlarm = ( !isAlarmed && wasAlarmed ); + + if (newAlarm) { + if (SOUND_ON_ALARM) { + // Enable the alarm sound + if (!msieVer) { + $j('#alarmSound').removeClass('hidden'); + } else { + $j('#MediaPlayer').trigger('play'); + } + } + if (POPUP_ON_ALARM) { + window.focus(); + } + if (this.onalarm) { + this.onalarm(); + } + } + if (oldAlarm) { // done with an event do a refresh + if (SOUND_ON_ALARM) { + // Disable alarm sound + if (!msieVer) { + $j('#alarmSound').addClass('hidden'); + } else { + $j('#MediaPlayer').trigger('pause'); + } + } + if (this.onalarm) { + this.onalarm(); + } + } + this.lastAlarmState = alarmState; + }; // end function setAlarmState( currentAlarmState ) + + this.onalarm = null; + this.setup_onalarm = function(func) { + this.onalarm = func; + }; + this.onFailure = function(jqxhr, textStatus, error) { + // Assuming temporary problem, retry in a bit. setTimeout(this.streamCmdQuery.bind(this), 1000*statusRefreshTimeout); logAjaxFail(jqxhr, textStatus, error); }; this.getStreamCmdResponse = function(respObj, respText) { - var stream = $j('#liveStream'+this.id)[0]; - + var stream = this.getElement(); if (!stream) { - console.log('No live stream'); return; } //watchdogOk('stream'); - if (streamCmdTimer) { - streamCmdTimer = clearTimeout(streamCmdTimer); + if (this.streamCmdTimer) { + this.streamCmdTimer = clearTimeout(this.streamCmdTimer); } - if ( respObj.result == 'Ok' ) { - if ( respObj.status ) { - this.status = respObj.status; - this.alarmState = this.status.state; - - var stateClass = ''; - if ( this.alarmState == STATE_ALARM ) { - stateClass = 'alarm'; - } else if ( this.alarmState == STATE_ALERT ) { - stateClass = 'alert'; - } else { - stateClass = 'idle'; - } + if (respObj.result == 'Ok') { + if (respObj.status) { + const streamStatus = this.status = respObj.status; if ( ( (typeof COMPACT_MONTAGE === 'undefined') || !COMPACT_MONTAGE) && (this.type != 'WebSite') ) { - const viewingFPSValue = $j('#vewingFPSValue'+this.id); + const viewingFPSValue = $j('#viewingFPSValue'+this.id); const captureFPSValue = $j('#captureFPSValue'+this.id); const analysisFPSValue = $j('#analysisFPSValue'+this.id); - const stateValue = $j('#stateValue'+this.id); - const monitorState = $j('#monitorState'+this.id); if (viewingFPSValue.length && (viewingFPSValue.text != this.status.fps)) { viewingFPSValue.text(this.status.fps); @@ -236,40 +301,104 @@ function MonitorStream(monitorData) { captureFPSValue.text(this.status.capturefps); } - if (stateValue.length) stateValue.text(stateStrings[this.alarmState]); - if (monitorState.length) this.setStateClass(monitorState, stateClass); - } - - this.setStateClass($j('#monitor'+this.id), stateClass); - - /*Stream could be an applet so can't use moo tools*/ - //stream.parentNode().className = stateClass; - - var isAlarmed = ( this.alarmState == STATE_ALARM || this.alarmState == STATE_ALERT ); - var wasAlarmed = ( this.lastAlarmState == STATE_ALARM || this.lastAlarmState == STATE_ALERT ); - - var newAlarm = ( isAlarmed && !wasAlarmed ); - var oldAlarm = ( !isAlarmed && wasAlarmed ); - - if (newAlarm) { - if (false && SOUND_ON_ALARM) { - // Enable the alarm sound - $j('#alarmSound').removeClass('hidden'); + const levelValue = $j('#levelValue'); + if (levelValue.length) { + levelValue.text(this.status.level); + var newClass = 'ok'; + if (this.status.level > 95) { + newClass = 'alarm'; + } else if (this.status.level > 80) { + newClass = 'alert'; + } + levelValue.removeClass(); + levelValue.addClass(newClass); } - if ((typeof POPUP_ON_ALARM !== 'undefined') && POPUP_ON_ALARM) { - windowToFront(); + + const delayString = secsToTime(this.status.delay); + + if (this.status.paused == true) { + $j('#modeValue'+this.id).text('Paused'); + $j('#rate'+this.id).addClass('hidden'); + $j('#delayValue'+this.id).text(delayString); + $j('#delay'+this.id).removeClass('hidden'); + $j('#level'+this.id).removeClass('hidden'); + this.onpause(); + } else if (this.status.delayed == true) { + $j('#modeValue'+this.id).text('Replay'); + $j('#rateValue'+this.id).text(this.status.rate); + $j('#rate'+this.id).removeClass('hidden'); + $j('#delayValue'+this.id).text(delayString); + $j('#delay'+this.id).removeClass('hidden'); + $j('#level'+this.id).removeClass('hidden'); + if (this.status.rate == 1) { + if (this.onplay) this.onplay(); + } else if (this.status.rate > 0) { + if (this.status.rate < 1) { + streamCmdSlowFwd(false); + } else { + streamCmdFastFwd(false); + } + } else { + if (this.status.rate > -1) { + streamCmdSlowRev(false); + } else { + streamCmdFastRev(false); + } + } // rate + } else { + $j('#modeValue'+this.id).text('Live'); + $j('#rate'+this.id).addClass('hidden'); + $j('#delay'+this.id).addClass('hidden'); + $j('#level'+this.id).addClass('hidden'); + if (this.onplay) this.onplay(); + } // end if paused or delayed + + $j('#zoomValue'+this.id).text(this.status.zoom); + if ('zoomOutBtn' in this.buttons) { + if (this.status.zoom == '1.0') { + setButtonState('zoomOutBtn', 'unavail'); + } else { + setButtonState('zoomOutBtn', 'inactive'); + } } - } - if (false && SOUND_ON_ALARM) { - if ( oldAlarm ) { - // Disable alarm sound - $j('#alarmSound').addClass('hidden'); + } // end if compact montage + + this.setAlarmState(this.status.state); + + if (canEdit.Monitors) { + if (streamStatus.enabled) { + if ('enableAlarmButton' in this.buttons) { + this.buttons.enableAlarmButton.addClass('disabled'); + this.buttons.enableAlarmButton.prop('title', disableAlarmsStr); + } + if ('forceAlarmButton' in this.buttons) { + if (streamStatus.forced) { + this.buttons.forceAlarmButton.addClass('disabled'); + this.buttons.forceAlarmButton.prop('title', cancelForcedAlarmStr); + } else { + this.buttons.forceAlarmButton.removeClass('disabled'); + this.buttons.forceAlarmButton.prop('title', forceAlarmStr); + } + this.buttons.forceAlarmButton.prop('disabled', false); + } + } else { + if ('enableAlarmButton' in this.buttons) { + this.buttons.enableAlarmButton.removeClass('disabled'); + this.buttons.enableAlarmButton.prop('title', enableAlarmsStr); + } + if ('forceAlarmButton' in this.buttons) { + this.buttons.forceAlarmButton.prop('disabled', true); + } } - } + if ('enableAlarmButton' in this.buttons) { + this.buttons.enableAlarmButton.prop('disabled', false); + } + } // end if canEdit.Monitors + if (this.status.auth) { if (this.status.auth != this.auth_hash) { // Try to reload the image stream. - if (stream) { + if (stream && stream.src) { const oldsrc = stream.src; stream.src = ''; stream.src = oldsrc.replace(/auth=\w+/i, 'auth='+this.status.auth); @@ -283,23 +412,25 @@ function MonitorStream(monitorData) { console.error(respObj.message); // Try to reload the image stream. if (stream) { - if ( stream.src ) { + if (stream.src) { console.log('Reloading stream: ' + stream.src); src = stream.src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) )); - if ( src != stream.src ) { + // Maybe navbar updated auth FIXME + if (src != stream.src) { stream.src = src; } else { console.log("Failed to update rand on stream src"); } - } else { } } else { console.log('No stream to reload?'); } } // end if Ok or not + }; - this.lastAlarmState = this.alarmState; - setTimeout(this.streamCmdQuery.bind(this), statusRefreshTimeout); + this.statusQuery = function() { + this.streamCmdQuery(CMD_QUERY); + setTimeout(this.statusQuery.bind(this), statusRefreshTimeout); }; this.streamCmdQuery = function(resent) { @@ -310,15 +441,44 @@ function MonitorStream(monitorData) { } }; - if ( this.type != 'WebSite' ) { + this.streamCommand = function(command) { + if (typeof(command) == 'object') { + for (const key in command) this.streamCmdParms[key] = command[key]; + } else { + this.streamCmdParms.command = command; + } + this.streamCmdReq(this.streamCmdParms); + }; + + this.alarmCommand = function(command) { + if (this.ajaxQueue) { + this.ajaxQueue.abort(); + } + const alarmCmdParms = Object.assign({}, this.streamCmdParms); + alarmCmdParms.request = 'alarm'; + alarmCmdParms.command = command; + alarmCmdParms.id = this.id; + + this.ajaxQueue = jQuery.ajaxQueue({ + url: this.url, + data: alarmCmdParms, dataType: "json"}) + .done(this.getStreamCmdResponse.bind(this)) + .fail(this.onFailure.bind(this)); + }; + + if (this.type != 'WebSite') { + $j.ajaxSetup({timeout: AJAX_TIMEOUT}); + if (auth_hash) { + this.streamCmdParms.auth = auth_hash; + } else if ( auth_relay ) { + this.streamCmdParms.auth_relay = ''; + } + this.streamCmdReq = function(streamCmdParms) { - if ( auth_hash ) { - this.streamCmdParms.auth = auth_hash; - } else if ( auth_relay ) { - this.streamCmdParms.auth_relay = ''; + if (this.ajaxQueue) { + this.ajaxQueue.abort(); } - $j.ajaxSetup({timeout: AJAX_TIMEOUT}); - $j.getJSON(this.url, streamCmdParms) + this.ajaxQueue = jQuery.ajaxQueue({url: this.url, data: streamCmdParms, dataType: "json"}) .done(this.getStreamCmdResponse.bind(this)) .fail(this.onFailure.bind(this)); }; @@ -326,7 +486,7 @@ function MonitorStream(monitorData) { this.analyse_frames = true; this.show_analyse_frames = function(toggle) { this.analyse_frames = toggle; - this.streamCmdParms.command = this.analyse_frames?CMD_ANALYZE_ON:CMD_ANALYZE_OFF; + this.streamCmdParms.command = this.analyse_frames ? CMD_ANALYZE_ON : CMD_ANALYZE_OFF; this.streamCmdReq(this.streamCmdParms); }; } // end function MonitorStream From 2da9c20c08be3103c66bc05775b95430b301c3a7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 11:49:59 -0500 Subject: [PATCH 336/501] Add ajaxQueue --- web/js/ajaxQueue.js | 48 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 web/js/ajaxQueue.js diff --git a/web/js/ajaxQueue.js b/web/js/ajaxQueue.js new file mode 100644 index 000000000..c706fb978 --- /dev/null +++ b/web/js/ajaxQueue.js @@ -0,0 +1,48 @@ +// See https://github.com/gnarf/jquery-ajaxQueue for license and copyright + +(function($) { + +// jQuery on an empty object, we are going to use this as our Queue +var ajaxQueue = $({}); + +$.ajaxQueue = function( ajaxOpts ) { + var jqXHR, + dfd = $.Deferred(), + promise = dfd.promise(); + + // run the actual query + function doRequest( next ) { + jqXHR = $.ajax( ajaxOpts ); + jqXHR.done( dfd.resolve ) + .fail( dfd.reject ) + .then( next, next ); + } + + // queue our ajax request + ajaxQueue.queue( doRequest ); + + // add the abort method + promise.abort = function( statusText ) { + + // proxy abort to the jqXHR if it is active + if ( jqXHR ) { + return jqXHR.abort( statusText ); + } + + // if there wasn't already a jqXHR we need to remove from queue + var queue = ajaxQueue.queue(), + index = $.inArray( doRequest, queue ); + + if ( index > -1 ) { + queue.splice( index, 1 ); + } + + // and then reject the deferred + dfd.rejectWith( ajaxOpts.context || ajaxOpts, [ promise, statusText, "" ] ); + return promise; + }; + + return promise; +}; + +})(jQuery); From 97f2c0a02b8f5a0b001883c2d7f9cf59ed0787ac Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 11:50:25 -0500 Subject: [PATCH 337/501] Load ajaxQueue --- web/skins/classic/includes/functions.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index c593601aa..beb6590e0 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -916,6 +916,7 @@ function xhtmlFooter() { ?> + /js/moment.min.js"> - + From 57bb91e10550a9fae50e983ed2b9ffaccb25320d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 11:53:05 -0500 Subject: [PATCH 342/501] add connKey to monitorData --- web/skins/classic/views/js/watch.js.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/skins/classic/views/js/watch.js.php b/web/skins/classic/views/js/watch.js.php index 2f383ab24..cef3cdd79 100644 --- a/web/skins/classic/views/js/watch.js.php +++ b/web/skins/classic/views/js/watch.js.php @@ -64,6 +64,7 @@ foreach ($monitors as $m) { ?> monitorData[monitorData.length] = { 'id': Id() ?>, + 'connKey': connKey() ?>, 'width': ViewWidth() ?>, 'height':ViewHeight() ?>, 'janusEnabled':JanusEnabled() ?>, From 91d6ff20298a66852e48ec974f43e9b5ee1cb059 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 11:53:35 -0500 Subject: [PATCH 343/501] Use Monitor->getStatusHTML to generate status html --- web/skins/classic/views/zone.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/skins/classic/views/zone.php b/web/skins/classic/views/zone.php index 5031c316f..4c2cc2d30 100644 --- a/web/skins/classic/views/zone.php +++ b/web/skins/classic/views/zone.php @@ -164,10 +164,10 @@ if ( count($other_zones) ) { Sorry, your browser does not support inline SVG - -
-  -  fps -
+ getMonitorStateHTML(); +?> +
From 4eb4e0eb185d2996e207f1039674e58bf4601ef8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jan 2022 12:02:17 -0500 Subject: [PATCH 345/501] ignore ajaxQueue.js --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index 2e2fdd39a..f5529e8ee 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,6 +14,7 @@ web/skins/classic/js/moment.js web/skins/classic/js/video.js web/tools/mootools web/js/janus.js +web/js/ajaxQueue.js # Cannot be parsed as JS web/skins/classic/includes/export_functions.php From 52e48c02b61bcb9fb2fedc79b5d8b546d74d2b2c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 21 Jan 2022 22:23:41 -0600 Subject: [PATCH 346/501] Add janus_path and janus_secret, allowing for more secure and flexible Janus installs --- .../lib/ZoneMinder/ConfigData.pm.in | 22 ++++++++++++++++ src/zm_monitor.cpp | 26 +++++++++++++++---- web/includes/functions.php | 2 +- web/js/MonitorStream.js | 4 ++- web/skins/classic/views/cycle.php | 1 + web/skins/classic/views/js/cycle.js | 9 ++++++- web/skins/classic/views/montage.php | 1 + web/skins/classic/views/watch.php | 1 + 8 files changed, 58 insertions(+), 8 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 48d494614..9b8a4e61e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -370,6 +370,28 @@ our @options = ( type => $types{boolean}, category => 'system', }, + { + name => 'ZM_JANUS_SECRET', + default => '', + description => 'Password for Janus streaming administration.', + help => q`This value should be set to a secure password, + and match the admin_key value in janus.plugin.streaming.config. + `, + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_JANUS_PATH', + default => '', + description => 'URL for Janus HTTP/S port', + help => q`Janus requires HTTP/S communication to administer + and initiate h.264 streams. If left blank, this will default to + the ZM hostname, port 8088/janus. This setting is particularly + useful for putting janus behind a reverse proxy. + `, + type => $types{string}, + category => 'system', + }, { name => 'ZM_ENABLE_CSRF_MAGIC', default => 'yes', diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 2179197e8..9a1b747b6 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3383,7 +3383,12 @@ size_t Monitor::WriteCallback(void *contents, size_t size, size_t nmemb, void *u int Monitor::add_to_janus() { //TODO clean this up, add error checking, etc std::string response; - std::string endpoint = "127.0.0.1:8088/janus/"; + std::string endpoint; + if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { + endpoint = config.janus_path; + } else { + endpoint = "127.0.0.1:8088/janus/"; + } std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; std::string rtsp_username; std::string rtsp_password; @@ -3425,6 +3430,7 @@ int Monitor::add_to_janus() { if (pos == std::string::npos) return -1; janus_id = response.substr(pos + 6, 16); response = ""; + endpoint += "/"; endpoint += janus_id; postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); @@ -3445,8 +3451,10 @@ int Monitor::add_to_janus() { //Assemble our actual request postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; - postData += "\"request\" : \"create\", \"admin_key\" : \"supersecret\", \"type\" : \"rtsp\", "; - postData += "\"url\" : \""; + postData += "\"request\" : \"create\", \"admin_key\" : \""; + postData += config.janus_secret; + postData += "\", \"type\" : \"rtsp\", "; + postData += "\"url\" : \""; postData += rtsp_path; postData += "\", \"rtsp_user\" : \""; postData += rtsp_username; @@ -3474,7 +3482,12 @@ int Monitor::add_to_janus() { int Monitor::remove_from_janus() { //TODO clean this up, add error checking, etc std::string response; - std::string endpoint = "127.0.0.1:8088/janus/"; + std::string endpoint; + if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { + endpoint = config.janus_path; + } else { + endpoint = "127.0.0.1:8088/janus/"; + } std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; std::size_t pos; CURLcode res; @@ -3495,6 +3508,7 @@ int Monitor::remove_from_janus() { if (pos == std::string::npos) return -1; std::string janus_id = response.substr(pos + 6, 16); response = ""; + endpoint += "/"; endpoint += janus_id; postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); @@ -3512,7 +3526,9 @@ int Monitor::remove_from_janus() { //Assemble our actual request postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; - postData += "\"request\" : \"destroy\", \"admin_key\" : \"supersecret\", \"id\" : "; + postData += "\"request\" : \"destroy\", \"admin_key\" : \""; + postData += config.janus_secret; + postData += "\", \"id\" : "; postData += std::to_string(id); postData += "}}"; diff --git a/web/includes/functions.php b/web/includes/functions.php index f9b8abf72..e346ceb34 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -2095,7 +2095,7 @@ function getStreamHTML($monitor, $options = array()) { ) ); return getVideoStreamHTML( 'liveStream'.$monitor->Id(), $streamSrc, $options['width'], $options['height'], ZM_MPEG_LIVE_FORMAT, $monitor->Name() ); } else if ( $monitor->JanusEnabled() ) { - return ''; + return ''; } else if ( $options['mode'] == 'stream' and canStream() ) { $options['mode'] = 'jpeg'; $streamSrc = $monitor->getStreamSrc($options); diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index 17f43c4b9..373902e1f 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -92,7 +92,9 @@ function MonitorStream(monitorData) { if (this.janusEnabled) { var id = parseInt(this.id); var server; - if (window.location.protocol=='https:') { + if (ZM_JANUS_PATH) { + server = ZM_JANUS_PATH; + } else if (window.location.protocol=='https:') { // Assume reverse proxy setup for now server = "https://" + window.location.hostname + "/janus"; } else { diff --git a/web/skins/classic/views/cycle.php b/web/skins/classic/views/cycle.php index ee9e03d4f..50bb42045 100644 --- a/web/skins/classic/views/cycle.php +++ b/web/skins/classic/views/cycle.php @@ -192,6 +192,7 @@ xhtmlHeaders(__FILE__, translate('CycleWatch')); + diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 670671faa..9e128d0ab 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -51,7 +51,14 @@ function initCycle() { if ( scale == '0' || scale == 'auto' ) changeScale(); if (monitorData[monIdx].janusEnabled) { - server = "http://" + window.location.hostname + ":8088/janus"; + if (ZM_JANUS_PATH) { + server = ZM_JANUS_PATH; + } else if (window.location.protocol=='https:') { + // Assume reverse proxy setup for now + server = "https://" + window.location.hostname + "/janus"; + } else { + server = "http://" + window.location.hostname + ":8088/janus"; + } opaqueId = "streamingtest-"+Janus.randomString(12); Janus.init({debug: "all", callback: function() { janus = new Janus({ diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index a96e8a88e..c59ce9f66 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -318,6 +318,7 @@ foreach (array_reverse($zones) as $zone) { + diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index 340ee8e33..46ee11f90 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -396,6 +396,7 @@ if ( ZM_WEB_SOUND_ON_ALARM ) { ?> + From 2e9bda1af190a3f18ae06eb7bbe10b8f8f08f49c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 21 Jan 2022 23:21:41 -0600 Subject: [PATCH 347/501] Add firefox specific workaround for Janus streaming --- web/js/MonitorStream.js | 3 +++ web/skins/classic/views/js/cycle.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index 373902e1f..b5f9d0b6a 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -521,6 +521,9 @@ async function attachVideo(id) { if (jsep !== undefined && jsep !== null) { Janus.debug("Handling SDP as well..."); Janus.debug(jsep); + if ((navigator.userAgent.toLowerCase().indexOf('firefox') > -1) && (jsep["sdp"].includes("420029"))) { //because firefox devs are stubborn + jsep["sdp"] = jsep["sdp"].replace("420029", "42e01f"); + } // Offer from the plugin, let's answer streaming[id].createAnswer({ jsep: jsep, diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 9e128d0ab..7e7bd0147 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -91,6 +91,9 @@ function initCycle() { if (jsep !== undefined && jsep !== null) { Janus.debug("Handling SDP as well..."); Janus.debug(jsep); + if ((navigator.userAgent.toLowerCase().indexOf('firefox') > -1) && (jsep["sdp"].includes("420029"))) { //because firefox devs are stubborn + jsep["sdp"] = jsep["sdp"].replace("420029", "42e01f"); + } // Offer from the plugin, let's answer streaming2.createAnswer({ jsep: jsep, From 45db266ede1036b79823298cc7bd6239954e885d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 22 Jan 2022 12:07:12 -0500 Subject: [PATCH 348/501] Put full config available to javascript --- web/skins/classic/js/skin.js.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 2b7980c3a..6cde755b6 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -105,3 +105,10 @@ stateStrings[STATE_PREALARM] = ""; stateStrings[STATE_ALARM] = ""; stateStrings[STATE_ALERT] = ""; stateStrings[STATE_TAPE] = ""; + +$c) { + echo $name . ' = \''.$c['Value'].'\''.PHP_EOL; +} +?> From 35efb111ac2f264387d999cbac7adb98451787d1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 22 Jan 2022 12:30:59 -0500 Subject: [PATCH 349/501] Add Private to Config --- db/zm_create.sql.in | 1 + db/zm_update-1.37.10.sql | 20 +++++++++++++++++++ .../ZoneMinder/lib/ZoneMinder/Config.pm.in | 5 +++-- .../lib/ZoneMinder/ConfigData.pm.in | 3 +++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 db/zm_update-1.37.10.sql diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index db72cb58d..7dc77cc47 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -39,6 +39,7 @@ CREATE TABLE `Config` ( `Help` text, `Category` varchar(32) NOT NULL default '', `Readonly` tinyint(3) unsigned NOT NULL default '0', + `Private` BOOLEAN NOT NULL DEFAULT FALSE, `Requires` text, PRIMARY KEY (`Name`) ) ENGINE=@ZM_MYSQL_ENGINE@; diff --git a/db/zm_update-1.37.10.sql b/db/zm_update-1.37.10.sql new file mode 100644 index 000000000..30c2d8766 --- /dev/null +++ b/db/zm_update-1.37.10.sql @@ -0,0 +1,20 @@ +-- +-- Update Config table to have Private +-- + +SELECT 'Checking for Private in Config'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Config' + AND table_schema = DATABASE() + AND column_name = 'Private' + ) > 0, +"SELECT 'Column Private already exists in Config'", +"ALTER TABLE `Config` ADD COLUMN `Private` BOOLEAN NOT NULL DEFAULT FALSE AFTER `Readonly`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + + diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 21f9a2d02..849ec5450 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -156,7 +156,7 @@ sub loadConfigFromDB { print("Error: unable to load options from database: $DBI::errstr\n"); return(0); } - my $sql = "select * from Config"; + my $sql = 'SELECT * FROM Config'; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() @@ -203,7 +203,7 @@ sub saveConfigToDB { my $res = $dbh->do( $sql ) or croak( "Can't do '$sql': ".$dbh->errstr() ); - $sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Requires = ?"; + $sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Private = ?, Requires = ?"; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); foreach my $option ( @options ) { @@ -240,6 +240,7 @@ sub saveConfigToDB { $option->{help}, $option->{category}, $option->{readonly} ? 1 : 0, + $option->{private} ? 1 : 0, $option->{db_requires} ) or croak("Can't execute when updating config entry $$option{name}: ".$sth->errstr() ); } # end foreach option diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 48d494614..d9d3c5306 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -304,6 +304,7 @@ our @options = ( { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{string}, + private => 1, category => 'system', }, { @@ -462,6 +463,7 @@ our @options = ( {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, + private => 1, category => 'system', }, { @@ -477,6 +479,7 @@ our @options = ( {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, + private => 1, category => 'system', }, { From ce2d605b3d0eaa498ddc51a2b09e38a07623f57e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 22 Jan 2022 12:31:15 -0500 Subject: [PATCH 350/501] load Private as well as Name,Value from Config --- web/includes/config.php.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/config.php.in b/web/includes/config.php.in index b30aebdea..c463a14ae 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -165,7 +165,7 @@ function loadConfig( $defineConsts=true ) { $config = array(); - $result = $dbConn->query('SELECT Name,Value FROM Config'); + $result = $dbConn->query('SELECT Name,Value,Private FROM Config'); if ( !$result ) echo mysql_error(); while( $row = dbFetchNext($result) ) { From 3a0b88c013709a1336b8e86c0d3ab83f06ed6a5f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 22 Jan 2022 12:31:36 -0500 Subject: [PATCH 351/501] Don't make private config entries available to js land. --- web/skins/classic/js/skin.js.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 6cde755b6..336c0a352 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -109,6 +109,7 @@ stateStrings[STATE_TAPE] = ""; $c) { - echo $name . ' = \''.$c['Value'].'\''.PHP_EOL; + if (!$c['Private']) + echo $name . ' = \''.$c['Value'].'\''.PHP_EOL; } ?> From 47aa1d1913319d47a540efe98147077b7717612e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 22 Jan 2022 12:31:56 -0500 Subject: [PATCH 352/501] Bump version to 1.37.10 --- distros/redhat/zoneminder.spec | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 16b8efb7e..b95d80626 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.9 +Version: 1.37.10 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index 938adc6f6..a9009bf2a 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.9 +1.37.10 From 90f75dae93214bf0f9300183cfb855f87d662445 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 22 Jan 2022 12:33:48 -0500 Subject: [PATCH 353/501] Make config entries const --- web/skins/classic/js/skin.js.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 336c0a352..2d443fbe1 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -110,6 +110,6 @@ stateStrings[STATE_TAPE] = ""; global $config; foreach ($config as $name=>$c) { if (!$c['Private']) - echo $name . ' = \''.$c['Value'].'\''.PHP_EOL; + echo 'const '. $name . ' = \''.$c['Value'].'\''.PHP_EOL; } ?> From 9443aaa2d866ee7d60fa3e4f9c083bdc2df0359a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 22 Jan 2022 13:33:39 -0500 Subject: [PATCH 354/501] Do state changes when in RECORD mode as well as it doesn't do motion detection Fixes #3411 --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 2179197e8..9952f727f 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2120,7 +2120,7 @@ bool Monitor::Analyse() { // snap->score -1 means didn't do motion detection so don't do state transition // In Nodect, we may still have a triggered event, so need this code to run to end the event. - } else if (!score and ((snap->score == 0) or (function == NODECT))) { + } else if (!score and ((snap->score == 0) or (function == NODECT || function == RECORD))) { Debug(1, "!score %s", State_Strings[state].c_str()); alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count From 07c5b23aa6668e33d8b21204953e408994d9a9db Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 22 Jan 2022 11:53:44 -0600 Subject: [PATCH 355/501] Adds Janus streaming checks to polling thread --- src/zm_monitor.cpp | 91 ++++++++++++++++++++++++++++++++++++++++------ src/zm_monitor.h | 1 + 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 9a1b747b6..cc426586c 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #if ZM_MEM_MAPPED #include @@ -1123,9 +1124,7 @@ bool Monitor::connect() { #if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { if (add_to_janus() != 0) { - if (add_to_janus() != 0) { //The initial attempt may fail. This is a temporary workaround. - Warning("Failed to add monitor stream to Janus!"); - } + Warning("Failed to add monitor stream to Janus!"); //The first attempt may fail. Will be reattempted in the Poller thread } } #else @@ -1797,6 +1796,8 @@ void Monitor::UpdateFPS() { //Thread where ONVIF polling, and other similar status polling can happen. //Since these can be blocking, run here to avoid intefering with other processing bool Monitor::Poll() { + //We want to trigger every 5 seconds or so. so grab the time at the beginning of the loop, and sleep at the end. + std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now(); #ifdef WITH_GSOAP if (ONVIF_Healthy) { @@ -1838,10 +1839,14 @@ bool Monitor::Poll() { } } } - } else { - std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep to avoid the busy loop. } #endif + if (janus_enabled) { + if (check_janus() != 1) { + add_to_janus(); + } + } + std::this_thread::sleep_until(loop_start_time + std::chrono::seconds(5)); return TRUE; } //end Poll @@ -3166,10 +3171,8 @@ int Monitor::PrimeCapture() { } } // end if rtsp_server -#ifdef WITH_GSOAP //For now, just don't run the thread if no ONVIF support. This may change if we add other long polling options. - //ONVIF Thread - - if (onvif_event_listener && ONVIF_Healthy) { + //Poller Thread + if (onvif_event_listener || janus_enabled) { if (!Poller) { Poller = zm::make_unique(this); @@ -3177,7 +3180,7 @@ int Monitor::PrimeCapture() { Poller->Start(); } } -#endif + if (decoding_enabled) { if (!decoder_it) decoder_it = packetqueue.get_video_it(false); if (!decoder) { @@ -3381,7 +3384,6 @@ size_t Monitor::WriteCallback(void *contents, size_t size, size_t nmemb, void *u } int Monitor::add_to_janus() { - //TODO clean this up, add error checking, etc std::string response; std::string endpoint; if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { @@ -3479,8 +3481,73 @@ int Monitor::add_to_janus() { curl_easy_cleanup(curl); return 0; } + +int Monitor::check_janus() { + std::string response; + std::string endpoint; + if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { + endpoint = config.janus_path; + } else { + endpoint = "127.0.0.1:8088/janus/"; + } + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + std::size_t pos; + CURLcode res; + + curl = curl_easy_init(); + if(!curl) return -1; + + + //Start Janus API init. Need to get a session_id and handle_id + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + + pos = response.find("\"id\": "); + if (pos == std::string::npos) return -1; + std::string janus_id = response.substr(pos + 6, 16); + response = ""; + endpoint += "/"; + endpoint += janus_id; + postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + + pos = response.find("\"id\": "); + if (pos == std::string::npos) return -1; + std::string handle_id = response.substr(pos + 6, 16); + endpoint += "/"; + endpoint += handle_id; + + //Assemble our actual request + postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"info\", \"id\" : "; + postData += std::to_string(id); + postData += "}}"; + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + + Debug(1, "Queried for stream status: %s", response.c_str()); + curl_easy_cleanup(curl); + if (response.find("No such mountpoint") == std::string::npos) { + return 1; + } else { + return 0; + } +} int Monitor::remove_from_janus() { - //TODO clean this up, add error checking, etc std::string response; std::string endpoint; if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 898349eb3..381855c97 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -521,6 +521,7 @@ public: bool OnvifEnabled() { return onvif_event_listener; } + int check_janus(); //returns 1 for healthy, 0 for success but missing stream, negative for error. #ifdef WITH_GSOAP bool OnvifHealthy() { return ONVIF_Healthy; From abbd27d1cba082ea9bb4ec16ebb75c528f8d7bb9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 22 Jan 2022 13:31:47 -0600 Subject: [PATCH 356/501] Remove hard-coded config Vars --- web/skins/classic/views/cycle.php | 1 - web/skins/classic/views/montage.php | 1 - web/skins/classic/views/watch.php | 1 - 3 files changed, 3 deletions(-) diff --git a/web/skins/classic/views/cycle.php b/web/skins/classic/views/cycle.php index 50bb42045..ee9e03d4f 100644 --- a/web/skins/classic/views/cycle.php +++ b/web/skins/classic/views/cycle.php @@ -192,7 +192,6 @@ xhtmlHeaders(__FILE__, translate('CycleWatch')); - diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index c59ce9f66..a96e8a88e 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -318,7 +318,6 @@ foreach (array_reverse($zones) as $zone) { - diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index 46ee11f90..340ee8e33 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -396,7 +396,6 @@ if ( ZM_WEB_SOUND_ON_ALARM ) { ?> - From d11098793571b6c9b0dc17f5aead5c5d3ad32e2d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 22 Jan 2022 13:37:44 -0600 Subject: [PATCH 357/501] Fix indentation for ESLint --- web/skins/classic/views/js/cycle.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 7e7bd0147..19f4497be 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -51,14 +51,14 @@ function initCycle() { if ( scale == '0' || scale == 'auto' ) changeScale(); if (monitorData[monIdx].janusEnabled) { - if (ZM_JANUS_PATH) { - server = ZM_JANUS_PATH; - } else if (window.location.protocol=='https:') { - // Assume reverse proxy setup for now - server = "https://" + window.location.hostname + "/janus"; - } else { - server = "http://" + window.location.hostname + ":8088/janus"; - } + if (ZM_JANUS_PATH) { + server = ZM_JANUS_PATH; + } else if (window.location.protocol=='https:') { + // Assume reverse proxy setup for now + server = "https://" + window.location.hostname + "/janus"; + } else { + server = "http://" + window.location.hostname + ":8088/janus"; + } opaqueId = "streamingtest-"+Janus.randomString(12); Janus.init({debug: "all", callback: function() { janus = new Janus({ From 5485c04bc6fdb0151b44a30ee7a2a95ca842189f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 23 Jan 2022 01:07:48 -0600 Subject: [PATCH 358/501] Rework of the Janus polling loop --- src/zm_monitor.cpp | 200 +++++++++++++++++++++++---------------------- src/zm_monitor.h | 2 + 2 files changed, 104 insertions(+), 98 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index cc426586c..25a8c9f73 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1123,6 +1123,7 @@ bool Monitor::connect() { #if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { + get_janus_session(); if (add_to_janus() != 0) { Warning("Failed to add monitor stream to Janus!"); //The first attempt may fail. Will be reattempted in the Poller thread } @@ -1842,7 +1843,10 @@ bool Monitor::Poll() { } #endif if (janus_enabled) { - if (check_janus() != 1) { + if (janus_session.empty()) { + get_janus_session(); + } + if (check_janus() == 0) { add_to_janus(); } } @@ -3395,7 +3399,6 @@ int Monitor::add_to_janus() { std::string rtsp_username; std::string rtsp_password; std::string rtsp_path = "rtsp://"; - std::string janus_id; std::size_t pos; std::size_t pos2; CURLcode res; @@ -3416,40 +3419,8 @@ int Monitor::add_to_janus() { rtsp_password = path.substr(pos+1, pos2 - pos - 1); rtsp_path += path.substr(pos2 + 1); - //Start Janus API init. Need to get a session_id and handle_id - curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - Error("Failed to curl_easy_perform getting session/handle id"); - curl_easy_cleanup(curl); - return -1; - } - - pos = response.find("\"id\": "); - if (pos == std::string::npos) return -1; - janus_id = response.substr(pos + 6, 16); - response = ""; endpoint += "/"; - endpoint += janus_id; - postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; - curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - Error("Failed to curl_easy_perform attaching"); - curl_easy_cleanup(curl); - return -1; - } - pos = response.find("\"id\": "); - if (pos == std::string::npos) return -1; - std::string handle_id = response.substr(pos + 6, 16); //TODO: This is an assumption that the string is always 16 - endpoint += "/"; - endpoint += handle_id; + endpoint += janus_session; //Assemble our actual request postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; @@ -3477,6 +3448,13 @@ int Monitor::add_to_janus() { curl_easy_cleanup(curl); return -1; } + if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) { + janus_session = ""; + curl_easy_cleanup(curl); + return -2; + } + //scan for missing session or handle id "No such session" "no such handle" + Debug(1,"Added stream to Janus: %s", response.c_str()); curl_easy_cleanup(curl); return 0; @@ -3490,41 +3468,15 @@ int Monitor::check_janus() { } else { endpoint = "127.0.0.1:8088/janus/"; } - std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; - std::size_t pos; + std::string postData; + //std::size_t pos; CURLcode res; curl = curl_easy_init(); if(!curl) return -1; - - //Start Janus API init. Need to get a session_id and handle_id - curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; - - pos = response.find("\"id\": "); - if (pos == std::string::npos) return -1; - std::string janus_id = response.substr(pos + 6, 16); - response = ""; endpoint += "/"; - endpoint += janus_id; - postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; - curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; - - pos = response.find("\"id\": "); - if (pos == std::string::npos) return -1; - std::string handle_id = response.substr(pos + 6, 16); - endpoint += "/"; - endpoint += handle_id; + endpoint += janus_session; //Assemble our actual request postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; @@ -3537,16 +3489,28 @@ int Monitor::check_janus() { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; + if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session + Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + janus_session = ""; + return -1; + } - Debug(1, "Queried for stream status: %s", response.c_str()); curl_easy_cleanup(curl); - if (response.find("No such mountpoint") == std::string::npos) { - return 1; - } else { + Debug(1, "Queried for stream status: %s", response.c_str()); + if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) { + Warning("Janus Session timed out"); + janus_session = ""; + return -2; + } else if (response.find("No such mountpoint") != std::string::npos) { + Warning("Mountpoint Missing"); return 0; + } else { + return 1; } } + + int Monitor::remove_from_janus() { std::string response; std::string endpoint; @@ -3556,40 +3520,14 @@ int Monitor::remove_from_janus() { endpoint = "127.0.0.1:8088/janus/"; } std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; - std::size_t pos; + //std::size_t pos; CURLcode res; curl = curl_easy_init(); if(!curl) return -1; - - //Start Janus API init. Need to get a session_id and handle_id - curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; - - pos = response.find("\"id\": "); - if (pos == std::string::npos) return -1; - std::string janus_id = response.substr(pos + 6, 16); - response = ""; endpoint += "/"; - endpoint += janus_id; - postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; - curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; - - pos = response.find("\"id\": "); - if (pos == std::string::npos) return -1; - std::string handle_id = response.substr(pos + 6, 16); - endpoint += "/"; - endpoint += handle_id; + endpoint += janus_session; //Assemble our actual request postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; @@ -3604,9 +3542,75 @@ int Monitor::remove_from_janus() { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); res = curl_easy_perform(curl); - if (res != CURLE_OK) return -1; + if (res != CURLE_OK) { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } Debug(1, "Removed stream from Janus: %s", response.c_str()); curl_easy_cleanup(curl); return 0; } + +int Monitor::get_janus_session() { + std::string response; + std::string endpoint; + if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { + endpoint = config.janus_path; + } else { + endpoint = "127.0.0.1:8088/janus/"; + } + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + std::size_t pos; + CURLcode res; + curl = curl_easy_init(); + if(!curl) return -1; + + //Start Janus API init. Need to get a session_id and handle_id + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } + + pos = response.find("\"id\": "); + if (pos == std::string::npos) + { + curl_easy_cleanup(curl); + return -1; + } + janus_session = response.substr(pos + 6, 16); + + response = ""; + endpoint += "/"; + endpoint += janus_session; + postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } + + pos = response.find("\"id\": "); + if (pos == std::string::npos) + { + curl_easy_cleanup(curl); + return -1; + } + janus_session += "/"; + janus_session += response.substr(pos + 6, 16); + curl_easy_cleanup(curl); + return 1; +} //get_janus_session diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 381855c97..98c36e5a9 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -455,6 +455,8 @@ protected: static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); int add_to_janus(); int remove_from_janus(); + int get_janus_session(); + std::string janus_session; // Used in check signal uint8_t red_val; From 146ea4822d6986950c5b19741dd44c05ca0c4f1e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 21 Jan 2022 09:44:44 -0500 Subject: [PATCH 359/501] Bump the db queue limit to 30 before we warn. I only have 1 server that gets over 20 and it is still ok. --- src/zm_db.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_db.cpp b/src/zm_db.cpp index f0b13d538..cc0797c12 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -251,7 +251,7 @@ void zmDbQueue::process() { mCondition.wait(lock); } while (!mQueue.empty()) { - if (mQueue.size() > 20) { + if (mQueue.size() > 30) { Logger *log = Logger::fetch(); Logger::Level db_level = log->databaseLevel(); log->databaseLevel(Logger::NOLOG); From 3cc243b9a8992a3a70a5e2bb67b61e9c3831a09c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 24 Jan 2022 09:23:19 -0500 Subject: [PATCH 360/501] Don't freshing config when doing update. That is it's own command --- scripts/zmupdate.pl.in | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index d096c70b5..f62b14316 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -444,11 +444,6 @@ if ( $version ) { print( "\nUpgrading database to version ".ZM_VERSION."\n" ); -# Update config first of all - migratePaths(); - ZoneMinder::Config::loadConfigFromDB(); - ZoneMinder::Config::saveConfigToDB(); - my $cascade = undef; if ( $cascade || $version eq "1.19.0" ) { # Patch the database From 26ae5052f4bd19e34857223887439b171e65a8ca Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 25 Jan 2022 11:55:21 -0500 Subject: [PATCH 361/501] Fix fail to get Sources in RTSP. the string msg although initially reserved to ZM_NETWORK_BUFSIZ, after use it's capacity is changed whatever it's contents are. So need to re-reserve. --- src/zm_comms.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_comms.h b/src/zm_comms.h index 7e7329d5d..ac772ae26 100644 --- a/src/zm_comms.h +++ b/src/zm_comms.h @@ -245,6 +245,7 @@ class Socket : public CommsBase { } virtual ssize_t recv(std::string &msg) const { + msg.reserve(ZM_NETWORK_BUFSIZ); std::vector buffer(msg.capacity()); ssize_t nBytes; if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 0)) < 0) { From cf82d767de9976d39f8f98a6cc5bfe29a4ce9416 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 25 Jan 2022 14:24:52 -0500 Subject: [PATCH 362/501] Remove the decoding code, just populate the av_packet. This fixes rtsp decoding because we weren't copying the decoded frame to shm raw image. --- dep/RtspServer | 2 +- src/zm_remote_camera_rtsp.cpp | 62 ++++++++++++++--------------------- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/dep/RtspServer b/dep/RtspServer index cd7fd49be..1b40f1661 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878 +Subproject commit 1b40f1661f93f50fd5805f239d1e466a3bcf888f diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 8cdecdf94..b49862845 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -168,8 +168,10 @@ int RemoteCameraRtsp::PrimeCapture() { } } // end foreach stream - if ( mVideoStreamId == -1 ) - Fatal("Unable to locate video stream"); + if ( mVideoStreamId == -1 ) { + Error("Unable to locate video stream"); + return -1; + } if ( mAudioStreamId == -1 ) Debug(3, "Unable to locate audio stream"); @@ -179,17 +181,22 @@ int RemoteCameraRtsp::PrimeCapture() { // Find the decoder for the video stream AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id); - if ( codec == nullptr ) - Panic("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); + if ( codec == nullptr ) { + Error("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); + return -1; + } // Open codec - if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) - Panic("Can't open codec"); + if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) { + Error("Can't open codec"); + return -1; + } int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); if ( (unsigned int)pSize != imagesize ) { - Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); + Error("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); + return -1; } return 1; @@ -208,18 +215,13 @@ int RemoteCameraRtsp::PreCapture() { int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { int frameComplete = false; AVPacket *packet = &zm_packet->packet; - if ( !zm_packet->image ) { - Debug(1, "Allocating image %dx%d %d colours %d", width, height, colours, subpixelorder); - zm_packet->image = new Image(width, height, colours, subpixelorder); - } - while (!frameComplete) { buffer.clear(); if (!rtspThread || rtspThread->IsStopped()) return -1; - if ( rtspThread->getFrame(buffer) ) { + if (rtspThread->getFrame(buffer)) { Debug(3, "Read frame %d bytes", buffer.size()); Hexdump(4, buffer.head(), 16); @@ -254,36 +256,20 @@ int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { //while ( (!frameComplete) && (buffer.size() > 0) ) { if ( buffer.size() > 0 ) { - packet->data = buffer.head(); + packet->data = (uint8_t*)av_malloc(buffer.size()); + memcpy(packet->data, buffer.head(), buffer.size()); + //packet->data = buffer.head(); packet->size = buffer.size(); bytes += packet->size; + buffer -= packet->size; struct timeval now; - gettimeofday(&now, NULL); + gettimeofday(&now, nullptr); packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec; - - int bytes_consumed = zm_packet->decode(mVideoCodecContext); - if ( bytes_consumed < 0 ) { - Error("Error while decoding frame %d", frameCount); - //Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size()); - } - buffer -= packet->size; - if ( bytes_consumed ) { - zm_dump_video_frame(zm_packet->in_frame, "remote_rtsp_decode"); - if (!mVideoStream->codecpar->width) { - zm_dump_codec(mVideoCodecContext); - zm_dump_codecpar(mVideoStream->codecpar); - mVideoStream->codecpar->width = zm_packet->in_frame->width; - mVideoStream->codecpar->height = zm_packet->in_frame->height; - zm_dump_codecpar(mVideoStream->codecpar); - } - zm_packet->codec_type = mVideoCodecContext->codec_type; - zm_packet->stream = mVideoStream; - frameComplete = true; - Debug(2, "Frame: %d - %d/%d", frameCount, bytes_consumed, buffer.size()); - packet->data = nullptr; - packet->size = 0; - } + zm_packet->codec_type = mVideoCodecContext->codec_type; + zm_packet->stream = mVideoStream; + frameComplete = true; + Debug(2, "Frame: %d - %d/%d", frameCount, packet->size, buffer.size()); } } /* getFrame() */ } // end while true From c8c09e560f071d2a5bb7ba4a04447c1a765e7879 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 25 Jan 2022 14:25:13 -0500 Subject: [PATCH 363/501] Fix mTerminate not being initialised. --- src/zm_rtp_source.cpp | 4 +++- src/zm_rtp_source.h | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index 1862c1886..56ca2cf0d 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -45,8 +45,10 @@ RtpSource::RtpSource( mFrame(65536), mFrameCount(0), mFrameGood(true), + prevM(false), mFrameReady(false), - mFrameProcessed(false) + mFrameProcessed(false), + mTerminate(false) { char hostname[256] = ""; gethostname(hostname, sizeof(hostname)); diff --git a/src/zm_rtp_source.h b/src/zm_rtp_source.h index a39e8225f..71be9af2c 100644 --- a/src/zm_rtp_source.h +++ b/src/zm_rtp_source.h @@ -91,8 +91,6 @@ private: bool mFrameGood; bool prevM; - bool mTerminate; - bool mFrameReady; std::condition_variable mFrameReadyCv; std::mutex mFrameReadyMutex; @@ -100,6 +98,7 @@ private: bool mFrameProcessed; std::condition_variable mFrameProcessedCv; std::mutex mFrameProcessedMutex; + bool mTerminate; private: void init(uint16_t seq); From 38da3b4d52dc21c2443bf4a295cd0c636f5683e7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 25 Jan 2022 14:25:27 -0500 Subject: [PATCH 364/501] add some brackets to make logic more clear --- src/zm_rtp_ctrl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_rtp_ctrl.cpp b/src/zm_rtp_ctrl.cpp index 25d34f0ff..a82ff2b2a 100644 --- a/src/zm_rtp_ctrl.cpp +++ b/src/zm_rtp_ctrl.cpp @@ -277,7 +277,7 @@ void RtpCtrlThread::Run() { TimePoint last_receive = std::chrono::steady_clock::now(); bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true. - while (!mTerminate && select.wait() >= 0) { + while (!mTerminate && (select.wait() >= 0)) { TimePoint now = std::chrono::steady_clock::now(); zm::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { From 4d90a816f86730ec1f6336f9e27eef0abffba2c6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 25 Jan 2022 16:22:33 -0500 Subject: [PATCH 365/501] Put a lock around jpeg writing. libjpeg is not thread safe --- src/zm_image.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index a4aad8b61..7260791a5 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -24,6 +24,7 @@ #include "zm_utils.h" #include #include +#include #include #include @@ -80,6 +81,8 @@ imgbufcpy_fptr_t fptr_imgbufcpy; /* Font */ static ZmFont font; +std::mutex jpeg_mutex; + void Image::update_function_pointers() { /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ if ( pixels % 16 || pixels % 12 ) { @@ -1083,6 +1086,9 @@ bool Image::WriteJpeg(const std::string &filename, const int &quality_override, SystemTimePoint timestamp, bool on_blocking_abort) const { + // jpeg libs are not thread safe + std::unique_lock lck(jpeg_mutex); + if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) { Image temp_image(*this); temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); @@ -1366,6 +1372,8 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr return temp_image.EncodeJpeg(outbuffer, outbuffer_size, quality_override); } + std::unique_lock lck(jpeg_mutex); + int quality = quality_override ? quality_override : config.jpeg_stream_quality; struct jpeg_compress_struct *cinfo = encodejpg_ccinfo[quality]; From 961256d2e78bb6c84912e621ca0fdb0b9b6cd54d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 25 Jan 2022 16:24:21 -0500 Subject: [PATCH 366/501] terminate when zm_terminate is set. Do a countdown instead of countup. Sleeo for 10000 microseconds instead of 100. This restores the old value --- src/zm_remote_camera_rtsp.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index b49862845..ffb4a061d 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -22,6 +22,7 @@ #include "zm_config.h" #include "zm_monitor.h" #include "zm_packet.h" +#include "zm_signal.h" RemoteCameraRtsp::RemoteCameraRtsp( const Monitor *monitor, @@ -126,8 +127,8 @@ int RemoteCameraRtsp::Disconnect() { int RemoteCameraRtsp::PrimeCapture() { Debug(2, "Waiting for sources"); - for (int i = 0; i < 100 && !rtspThread->hasSources(); i++) { - std::this_thread::sleep_for(Microseconds(100)); + for (int i = 100; i && !zm_terminate && !rtspThread->hasSources(); i--) { + std::this_thread::sleep_for(Microseconds(10000)); } if (!rtspThread->hasSources()) { @@ -218,7 +219,7 @@ int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { while (!frameComplete) { buffer.clear(); - if (!rtspThread || rtspThread->IsStopped()) + if (!rtspThread || rtspThread->IsStopped() || zm_terminate) return -1; if (rtspThread->getFrame(buffer)) { From ec9403fb6f6635cf42d0cf7fd82bb40894f8769e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 25 Jan 2022 22:27:11 -0600 Subject: [PATCH 367/501] Adds Amcrest On-camera Motion Detection --- CMakeLists.txt | 2 +- db/zm_create.sql.in | 1 + db/zm_update-1.37.11.sql | 18 ++ scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 1 + src/zm_monitor.cpp | 219 ++++++++++++++----- src/zm_monitor.h | 11 +- web/includes/Monitor.php | 1 + web/skins/classic/views/js/monitor.js | 17 ++ web/skins/classic/views/monitor.php | 4 + 9 files changed, 214 insertions(+), 60 deletions(-) create mode 100644 db/zm_update-1.37.11.sql diff --git a/CMakeLists.txt b/CMakeLists.txt index 7df6aaa70..1a65f85eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,7 +318,7 @@ endif() # Do not check for cURL if ZM_NO_CURL is on if(NOT ZM_NO_CURL) # cURL - find_package(CURL) + find_package(CURL REQUIRED) if(CURL_FOUND) set(HAVE_LIBCURL 1) list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 7dc77cc47..040bc593c 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -467,6 +467,7 @@ CREATE TABLE `Monitors` ( `ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Options` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Event_Listener` BOOLEAN NOT NULL DEFAULT FALSE, + `use_Amcrest_API` BOOLEAN NOT NULL DEFAULT FALSE, `Device` tinytext NOT NULL default '', `Channel` tinyint(3) unsigned NOT NULL default '0', `Format` int(10) unsigned NOT NULL default '0', diff --git a/db/zm_update-1.37.11.sql b/db/zm_update-1.37.11.sql new file mode 100644 index 000000000..58a3d0cf1 --- /dev/null +++ b/db/zm_update-1.37.11.sql @@ -0,0 +1,18 @@ +-- +-- Update Monitors table to have use_Amcrest_API +-- + +SELECT 'Checking for use_Amcrest_API in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'use_Amcrest_API' + ) > 0, +"SELECT 'Column use_Amcrest_API already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `use_Amcrest_API` BOOLEAN NOT NULL default false AFTER `ONVIF_Event_Listener`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index 54ce25c87..10cfffe8b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -63,6 +63,7 @@ $serial = $primary_key = 'Id'; ONVIF_Password ONVIF_Options ONVIF_Event_Listener + use_Amcrest_API Device Channel Format diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4ae155e23..558a30380 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -92,7 +92,7 @@ std::string load_monitor_sql = "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," "`RTSPServer`, `RTSPStreamName`," -"`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, " +"`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, `use_Amcrest_API`, " "`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`"; std::string CameraType_Strings[] = { @@ -461,7 +461,7 @@ Monitor::Monitor() "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," "`RTSPServer`,`RTSPStreamName`, - "`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, " + "`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, `use_Amcrest_API`, " "SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ @@ -639,6 +639,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { onvif_password = std::string(dbrow[col] ? dbrow[col] : ""); col++; onvif_options = std::string(dbrow[col] ? dbrow[col] : ""); col++; onvif_event_listener = (*dbrow[col] != '0'); col++; + use_Amcrest_API = (*dbrow[col] != '0'); col++; /*"SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ signal_check_points = atoi(dbrow[col]); col++; @@ -1040,6 +1041,7 @@ bool Monitor::connect() { Debug(3, "Allocated %zu %zu image buffers", image_buffer.capacity(), image_buffer.size()); if (purpose == CAPTURE) { + curl_global_init(CURL_GLOBAL_DEFAULT); //May not be the appropriate place. Need to do this before any other curl calls, and any other threads start. memset(mem_ptr, 0, mem_size); shared_data->size = sizeof(SharedData); shared_data->active = enabled; @@ -1079,47 +1081,59 @@ bool Monitor::connect() { usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal shared_data->valid = true; -#ifdef WITH_GSOAP - //ONVIF Setup + //ONVIF and Amcrest Setup + //since they serve the same function, handling them as two options of the same feature. ONVIF_Trigger_State = FALSE; - if (onvif_event_listener) { //Temporarily using this option to enable the feature + if (onvif_event_listener) { // Debug(1, "Starting ONVIF"); ONVIF_Healthy = FALSE; if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message ONVIF_Closes_Event = TRUE; } - tev__PullMessages.Timeout = "PT600S"; - tev__PullMessages.MessageLimit = 100; - soap = soap_new(); - soap->connect_timeout = 5; - soap->recv_timeout = 5; - soap->send_timeout = 5; - soap_register_plugin(soap, soap_wsse); - proxyEvent = PullPointSubscriptionBindingProxy(soap); - std::string full_url = onvif_url + "/Events"; - proxyEvent.soap_endpoint = full_url.c_str(); + if (use_Amcrest_API) { + curl_multi = curl_multi_init(); + start_Amcrest(); + //spin up curl_multi + //use the onvif_user and onvif_pass and onvif_url here. + //going to use the non-blocking curl api, and in the polling thread, block for 5 seconds waiting for input, just like onvif + //note that it's not possible for a single camera to use both. + } else { //using GSOAP +#ifdef WITH_GSOAP + tev__PullMessages.Timeout = "PT600S"; + tev__PullMessages.MessageLimit = 100; + soap = soap_new(); + soap->connect_timeout = 5; + soap->recv_timeout = 5; + soap->send_timeout = 5; + soap_register_plugin(soap, soap_wsse); + proxyEvent = PullPointSubscriptionBindingProxy(soap); + std::string full_url = onvif_url + "/Events"; + proxyEvent.soap_endpoint = full_url.c_str(); - set_credentials(soap); - Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); - if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { - Error("Couldn't create subscription! %s, %s", soap_fault_string(soap), soap_fault_detail(soap)); - } else { - //Empty the stored messages set_credentials(soap); - if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) && - ( soap->error != SOAP_EOF)) { //SOAP_EOF could indicate no messages to pull. - Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap)); + Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); + if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { + Error("Couldn't create subscription! %s, %s", soap_fault_string(soap), soap_fault_detail(soap)); } else { - Debug(1, "Good Initial ONVIF Pull"); - ONVIF_Healthy = TRUE; + //Empty the stored messages + set_credentials(soap); + if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) && + ( soap->error != SOAP_EOF)) { //SOAP_EOF could indicate no messages to pull. + Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap)); + } else { + Debug(1, "Good Initial ONVIF Pull"); + ONVIF_Healthy = TRUE; + } } +#else + Error("zmc not compiled with GSOAP. ONVIF support not built in!"); +#endif } } else { Debug(1, "Not Starting ONVIF"); } //End ONVIF Setup -#endif #if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { @@ -1228,6 +1242,12 @@ Monitor::~Monitor() { sws_freeContext(convert_context); convert_context = nullptr; } + if (Amcrest_handle != nullptr) { + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + if (curl_multi != nullptr) curl_multi_cleanup(curl_multi); + curl_global_cleanup(); } // end Monitor::~Monitor() void Monitor::AddPrivacyBitmask() { @@ -1800,36 +1820,25 @@ bool Monitor::Poll() { //We want to trigger every 5 seconds or so. so grab the time at the beginning of the loop, and sleep at the end. std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now(); -#ifdef WITH_GSOAP if (ONVIF_Healthy) { - set_credentials(soap); - int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse); - if (result != SOAP_OK) { - if (result != SOAP_EOF) { //Ignore the timeout error - Error("Failed to get ONVIF messages! %s", soap_fault_string(soap)); - ONVIF_Healthy = FALSE; - } - } else { - Debug(1, "Got Good Response! %i", result); - for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) { - if (msg->Topic->__any.text != NULL && - std::strstr(msg->Topic->__any.text, "MotionAlarm") && - msg->Message.__any.elts != NULL && - msg->Message.__any.elts->next != NULL && - msg->Message.__any.elts->next->elts != NULL && - msg->Message.__any.elts->next->elts->atts != NULL && - msg->Message.__any.elts->next->elts->atts->next != NULL && - msg->Message.__any.elts->next->elts->atts->next->text != NULL) { - Debug(1,"Got Motion Alarm!"); - if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) { + if(use_Amcrest_API) { + int open_handles; + int transfers; + curl_multi_perform(curl_multi, &open_handles); + if (open_handles == 0) { + start_Amcrest(); //http transfer ended, need to restart. + } else { + curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event. + if (transfers > 0) { //have data to deal with + curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response + if (amcrest_response.find("action=Start") != std::string::npos) { //Event Start - Debug(1,"Triggered on ONVIF"); + Debug(1,"Triggered on ONVIF"); if (!ONVIF_Trigger_State) { Debug(1,"Triggered Event"); ONVIF_Trigger_State = TRUE; - std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep } - } else { + } else if (amcrest_response.find("action=Stop") != std::string::npos){ Debug(1, "Triggered off ONVIF"); ONVIF_Trigger_State = FALSE; if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them. @@ -1837,11 +1846,53 @@ bool Monitor::Poll() { Debug(1,"Setting ClosesEvent"); } } + amcrest_response.clear(); //We've dealt with the message, need to clear the queue } } + } else { + +#ifdef WITH_GSOAP + set_credentials(soap); + int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse); + if (result != SOAP_OK) { + if (result != SOAP_EOF) { //Ignore the timeout error + Error("Failed to get ONVIF messages! %s", soap_fault_string(soap)); + ONVIF_Healthy = FALSE; + } + } else { + Debug(1, "Got Good Response! %i", result); + for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) { + if (msg->Topic->__any.text != NULL && + std::strstr(msg->Topic->__any.text, "MotionAlarm") && + msg->Message.__any.elts != NULL && + msg->Message.__any.elts->next != NULL && + msg->Message.__any.elts->next->elts != NULL && + msg->Message.__any.elts->next->elts->atts != NULL && + msg->Message.__any.elts->next->elts->atts->next != NULL && + msg->Message.__any.elts->next->elts->atts->next->text != NULL) { + Debug(1,"Got Motion Alarm!"); + if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) { + //Event Start + Debug(1,"Triggered on ONVIF"); + if (!ONVIF_Trigger_State) { + Debug(1,"Triggered Event"); + ONVIF_Trigger_State = TRUE; + std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep + } + } else { + Debug(1, "Triggered off ONVIF"); + ONVIF_Trigger_State = FALSE; + if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them. + ONVIF_Closes_Event = TRUE; + Debug(1,"Setting ClosesEvent"); + } + } + } + } + } +#endif } } -#endif if (janus_enabled) { if (janus_session.empty()) { get_janus_session(); @@ -3223,11 +3274,15 @@ int Monitor::Close() { analysis_thread->Stop(); } -#ifdef WITH_GSOAP //ONVIF Teardown if (Poller) { Poller->Stop(); } + if (curl_multi != nullptr) { + curl_multi_cleanup(curl_multi); + curl_multi = nullptr; + } +#ifdef WITH_GSOAP if (onvif_event_listener && (soap != nullptr)) { Debug(1, "Tearing Down Onvif"); _wsnt__Unsubscribe wsnt__Unsubscribe; @@ -3240,9 +3295,9 @@ int Monitor::Close() { } //End ONVIF #endif #if HAVE_LIBCURL //Janus Teardown - if (janus_enabled && (purpose == CAPTURE)) { - remove_from_janus(); - } + if (janus_enabled && (purpose == CAPTURE)) { + remove_from_janus(); + } #endif packetqueue.clear(); @@ -3614,3 +3669,55 @@ int Monitor::get_janus_session() { curl_easy_cleanup(curl); return 1; } //get_janus_session + +int Monitor::start_Amcrest() { + //init the transfer and start it in multi-handle + int running_handles; + long response_code; + struct CURLMsg *m; + CURLMcode curl_error; + if (Amcrest_handle != nullptr) { //potentially clean up the old handle + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + + std::string full_url = onvif_url; + if (full_url.back() != '/') full_url += '/'; + full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]"; + Amcrest_handle = curl_easy_init(); + if (!Amcrest_handle){ + Warning("Handle is null!"); + return -1; + } + curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response); + curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, onvif_username.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, onvif_password.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle); + Warning("error of %i", curl_error); + curl_error = curl_multi_perform(curl_multi, &running_handles); + if (curl_error == CURLM_OK) { + curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code); + int msgq = 0; + m = curl_multi_info_read(curl_multi, &msgq); + if (m && (m->msg == CURLMSG_DONE)) { + Warning("Libcurl exited Early: %i", m->data.result); + } + + curl_multi_wait(curl_multi, NULL, 0, 300, NULL); + curl_error = curl_multi_perform(curl_multi, &running_handles); + } + + if ((curl_error == CURLM_OK) && (running_handles > 0)) { + ONVIF_Healthy = TRUE; + } else { + Warning("Response: %s", amcrest_response.c_str()); + Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str()); + curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code); + Warning("Response code: %lu", response_code); + } + +return 0; +} diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 98c36e5a9..7a4b7ad83 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -285,7 +285,9 @@ protected: std::string onvif_username; std::string onvif_password; std::string onvif_options; + std::string amcrest_response; bool onvif_event_listener; + bool use_Amcrest_API; std::string device; int palette; @@ -437,7 +439,7 @@ protected: //ONVIF #ifdef WITH_GSOAP - struct soap *soap; + struct soap *soap = nullptr; bool ONVIF_Trigger_State; bool ONVIF_Healthy; bool ONVIF_Closes_Event; @@ -449,8 +451,11 @@ protected: void set_credentials(struct soap *soap); #endif - //curl stuff for Janus - CURL *curl; + //curl stuff + CURL *curl = nullptr; + CURLM *curl_multi = nullptr; + CURL *Amcrest_handle = nullptr; + int start_Amcrest(); //helper class for CURL static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); int add_to_janus(); diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 09d4657a8..9c7d8d25c 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -66,6 +66,7 @@ class Monitor extends ZM_Object { 'ONVIF_Password' => '', 'ONVIF_Options' => '', 'ONVIF_Event_Listener' => '0', + 'use_Amcrest_API' => '0', 'Device' => '', 'Channel' => 0, 'Format' => '0', diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index 62c0c6514..30a906df6 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -275,6 +275,23 @@ function initPage() { } }); + // Amcrest API controller + if (document.getElementsByName("newMonitor[ONVIF_Event_Listener]")[0].checked) { + document.getElementById("function_use_Amcrest_API").hidden = false; + } else { + document.getElementById("function_use_Amcrest_API").hidden = true; + } + document.getElementsByName("newMonitor[ONVIF_Event_Listener]")[0].addEventListener('change', function() { + if (this.checked) { + document.getElementById("function_use_Amcrest_API").hidden = false; + } + }); + document.getElementsByName("newMonitor[ONVIF_Event_Listener]")[1].addEventListener('change', function() { + if (this.checked) { + document.getElementById("function_use_Amcrest_API").hidden = true; + } + }); + if ( ZM_OPT_USE_GEOLOCATION ) { if ( window.L ) { var form = document.getElementById('contentForm'); diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 6756c239b..deba63879 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -730,6 +730,10 @@ if (count($available_monitor_ids)) { + + + + Date: Tue, 25 Jan 2022 22:27:48 -0600 Subject: [PATCH 368/501] Remove redundant JS define --- web/skins/classic/views/js/monitor.js.php | 1 - 1 file changed, 1 deletion(-) diff --git a/web/skins/classic/views/js/monitor.js.php b/web/skins/classic/views/js/monitor.js.php index c635af233..49f313143 100644 --- a/web/skins/classic/views/js/monitor.js.php +++ b/web/skins/classic/views/js/monitor.js.php @@ -1,4 +1,3 @@ -var ZM_OPT_USE_GEOLOCATION = '' == '1' ? true : false; Date: Tue, 25 Jan 2022 22:46:09 -0600 Subject: [PATCH 369/501] Move variables out of the ifdef gsoap block, as they are used for Amcrest support, too --- src/zm_monitor.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 7a4b7ad83..4ce0a9b08 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -438,11 +438,12 @@ protected: std::string diag_path_delta; //ONVIF -#ifdef WITH_GSOAP - struct soap *soap = nullptr; - bool ONVIF_Trigger_State; + bool ONVIF_Trigger_State; //Re-using some variables for Amcrest API support bool ONVIF_Healthy; bool ONVIF_Closes_Event; + +#ifdef WITH_GSOAP + struct soap *soap = nullptr; _tev__CreatePullPointSubscription request; _tev__CreatePullPointSubscriptionResponse response; _tev__PullMessages tev__PullMessages; From 138bada0951453e989029bcc1751fee5b4902943 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 26 Jan 2022 09:27:29 -0500 Subject: [PATCH 370/501] Add libdatetime-perl. Some Control modules need it --- distros/ubuntu2004/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index 37c0e5e51..dda0e7852 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -42,7 +42,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libswscale5|libswscale4 ,libswresample3|libswresample2 ,ffmpeg - ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl + ,libdatetime-perl, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl ,libphp-serialization-perl ,libmodule-load-conditional-perl From 42e24614d67fd12f47d10eeaf17b609c08faa37d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 26 Jan 2022 11:01:09 -0500 Subject: [PATCH 371/501] Include filename in debugs when writing out jpegs --- src/zm_event.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index e14c1a343..e0ffdff46 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -423,7 +423,7 @@ void Event::AddFrame(Image *image, // If this is the first frame, we should add a thumbnail to the event directory if ((frames == 1) || (score > max_score)) { write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. - Debug(1, "Writing snapshot"); + Debug(1, "Writing snapshot to %s", snapshot_file.c_str()); WriteFrameImage(image, timestamp, snapshot_file.c_str()); } else { Debug(1, "Not Writing snapshot because frames %d score %d > max %d", frames, score, max_score); @@ -435,17 +435,19 @@ void Event::AddFrame(Image *image, if (!alarm_frame_written) { write_to_db = true; // OD processing will need it, so the db needs to know about it alarm_frame_written = true; - Debug(1, "Writing alarm image"); - WriteFrameImage(image, timestamp, alarm_file.c_str()); + Debug(1, "Writing alarm image to %s", alarm_file.c_str()); + if (!WriteFrameImage(image, timestamp, alarm_file.c_str())) { + Error("Failed to write alarm frame image to %s", alarm_file.c_str()); + } } else { Debug(3, "Not Writing alarm image because alarm frame already written"); } if (alarm_image and (save_jpegs & 2)) { std::string event_file = stringtf(staticConfig.analyse_file_format.c_str(), path.c_str(), frames); - Debug(1, "Writing analysis frame %d", frames); + Debug(1, "Writing analysis frame %d to %s", frames, event_file.c_str()); if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) { - Error("Failed to write analysis frame image"); + Error("Failed to write analysis frame image to %s", event_file.c_str()); } } } // end if is an alarm frame From fe8747e5e79b288f9d594ed75d69ca9cdf4922e2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 26 Jan 2022 11:45:04 -0500 Subject: [PATCH 372/501] Make ONVIF inputs 100% --- web/skins/classic/css/base/views/monitor.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/css/base/views/monitor.css b/web/skins/classic/css/base/views/monitor.css index f827af5ca..28afbf480 100644 --- a/web/skins/classic/css/base/views/monitor.css +++ b/web/skins/classic/css/base/views/monitor.css @@ -15,7 +15,11 @@ input[name="newMonitor[Path]"], input[name="newMonitor[SecondPath]"], input[name="newMonitor[LabelFormat]"], input[name="newMonitor[ControlDevice]"], -input[name="newMonitor[ControlAddress]"] { +input[name="newMonitor[ControlAddress]"], +input[name="newMonitor[ONVIF_URL]"], +input[name="newMonitor[ONVIF_Username]"], +input[name="newMonitor[ONVIF_Password]"], +input[name="newMonitor[ONVIF_Options]"] { width: 100%; } input[name="newMonitor[LabelFormat]"]{ From 8e62562afdeb87be0ce2a241b5aa02cadc67bd3c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 26 Jan 2022 21:39:29 -0500 Subject: [PATCH 373/501] Move onclick to the surrounding div instead of the img. Fixes clicking on montage --- web/js/MonitorStream.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index b5f9d0b6a..86b9b97e0 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -38,6 +38,14 @@ function MonitorStream(monitorData) { } return this.element; }; + this.getFrame = function() { + if (this.frame) return this.frame; + this.frame = document.getElementById('imageFeed'+this.id); + if (!this.frame) { + console.error("No frame div for #imageFeed"+this.id); + } + return this.frame; + }; /* if the img element didn't have a src, this would fill it in, causing it to show. */ this.show = function() { @@ -161,7 +169,7 @@ function MonitorStream(monitorData) { this.setup_onclick = function(func) { this.onclick = func; - const el = this.getElement(); + const el = this.getFrame(); if (!el) return; el.addEventListener('click', this.onclick, false); }; From d567d8aafcfd1ead184f7685234768ea88aadc0d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 27 Jan 2022 17:06:18 -0500 Subject: [PATCH 374/501] Merge upstream patches to RTSPServer --- dep/RtspServer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dep/RtspServer b/dep/RtspServer index 1b40f1661..333717a3a 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit 1b40f1661f93f50fd5805f239d1e466a3bcf888f +Subproject commit 333717a3ac59b8f98389905c1281909e5543bff6 From a91e7d96f8a1bd37d657fd7148cb068597c5fc67 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 28 Jan 2022 12:42:41 -0500 Subject: [PATCH 375/501] Merge fix for VP8 --- dep/RtspServer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dep/RtspServer b/dep/RtspServer index 333717a3a..eab328514 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit 333717a3ac59b8f98389905c1281909e5543bff6 +Subproject commit eab32851421ffe54fec0229c3efc44c642bc8d46 From e5792c21c923a19a315cefb2127fe27c3bb66516 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 28 Jan 2022 15:38:18 -0500 Subject: [PATCH 376/501] Push locked packets into event packetqueue instead of unlcoked contents. Prevents freeing of data before writing to event, hence crashing. --- src/zm_event.cpp | 8 ++++--- src/zm_event.h | 4 ++-- src/zm_monitor.cpp | 60 +++++++++++++++++++++++----------------------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index e0ffdff46..3ef7ea887 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -301,9 +301,9 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { } // end if update } // void Event::updateNotes(const StringSetMap &newNoteSetMap) -void Event::AddPacket(const std::shared_ptr&packet) { +void Event::AddPacket(ZMLockedPacket *packetlock) { std::unique_lock lck(packet_queue_mutex); - packet_queue.push(packet); + packet_queue.push(packetlock); packet_queue_condition.notify_one(); } @@ -684,7 +684,9 @@ void Event::Run() { while (true) { if (!packet_queue.empty()) { Debug(1, "adding packet"); - this->AddPacket_(packet_queue.front()); + const ZMLockedPacket * packet_lock = packet_queue.front(); + this->AddPacket_(packet_lock->packet_); + delete packet_lock; packet_queue.pop(); } else { if (terminate_ or zm_terminate) { diff --git a/src/zm_event.h b/src/zm_event.h index 31a23fad4..2029c062a 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -105,7 +105,7 @@ class Event { void createNotes(std::string ¬es); - std::queue> packet_queue; + std::queue packet_queue; std::mutex packet_queue_mutex; std::condition_variable packet_queue_condition; @@ -134,7 +134,7 @@ class Event { SystemTimePoint EndTime() const { return end_time; } TimePoint::duration Duration() const { return end_time - start_time; }; - void AddPacket(const std::shared_ptr &p); + void AddPacket(ZMLockedPacket *); void AddPacket_(const std::shared_ptr &p); bool WritePacket(const std::shared_ptr &p); bool SendFrameImage(const Image *image, bool alarm_frame=false); diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4ae155e23..b55d2f91d 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2270,34 +2270,34 @@ bool Monitor::Analyse() { shared_data->state = state = IDLE; } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - if (event) event->AddPacket(snap); + if (event) event->AddPacket(packet_lock); + + // In the case where people have pre-alarm frames, the web ui will generate the frame images + // from the mp4. So no one will notice anyways. + if (snap->image and (videowriter == PASSTHROUGH)) { + if (!savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; + } + if (snap->analysis_image and !(savejpegs & 2)) { + Debug(1, "Deleting analysis image data for %d", snap->image_index); + delete snap->analysis_image; + snap->analysis_image = nullptr; + } + } + + packetqueue.clearPackets(snap); + + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { + // Only do these if it's a video packet. + shared_data->last_read_index = snap->image_index; + analysis_image_count++; + } + packetqueue.increment_it(analysis_it); + if (!event) delete packet_lock; } // end scope for event_lock - - // In the case where people have pre-alarm frames, the web ui will generate the frame images - // from the mp4. So no one will notice anyways. - if (snap->image and (videowriter == PASSTHROUGH)) { - if (!savejpegs) { - Debug(1, "Deleting image data for %d", snap->image_index); - // Don't need raw images anymore - delete snap->image; - snap->image = nullptr; - } - if (snap->analysis_image and !(savejpegs & 2)) { - Debug(1, "Deleting analysis image data for %d", snap->image_index); - delete snap->analysis_image; - snap->analysis_image = nullptr; - } - } - - packetqueue.clearPackets(snap); - - if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { - // Only do these if it's a video packet. - shared_data->last_read_index = snap->image_index; - analysis_image_count++; - } - packetqueue.increment_it(analysis_it); - delete packet_lock; //packetqueue.unlock(packet_lock); shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); @@ -2870,16 +2870,16 @@ Event * Monitor::openEvent( // Write out starting packets, do not modify packetqueue it will garbage collect itself while (starting_packet and ((*start_it) != *analysis_it)) { - event->AddPacket(starting_packet); + event->AddPacket(starting_packet_lock); // Have added the packet, don't want to unlock it until we have locked the next packetqueue.increment_it(start_it); if ((*start_it) == *analysis_it) { - if (starting_packet_lock) delete starting_packet_lock; + //if (starting_packet_lock) delete starting_packet_lock; break; } ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; + //delete starting_packet_lock; if (!lp) return nullptr; // only on terminate FIXME starting_packet_lock = lp; starting_packet = lp->packet_; From 61028a3ae1f09003403eec6bf462d2791490e838 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 31 Jan 2022 10:09:31 -0500 Subject: [PATCH 377/501] Remove travis badge, use Isaac's full name. Fixes #3417 --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index da96b3e60..bcb53ba8a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ZoneMinder ========== -[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bounty Source](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) [![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE) @@ -25,7 +24,7 @@ https://github.com/ZoneMinder/zmdockerfiles This is the recommended method to install ZoneMinder onto your system. ZoneMinder packages are maintained for the following distros: -- Ubuntu via [Iconnor's PPA](https://launchpad.net/~iconnor) +- Ubuntu via [Isaac Connor's PPA](https://launchpad.net/~iconnor) - Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder) - RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org) - Fedora via [RPM Fusion](http://rpmfusion.org) From 46ad8e74e600b8efd74cccf83d3157868fbf5935 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 31 Jan 2022 11:01:51 -0500 Subject: [PATCH 378/501] Add publish --- .github/workflows/create-packages.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml index 6a76da322..29e5d471a 100644 --- a/.github/workflows/create-packages.yml +++ b/.github/workflows/create-packages.yml @@ -29,3 +29,13 @@ jobs: DIST: ${{ matrix.os_dist.dist }} DOCKER_REPO: iconzm/packpack run: utils/packpack/startpackpack.sh + + - name: Publish + uses: nogsantos/scp-deploy@master + with: + src: ./build/*.{deb,dsc,tar.xz,buildinfo,changes} + host: ${{ secrets.ZMREPO_HOST }} + remote: debian/master/mini-dinstall/incoming/ + port: ${{ secrets.ZMREPO_SSH_PORT }} + user: ${{ secrets.ZMREPO_SSH_USER }} + key: ${{ secrets.ZMREPO_SSH_KEY }} From 3a2484b874af8b870dd70d487529c79768d1b513 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 31 Jan 2022 11:19:36 -0500 Subject: [PATCH 379/501] Try a different ssh implementation --- .github/workflows/create-packages.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml index 29e5d471a..2fdc3577c 100644 --- a/.github/workflows/create-packages.yml +++ b/.github/workflows/create-packages.yml @@ -31,11 +31,11 @@ jobs: run: utils/packpack/startpackpack.sh - name: Publish - uses: nogsantos/scp-deploy@master - with: - src: ./build/*.{deb,dsc,tar.xz,buildinfo,changes} - host: ${{ secrets.ZMREPO_HOST }} - remote: debian/master/mini-dinstall/incoming/ - port: ${{ secrets.ZMREPO_SSH_PORT }} - user: ${{ secrets.ZMREPO_SSH_USER }} - key: ${{ secrets.ZMREPO_SSH_KEY }} + uses: easingthemes/ssh-deploy@main + env: + SSH_PRIVATE_KEY: ${{ secrets.ZMREPO_SSH_KEY }} + ARGS: "-rltgoDzvO" + SOURCE: ./build/*.{deb,dsc,tar.xz,buildinfo,changes} + REMOTE_HOST: ${{ secrets.ZMREPO_HOST }} + REMOTE_USER: ${{ secrets.ZMREPO_SSH_USER }} + TARGET: debian/master/mini-dinstall/incoming/ From b00ca5ce91b4f6343737a2983fbd38f4919e0838 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 31 Jan 2022 11:47:42 -0500 Subject: [PATCH 380/501] upload entire build dir as shell expansions don't seem to work --- .github/workflows/create-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml index 2fdc3577c..87d46246d 100644 --- a/.github/workflows/create-packages.yml +++ b/.github/workflows/create-packages.yml @@ -35,7 +35,7 @@ jobs: env: SSH_PRIVATE_KEY: ${{ secrets.ZMREPO_SSH_KEY }} ARGS: "-rltgoDzvO" - SOURCE: ./build/*.{deb,dsc,tar.xz,buildinfo,changes} + SOURCE: build/ REMOTE_HOST: ${{ secrets.ZMREPO_HOST }} REMOTE_USER: ${{ secrets.ZMREPO_SSH_USER }} TARGET: debian/master/mini-dinstall/incoming/ From 6b8cc14723024bbdcc8c3eca8785b8c39c58bd9a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 31 Jan 2022 12:07:09 -0500 Subject: [PATCH 381/501] Don't free image data if we added the packet to an event. The lock moves to the event so it's not safe --- src/zm_monitor.cpp | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index b55d2f91d..2c56ec6fe 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2270,24 +2270,6 @@ bool Monitor::Analyse() { shared_data->state = state = IDLE; } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - if (event) event->AddPacket(packet_lock); - - // In the case where people have pre-alarm frames, the web ui will generate the frame images - // from the mp4. So no one will notice anyways. - if (snap->image and (videowriter == PASSTHROUGH)) { - if (!savejpegs) { - Debug(1, "Deleting image data for %d", snap->image_index); - // Don't need raw images anymore - delete snap->image; - snap->image = nullptr; - } - if (snap->analysis_image and !(savejpegs & 2)) { - Debug(1, "Deleting analysis image data for %d", snap->image_index); - delete snap->analysis_image; - snap->analysis_image = nullptr; - } - } - packetqueue.clearPackets(snap); if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -2295,9 +2277,30 @@ bool Monitor::Analyse() { shared_data->last_read_index = snap->image_index; analysis_image_count++; } - packetqueue.increment_it(analysis_it); - if (!event) delete packet_lock; + + if (event) { + event->AddPacket(packet_lock); + } else { + // In the case where people have pre-alarm frames, the web ui will generate the frame images + // from the mp4. So no one will notice anyways. + if (snap->image and (videowriter == PASSTHROUGH)) { + if (!savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; + } + if (snap->analysis_image and !(savejpegs & 2)) { + Debug(1, "Deleting analysis image data for %d", snap->image_index); + delete snap->analysis_image; + snap->analysis_image = nullptr; + } + } + delete packet_lock; + } } // end scope for event_lock + + packetqueue.increment_it(analysis_it); //packetqueue.unlock(packet_lock); shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); From 0eb4d95d0e048c71adacca9eb38a3a123fa0f4a9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 31 Jan 2022 18:32:58 -0500 Subject: [PATCH 382/501] remove geolocation copies from config to javscript land. Now all of config is brought in my skin.js.php --- web/skins/classic/views/js/monitor.js.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/web/skins/classic/views/js/monitor.js.php b/web/skins/classic/views/js/monitor.js.php index c635af233..7b06905f7 100644 --- a/web/skins/classic/views/js/monitor.js.php +++ b/web/skins/classic/views/js/monitor.js.php @@ -1,10 +1,3 @@ -var ZM_OPT_USE_GEOLOCATION = '' == '1' ? true : false; - var optControl = ; var hasOnvif = ; var defaultAspectRatio = ''; From 34d7f192b4d10da46294185c97db73c5371c2c7c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 1 Feb 2022 07:23:12 -0500 Subject: [PATCH 383/501] Remove field that ffmpeg 5.0 doesn't have. --- src/zm_ffmpeg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 3986b38b8..4fe14051f 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -257,8 +257,8 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) Debug(1, "ids [0x%x]", st->id); if (lang) Debug(1, "language (%s)", lang->value); - Debug(1, "frames:%d, frame_size:%d stream timebase: %d/%d", - st->codec_info_nb_frames, codec->frame_size, + Debug(1, "frame_size:%d stream timebase: %d/%d", + codec->frame_size, st->time_base.num, st->time_base.den ); From 462ec45a0725bc12d15ffb64f880091935c602cb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 1 Feb 2022 09:46:37 -0500 Subject: [PATCH 384/501] Escape newlines in config values. --- web/skins/classic/js/skin.js.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 2d443fbe1..52beb07ce 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -109,7 +109,9 @@ stateStrings[STATE_TAPE] = ""; $c) { - if (!$c['Private']) - echo 'const '. $name . ' = \''.$c['Value'].'\''.PHP_EOL; + if (!$c['Private']) { + + echo 'const '. $name . ' = \''.preg_replace('/(\n\r?)/', '\$1', $c['Value']).'\''.PHP_EOL; + } } ?> From bc4884afe5ef2da04e6778827fba16a003f9d678 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 1 Feb 2022 10:07:04 -0500 Subject: [PATCH 385/501] fix replacement on escape. For some reason needs double bacl slash --- web/skins/classic/js/skin.js.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 52beb07ce..67cf74fd0 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -110,8 +110,7 @@ stateStrings[STATE_TAPE] = ""; global $config; foreach ($config as $name=>$c) { if (!$c['Private']) { - - echo 'const '. $name . ' = \''.preg_replace('/(\n\r?)/', '\$1', $c['Value']).'\''.PHP_EOL; + echo 'const '. $name . ' = \''.preg_replace('/(\n\r?)/', '\\\\$1', $c['Value']).'\';'.PHP_EOL; } } ?> From 80461cb1350b3cba4c2f4bb9190c389e44300d54 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 1 Feb 2022 13:36:11 -0500 Subject: [PATCH 386/501] add libcurl4 as a dependency as it was in build-depends. --- distros/ubuntu2004/control | 1 + 1 file changed, 1 insertion(+) diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index dda0e7852..54ca58af1 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -42,6 +42,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libswscale5|libswscale4 ,libswresample3|libswresample2 ,ffmpeg + ,libcurl4 ,libdatetime-perl, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl ,libphp-serialization-perl From 4949159b88fd45663cc483400daaaf69e9233805 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 1 Feb 2022 14:06:12 -0500 Subject: [PATCH 387/501] Rough in pcm alaw support. Untested. --- src/zm_rtsp_server.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/zm_rtsp_server.cpp b/src/zm_rtsp_server.cpp index ea6f4e2b7..3da273e00 100644 --- a/src/zm_rtsp_server.cpp +++ b/src/zm_rtsp_server.cpp @@ -298,6 +298,14 @@ int main(int argc, char *argv[]) { session->GetMediaSessionId(), xop::channel_1, audioFifoPath); audioSource->setFrequency(monitor->GetAudioFrequency()); audioSource->setChannels(monitor->GetAudioChannels()); + } else if (std::string::npos != audioFifoPath.find("pcm_alaw")) { + Debug(1, "Adding G711A source at %dHz %d channels", + monitor->GetAudioFrequency(), monitor->GetAudioChannels()); + session->AddSource(xop::channel_1, xop::G711ASource::CreateNew()); + audioSource = new ADTS_ZoneMinderFifoSource(rtspServer, + session->GetMediaSessionId(), xop::channel_1, audioFifoPath); + audioSource->setFrequency(monitor->GetAudioFrequency()); + audioSource->setChannels(monitor->GetAudioChannels()); } else { Warning("Unknown format in %s", audioFifoPath.c_str()); } From 18532096c59b0244e1984dae5ae48da2c20c63dc Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 1 Feb 2022 16:10:46 -0500 Subject: [PATCH 388/501] Need full commits to get # of commits since last version change --- .github/workflows/create-packages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml index 87d46246d..93279cc57 100644 --- a/.github/workflows/create-packages.yml +++ b/.github/workflows/create-packages.yml @@ -21,6 +21,7 @@ jobs: steps: - uses: actions/checkout@v2 with: + fetch-depth: '0' submodules: recursive - name: Run packpack env: From ad0a095886fde17650533848249c0cc9faf2c1d9 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 2 Feb 2022 11:35:36 +1100 Subject: [PATCH 389/501] Update MariaDB Bullseye info Specifically note that setting a root password is not required (or recommended; unix socket authentication is better than simple password auth). --- docs/installationguide/debian.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index b92ff267a..cf8bffdc5 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -35,7 +35,7 @@ Run the following commands. sudo apt install mariadb-server sudo apt install zoneminder -When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``. +By default MariaDB uses `unix socket authentication`_, so no root user password is required (root MariaDB user access only avaialble to local root Linux user). If you wish, you can set a root MariaDB password (and apply other security tweaks) by running `mariadb-secure-installation`_. **Step 3:** Setup permissions for zm.conf @@ -337,3 +337,6 @@ Reload Apache to enable your changes and then start ZoneMinder. sudo systemctl start zoneminder You are now ready to go with ZoneMinder. Open a browser and type either ``localhost/zm`` one the local machine or ``{IP-OF-ZM-SERVER}/zm`` if you connect from a remote computer. + +.. _unix socket authentication: https://mariadb.com/kb/en/authentication-plugin-unix-socket/ +.. _mariadb-secure-installation: https://mariadb.com/kb/en/mysql_secure_installation/ From 63b3042d3726a32aeea239a77c4308e8a402422d Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 2 Feb 2022 14:18:26 +1100 Subject: [PATCH 390/501] fix typo --- docs/installationguide/debian.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index cf8bffdc5..a5162d185 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -35,7 +35,7 @@ Run the following commands. sudo apt install mariadb-server sudo apt install zoneminder -By default MariaDB uses `unix socket authentication`_, so no root user password is required (root MariaDB user access only avaialble to local root Linux user). If you wish, you can set a root MariaDB password (and apply other security tweaks) by running `mariadb-secure-installation`_. +By default MariaDB uses `unix socket authentication`_, so no root user password is required (root MariaDB user access only available to local root Linux user). If you wish, you can set a root MariaDB password (and apply other security tweaks) by running `mariadb-secure-installation`_. **Step 3:** Setup permissions for zm.conf From 4874dedc1ff92592a83318569bb7c20f68115c11 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 2 Feb 2022 10:28:18 -0500 Subject: [PATCH 391/501] Bump version of RtspServer used --- utils/packpack/startpackpack.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 2ea949078..9737c794a 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -118,7 +118,7 @@ commonprep () { fi fi - RTSPVER="cd7fd49becad6010a1b8466bfebbd93999a39878" + RTSPVER="eab32851421ffe54fec0229c3efc44c642bc8d46" if [ -e "build/RtspServer-${RTSPVER}.tar.gz" ]; then echo "Found existing RtspServer ${RTSPVER} tarball..." else From 31eff49a46a66439890c94e1519cab9a7975a1b8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 2 Feb 2022 10:49:05 -0500 Subject: [PATCH 392/501] Implement filter limits. Which go before pagination/advanced search limits --- web/ajax/events.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/web/ajax/events.php b/web/ajax/events.php index 4db6b99cc..c95b404c4 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -187,6 +187,9 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $col_str = 'E.*, M.Name AS Monitor'; $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort.' '.$order:''); + if ($filter->limit() and !count($filter->pre_sql_conditions()) and !count($filter->post_sql_conditions())) { + $sql .= ' LIMIT '.$filter->limit(); + } $storage_areas = ZM\Storage::find(); $StorageById = array(); @@ -213,6 +216,12 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $unfiltered_rows[] = $row; } # end foreach row + # Filter limits come before pagination limits. + if ($filter->limit() and ($filter->limit() > count($unfiltered_rows))) { + ZM\Debug("Filtering rows due to filter->limit " . count($unfiltered_rows)." limit: ".$filter->limit()); + $unfiltered_rows = array_slice($unfiltered_rows, 0, $filter->limit()); + } + ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); $filtered_rows = null; @@ -251,8 +260,10 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $filtered_rows = $unfiltered_rows; } # end if search_filter->terms() > 1 - if ($limit) + if ($limit) { + ZM\Debug("Filtering rows due to limit " . count($filtered_rows)." offset: $offset limit: $limit"); $filtered_rows = array_slice($filtered_rows, $offset, $limit); + } $returned_rows = array(); foreach ($filtered_rows as $row) { From 1f9e654821131c2ebf9e934e74e55d3f8f62e881 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 2 Feb 2022 11:02:34 -0500 Subject: [PATCH 393/501] Default limit to 0 which means no limit --- web/includes/Filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 4fdd8e43f..2b599fe36 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -240,7 +240,7 @@ class Filter extends ZM_Object { } if ( isset( $this->Query()['limit'] ) ) return $this->{'Query'}['limit']; - return 100; + return 0; #return $this->defaults{'limit'}; } From 70f4a68269694ace8682b3164911341c164d857a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 2 Feb 2022 13:13:00 -0500 Subject: [PATCH 394/501] bump version for use_Amcrest_API --- distros/redhat/zoneminder.spec | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index b95d80626..f15e60326 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.10 +Version: 1.37.11 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index a9009bf2a..f951feb88 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.10 +1.37.11 From 606c47ba8758d2b88595f4585f73a820bea9919f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 2 Feb 2022 14:33:58 -0500 Subject: [PATCH 395/501] If possible, use the v-channel from the yuv420p image for motion detection instead of the rgba data. --- src/zm_monitor.cpp | 81 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 56b3dfc04..c3333e051 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -422,9 +422,9 @@ Monitor::Monitor() privacy_bitmask(nullptr), n_linked_monitors(0), linked_monitors(nullptr), + ONVIF_Closes_Event(FALSE), #ifdef WITH_GSOAP soap(nullptr), - ONVIF_Closes_Event(FALSE), #endif red_val(0), green_val(0), @@ -1996,8 +1996,19 @@ bool Monitor::Analyse() { } else { event->addNote(SIGNAL_CAUSE, "Reacquired"); } - if (snap->image) + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "assigning refimage from v-channel"); + Image v_image(snap->in_frame->width, + snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Assign(v_image); + } else if (snap->image) { + Debug(1, "assigning refimage from snap->image"); ref_image.Assign(*(snap->image)); + } } shared_data->state = state = IDLE; shared_data->active = signal; @@ -2076,12 +2087,32 @@ bool Monitor::Analyse() { // decoder may not have been able to provide an image if (!ref_image.Buffer()) { Debug(1, "Assigning instead of Detecting"); - ref_image.Assign(*(snap->image)); + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "assigning refimage from v-channel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Assign(v_image); + } else { + Debug(1, "assigning refimage from snap->image"); + ref_image.Assign(*(snap->image)); + } alarm_image.Assign(*(snap->image)); } else if (!(analysis_image_count % (motion_frame_skip+1))) { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. - snap->score = DetectMotion(*(snap->image), zoneSet); + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + snap->score = DetectMotion(v_image, zoneSet); + } else { + snap->score = DetectMotion(*(snap->image), zoneSet); + } if (!snap->analysis_image) snap->analysis_image = new Image(*(snap->image)); @@ -2298,16 +2329,42 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if ((function == MODECT or function == MOCORD) and snap->image) { + if (function == MODECT or function == MOCORD) { if (!ref_image.Buffer()) { - Debug(1, "Assigning"); - ref_image.Assign(*(snap->image)); + if (snap->in_frame && ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) ) { + Debug(1, "Assigning from vchannel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Assign(v_image); + } else if (snap->image) { + Debug(1, "Assigning"); + ref_image.Assign(*(snap->image)); + } } else { - Debug(1, "Blending"); - ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); - Debug(1, "Done Blending"); - } - } + if (snap->in_frame && + ( + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) + || + ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) + ) + ) { + Debug(1, "Blending from vchannel"); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + ref_image.Blend(v_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + } else if (snap->image) { + Debug(1, "Blending because %p and format %d != %d, %d", snap->in_frame, + snap->in_frame->format, + AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ420P + ); + ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + Debug(1, "Done Blending"); + } + } // end if have image + } // end if detecting last_signal = signal; } // end if videostream } // end if signal From 339f102196091f4466b939fea31b9c0e661b565b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 2 Feb 2022 14:34:53 -0500 Subject: [PATCH 396/501] Implement reboot and ping methods for Trendnet PTZ Control --- .../lib/ZoneMinder/Control/Trendnet.pm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm index 8bdf1db42..cc437974f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm @@ -374,6 +374,25 @@ sub reset { $self->sendCmdPost($url,$cmd); } +sub reboot { + my $self = shift; + Debug('Camera Reboot'); + $self->sendCmdPost('/eng/admin/reboot.cgi', { reboot => 'true' }); + #$referer = 'http://'.$HI->ip().'/eng/admin/tools_default.cgi'; + #$initial_url = $HI->ip().'/eng/admin/tools_default.cgi'; +} + +sub ping { + return -1 if ! $ADDRESS; + + require Net::Ping; + + my $p = Net::Ping->new(); + my $rv = $p->ping($ADDRESS); + $p->close(); + return $rv; +} + 1; __END__ From 89e1c5d53c31776d2c5e0e2f29468d3c0896bd8a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 26 Jan 2022 16:54:27 -0600 Subject: [PATCH 397/501] Move Monitor::MonitorLink to dedicated file --- src/CMakeLists.txt | 1 + src/zm_monitor_monitorlink.cpp | 198 +++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 src/zm_monitor_monitorlink.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6878a6589..439a77291 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ set(ZM_BIN_SRC_FILES zm_libvnc_camera.cpp zm_local_camera.cpp zm_monitor.cpp + zm_monitor_monitorlink.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_ffmpeg_camera.cpp diff --git a/src/zm_monitor_monitorlink.cpp b/src/zm_monitor_monitorlink.cpp new file mode 100644 index 000000000..95432388a --- /dev/null +++ b/src/zm_monitor_monitorlink.cpp @@ -0,0 +1,198 @@ +// +// ZoneMinder Monitor Class Implementation, $Date$, $Revision$ +// Copyright (C) 2001-2008 Philip Coombes +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#include "zm_monitor.h" + +#include + +#if ZM_MEM_MAPPED +#include +#include +#else // ZM_MEM_MAPPED +#include +#include +#endif // ZM_MEM_MAPPED + +Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) : + id(p_id), + shared_data(nullptr), + trigger_data(nullptr), + video_store_data(nullptr) +{ + strncpy(name, p_name, sizeof(name)-1); + +#if ZM_MEM_MAPPED + map_fd = -1; + mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); +#else // ZM_MEM_MAPPED + shm_id = 0; +#endif // ZM_MEM_MAPPED + mem_size = 0; + mem_ptr = nullptr; + + last_event_id = 0; + last_state = IDLE; + + last_connect_time = 0; + connected = false; +} + +Monitor::MonitorLink::~MonitorLink() { + disconnect(); +} + +bool Monitor::MonitorLink::connect() { + SystemTimePoint now = std::chrono::system_clock::now(); + if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(60)) { + last_connect_time = std::chrono::system_clock::to_time_t(now); + + mem_size = sizeof(SharedData) + sizeof(TriggerData); + + Debug(1, "link.mem.size=%jd", static_cast(mem_size)); +#if ZM_MEM_MAPPED + map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600); + if (map_fd < 0) { + Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); + disconnect(); + return false; + } + while (map_fd <= 2) { + int new_map_fd = dup(map_fd); + Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd); + close(map_fd); + map_fd = new_map_fd; + } + + struct stat map_stat; + if (fstat(map_fd, &map_stat) < 0) { + Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); + disconnect(); + return false; + } + + if (map_stat.st_size == 0) { + Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno)); + disconnect(); + return false; + } else if (map_stat.st_size < mem_size) { + Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast(mem_size)); + disconnect(); + return false; + } + + mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); + if (mem_ptr == MAP_FAILED) { + Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast(mem_size), strerror(errno)); + disconnect(); + return false; + } +#else // ZM_MEM_MAPPED + shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700); + if (shm_id < 0) { + Debug(3, "Can't shmget link memory: %s", strerror(errno)); + connected = false; + return false; + } + mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); + if ((int)mem_ptr == -1) { + Debug(3, "Can't shmat link memory: %s", strerror(errno)); + connected = false; + return false; + } +#endif // ZM_MEM_MAPPED + + shared_data = (SharedData *)mem_ptr; + trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); + + if (!shared_data->valid) { + Debug(3, "Linked memory not initialised by capture daemon"); + disconnect(); + return false; + } + + last_state = shared_data->state; + last_event_id = shared_data->last_event_id; + connected = true; + + return true; + } + return false; +} // end bool Monitor::MonitorLink::connect() + +bool Monitor::MonitorLink::disconnect() { + if (connected) { + connected = false; + +#if ZM_MEM_MAPPED + if (mem_ptr > (void *)0) { + msync(mem_ptr, mem_size, MS_ASYNC); + munmap(mem_ptr, mem_size); + } + if (map_fd >= 0) + close(map_fd); + + map_fd = -1; +#else // ZM_MEM_MAPPED + struct shmid_ds shm_data; + if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; + } + + shm_id = 0; + + if (shm_data.shm_nattch <= 1) { + if (shmctl(shm_id, IPC_RMID, 0) < 0) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; + } + } + + if (shmdt(mem_ptr) < 0) { + Debug(3, "Can't shmdt: %s", strerror(errno)); + return false; + } +#endif // ZM_MEM_MAPPED + mem_size = 0; + mem_ptr = nullptr; + } + return true; +} + +bool Monitor::MonitorLink::isAlarmed() { + if (!connected) { + return false; + } + return( shared_data->state == ALARM ); +} + +bool Monitor::MonitorLink::inAlarm() { + if (!connected) { + return false; + } + return( shared_data->state == ALARM || shared_data->state == ALERT ); +} + +bool Monitor::MonitorLink::hasAlarmed() { + if (shared_data->state == ALARM) { + return true; + } + last_event_id = shared_data->last_event_id; + return false; +} From 7e9c707f92d5c8c7221e36449441f5171fcccfd3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 08:19:08 -0500 Subject: [PATCH 398/501] V plane is actually the 3rd array entry, not the 4th --- src/zm_monitor.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index c3333e051..2b6436379 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2003,7 +2003,7 @@ bool Monitor::Analyse() { ) ) { Debug(1, "assigning refimage from v-channel"); Image v_image(snap->in_frame->width, - snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); ref_image.Assign(v_image); } else if (snap->image) { Debug(1, "assigning refimage from snap->image"); @@ -2093,7 +2093,7 @@ bool Monitor::Analyse() { ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) ) ) { Debug(1, "assigning refimage from v-channel"); - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); ref_image.Assign(v_image); } else { Debug(1, "assigning refimage from snap->image"); @@ -2108,7 +2108,7 @@ bool Monitor::Analyse() { || ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) ) ) { - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); snap->score = DetectMotion(v_image, zoneSet); } else { snap->score = DetectMotion(*(snap->image), zoneSet); @@ -2337,7 +2337,7 @@ bool Monitor::Analyse() { ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) ) ) { Debug(1, "Assigning from vchannel"); - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); ref_image.Assign(v_image); } else if (snap->image) { Debug(1, "Assigning"); @@ -2352,7 +2352,7 @@ bool Monitor::Analyse() { ) ) { Debug(1, "Blending from vchannel"); - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[3], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); ref_image.Blend(v_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); } else if (snap->image) { Debug(1, "Blending because %p and format %d != %d, %d", snap->in_frame, From b05bc211f744870e82193a5737985866b19dbb18 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 10:50:40 -0500 Subject: [PATCH 399/501] Apparently it is actually the Y channel that we want. --- src/zm_monitor.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 2b6436379..a33b00c40 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2003,7 +2003,7 @@ bool Monitor::Analyse() { ) ) { Debug(1, "assigning refimage from v-channel"); Image v_image(snap->in_frame->width, - snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); + snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); ref_image.Assign(v_image); } else if (snap->image) { Debug(1, "assigning refimage from snap->image"); @@ -2093,7 +2093,7 @@ bool Monitor::Analyse() { ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) ) ) { Debug(1, "assigning refimage from v-channel"); - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); ref_image.Assign(v_image); } else { Debug(1, "assigning refimage from snap->image"); @@ -2108,7 +2108,7 @@ bool Monitor::Analyse() { || ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) ) ) { - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); snap->score = DetectMotion(v_image, zoneSet); } else { snap->score = DetectMotion(*(snap->image), zoneSet); @@ -2337,7 +2337,7 @@ bool Monitor::Analyse() { ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) ) ) { Debug(1, "Assigning from vchannel"); - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); ref_image.Assign(v_image); } else if (snap->image) { Debug(1, "Assigning"); @@ -2352,7 +2352,7 @@ bool Monitor::Analyse() { ) ) { Debug(1, "Blending from vchannel"); - Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[2], 0); + Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); ref_image.Blend(v_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); } else if (snap->image) { Debug(1, "Blending because %p and format %d != %d, %d", snap->in_frame, From 838704e58e2ac24ef2af10282ddfa27be3ce4a39 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 10:07:21 -0500 Subject: [PATCH 400/501] Add more stop_ tests so that when we are told to terminate, we actually do so. --- src/zm_rtsp_server_fifo_source.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/zm_rtsp_server_fifo_source.cpp b/src/zm_rtsp_server_fifo_source.cpp index 40dbc14fc..1845c96ef 100644 --- a/src/zm_rtsp_server_fifo_source.cpp +++ b/src/zm_rtsp_server_fifo_source.cpp @@ -109,7 +109,7 @@ void ZoneMinderFifoSource::WriteRun() { fuNal.buffer()[2] = fuNal.buffer()[2]&~0x80; // FU header (no S bit) headerSize = 3; } - while (nalRemaining) { + while (nalRemaining && !stop_) { if ( nalRemaining < maxNalSize ) { // This is the last fragment: fuNal.buffer()[headerSize-1] |= 0x40; // set the E bit in the FU header @@ -166,7 +166,7 @@ int ZoneMinderFifoSource::getNextFrame() { } Debug(3, "%s bytes read %d bytes, buffer size %u", m_fifo.c_str(), bytes_read, m_buffer.size()); - while (m_buffer.size()) { + while (m_buffer.size() and !stop_) { unsigned int data_size = 0; int64_t pts; unsigned char *header_end = nullptr; @@ -224,7 +224,7 @@ int ZoneMinderFifoSource::getNextFrame() { int bytes_needed = data_size - (m_buffer.size() - header_size); if (bytes_needed > 0) { Debug(4, "Need another %d bytes. Trying to read them", bytes_needed); - while (bytes_needed) { + while (bytes_needed and !stop_) { bytes_read = m_buffer.read_into(m_fd, bytes_needed); if (bytes_read <= 0) { Debug(1, "Failed to read another %d bytes, got %d.", bytes_needed, bytes_read); @@ -252,13 +252,14 @@ int ZoneMinderFifoSource::getNextFrame() { { std::unique_lock lck(mutex_); Debug(3, "have lock"); - while (framesList.size()) { + while (!stop_ && framesList.size()) { std::pair nal = framesList.front(); framesList.pop_front(); NAL_Frame *Nal = new NAL_Frame(nal.first, nal.second, pts); m_nalQueue.push(Nal); } } + Debug(3, "notifying"); condition_.notify_all(); } // end while m_buffer.size() return 1; From 67ac0fdffd2003e7605a8b9ff1138205223c6115 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 11:57:00 -0500 Subject: [PATCH 401/501] Disable alleged y-channel image motion detection. Doesn't work. --- src/zm_monitor.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index a33b00c40..6b2b44e63 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1996,7 +1996,7 @@ bool Monitor::Analyse() { } else { event->addNote(SIGNAL_CAUSE, "Reacquired"); } - if (snap->in_frame && ( + if (0 and snap->in_frame && ( ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) || ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) @@ -2087,7 +2087,7 @@ bool Monitor::Analyse() { // decoder may not have been able to provide an image if (!ref_image.Buffer()) { Debug(1, "Assigning instead of Detecting"); - if (snap->in_frame && ( + if (0 and snap->in_frame && ( ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) || ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) @@ -2103,7 +2103,7 @@ bool Monitor::Analyse() { } else if (!(analysis_image_count % (motion_frame_skip+1))) { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. - if (snap->in_frame && ( + if (0 and snap->in_frame && ( ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) || ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) @@ -2331,7 +2331,7 @@ bool Monitor::Analyse() { if (function == MODECT or function == MOCORD) { if (!ref_image.Buffer()) { - if (snap->in_frame && ( + if (0 snap->in_frame && ( ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) || ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) From 02a2c783e495edeb2c22a8f24a3d3a4d0e84a34b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 11:57:27 -0500 Subject: [PATCH 402/501] Disable alleged y-channel image motion detection. Doesn't work. --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 6b2b44e63..3a4879e5c 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2331,7 +2331,7 @@ bool Monitor::Analyse() { if (function == MODECT or function == MOCORD) { if (!ref_image.Buffer()) { - if (0 snap->in_frame && ( + if (0 and snap->in_frame && ( ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) || ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUVJ420P) From d24b419f0e6a6af95f0c38a125b3e46ed17deab9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 12:06:00 -0500 Subject: [PATCH 403/501] Only create packages for master --- .github/workflows/create-packages.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml index 93279cc57..e62c5aec1 100644 --- a/.github/workflows/create-packages.yml +++ b/.github/workflows/create-packages.yml @@ -2,8 +2,7 @@ name: Create packages on: push: - branches: - - '*' + branches: [ master ] pull_request: branches: [ master ] From da4c8802333c4e5acbb11c8cfa2752b222b84f27 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 12:16:01 -0500 Subject: [PATCH 404/501] 0 out another used of y-image --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 3a4879e5c..fcd8e4b9e 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2344,7 +2344,7 @@ bool Monitor::Analyse() { ref_image.Assign(*(snap->image)); } } else { - if (snap->in_frame && + if (0 and snap->in_frame && ( ((AVPixelFormat)snap->in_frame->format == AV_PIX_FMT_YUV420P) || From 7515711eb848293542a6f5a8f123c9893ae0a90c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 14:45:17 -0500 Subject: [PATCH 405/501] Implement Server function which figures out which Server likely has the video. Use it to remove duplicate logic --- web/includes/Event.php | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/web/includes/Event.php b/web/includes/Event.php index e042849e9..6f180ef17 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -214,22 +214,24 @@ class Event extends ZM_Object { } } # end Event->delete - public function getStreamSrc( $args=array(), $querySep='&' ) { - - $streamSrc = ''; - $Server = null; + public function Server() { if ( $this->Storage()->ServerId() ) { # The Event may have been moved to Storage on another server, # So prefer viewing the Event from the Server that is actually # storing the video - $Server = $this->Storage()->Server(); + return $this->Storage()->Server(); } else if ( $this->Monitor()->ServerId() ) { # Assume that the server that recorded it has it - $Server = $this->Monitor()->Server(); - } else { - # A default Server will result in the use of ZM_DIR_EVENTS - $Server = new Server(); + return $this->Monitor()->Server(); } + # A default Server will result in the use of ZM_DIR_EVENTS + return new Server(); + } + + public function getStreamSrc( $args=array(), $querySep='&' ) { + + $streamSrc = ''; + $Server = $this->Server(); # If we are in a multi-port setup, then use the multiport, else by # passing null Server->Url will use the Port set in the Server setting @@ -354,15 +356,7 @@ class Event extends ZM_Object { # We always store at least 1 image when capturing $streamSrc = ''; - $Server = null; - if ( $this->Storage()->ServerId() ) { - $Server = $this->Storage()->Server(); - } else if ( $this->Monitor()->ServerId() ) { - # Assume that the server that recorded it has it - $Server = $this->Monitor()->Server(); - } else { - $Server = new Server(); - } + $Server = $this->Server(); $streamSrc .= $Server->UrlToIndex( ZM_MIN_STREAMING_PORT ? ZM_MIN_STREAMING_PORT+$this->{'MonitorId'} : @@ -514,7 +508,7 @@ class Event extends ZM_Object { return false; } $Storage= $this->Storage(); - $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); + $Server = $this->Server(); if ( $Server->Id() != ZM_SERVER_ID ) { $url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json'; @@ -562,7 +556,7 @@ class Event extends ZM_Object { return false; } $Storage= $this->Storage(); - $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); + $Server = $this->Server(); if ( $Server->Id() != ZM_SERVER_ID ) { $url = $Server->UrlToApi().'/events/'.$this->{'Id'}.'.json'; From 7a95aa7210e0484b29b917ddc1792cbce6933eb9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 14:46:39 -0500 Subject: [PATCH 406/501] Don't render cues if we don't have any. This occurs on initial load we call changeScale which would re-render the cues but the cur ajax hasn't completed yet, so this just avoids an error being logged --- web/skins/classic/views/js/event.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index ac05f51fe..31363e154 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -203,7 +203,10 @@ function changeScale() { streamScale(scale == '0' ? autoScale : scale); drawProgressBar(); } - alarmCue.html(renderAlarmCues(eventViewer));//just re-render alarmCues. skip ajax call + if (cueFrames) { + //just re-render alarmCues. skip ajax call + alarmCue.html(renderAlarmCues(eventViewer)); + } setCookie('zmEventScale'+eventData.MonitorId, scale, 3600); // After a resize, check if we still have room to display the event stats table From 3c655807e841d9194fc9612b9337b6ce85d55796 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 14:47:32 -0500 Subject: [PATCH 407/501] Use new Event->Server function to return the correct (and matching url to zms) url to use for ajax status calls. Fixes errors in a multi-server environment. --- web/skins/classic/views/js/event.js.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/event.js.php b/web/skins/classic/views/js/event.js.php index 5d4d57f7c..c2b61f395 100644 --- a/web/skins/classic/views/js/event.js.php +++ b/web/skins/classic/views/js/event.js.php @@ -87,7 +87,7 @@ var eventDataStrings = { Emailed: '' }; -var monitorUrl = 'Storage()->Server()->UrlToIndex(); ?>'; +var monitorUrl = 'Server()->UrlToIndex(); ?>'; var filterQuery = ''; var sortQuery = ''; From ac39be33f50403974476c9baa638b71a55291754 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 17:24:33 -0500 Subject: [PATCH 408/501] Don't assume filename of mp4. We store it in the event record for a reason. Fixes #3422 --- scripts/zmfilter.pl.in | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 960c090cb..3fb286039 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -464,15 +464,18 @@ sub generateImage { my $event_path = $Event->Path(); my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', $event_path, $frame->{FrameId}); my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', $event_path, $frame->{FrameId}) if $analyse; - my $video_path = sprintf('%s/%d-video.mp4', $event_path, $Event->{Id}); + my $video_path = sprintf('%s/%s', $event_path, $Event->{DefaultVideo}); my $image_path = ''; # check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video if ( $analyse && -r $analyse_image_path ) { + Debug("Using analysis and jpeg exists $analyse_image_path"); $image_path = $analyse_image_path; } elsif ( -r $capture_image_path ) { + Debug("Using captures and jpeg exists $capture_image_path"); $image_path = $capture_image_path; } elsif ( -r $video_path ) { + Debug("mp4 exists $video_path"); my $command ="ffmpeg -nostdin -ss $$frame{Delta} -i '$video_path' -frames:v 1 '$capture_image_path'"; #$command = "ffmpeg -y -v 0 -i $video_path -vf 'select=gte(n\\,$$frame{FrameId}),setpts=PTS-STARTPTS' -vframes 1 -f image2 $capture_image_path"; my $output = qx($command); @@ -486,6 +489,8 @@ sub generateImage { } else { $image_path = $capture_image_path; } + } else { + Debug("No files found at $analyse_image_path, $capture_image_path or $video_path"); } return $image_path; } @@ -723,7 +728,7 @@ sub substituteTags { if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { - Warning("Path to first image does not exist at $path"); + Warning("Path to first image does not exist at $path for image $first_alarm_frame"); } } From a6dc7ba0fc769f5af8aceebaa4669d429df557fe Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 17:30:38 -0500 Subject: [PATCH 409/501] Add debugging, but commented out --- web/includes/Object.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/includes/Object.php b/web/includes/Object.php index ff5d40baa..5023587fd 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -237,10 +237,13 @@ class ZM_Object { $changes = array(); if ($defaults) { + // FIXME: This code basically means that the new_values must be a full object, not a subset + // Perhaps if it only concerned itself with the keys of new_values foreach ($defaults as $field => $type) { if (isset($new_values[$field])) continue; if (isset($this->defaults[$field])) { + //Debug("Setting default for $field"); if (is_array($this->defaults[$field])) { $new_values[$field] = $this->defaults[$field]['default']; } else { @@ -255,9 +258,11 @@ class ZM_Object { if (array_key_exists($field, $this->defaults) && is_array($this->defaults[$field]) && isset($this->defaults[$field]['filter_regexp'])) { if (is_array($this->defaults[$field]['filter_regexp'])) { foreach ($this->defaults[$field]['filter_regexp'] as $regexp) { + //Debug("regexping array $field $value to " . preg_replace($regexp, '', trim($value))); $value = preg_replace($regexp, '', trim($value)); } } else { + //Debug("regexping $field $value to " . preg_replace($this->defaults[$field]['filter_regexp'], '', trim($value))); $value = preg_replace($this->defaults[$field]['filter_regexp'], '', trim($value)); } } @@ -265,10 +270,12 @@ class ZM_Object { $old_value = $this->$field(); if (is_array($old_value)) { $diff = array_recursive_diff($old_value, $value); + //Debug("$field array old: " .print_r($old_value, true) . " new: " . print_r($value, true). ' diff: '. print_r($diff, true)); if ( count($diff) ) { $changes[$field] = $value; } } else if ( $this->$field() != $value ) { + //Debug("$field != $value"); $changes[$field] = $value; } } else if (property_exists($this, $field)) { From dde3884084880ba308bad7cab6664ee6ba598f7a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 3 Feb 2022 16:31:07 -0600 Subject: [PATCH 410/501] Moves Janus and Amcrest into their own nested classes --- src/CMakeLists.txt | 2 + src/zm_monitor.cpp | 575 +++---------------------------------- src/zm_monitor.h | 79 +++-- src/zm_monitor_amcrest.cpp | 126 ++++++++ src/zm_monitor_janus.cpp | 316 ++++++++++++++++++++ 5 files changed, 538 insertions(+), 560 deletions(-) create mode 100644 src/zm_monitor_amcrest.cpp create mode 100644 src/zm_monitor_janus.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 439a77291..6943a102a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,8 @@ set(ZM_BIN_SRC_FILES zm_local_camera.cpp zm_monitor.cpp zm_monitor_monitorlink.cpp + zm_monitor_janus.cpp + zm_monitor_amcrest.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_ffmpeg_camera.cpp diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index c3333e051..576b4d517 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -130,175 +130,7 @@ std::string TriggerState_Strings[] = { "Cancel", "On", "Off" }; -Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) : - id(p_id), - shared_data(nullptr), - trigger_data(nullptr), - video_store_data(nullptr) -{ - strncpy(name, p_name, sizeof(name)-1); - -#if ZM_MEM_MAPPED - map_fd = -1; - mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); -#else // ZM_MEM_MAPPED - shm_id = 0; -#endif // ZM_MEM_MAPPED - mem_size = 0; - mem_ptr = nullptr; - - last_event_id = 0; - last_state = IDLE; - - last_connect_time = 0; - connected = false; -} - -Monitor::MonitorLink::~MonitorLink() { - disconnect(); -} - -bool Monitor::MonitorLink::connect() { - SystemTimePoint now = std::chrono::system_clock::now(); - if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(60)) { - last_connect_time = std::chrono::system_clock::to_time_t(now); - - mem_size = sizeof(SharedData) + sizeof(TriggerData); - - Debug(1, "link.mem.size=%jd", static_cast(mem_size)); -#if ZM_MEM_MAPPED - map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600); - if (map_fd < 0) { - Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); - disconnect(); - return false; - } - while (map_fd <= 2) { - int new_map_fd = dup(map_fd); - Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd); - close(map_fd); - map_fd = new_map_fd; - } - - struct stat map_stat; - if (fstat(map_fd, &map_stat) < 0) { - Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); - disconnect(); - return false; - } - - if (map_stat.st_size == 0) { - Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno)); - disconnect(); - return false; - } else if (map_stat.st_size < mem_size) { - Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast(mem_size)); - disconnect(); - return false; - } - - mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); - if (mem_ptr == MAP_FAILED) { - Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast(mem_size), strerror(errno)); - disconnect(); - return false; - } -#else // ZM_MEM_MAPPED - shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700); - if (shm_id < 0) { - Debug(3, "Can't shmget link memory: %s", strerror(errno)); - connected = false; - return false; - } - mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); - if ((int)mem_ptr == -1) { - Debug(3, "Can't shmat link memory: %s", strerror(errno)); - connected = false; - return false; - } -#endif // ZM_MEM_MAPPED - - shared_data = (SharedData *)mem_ptr; - trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - - if (!shared_data->valid) { - Debug(3, "Linked memory not initialised by capture daemon"); - disconnect(); - return false; - } - - last_state = shared_data->state; - last_event_id = shared_data->last_event_id; - connected = true; - - return true; - } - return false; -} // end bool Monitor::MonitorLink::connect() - -bool Monitor::MonitorLink::disconnect() { - if (connected) { - connected = false; - -#if ZM_MEM_MAPPED - if (mem_ptr > (void *)0) { - msync(mem_ptr, mem_size, MS_ASYNC); - munmap(mem_ptr, mem_size); - } - if (map_fd >= 0) - close(map_fd); - - map_fd = -1; -#else // ZM_MEM_MAPPED - struct shmid_ds shm_data; - if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { - Debug(3, "Can't shmctl: %s", strerror(errno)); - return false; - } - - shm_id = 0; - - if (shm_data.shm_nattch <= 1) { - if (shmctl(shm_id, IPC_RMID, 0) < 0) { - Debug(3, "Can't shmctl: %s", strerror(errno)); - return false; - } - } - - if (shmdt(mem_ptr) < 0) { - Debug(3, "Can't shmdt: %s", strerror(errno)); - return false; - } -#endif // ZM_MEM_MAPPED - mem_size = 0; - mem_ptr = nullptr; - } - return true; -} - -bool Monitor::MonitorLink::isAlarmed() { - if (!connected) { - return false; - } - return( shared_data->state == ALARM ); -} - -bool Monitor::MonitorLink::inAlarm() { - if (!connected) { - return false; - } - return( shared_data->state == ALARM || shared_data->state == ALERT ); -} - -bool Monitor::MonitorLink::hasAlarmed() { - if (shared_data->state == ALARM) { - return true; - } - last_event_id = shared_data->last_event_id; - return false; -} - -Monitor::Monitor() +Monitor::Monitor() : id(0), name(""), server_id(0), @@ -317,7 +149,7 @@ Monitor::Monitor() //user //pass //path - //device + //device palette(0), channel(0), format(0), @@ -422,7 +254,9 @@ Monitor::Monitor() privacy_bitmask(nullptr), n_linked_monitors(0), linked_monitors(nullptr), - ONVIF_Closes_Event(FALSE), + Event_Poller_Closes_Event(FALSE), + Janus_Manager(nullptr), + Amcrest_Manager(nullptr), #ifdef WITH_GSOAP soap(nullptr), #endif @@ -1083,21 +917,16 @@ bool Monitor::connect() { //ONVIF and Amcrest Setup - //since they serve the same function, handling them as two options of the same feature. - ONVIF_Trigger_State = FALSE; + //For now, only support one event type per camera, so share some state. + Poll_Trigger_State = FALSE; if (onvif_event_listener) { // Debug(1, "Starting ONVIF"); - ONVIF_Healthy = FALSE; + Event_Poller_Healthy = FALSE; if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message - ONVIF_Closes_Event = TRUE; + Event_Poller_Closes_Event = TRUE; } if (use_Amcrest_API) { - curl_multi = curl_multi_init(); - start_Amcrest(); - //spin up curl_multi - //use the onvif_user and onvif_pass and onvif_url here. - //going to use the non-blocking curl api, and in the polling thread, block for 5 seconds waiting for input, just like onvif - //note that it's not possible for a single camera to use both. + Amcrest_Manager = new AmcrestAPI(this); } else { //using GSOAP #ifdef WITH_GSOAP tev__PullMessages.Timeout = "PT600S"; @@ -1123,7 +952,7 @@ bool Monitor::connect() { Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap)); } else { Debug(1, "Good Initial ONVIF Pull"); - ONVIF_Healthy = TRUE; + Event_Poller_Healthy = TRUE; } } #else @@ -1135,17 +964,9 @@ bool Monitor::connect() { } //End ONVIF Setup -#if HAVE_LIBCURL //janus setup. Depends on libcurl. - if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { - get_janus_session(); - if (add_to_janus() != 0) { - Warning("Failed to add monitor stream to Janus!"); //The first attempt may fail. Will be reattempted in the Poller thread - } + if (janus_enabled) { + Janus_Manager = new JanusManager(this); } -#else - if (janus_enabled) - Error("zmc not compiled with LIBCURL. Janus support not built in!"); -#endif } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); @@ -1242,12 +1063,12 @@ Monitor::~Monitor() { sws_freeContext(convert_context); convert_context = nullptr; } - if (Amcrest_handle != nullptr) { - curl_multi_remove_handle(curl_multi, Amcrest_handle); - curl_easy_cleanup(Amcrest_handle); + if (Amcrest_Manager != nullptr) { + delete Amcrest_Manager; + } + if (purpose == CAPTURE) { + curl_global_cleanup(); //not sure about this location. } - if (curl_multi != nullptr) curl_multi_cleanup(curl_multi); - curl_global_cleanup(); } // end Monitor::~Monitor() void Monitor::AddPrivacyBitmask() { @@ -1817,38 +1638,13 @@ void Monitor::UpdateFPS() { //Thread where ONVIF polling, and other similar status polling can happen. //Since these can be blocking, run here to avoid intefering with other processing bool Monitor::Poll() { + //We want to trigger every 5 seconds or so. so grab the time at the beginning of the loop, and sleep at the end. std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now(); - if (ONVIF_Healthy) { + if (Event_Poller_Healthy) { if(use_Amcrest_API) { - int open_handles; - int transfers; - curl_multi_perform(curl_multi, &open_handles); - if (open_handles == 0) { - start_Amcrest(); //http transfer ended, need to restart. - } else { - curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event. - if (transfers > 0) { //have data to deal with - curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response - if (amcrest_response.find("action=Start") != std::string::npos) { - //Event Start - Debug(1,"Triggered on ONVIF"); - if (!ONVIF_Trigger_State) { - Debug(1,"Triggered Event"); - ONVIF_Trigger_State = TRUE; - } - } else if (amcrest_response.find("action=Stop") != std::string::npos){ - Debug(1, "Triggered off ONVIF"); - ONVIF_Trigger_State = FALSE; - if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them. - ONVIF_Closes_Event = TRUE; - Debug(1,"Setting ClosesEvent"); - } - } - amcrest_response.clear(); //We've dealt with the message, need to clear the queue - } - } + Amcrest_Manager->WaitForMessage(); } else { #ifdef WITH_GSOAP @@ -1857,7 +1653,7 @@ bool Monitor::Poll() { if (result != SOAP_OK) { if (result != SOAP_EOF) { //Ignore the timeout error Error("Failed to get ONVIF messages! %s", soap_fault_string(soap)); - ONVIF_Healthy = FALSE; + Event_Poller_Healthy = FALSE; } } else { Debug(1, "Got Good Response! %i", result); @@ -1874,16 +1670,16 @@ bool Monitor::Poll() { if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) { //Event Start Debug(1,"Triggered on ONVIF"); - if (!ONVIF_Trigger_State) { + if (!Poll_Trigger_State) { Debug(1,"Triggered Event"); - ONVIF_Trigger_State = TRUE; + Poll_Trigger_State = TRUE; std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep } } else { Debug(1, "Triggered off ONVIF"); - ONVIF_Trigger_State = FALSE; - if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them. - ONVIF_Closes_Event = TRUE; + Poll_Trigger_State = FALSE; + if (!Event_Poller_Closes_Event) { //If we get a close event, then we know to expect them. + Event_Poller_Closes_Event = TRUE; Debug(1,"Setting ClosesEvent"); } } @@ -1894,11 +1690,9 @@ bool Monitor::Poll() { } } if (janus_enabled) { - if (janus_session.empty()) { - get_janus_session(); - } - if (check_janus() == 0) { - add_to_janus(); + + if (Janus_Manager->check_janus() == 0) { + Janus_Manager->add_to_janus(); } } std::this_thread::sleep_until(loop_start_time + std::chrono::seconds(5)); @@ -1953,8 +1747,8 @@ bool Monitor::Analyse() { Event::StringSetMap noteSetMap; #ifdef WITH_GSOAP - if (onvif_event_listener && ONVIF_Healthy) { - if (ONVIF_Trigger_State) { + if (onvif_event_listener && Event_Poller_Healthy) { + if (Poll_Trigger_State) { score += 9; Debug(1, "Triggered on ONVIF"); Event::StringSet noteSet; @@ -1962,10 +1756,10 @@ bool Monitor::Analyse() { noteSetMap[MOTION_CAUSE] = noteSet; cause += "ONVIF"; //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. - if (!ONVIF_Closes_Event && state == ALARM) - ONVIF_Trigger_State = FALSE; + if (!Event_Poller_Closes_Event && state == ALARM) + Poll_Trigger_State = FALSE; } // end ONVIF_Trigger - } // end if (onvif_event_listener && ONVIF_Healthy) + } // end if (onvif_event_listener && Event_Poller_Healthy) #endif // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM @@ -3287,8 +3081,7 @@ int Monitor::PrimeCapture() { } // end if rtsp_server //Poller Thread - if (onvif_event_listener || janus_enabled) { - + if (onvif_event_listener || janus_enabled || use_Amcrest_API) { if (!Poller) { Poller = zm::make_unique(this); } else { @@ -3338,10 +3131,6 @@ int Monitor::Close() { if (Poller) { Poller->Stop(); } - if (curl_multi != nullptr) { - curl_multi_cleanup(curl_multi); - curl_multi = nullptr; - } #ifdef WITH_GSOAP if (onvif_event_listener && (soap != nullptr)) { Debug(1, "Tearing Down Onvif"); @@ -3354,11 +3143,11 @@ int Monitor::Close() { soap = nullptr; } //End ONVIF #endif -#if HAVE_LIBCURL //Janus Teardown + //Janus Teardown if (janus_enabled && (purpose == CAPTURE)) { - remove_from_janus(); + delete Janus_Manager; } -#endif + packetqueue.clear(); if (audio_fifo) { @@ -3495,289 +3284,3 @@ int SOAP_ENV__Fault(struct soap *soap, char *faultcode, char *faultstring, char return soap_send_empty_response(soap, SOAP_OK); } #endif - -size_t Monitor::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) -{ - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; -} - -int Monitor::add_to_janus() { - std::string response; - std::string endpoint; - if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { - endpoint = config.janus_path; - } else { - endpoint = "127.0.0.1:8088/janus/"; - } - std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; - std::string rtsp_username; - std::string rtsp_password; - std::string rtsp_path = "rtsp://"; - std::size_t pos; - std::size_t pos2; - CURLcode res; - - curl = curl_easy_init(); - if (!curl) { - Error("Failed to init curl"); - return -1; - } - //parse username and password - pos = path.find(":", 7); - if (pos == std::string::npos) return -1; - rtsp_username = path.substr(7, pos-7); - - pos2 = path.find("@", pos); - if (pos2 == std::string::npos) return -1; - - rtsp_password = path.substr(pos+1, pos2 - pos - 1); - rtsp_path += path.substr(pos2 + 1); - - endpoint += "/"; - endpoint += janus_session; - - //Assemble our actual request - postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; - postData += "\"request\" : \"create\", \"admin_key\" : \""; - postData += config.janus_secret; - postData += "\", \"type\" : \"rtsp\", "; - postData += "\"url\" : \""; - postData += rtsp_path; - postData += "\", \"rtsp_user\" : \""; - postData += rtsp_username; - postData += "\", \"rtsp_pwd\" : \""; - postData += rtsp_password; - postData += "\", \"id\" : "; - postData += std::to_string(id); - if (janus_audio_enabled) postData += ", \"audio\" : true"; - postData += ", \"video\" : true}}"; - - curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - Error("Failed to curl_easy_perform adding rtsp stream"); - curl_easy_cleanup(curl); - return -1; - } - if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) { - janus_session = ""; - curl_easy_cleanup(curl); - return -2; - } - //scan for missing session or handle id "No such session" "no such handle" - - Debug(1,"Added stream to Janus: %s", response.c_str()); - curl_easy_cleanup(curl); - return 0; -} - -int Monitor::check_janus() { - std::string response; - std::string endpoint; - if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { - endpoint = config.janus_path; - } else { - endpoint = "127.0.0.1:8088/janus/"; - } - std::string postData; - //std::size_t pos; - CURLcode res; - - curl = curl_easy_init(); - if(!curl) return -1; - - endpoint += "/"; - endpoint += janus_session; - - //Assemble our actual request - postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; - postData += "\"request\" : \"info\", \"id\" : "; - postData += std::to_string(id); - postData += "}}"; - - curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session - Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); - curl_easy_cleanup(curl); - janus_session = ""; - return -1; - } - - curl_easy_cleanup(curl); - Debug(1, "Queried for stream status: %s", response.c_str()); - if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) { - Warning("Janus Session timed out"); - janus_session = ""; - return -2; - } else if (response.find("No such mountpoint") != std::string::npos) { - Warning("Mountpoint Missing"); - return 0; - } else { - return 1; - } -} - - -int Monitor::remove_from_janus() { - std::string response; - std::string endpoint; - if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { - endpoint = config.janus_path; - } else { - endpoint = "127.0.0.1:8088/janus/"; - } - std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; - //std::size_t pos; - CURLcode res; - - curl = curl_easy_init(); - if(!curl) return -1; - - endpoint += "/"; - endpoint += janus_session; - - //Assemble our actual request - postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; - postData += "\"request\" : \"destroy\", \"admin_key\" : \""; - postData += config.janus_secret; - postData += "\", \"id\" : "; - postData += std::to_string(id); - postData += "}}"; - - curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); - curl_easy_cleanup(curl); - return -1; - } - - Debug(1, "Removed stream from Janus: %s", response.c_str()); - curl_easy_cleanup(curl); - return 0; -} - -int Monitor::get_janus_session() { - std::string response; - std::string endpoint; - if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { - endpoint = config.janus_path; - } else { - endpoint = "127.0.0.1:8088/janus/"; - } - std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; - std::size_t pos; - CURLcode res; - curl = curl_easy_init(); - if(!curl) return -1; - - //Start Janus API init. Need to get a session_id and handle_id - curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); - curl_easy_cleanup(curl); - return -1; - } - - pos = response.find("\"id\": "); - if (pos == std::string::npos) - { - curl_easy_cleanup(curl); - return -1; - } - janus_session = response.substr(pos + 6, 16); - - response = ""; - endpoint += "/"; - endpoint += janus_session; - postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; - curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - res = curl_easy_perform(curl); - if (res != CURLE_OK) - { - Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); - curl_easy_cleanup(curl); - return -1; - } - - pos = response.find("\"id\": "); - if (pos == std::string::npos) - { - curl_easy_cleanup(curl); - return -1; - } - janus_session += "/"; - janus_session += response.substr(pos + 6, 16); - curl_easy_cleanup(curl); - return 1; -} //get_janus_session - -int Monitor::start_Amcrest() { - //init the transfer and start it in multi-handle - int running_handles; - long response_code; - struct CURLMsg *m; - CURLMcode curl_error; - if (Amcrest_handle != nullptr) { //potentially clean up the old handle - curl_multi_remove_handle(curl_multi, Amcrest_handle); - curl_easy_cleanup(Amcrest_handle); - } - - std::string full_url = onvif_url; - if (full_url.back() != '/') full_url += '/'; - full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]"; - Amcrest_handle = curl_easy_init(); - if (!Amcrest_handle){ - Warning("Handle is null!"); - return -1; - } - curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str()); - curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response); - curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, onvif_username.c_str()); - curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, onvif_password.c_str()); - curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle); - Warning("error of %i", curl_error); - curl_error = curl_multi_perform(curl_multi, &running_handles); - if (curl_error == CURLM_OK) { - curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code); - int msgq = 0; - m = curl_multi_info_read(curl_multi, &msgq); - if (m && (m->msg == CURLMSG_DONE)) { - Warning("Libcurl exited Early: %i", m->data.result); - } - - curl_multi_wait(curl_multi, NULL, 0, 300, NULL); - curl_error = curl_multi_perform(curl_multi, &running_handles); - } - - if ((curl_error == CURLM_OK) && (running_handles > 0)) { - ONVIF_Healthy = TRUE; - } else { - Warning("Response: %s", amcrest_response.c_str()); - Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str()); - curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code); - Warning("Response code: %lu", response_code); - } - -return 0; -} diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 4ce0a9b08..30bc57d87 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -93,7 +93,7 @@ public: } Orientation; typedef enum { - DEINTERLACE_DISABLED = 0x00000000, + DEINTERLACE_DISABLED = 0x00000000, DEINTERLACE_FOUR_FIELD_SOFT = 0x00001E04, DEINTERLACE_FOUR_FIELD_MEDIUM = 0x00001404, DEINTERLACE_FOUR_FIELD_HARD = 0x00000A04, @@ -154,12 +154,12 @@ protected: uint32_t last_frame_score; /* +60 */ uint32_t audio_frequency; /* +64 */ uint32_t audio_channels; /* +68 */ - /* + /* ** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038. ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. - ** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple + ** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple ** of 8. Add or delete epadding's to achieve this. - */ + */ union { /* +72 */ time_t startup_time; /* When the zmc process started. zmwatch uses this to see how long the process has been running without getting any images */ uint64_t extrapad1; @@ -256,7 +256,49 @@ protected: bool hasAlarmed(); }; + class AmcrestAPI { protected: + Monitor *parent; + std::string amcrest_response; + CURLM *curl_multi = nullptr; + CURL *Amcrest_handle = nullptr; + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); + + public: + AmcrestAPI( Monitor *parent_); + ~AmcrestAPI(); + int API_Connect(); + void WaitForMessage(); + bool Amcrest_Alarmed; + int start_Amcrest(); + }; + + class JanusManager { + protected: + Monitor *parent; + CURL *curl = nullptr; + //helper class for CURL + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); + bool Janus_Healthy; + std::string janus_session; + std::string janus_handle; + std::string janus_endpoint; + std::string stream_key; + std::string rtsp_username; + std::string rtsp_password; + std::string rtsp_path; + + public: + JanusManager(Monitor *parent_); + ~JanusManager(); + int add_to_janus(); + int check_janus(); + int remove_from_janus(); + int get_janus_session(); + int get_janus_handle(); + int get_janus_plugin(); + std::string get_stream_key(); + }; // These are read from the DB and thereafter remain unchanged @@ -285,7 +327,6 @@ protected: std::string onvif_username; std::string onvif_password; std::string onvif_options; - std::string amcrest_response; bool onvif_event_listener; bool use_Amcrest_API; @@ -438,9 +479,12 @@ protected: std::string diag_path_delta; //ONVIF - bool ONVIF_Trigger_State; //Re-using some variables for Amcrest API support - bool ONVIF_Healthy; - bool ONVIF_Closes_Event; + bool Poll_Trigger_State; + bool Event_Poller_Healthy; + bool Event_Poller_Closes_Event; + + JanusManager *Janus_Manager; + AmcrestAPI *Amcrest_Manager; #ifdef WITH_GSOAP struct soap *soap = nullptr; @@ -452,17 +496,6 @@ protected: void set_credentials(struct soap *soap); #endif - //curl stuff - CURL *curl = nullptr; - CURLM *curl_multi = nullptr; - CURL *Amcrest_handle = nullptr; - int start_Amcrest(); - //helper class for CURL - static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); - int add_to_janus(); - int remove_from_janus(); - int get_janus_session(); - std::string janus_session; // Used in check signal uint8_t red_val; @@ -530,11 +563,9 @@ public: return onvif_event_listener; } int check_janus(); //returns 1 for healthy, 0 for success but missing stream, negative for error. -#ifdef WITH_GSOAP - bool OnvifHealthy() { - return ONVIF_Healthy; + bool EventPollerHealthy() { + return Event_Poller_Healthy; } -#endif inline const char *EventPrefix() const { return event_prefix.c_str(); } inline bool Ready() const { if ( image_count >= ready_count ) { @@ -583,7 +614,7 @@ public: void SetVideoWriterStartTime(SystemTimePoint t) { video_store_data->recording = zm::chrono::duration_cast(t.time_since_epoch()); } - + unsigned int GetPreEventCount() const { return pre_event_count; }; int32_t GetImageBufferCount() const { return image_buffer_count; }; State GetState() const { return (State)shared_data->state; } diff --git a/src/zm_monitor_amcrest.cpp b/src/zm_monitor_amcrest.cpp new file mode 100644 index 000000000..04af8734c --- /dev/null +++ b/src/zm_monitor_amcrest.cpp @@ -0,0 +1,126 @@ +// +// ZoneMinder Monitor::AmcrestAPI Class Implementation, $Date$, $Revision$ +// Copyright (C) 2022 Jonathan Bennett +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#include "zm_monitor.h" + + +Monitor::AmcrestAPI::AmcrestAPI(Monitor *parent_) { + parent = parent_; + curl_multi = curl_multi_init(); + start_Amcrest(); +} + +Monitor::AmcrestAPI::~AmcrestAPI() { + if (Amcrest_handle != nullptr) { //potentially clean up the old handle + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + if (curl_multi != nullptr) curl_multi_cleanup(curl_multi); +} + +int Monitor::AmcrestAPI::start_Amcrest() { + //init the transfer and start it in multi-handle + int running_handles; + long response_code; + struct CURLMsg *m; + CURLMcode curl_error; + if (Amcrest_handle != nullptr) { //potentially clean up the old handle + curl_multi_remove_handle(curl_multi, Amcrest_handle); + curl_easy_cleanup(Amcrest_handle); + } + + std::string full_url = parent->onvif_url; + if (full_url.back() != '/') full_url += '/'; + full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]"; + Amcrest_handle = curl_easy_init(); + if (!Amcrest_handle){ + Warning("Handle is null!"); + return -1; + } + curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response); + curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, parent->onvif_username.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, parent->onvif_password.c_str()); + curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle); + if (curl_error != CURLM_OK) { + Warning("error of %i", curl_error); + } + curl_error = curl_multi_perform(curl_multi, &running_handles); + if (curl_error == CURLM_OK) { + curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code); + int msgq = 0; + m = curl_multi_info_read(curl_multi, &msgq); + if (m && (m->msg == CURLMSG_DONE)) { + Warning("Libcurl exited Early: %i", m->data.result); + } + + curl_multi_wait(curl_multi, NULL, 0, 300, NULL); + curl_error = curl_multi_perform(curl_multi, &running_handles); + } + + if ((curl_error == CURLM_OK) && (running_handles > 0)) { + parent->Event_Poller_Healthy = TRUE; + } else { + Warning("Response: %s", amcrest_response.c_str()); + Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str()); + curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code); + Warning("Response code: %lu", response_code); + } + +return 0; +} + +void Monitor::AmcrestAPI::WaitForMessage() { + int open_handles; + int transfers; + curl_multi_perform(curl_multi, &open_handles); + if (open_handles == 0) { + start_Amcrest(); //http transfer ended, need to restart. + } else { + curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event. + if (transfers > 0) { //have data to deal with + curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response + if (amcrest_response.find("action=Start") != std::string::npos) { + //Event Start + Debug(1,"Triggered on ONVIF"); + if (!parent->Poll_Trigger_State) { + Debug(1,"Triggered Event"); + parent->Poll_Trigger_State = TRUE; + } + } else if (amcrest_response.find("action=Stop") != std::string::npos){ + Debug(1, "Triggered off ONVIF"); + parent->Poll_Trigger_State = FALSE; + if (!parent->Event_Poller_Closes_Event) { //If we get a close event, then we know to expect them. + parent->Event_Poller_Closes_Event = TRUE; + Debug(1,"Setting ClosesEvent"); + } + } + amcrest_response.clear(); //We've dealt with the message, need to clear the queue + } + } + return; +} + +size_t Monitor::AmcrestAPI::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} diff --git a/src/zm_monitor_janus.cpp b/src/zm_monitor_janus.cpp new file mode 100644 index 000000000..2fe9b3f26 --- /dev/null +++ b/src/zm_monitor_janus.cpp @@ -0,0 +1,316 @@ +// +// ZoneMinder Monitor::JanusManager Class Implementation, $Date$, $Revision$ +// Copyright (C) 2022 Jonathan Bennett +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#include "zm_monitor.h" + + +Monitor::JanusManager::JanusManager(Monitor *parent_) { //constructor takes care of init and calls add_to + std::string response; + std::size_t pos; + parent = parent_; + if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) { + janus_endpoint = config.janus_path; //TODO: strip trailing / + } else { + janus_endpoint = "127.0.0.1:8088/janus"; + } + if (janus_endpoint.back() == '/') janus_endpoint.pop_back(); //remove the trailing slash if present + std::size_t pos2 = parent->path.find("@", pos); + if (pos2 != std::string::npos) { //If we find an @ symbol, we have a username/password. Otherwise, passwordless login. + + std::size_t pos = parent->path.find(":", 7); //Search for the colon, but only after the RTSP:// text. + if (pos == std::string::npos) throw std::runtime_error("Cannot Parse URL for Janus."); //Looks like an invalid url + rtsp_username = parent->path.substr(7, pos-7); + + rtsp_password = parent->path.substr(pos+1, pos2 - pos - 1); + rtsp_path = "RTSP://"; + rtsp_path += parent->path.substr(pos2 + 1); + + } else { + rtsp_username = ""; + rtsp_password = ""; + rtsp_path = parent->path; + } +} + +Monitor::JanusManager::~JanusManager() { + if (janus_session.empty()) get_janus_session(); + if (janus_handle.empty()) get_janus_handle(); + + std::string response; + std::string endpoint; + + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + //std::size_t pos; + CURLcode res; + + curl = curl_easy_init(); + if(!curl) return; + + endpoint = janus_endpoint; + endpoint += "/"; + endpoint += janus_session; + endpoint += "/"; + endpoint += janus_handle; + + //Assemble our actual request + postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"destroy\", \"admin_key\" : \""; + postData += config.janus_secret; + postData += "\", \"id\" : "; + postData += std::to_string(parent->id); + postData += "}}"; + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return; + } + + Debug(1, "Removed stream from Janus: %s", response.c_str()); + curl_easy_cleanup(curl); + return; +} + + + +int Monitor::JanusManager::check_janus() { + if (janus_session.empty()) get_janus_session(); + if (janus_handle.empty()) get_janus_handle(); + + std::string response; + std::string endpoint = janus_endpoint; + std::string postData; + //std::size_t pos; + CURLcode res; + + curl = curl_easy_init(); + if(!curl) return -1; + + endpoint += "/"; + endpoint += janus_session; + endpoint += "/"; + endpoint += janus_handle; + + //Assemble our actual request + postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"info\", \"id\" : "; + postData += std::to_string(parent->id); + postData += "}}"; + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session + Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + janus_session = ""; + janus_handle = ""; + return -1; + } + + curl_easy_cleanup(curl); + Debug(1, "Queried for stream status: %s", response.c_str()); + if (response.find("\"janus\": \"error\"") != std::string::npos) { + if (response.find("No such session") != std::string::npos) { + Warning("Janus Session timed out"); + janus_session = ""; + return -2; + } else if (response.find("No such handle") != std::string::npos) { + Warning("Janus Handle timed out"); + janus_handle = ""; + return -2; + } + } else if (response.find("No such mountpoint") != std::string::npos) { + Warning("Mountpoint Missing"); + return 0; + } + return 1; +} + +int Monitor::JanusManager::add_to_janus() { + if (janus_session.empty()) get_janus_session(); + if (janus_handle.empty()) get_janus_handle(); + + std::string response; + std::string endpoint = janus_endpoint; + + CURLcode res; + + curl = curl_easy_init(); + if (!curl) { + Error("Failed to init curl"); + return -1; + } + + endpoint += "/"; + endpoint += janus_session; + endpoint += "/"; + endpoint += janus_handle; + + //Assemble our actual request + std::string postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"create\", \"admin_key\" : \""; + postData += config.janus_secret; + postData += "\", \"type\" : \"rtsp\", "; + postData += "\"url\" : \""; + postData += rtsp_path; + if (rtsp_username != "") { + postData += "\", \"rtsp_user\" : \""; + postData += rtsp_username; + postData += "\", \"rtsp_pwd\" : \""; + postData += rtsp_password; + } + postData += "\", \"id\" : "; + postData += std::to_string(parent->id); + if (parent->janus_audio_enabled) postData += ", \"audio\" : true"; + postData += ", \"video\" : true}}"; + Warning("Sending %s to %s", postData.c_str(), endpoint.c_str()); + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + Error("Failed to curl_easy_perform adding rtsp stream"); + curl_easy_cleanup(curl); + return -1; + } + if (response.find("\"janus\": \"error\"") != std::string::npos) { + if (response.find("No such session") != std::string::npos) { + Warning("Janus Session timed out"); + janus_session = ""; + return -2; + } else if (response.find("No such handle") != std::string::npos) { + Warning("Janus Handle timed out"); + janus_handle = ""; + return -2; + } + } + //scan for missing session or handle id "No such session" "no such handle" + + Debug(1,"Added stream to Janus: %s", response.c_str()); + curl_easy_cleanup(curl); + return 0; +} + + +size_t Monitor::JanusManager::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +/* +void Monitor::JanusManager::generateKey() +{ + const std::string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + std::random_device random_device; + std::mt19937 generator(random_device()); + std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1); + + std::string random_string; + + for (std::size_t i = 0; i < 16; ++i) + { + random_string += CHARACTERS[distribution(generator)]; + } + + stream_key = random_string; +} +*/ + + +int Monitor::JanusManager::get_janus_session() { + janus_session = ""; + std::string endpoint = janus_endpoint; + + std::string response; + + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + std::size_t pos; + CURLcode res; + curl = curl_easy_init(); + if(!curl) return -1; + + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } + + pos = response.find("\"id\": "); + if (pos == std::string::npos) + { + curl_easy_cleanup(curl); + return -1; + } + janus_session = response.substr(pos + 6, 16); + curl_easy_cleanup(curl); + return 1; + +} //get_janus_session + +int Monitor::JanusManager::get_janus_handle() { + std::string response = ""; + std::string endpoint = janus_endpoint; + std::size_t pos; + + CURLcode res; + curl = curl_easy_init(); + if(!curl) return -1; + + endpoint += "/"; + endpoint += janus_session; + std::string postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res)); + curl_easy_cleanup(curl); + return -1; + } + + pos = response.find("\"id\": "); + if (pos == std::string::npos) + { + curl_easy_cleanup(curl); + return -1; + } + janus_handle = response.substr(pos + 6, 16); + curl_easy_cleanup(curl); + return 1; +} //get_janus_handle From b3092f2f591f97e453d7b7acf1e5843c5a1bb3c8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 17:31:37 -0500 Subject: [PATCH 411/501] Add special handling for skip_locked, as it is a checkbox. Don't update REQUEST['Id'] on execute so we can redirect to the original filter. --- web/includes/actions/filter.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/web/includes/actions/filter.php b/web/includes/actions/filter.php index 058a004ed..4f627b0b0 100644 --- a/web/includes/actions/filter.php +++ b/web/includes/actions/filter.php @@ -48,6 +48,7 @@ if (isset($_REQUEST['object']) and ($_REQUEST['object'] == 'filter')) { $_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']); $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); + $_REQUEST['filter']['Query']['skip_locked'] = isset($_REQUEST['filter']['Query']['skip_locked']) ? validInt($_REQUEST['filter']['Query']['skip_locked']) : 0; $_REQUEST['filter']['AutoCopy'] = empty($_REQUEST['filter']['AutoCopy']) ? 0 : 1; $_REQUEST['filter']['AutoCopyTo'] = empty($_REQUEST['filter']['AutoCopyTo']) ? 0 : $_REQUEST['filter']['AutoCopyTo']; @@ -80,21 +81,23 @@ if (isset($_REQUEST['object']) and ($_REQUEST['object'] == 'filter')) { $error_message = $filter->get_last_error(); return; } - // We update the request id so that the newly saved filter is auto-selected - $_REQUEST['Id'] = $filter->Id(); + if ($action == 'Save' or $action == 'SaveAs' ) { + // We update the request id so that the newly saved filter is auto-selected + $_REQUEST['Id'] = $filter->Id(); + } } # end if changes if ($action == 'execute') { $filter->execute(); if (count($changes)) { $filter->delete(); - $filter->Id(null); + $filter->Id($_REQUEST['Id']); } } else if ($filter->Background()) { $filter->control('start'); } global $redirect; - $redirect = '?view=filter'.$filter->querystring('filter', '&'); + $redirect = '?view=filter&Id='.$_REQUEST['Id'].$filter->querystring('filter', '&'); } else if ($action == 'control') { if ( $_REQUEST['command'] == 'start' From ac909d404aac7716b55c9c593fabe5b3d2d32142 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 3 Feb 2022 18:19:01 -0500 Subject: [PATCH 412/501] Use the reported move with x=0 y=0 for autostop in addition to old stop movement code --- .../ZoneMinder/lib/ZoneMinder/Control/Netcat.pm | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm index f10b7aabb..8043eedad 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm @@ -194,7 +194,6 @@ sub getCamParams { } } -#autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; @@ -202,13 +201,19 @@ sub autoStop { if ( $autostop ) { Debug('Auto Stop'); - my $cmd = 'onvif/PTZ'; - my $msg = '' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . 'truefalse'; - my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; usleep($autostop); + + my $cmd = 'onvif/PTZ'; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + + my $msg =''.((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '').'' . $profileToken . ''; + $self->sendCmd($cmd, $msg, $content_type); + + # Reported to not work, so superceded by the cmd above + $msg = '' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . 'truefalse'; $self->sendCmd($cmd, $msg, $content_type); } -} +} # end sub autoStop # Reset the Camera sub reset { From 02d896abe2e5681ad2a1bc8f9cffaf2b4f78203c Mon Sep 17 00:00:00 2001 From: r01k Date: Fri, 4 Feb 2022 22:19:15 -0500 Subject: [PATCH 413/501] Fixed in export.php "Class 'Filter' not found" and construction of SQL query with wrong syntax --- web/skins/classic/views/export.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/export.php b/web/skins/classic/views/export.php index 306a61083..ab444e551 100644 --- a/web/skins/classic/views/export.php +++ b/web/skins/classic/views/export.php @@ -86,7 +86,7 @@ $limitQuery = ''; if ( $user['MonitorIds'] ) { $user_monitor_ids = ' M.Id in ('.$user['MonitorIds'].')'; $eventsSql .= $user_monitor_ids; -} else { +} else if ( !isset($_REQUEST['filter']) ) { $eventsSql .= ' 1'; } @@ -98,7 +98,7 @@ if ( isset($_REQUEST['eid']) and $_REQUEST['eid'] ) { $eventsValues += $_REQUEST['eids']; } else if ( isset($_REQUEST['filter']) ) { parseSort(); - $filter = Filter::parse($_REQUEST['filter']); + $filter = ZM\Filter::parse($_REQUEST['filter']); $filterQuery = $filter->querystring(); if ( $filter->sql() ) { From 69053424cdfa00af7e6ab9fc036149a7cf0487ba Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 6 Feb 2022 19:06:35 -0500 Subject: [PATCH 414/501] When adding a new monitor, ModelId and ManufacturerId are not defined, so handle that --- web/includes/Monitor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 9c7d8d25c..e3599bbf4 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -698,7 +698,7 @@ class Monitor extends ZM_Object { } function Model() { if (!property_exists($this, 'Model')) { - if ($this->{'ModelId'}) { + if (property_exists($this, 'ModelId') and $this->{'ModelId'}) { $this->{'Model'} = Model::find_one(array('Id'=>$this->ModelId())); if (!$this->{'Model'}) $this->{'Model'} = new Model(); @@ -710,7 +710,7 @@ class Monitor extends ZM_Object { } function Manufacturer() { if (!property_exists($this, 'Manufacturer')) { - if ($this->{'ManufacturerId'}) { + if (property_exists($this, 'ManufacturerId') and $this->{'ManufacturerId'}) { $this->{'Manufacturer'} = Manufacturer::find_one(array('Id'=>$this->ManufacturerId())); if (!$this->{'Manufacturer'}) $this->{'Manufacturer'} = new Manufacturer(); From 5078eecdfd867be58c8e9f38c3277bcadbd458e0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 7 Feb 2022 12:31:31 -0500 Subject: [PATCH 415/501] Add in get_networks and get_subnets as utilities to parse devices and networks in preparation for scanning/probing --- web/includes/functions.php | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/web/includes/functions.php b/web/includes/functions.php index e346ceb34..bfa4b9746 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -2414,4 +2414,70 @@ function i18n() { return implode('-', $string); } + +function get_networks() { + $interfaces = array(); + + exec('ip link', $output, $status); + if ( $status ) { + $html_output = implode('
', $output); + ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); + } else { + foreach ( $output as $line ) { + if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) { + if ( $matches[1] != 'lo' ) { + $interfaces[$matches[1]] = $matches[1]; + } else { + ZM\Debug("No match for $line"); + } + } + } + } + $routes = array(); + exec('ip route', $output, $status); + if ( $status ) { + $html_output = implode('
', $output); + ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); + } else { + foreach ( $output as $line ) { + if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) { + $interfaces['default'] = $matches[1]; + } else if ( preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) { + $interfaces[$matches[2]] .= ' ' . $matches[1]; + ZM\Debug("Matched $line: $matches[2] .= $matches[1]"); + } else { + ZM\Debug("Didn't match $line"); + } + } # end foreach line of output + } + return $interfaces; +} + +# Returns an array of subnets like 192.168.1.0/24 for a given interface. +# Will ignore mdns networks. + +function get_subnets($interface) { + $subnets = array(); + exec('ip route', $output, $status); + if ( $status ) { + $html_output = implode('
', $output); + ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); + } else { + foreach ($output as $line) { + if (preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches)) { + if ($matches[1] == '169.254.0.0/16') { + # Ignore mdns + } else if ($matches[2] == $interface) { + $subnets[] = $matches[1]; + } else { + ZM\Debug("Wrong interface $matches[1] != $interface"); + } + } else { + ZM\Debug("Didn't match $line"); + } + } # end foreach line of output + } + return $subnets; +} + ?> From d056f15e54590496c779ec56b49dfd12b62f68d5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 7 Feb 2022 12:32:16 -0500 Subject: [PATCH 416/501] Add arp-scan to executable for use in network probing for cameras. --- CMakeLists.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a65f85eb..6337e50c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ mark_as_advanced( ZM_TARGET_DISTRO ZM_PATH_MAP ZM_PATH_ARP + ZM_PATH_ARP_SCAN ZM_CONFIG_DIR ZM_CONFIG_SUBDIR ZM_SYSTEMD @@ -145,6 +146,8 @@ set(ZM_PATH_MAP "/dev/shm" CACHE PATH "Location to save mapped memory files, default: /dev/shm") set(ZM_PATH_ARP "" CACHE PATH "Full path to compatible arp binary. Leave empty for automatic detection.") +set(ZM_PATH_ARP_SCAN "" CACHE PATH + "Full path to compatible scan_arp binary. Leave empty for automatic detection.") set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH "Location of ZoneMinder configuration, default system config directory") set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH @@ -641,6 +644,18 @@ if(ZM_PATH_ARP STREQUAL "") endif() endif() +# Find the path to an arp-scan compatible executable +if(ZM_PATH_ARP_SCAN STREQUAL "") + find_program(ARP_SCAN_EXECUTABLE arp-scan) + if(ARP_SCAN_EXECUTABLE) + set(ZM_PATH_ARP_SCAN "${ARP_SCAN_EXECUTABLE}") + mark_as_advanced(ARP_SCAN_EXECUTABLE) + endif() + if(ARP_SCAN_EXECUTABLE-NOTFOUND) + message(WARNING "Unable to find a compatible arp-scan binary. Monitor probe will be less powerful.") + endif() +endif() + # Some variables that zm expects set(ZM_PID "${ZM_RUNDIR}/zm.pid") set(ZM_CONFIG "${ZM_CONFIG_DIR}/zm.conf") From 2b468fa11591851352dfe18a67b199bddad8dd4e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 7 Feb 2022 12:32:50 -0500 Subject: [PATCH 417/501] Add policykit rules for arp-scan --- misc/CMakeLists.txt | 4 ++++ misc/com.zoneminder.arp-scan.policy.in | 21 +++++++++++++++++++++ misc/com.zoneminder.arp-scan.rules.in | 7 +++++++ 3 files changed, 32 insertions(+) create mode 100644 misc/com.zoneminder.arp-scan.policy.in create mode 100644 misc/com.zoneminder.arp-scan.rules.in diff --git a/misc/CMakeLists.txt b/misc/CMakeLists.txt index e51636771..c705b63bb 100644 --- a/misc/CMakeLists.txt +++ b/misc/CMakeLists.txt @@ -7,6 +7,8 @@ configure_file(logrotate.conf.in "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" @O configure_file(syslog.conf.in "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" @ONLY) configure_file(com.zoneminder.systemctl.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" @ONLY) configure_file(com.zoneminder.systemctl.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" @ONLY) +configure_file(com.zoneminder.arp-scan.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" @ONLY) +configure_file(com.zoneminder.arp-scan.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" @ONLY) configure_file(zoneminder.service.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.service" @ONLY) configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder-tmpfiles.conf" @ONLY) configure_file(zoneminder.desktop.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" @ONLY) @@ -19,6 +21,8 @@ configure_file(zm-sudo.in "${CMAKE_CURRENT_BINARY_DIR}/zm-sudo" @ONLY) if(WITH_SYSTEMD) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") endif() install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" DESTINATION "${CMAKE_INSTALL_DATADIR}/applications") diff --git a/misc/com.zoneminder.arp-scan.policy.in b/misc/com.zoneminder.arp-scan.policy.in new file mode 100644 index 000000000..7b5b6043e --- /dev/null +++ b/misc/com.zoneminder.arp-scan.policy.in @@ -0,0 +1,21 @@ + + + + + The ZoneMinder Project + http://www.zoneminder.com/ + + + Allow the ZoneMinder webuser to run arp-scan + The ZoneMinder webuser is trusted to run arp-scan + + yes + yes + yes + + /usr/sbin/arp-scan + + + diff --git a/misc/com.zoneminder.arp-scan.rules.in b/misc/com.zoneminder.arp-scan.rules.in new file mode 100644 index 000000000..74ac25af0 --- /dev/null +++ b/misc/com.zoneminder.arp-scan.rules.in @@ -0,0 +1,7 @@ +polkit.addRule(function(action, subject) { + if (action.id == "com.zoneminder.policykit.pkexec.run-arp-scan" && + subject.user != "@WEB_USER@") { + return polkit.Result.NO; + } + +}); From 2c23e18ea0071268b376b128c4fdd5a8625bbba1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 7 Feb 2022 12:33:05 -0500 Subject: [PATCH 418/501] Add system path for arp-scan --- conf.d/01-system-paths.conf.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conf.d/01-system-paths.conf.in b/conf.d/01-system-paths.conf.in index 277f63b70..523a8c0c8 100644 --- a/conf.d/01-system-paths.conf.in +++ b/conf.d/01-system-paths.conf.in @@ -47,5 +47,9 @@ ZM_PATH_SWAP=@ZM_TMPDIR@ # ZoneMinder will find the arp binary automatically on most systems ZM_PATH_ARP="@ZM_PATH_ARP@" +# Full path to optional arp-scan binary +# ZoneMinder will find the arp-scan binary automatically on most systems +ZM_PATH_ARP_SCAN="@ZM_PATH_ARP_SCAN@" + #Full path to shutdown binary ZM_PATH_SHUTDOWN="@ZM_PATH_SHUTDOWN@" From 33473eac6aa4797d50d456292c6699f80b356caf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 7 Feb 2022 12:33:53 -0500 Subject: [PATCH 419/501] Add arp-scan as a tool for getting list of devices on network. Add an interface specifier to monitor probe just like on onvif-probe. Rough in support for Hikvision cameras --- web/skins/classic/views/js/monitorprobe.js | 4 + web/skins/classic/views/monitorprobe.php | 128 ++++++++++++++++++--- web/skins/classic/views/onvifprobe.php | 37 +----- 3 files changed, 121 insertions(+), 48 deletions(-) diff --git a/web/skins/classic/views/js/monitorprobe.js b/web/skins/classic/views/js/monitorprobe.js index 96f0c701e..1ecd93d23 100644 --- a/web/skins/classic/views/js/monitorprobe.js +++ b/web/skins/classic/views/js/monitorprobe.js @@ -11,3 +11,7 @@ function configureButtons( element ) { var form = element.form; form.saveBtn.disabled = (form.probe.selectedIndex==0); } + +function changeInterface(element) { + element.form.submit(); +} diff --git a/web/skins/classic/views/monitorprobe.php b/web/skins/classic/views/monitorprobe.php index 7bb15a68d..f9d47fcad 100644 --- a/web/skins/classic/views/monitorprobe.php +++ b/web/skins/classic/views/monitorprobe.php @@ -194,6 +194,21 @@ function probeActi($ip) { return $camera; } +function probeHikvision($ip) { + $url = 'rtsp://admin:password@'.$ip.':554/Streaming/Channels/101?transportmode=unicast'; + $camera = array( + 'model' => 'Unknown Hikvision Camera', + 'monitor' => array( + 'Type' => 'FFmpeg', + 'Path' => $url, + 'Colours' => 4, + 'Width' => 1920, + 'Height' => 1080, + ), + ); + return $camera; +} + function probeVivotek($ip) { $url = 'http://'.$ip.'/cgi-bin/viewer/getparam.cgi'; $camera = array( @@ -244,20 +259,60 @@ function probeWansview($ip) { return $camera; } -function probeNetwork() { - $cameras = array(); +function get_arp_results() { + $results = array(); $arp_command = ZM_PATH_ARP; $result = explode(' ', $arp_command); if ( !is_executable($result[0]) ) { ZM\Error('ARP compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP points to a valid arp tool.'); - return $cameras; + return $results; + } + if (count($result)==1) { + $arp_command .= ' -n'; } $result = exec(escapeshellcmd($arp_command), $output, $status); - if ( $status ) { + if ($status) { ZM\Error("Unable to probe network cameras, status is '$status'"); - return $cameras; + return $results; } + foreach ($output as $line) { + if ( !preg_match('/(\d+\.\d+\.\d+\.\d+).*(([0-9a-f]{2}:){5})/', $line, $matches) ) { + ZM\Debug("Didn't match preg $line"); + continue; + } + $results[$matches[2]] = $matches[1]; // results[mac] = ip + } + return $results; +} + +function get_arp_scan_results($network) { + ZM\Debug("arp-scanning $network"); + $results = array(); + $arp_scan_command = ZM_PATH_ARP_SCAN; + $result = explode(' ', $arp_scan_command); + if (!is_executable($result[0])) { + ZM\Error('arp-scan compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP_SCAN points to a valid arp-scan tool.'); + return $results; + } + $arp_scan_command = '/usr/bin/pkexec '.ZM_PATH_ARP_SCAN.' '.$network.' 2>&1'; + $result = exec(escapeshellcmd($arp_scan_command), $output, $status); + if ($status) { + ZM\Error("Unable to probe network cameras, command was $arp_scan_command, status is '$status' output: ".implode(PHP_EOL, $output)); + return $results; + } + foreach ($output as $line) { + if (preg_match('/(\d+\.\d+\.\d+\.\d+)\s+(([0-9a-f]{2}:){5})/', $line, $matches)) { + $results[$matches[2]] = $matches[1]; + } else { + ZM\Debug("Didn't match preg $line"); + } + } + return $results; +} + +function probeNetwork() { + $cameras = array(); $monitors = array(); foreach ( dbFetchAll("SELECT `Id`, `Name`, `Host` FROM `Monitors` WHERE `Type` = 'Remote' ORDER BY `Host`") as $monitor ) { @@ -269,25 +324,26 @@ function probeNetwork() { $monitors[gethostbyname($monitor['Host'])] = $monitor; } } + foreach ( dbFetchAll("SELECT `Id`, `Name`, `Path` FROM `Monitors` WHERE `Type` = 'Ffmpeg' ORDER BY `Path`") as $monitor ) { + $url_parts = parse_url($monitor['Path']); + ZM\Debug("Ffmpeg monitor ${url_parts['host']} = ${monitor['Id']} ${monitor['Name']}"); + $monitors[gethostbyname($url_parts['host'])] = $monitor; + } $macBases = array( - '00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'), - '00:80:f0' => array('type'=>'Panasonic','probeFunc'=>'probePana'), '00:0f:7c' => array('type'=>'ACTi','probeFunc'=>'probeACTi'), + '00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'), + '2c:a5:9c' => array('type'=>'Hikvision', 'probeFunc'=>'probeHikvision'), + '00:80:f0' => array('type'=>'Panasonic','probeFunc'=>'probePana'), '00:02:d1' => array('type'=>'Vivotek','probeFunc'=>'probeVivotek'), '7c:dd:90' => array('type'=>'Wansview','probeFunc'=>'probeWansview'), '78:a5:dd' => array('type'=>'Wansview','probeFunc'=>'probeWansview') ); - foreach ( $output as $line ) { - if ( !preg_match('/(\d+\.\d+\.\d+\.\d+).*(([0-9a-f]{2}:){5})/', $line, $matches) ) - continue; - $ip = $matches[1]; - $host = $ip; - $mac = $matches[2]; - //echo "I:$ip, H:$host, M:$mac
"; + foreach ( get_arp_results() as $mac=>$ip ) { $macRoot = substr($mac,0,8); if ( isset($macBases[$macRoot]) ) { + ZM\Debug("Have match for $macRoot ".$macBases[$macRoot]['type']); $macBase = $macBases[$macRoot]; $camera = call_user_func($macBase['probeFunc'], $ip); $sourceDesc = base64_encode(json_encode($camera['monitor'])); @@ -299,8 +355,36 @@ function probeNetwork() { $sourceString .= ' - '.translate('Available'); } $cameras[$sourceDesc] = $sourceString; + } else { + ZM\Debug("No match for $macRoot"); } } # end foreach output line + + if (isset($_REQUEST['interface']) and $_REQUEST['interface']) { + foreach (get_subnets($_REQUEST['interface']) as $network) { + foreach ( get_arp_scan_results($network) as $mac=>$ip ) { + $macRoot = substr($mac,0,8); + ZM\Debug("Got $macRoot from $mac"); + if (isset($macBases[$macRoot])) { + ZM\Debug("Have match for $macRoot $ip ".$macBases[$macRoot]['type']); + $macBase = $macBases[$macRoot]; + $camera = call_user_func($macBase['probeFunc'], $ip); + $sourceDesc = base64_encode(json_encode($camera['monitor'])); + $sourceString = $camera['model'].' @ '.$host; + if (isset($monitors[$ip])) { + $monitor = $monitors[$ip]; + $sourceString .= ' ('.$monitor['Name'].')'; + } else { + $sourceString .= ' - '.translate('Available'); + } + $cameras[$sourceDesc] = $sourceString; + } else { + ZM\Debug("No match for $macRoot"); + } + } # end foreach output line + } # end foreach network + } # end if we have a network specified + return $cameras; } # end function probeNetwork() @@ -324,11 +408,25 @@ xhtmlHeaders(__FILE__, translate('MonitorProbe') );

- + +

+

+'changeInterface') ); + +?> +

'configureButtons')); ?> diff --git a/web/skins/classic/views/onvifprobe.php b/web/skins/classic/views/onvifprobe.php index 754bb50ef..bcc349192 100644 --- a/web/skins/classic/views/onvifprobe.php +++ b/web/skins/classic/views/onvifprobe.php @@ -173,39 +173,10 @@ if ( !isset($_REQUEST['step']) || ($_REQUEST['step'] == '1') ) {

- ', $output); - ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); - } else { - foreach ( $output as $line ) { - if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) { - if ( $matches[1] != 'lo' ) { - $interfaces[$matches[1]] = $matches[1]; - } else { - ZM\Debug("No match for $line"); - } - } - } - } - $routes = array(); - exec('ip route', $output, $status); - if ( $status ) { - $html_output = implode('
', $output); - ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); - } else { - foreach ( $output as $line ) { - if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) { - $default_interface = $matches[1]; - } else if ( preg_match('/^([.\/[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) { - $interfaces[$matches[2]] .= ' ' . $matches[1]; - } - } # end foreach line of output - } + Date: Tue, 8 Feb 2022 09:57:56 -0500 Subject: [PATCH 420/501] Don't need to test for end() because we added our packet to the queue and still have the lock --- src/zm_packetqueue.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 8b831ec2d..e7e58b6e8 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -117,7 +117,8 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { for ( auto it = ++pktQueue.begin(); - it != pktQueue.end() and *it != add_packet; + //it != pktQueue.end() and // can't git end because we added our packet + *it != add_packet; // iterator is incremented by erase ) { std::shared_ptr zm_packet = *it; From 2f5a403fc4a54ecefbb7f832bef7900a08c24baf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 10:04:19 -0500 Subject: [PATCH 421/501] Handle when no swap is configured --- web/skins/classic/includes/functions.php | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index beb6590e0..6055a5805 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -472,19 +472,21 @@ function getRamHTML() { } else if ($mem_used_percent > 90) { $used_class = 'text-warning'; } - $swap_used = $meminfo['SwapTotal'] - $meminfo['SwapFree']; - $swap_used_percent = (int)(100*$swap_used/$meminfo['SwapTotal']); - $swap_class = ''; - if ($swap_used_percent > 95) { - $swap_class = 'text-danger'; - } else if ($swap_used_percent > 90) { - $swap_class = 'text-warning'; - } - $result .= '

'.PHP_EOL; + ''.translate('Memory').': '.$mem_used_percent.'% '; + + if ($meminfo['SwapTotal']) { + $swap_used = $meminfo['SwapTotal'] - $meminfo['SwapFree']; + $swap_used_percent = (int)(100*$swap_used/$meminfo['SwapTotal']); + $swap_class = ''; + if ($swap_used_percent > 95) { + $swap_class = 'text-danger'; + } else if ($swap_used_percent > 90) { + $swap_class = 'text-warning'; + } + $result .= ''.translate('Swap').': '.$swap_used_percent.'% '; + } # end if SwapTotal + $result .= ''.PHP_EOL; return $result; } From 8e689340ce83fdf1b98ba7b1c08fc0e1137724bb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 10:06:32 -0500 Subject: [PATCH 422/501] Don't need to check for end of queue as we already did that when adding packet to the queue --- src/zm_packetqueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index e7e58b6e8..7daf0503d 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -138,7 +138,7 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { ) { auto iterator_it = *iterators_it; // Have to check each iterator and make sure it doesn't point to the packet we are about to delete - if ((*iterator_it!=pktQueue.end()) and (*(*iterator_it) == zm_packet)) { + if (*(*iterator_it) == zm_packet) { Debug(1, "Bumping IT because it is at the front that we are deleting"); ++(*iterator_it); } From 8c13aa7d3ad1ed6ed504e4f3838b51d8c6d78df9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 10:10:00 -0500 Subject: [PATCH 423/501] Cleanup LockedPacket, use RAII --- src/zm_packetqueue.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 7daf0503d..d5407b05d 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -123,10 +123,9 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { ) { std::shared_ptr zm_packet = *it; - ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); - if (!lp->trylock()) { + ZMLockedPacket lp(zm_packet); + if (!lp.trylock()) { Warning("Found locked packet when trying to free up video packets. This basically means that decoding is not keeping up."); - delete lp; ++it; continue; } @@ -155,8 +154,6 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { max_video_packet_count, pktQueue.size()); - delete lp; - if (zm_packet->packet.stream_index == video_stream_id) break; } // end while From 2768975f960f338ab405455bdccaf1f6114f4965 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 10:12:29 -0500 Subject: [PATCH 424/501] Only notify one. Anyone waiting is waiting on a lock and only 1 process can get that lock, so only one should try. --- src/zm_packetqueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index d5407b05d..26f19de75 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -161,7 +161,7 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { } // end lock scope // We signal on every packet because someday we may analyze sound Debug(4, "packetqueue queuepacket, unlocked signalling"); - condition.notify_all(); + condition.notify_one(); return true; } // end bool PacketQueue::queuePacket(ZMPacket* zm_packet) From a7dc9d4e36521678d438d7593af1ebce3d6c22d0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 10:14:00 -0500 Subject: [PATCH 425/501] Implement General::jsonLoad --- scripts/ZoneMinder/lib/ZoneMinder/General.pm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index b14b08aae..90e7399ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -28,6 +28,7 @@ our %EXPORT_TAGS = ( makePath jsonEncode jsonDecode + jsonLoad systemStatus packageControl daemonControl @@ -536,6 +537,23 @@ sub jsonDecode { return $result; } +sub jsonLoad { + my $file = shift; + my $json = undef; + eval { + require File::Slurp; + my $contents = File::Slurp::read_file($file); + if (!$contents) { + Error("No contents for $file"); + return $json; + } + require JSON; + $json = JSON::decode_json($contents); + }; + Error($@) if $@; + return $json; +} + sub parseNameEqualsValueToHash { my %settings; foreach my $line ( split ( /\r?\n/, $_[0] ) ) { From 03f66370608ec8dc131ed34bac24980e22c3b93c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 11:03:25 -0500 Subject: [PATCH 426/501] Only load janus.js if we are using it --- web/skins/classic/views/watch.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index 340ee8e33..fdcdae06d 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -397,6 +397,12 @@ if ( ZM_WEB_SOUND_ON_ALARM ) {
+JanusEnabled() ) { +?> + From 8cdd96f286c589e808f45f09f716c578c3ba2299 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 13:29:51 -0500 Subject: [PATCH 427/501] Implement a check on change of language. Make sure that the specified language file exists. Reports errors to UI --- web/includes/actions/options.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/web/includes/actions/options.php b/web/includes/actions/options.php index 07b71c001..9b0033f4e 100644 --- a/web/includes/actions/options.php +++ b/web/includes/actions/options.php @@ -24,6 +24,8 @@ if ( !canEdit('System') ) { return; } +global $error_message; + if ( $action == 'delete' ) { if ( isset($_REQUEST['object']) ) { if ( $_REQUEST['object'] == 'server' ) { @@ -65,10 +67,19 @@ if ( $action == 'delete' ) { } if ( isset($newValue) && ($newValue != $config['Value']) ) { + # Handle special cases first + if ($config['Name'] == 'ZM_LANG_DEFAULT') { + # Verify that the language file exists in the lang directory. + if (!file_exists(ZM_PATH_WEB.'/lang/'.$newValue.'.php')) { + $error_message .= 'Error setting ' . $config['Name'].'. New value ' .$newValue.' not saved because '.ZM_PATH_WEB.'/lang/'.$newValue.'.php doesn\'t exist.
'; + ZM\Error($error_message); + continue; + } + } dbQuery('UPDATE Config SET Value=? WHERE Name=?', array($newValue, $config['Name'])); $changed = true; - } - } + } # end if value changed + } # end foreach config entry if ( $changed ) { switch ( $_REQUEST['tab'] ) { case 'system' : From cc9306afe36228a2777980f3494191a51c0fc45e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 13:32:37 -0500 Subject: [PATCH 428/501] Change the error message banner to always take up space and be seen. --- web/skins/classic/css/base/skin.css | 8 ++++++++ web/skins/classic/includes/functions.php | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/css/base/skin.css b/web/skins/classic/css/base/skin.css index ff37e5dd8..07e79bf20 100644 --- a/web/skins/classic/css/base/skin.css +++ b/web/skins/classic/css/base/skin.css @@ -340,6 +340,14 @@ ul.tabList li.active a { .alarm, .errorText, .error { color: #ff3f34; } +/* Refers to the error box at the top of the web UI */ +#error { + width: 100%; + padding: 5px; + font-weight: bold; + background-color: white; + color: #ff3f34; +} .timedErrorBox { color:white; diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 6055a5805..0f0a9352b 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -169,7 +169,7 @@ function getBodyTopHTML() { '; global $error_message; if ( $error_message ) { - echo '
'.$error_message.'
'; + echo '
'.$error_message.'
'; } } // end function getBodyTopHTML @@ -203,7 +203,7 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin) $status = runtimeStatus($running); ?> -
+
- +JanusEnabled() ) { +?> + + diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index a96e8a88e..b880d42e6 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -117,6 +117,7 @@ include('_monitor_filters.php'); $filterbar = ob_get_contents(); ob_end_clean(); +$need_janus = false; $monitors = array(); foreach ( $displayMonitors as &$row ) { if ( $row['Function'] == 'None' ) @@ -133,7 +134,10 @@ foreach ( $displayMonitors as &$row ) { if ( ! isset($heights[$row['Height']]) ) { $heights[$row['Height']] = $row['Height'].'px'; } - $monitors[] = new ZM\Monitor($row); + $monitor = $monitors[] = new ZM\Monitor($row); + if ($monitor->JanusEnabled()) { + $need_janus = true; + } } # end foreach Monitor xhtmlHeaders(__FILE__, translate('Montage')); @@ -319,6 +323,8 @@ foreach (array_reverse($zones) as $zone) {
+ + From b1d235f9910900aaffa8371f7f44c2fc6d8c252f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 8 Feb 2022 13:33:22 -0500 Subject: [PATCH 430/501] Use getBodyTopHTML so that we get error messages in UI --- web/skins/classic/views/options.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php index 25cad40cc..5f648ba27 100644 --- a/web/skins/classic/views/options.php +++ b/web/skins/classic/views/options.php @@ -54,9 +54,9 @@ else $focusWindow = true; xhtmlHeaders(__FILE__, translate('Options')); +getBodyTopHTML(); +echo getNavBarHTML(); ?> - -
+
+

+

+

+

+
-
-
- <?php echo translate('ViewEvent') ?> -
-
-
-
-
-

-

-

-

-
-
+ +
+
+
+ <?php echo translate('ViewEvent') ?>
+
+
+
+
+
+
+
+ -
From 6e7e6d9e8ec28b4ba8acd6ae54790588ed88f802 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 11 Feb 2022 12:43:10 -0500 Subject: [PATCH 440/501] small code cleanup, add Warnings when using slow blend functions. --- src/zm_image.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 7260791a5..ea785195e 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -95,6 +95,7 @@ void Image::update_function_pointers() { delta8_abgr = &std_delta8_abgr; delta8_gray8 = &std_delta8_gray8; blend = &std_blend; + Warning("Using slow std functions"); } else { // Use either sse or neon, or loop unrolled version delta8_rgb = fptr_delta8_rgb; @@ -1737,7 +1738,6 @@ void Image::Overlay( const Image &image, const unsigned int lo_x, const unsigned } // end void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) void Image::Blend( const Image &image, int transparency ) { - uint8_t* new_buffer; if ( !( width == image.width && height == image.height @@ -1751,7 +1751,7 @@ void Image::Blend( const Image &image, int transparency ) { if ( transparency <= 0 ) return; - new_buffer = AllocBuffer(size); + uint8_t* new_buffer = AllocBuffer(size); #ifdef ZM_IMAGE_PROFILING TimePoint start = std::chrono::steady_clock::now(); @@ -3397,6 +3397,7 @@ __attribute__((noinline)) void neon64_armv8_fastblend(const uint8_t* col1, const } __attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { + Warning("Using slow std_blend"); double divide = blendpercent / 100.0; double opacity = 1.0 - divide; const uint8_t* const max_ptr = result + count; From 91b536b868fe7be14bc3f5a957f2a421adeada9e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 14 Feb 2022 09:43:48 -0500 Subject: [PATCH 441/501] Change the test for slow functions to mod4. The unrolled and SSE function work on 4 pixels at a time or 16 or 12 bytes depending on colours.This will improve cpu use on resolutions like 1280x800 --- src/zm_image.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index ea785195e..35efa8774 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -84,8 +84,11 @@ static ZmFont font; std::mutex jpeg_mutex; void Image::update_function_pointers() { - /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ - if ( pixels % 16 || pixels % 12 ) { + /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements + * previous tests were %16 or %12 but that is incorrect. Should just be %4 + */ + + if (pixels %4) { // have to use non-loop unrolled functions delta8_rgb = &std_delta8_rgb; delta8_bgr = &std_delta8_bgr; @@ -95,7 +98,7 @@ void Image::update_function_pointers() { delta8_abgr = &std_delta8_abgr; delta8_gray8 = &std_delta8_gray8; blend = &std_blend; - Warning("Using slow std functions"); + Warning("Using slow std functions because pixels %d mod 4=%d", pixels, pixels%4); } else { // Use either sse or neon, or loop unrolled version delta8_rgb = fptr_delta8_rgb; From b6d2d96d11af82bace0bf5fd73270187b3b50a99 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 14 Feb 2022 09:47:59 -0500 Subject: [PATCH 442/501] Remove offending extra comma --- web/skins/classic/views/frame.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/frame.php b/web/skins/classic/views/frame.php index 7a281a29a..bf434448a 100644 --- a/web/skins/classic/views/frame.php +++ b/web/skins/classic/views/frame.php @@ -115,10 +115,11 @@ xhtmlHeaders(__FILE__, translate('Frame').' - '.$Event->Id().' - '.$Frame->Frame

', - $Event->Id(), $Frame->FrameId(), $scale, ( $show=='anal'?'capt':'anal' ), - ( $show=='anal'?'without':'with' ), + $Event->Id(), $Frame->FrameId(), $scale, + ($show=='anal'?'capt':'anal'), + ($show=='anal'?'without':'with') ); } ?> From 13f2076dc98f674b8659a1b6330ccfff2d0ac273 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 15 Feb 2022 14:15:08 +0100 Subject: [PATCH 443/501] Add SQL opTypes IN, NOT IN to filter view, #3432 This possibly fixes the issue where changing the filter for an event view via the filter button would produce invalid SQL and SQL errors. web_php[43835].ERR [1.2.3.4] [SQL-ERR 'SQLSTATE[HY000]: General error: 4078 Illegal parameter data types int and row for operation '='', statement was 'SELECT E.*, M.Name AS Monitor FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE ( ( E.StartDateTime >= '2022-02-14 00:47:41' and E.StartDateTime <= '2022-02-14 11:47:41' ) and E.MonitorId = ('1','3') ) ORDER BY E.StartDateTime DESC' params:] at /usr/share/zoneminder/www/includes/database.php line 161 When in the events view, clicking the filterBtn sends us to the filter view with this query term (url-decoded): &filter[Query][terms][2][attr]=MonitorId&filter[Query][terms][2][op]=IN&filter[Query][terms][2][val]=1,3&filter[Query][sort_asc]=0 but the filter view displays this as "equals", not "in set", because it doesn't know IN from opTypes, only =[]. --- web/skins/classic/views/filter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index 8445d2c12..de2016489 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -130,6 +130,8 @@ $opTypes = array( '!~' => translate('OpNotMatches'), '=[]' => translate('OpIn'), '![]' => translate('OpNotIn'), + 'IN' => translate('OpIn'), + 'NOT IN' => translate('OpNotIn'), 'IS' => translate('OpIs'), 'IS NOT' => translate('OpIsNot'), 'LIKE' => translate('OpLike'), From 5fd5c7fdd11beb59dbb4dad46c7f4e65f04d01a1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 10:04:31 -0500 Subject: [PATCH 444/501] Maybe fix build on freebsd by including unistd.h --- src/zm_monitor_monitorlink.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_monitor_monitorlink.cpp b/src/zm_monitor_monitorlink.cpp index 95432388a..9e7a60d49 100644 --- a/src/zm_monitor_monitorlink.cpp +++ b/src/zm_monitor_monitorlink.cpp @@ -24,6 +24,7 @@ #if ZM_MEM_MAPPED #include #include +#include #else // ZM_MEM_MAPPED #include #include From 554840d811f6a7f39bc47dde76aa13501f370224 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 10:07:48 -0500 Subject: [PATCH 445/501] Revert "Add SQL opTypes IN, NOT IN to filter view, #3432" --- web/skins/classic/views/filter.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index de2016489..8445d2c12 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -130,8 +130,6 @@ $opTypes = array( '!~' => translate('OpNotMatches'), '=[]' => translate('OpIn'), '![]' => translate('OpNotIn'), - 'IN' => translate('OpIn'), - 'NOT IN' => translate('OpNotIn'), 'IS' => translate('OpIs'), 'IS NOT' => translate('OpIsNot'), 'LIKE' => translate('OpLike'), From 59039de98527be1cad4d0c7c5165573deaa4270e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 10:23:30 -0500 Subject: [PATCH 446/501] Use =[] instead of IN as operator. Fixes #3432 --- web/skins/classic/views/report_event_audit.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/report_event_audit.php b/web/skins/classic/views/report_event_audit.php index be53c87b0..fa2a07004 100644 --- a/web/skins/classic/views/report_event_audit.php +++ b/web/skins/classic/views/report_event_audit.php @@ -42,7 +42,7 @@ $filter = new ZM\Filter(); $filter->addTerm(array('attr'=>'StartDateTime', 'op'=>'>=', 'val'=>$minTime, 'obr'=>'1')); $filter->addTerm(array('attr'=>'StartDateTime', 'op'=>'<=', 'val'=>$maxTime, 'cnj'=>'and', 'cbr'=>'1')); if ( count($selected_monitor_ids) ) { - $filter->addTerm(array('attr'=>'MonitorId', 'op'=>'IN', 'val'=>implode(',', $selected_monitor_ids), 'cnj'=>'and')); + $filter->addTerm(array('attr'=>'MonitorId', 'op'=>'=[]', 'val'=>implode(',', $selected_monitor_ids), 'cnj'=>'and')); } else if ( ( $group_id != 0 || isset($_SESSION['ServerId']) || isset($_SESSION['StorageId']) || isset($_SESSION['Status']) ) ) { # this should be redundant for ( $i=0; $i < count($displayMonitors); $i++ ) { @@ -164,11 +164,11 @@ for ( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { if ( count($FileMissing) ) { $FileMissing_filter = new ZM\Filter(); - $FileMissing_filter->addTerm(array('attr'=>'Id', 'op'=>'IN', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $FileMissing)))); + $FileMissing_filter->addTerm(array('attr'=>'Id', 'op'=>'=[]', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $FileMissing)))); } if ( count($ZeroSize) ) { $ZeroSize_filter = new ZM\Filter(); - $ZeroSize_filter->addTerm(array('attr'=>'Id', 'op'=>'IN', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $ZeroSize)))); + $ZeroSize_filter->addTerm(array('attr'=>'Id', 'op'=>'=[]', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $ZeroSize)))); } ?>

From b2e0f805bd262c86c46f6692e739c162ba276c60 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 10:29:58 -0500 Subject: [PATCH 447/501] Add NOT IN case. Also, fix bad SQL when value evals to false. Test for empty string instead. Fixes #3425 --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 726685a7e..0a10baa3b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -231,7 +231,7 @@ sub Sql { } else { ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; # Empty value will result in () from split - foreach my $temp_value ( $stripped_value ? split( /["'\s]*?,["'\s]*?/, $stripped_value ) : $stripped_value ) { + foreach my $temp_value ( $stripped_value ne '' ? split( /["'\s]*?,["'\s]*?/, $stripped_value ) : $stripped_value ) { if ( $term->{attr} eq 'AlarmedZoneId' ) { $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')'; } elsif ( $term->{attr} =~ /^MonitorName/ ) { @@ -323,7 +323,7 @@ sub Sql { $self->{Sql} .= ' IS NOT '.$value; } elsif ( $term->{op} eq '=[]' or $term->{op} eq 'IN' ) { $self->{Sql} .= ' IN ('.join(',', @value_list).")"; - } elsif ( $term->{op} eq '![]' ) { + } elsif ( $term->{op} eq '![]' or $term->{op} eq 'NOT IN') { $self->{Sql} .= ' NOT IN ('.join(',', @value_list).')'; } elsif ( $term->{op} eq 'LIKE' ) { $self->{Sql} .= ' LIKE '.$value; From a149fa37fc56044b8d9c97953196e53ac2c3b0e0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 10:37:14 -0500 Subject: [PATCH 448/501] Make filter debug modal work on non-saved filter --- web/ajax/modals/filterdebug.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/web/ajax/modals/filterdebug.php b/web/ajax/modals/filterdebug.php index f3a961874..d3ffaaf01 100644 --- a/web/ajax/modals/filterdebug.php +++ b/web/ajax/modals/filterdebug.php @@ -11,13 +11,20 @@ No filter id specified.'; - } else { - $filter = new ZM\Filter($_REQUEST['fid']); + + $filter = null; + if ($fid) { + $filter = new ZM\Filter($fid); if (!$filter->Id()) { echo '
Filter not found for id '.$_REQUEST['fid'].'
'; } + } else { + $filter = new ZM\Filter(); + if ( isset($_REQUEST['filter'])) { + $filter->set($_REQUEST['filter']); + } else { + echo '
No filter id or contents specified.
'; + } } ?> From 956579d557671b5fadca210c78d683785fd438fe Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 10:41:42 -0500 Subject: [PATCH 449/501] handle snap->in_frame being null. --- src/zm_monitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 062e10ac7..c094aecff 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2150,7 +2150,7 @@ bool Monitor::Analyse() { ref_image.Blend(v_image, ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); } else if (snap->image) { Debug(1, "Blending because %p and format %d != %d, %d", snap->in_frame, - snap->in_frame->format, + (snap->in_frame ? snap->in_frame->format : -1), AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P ); @@ -2674,7 +2674,7 @@ bool Monitor::Decode() { if (config.timestamp_on_capture) { Debug(3, "Timestamping"); - TimestampImage(packet->image, packet->timestamp); + TimestampImage(capture_image, packet->timestamp); } image_buffer[index]->Assign(*(packet->image)); From 33f23cbd84c5d8f767ed7abf68a06c5b7610be9e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 18:51:10 -0500 Subject: [PATCH 450/501] Sync up n_frames, frame_count, curr_frame_id on int instead of a mix of long int, int and unsigned long int --- src/zm_eventstream.cpp | 72 ++++++++++++++++++++++-------------------- src/zm_eventstream.h | 12 +++---- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 28a0e50d6..34f693f36 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -68,17 +68,17 @@ bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_tim curr_frame_id = 1; // curr_frame_id is 1-based if (event_time >= event_data->start_time) { Debug(2, "event time is after event start"); - for (unsigned int i = 0; i < event_data->frame_count; i++) { + for (int i = 0; i < event_data->frame_count; i++) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); if (event_data->frames[i].timestamp >= event_time) { curr_frame_id = i + 1; - Debug(3, "Set curr_stream_time: %.2f, curr_frame_id: %ld", + Debug(3, "Set curr_stream_time: %.2f, curr_frame_id: %d", FPSeconds(curr_stream_time.time_since_epoch()).count(), curr_frame_id); break; } } // end foreach frame - Debug(3, "Skipping %ld frames", event_data->frame_count); + Debug(3, "Skipping %d frames", event_data->frame_count); } else { Warning("Requested an event time less than the start of the event. event_time %" PRIi64 " < start_time %" PRIi64, static_cast(std::chrono::duration_cast(event_time.time_since_epoch()).count()), @@ -90,13 +90,13 @@ bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_tim bool EventStream::loadInitialEventData( uint64_t init_event_id, - unsigned int init_frame_id + int init_frame_id ) { loadEventData(init_event_id); if ( init_frame_id ) { if ( init_frame_id >= event_data->frame_count ) { - Error("Invalid frame id specified. %d > %lu", init_frame_id, event_data->frame_count); + Error("Invalid frame id specified. %d > %d", init_frame_id, event_data->frame_count); curr_stream_time = event_data->start_time; curr_frame_id = 1; } else { @@ -225,20 +225,24 @@ bool EventStream::loadEventData(uint64_t event_id) { sql = stringtf("SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` " "FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id); - result = zmDbFetch(sql); if (!result) { exit(-1); } event_data->n_frames = mysql_num_rows(result); - event_data->frames = new FrameData[event_data->frame_count]; + if (event_data->frame_count < event_data->n_frames) { + event_data->frame_count = event_data->n_frames; + Warning("Event %" PRId64 " has more frames in the Frames table (%d) than in the Event record (%d)", + event_data->event_id, event_data->n_frames, event_data->frame_count); + } + int last_id = 0; SystemTimePoint last_timestamp = event_data->start_time; Microseconds last_delta = Seconds(0); - while ( ( dbrow = mysql_fetch_row(result) ) ) { + while ((dbrow = mysql_fetch_row(result))) { int id = atoi(dbrow[0]); //timestamp = atof(dbrow[1]); Microseconds delta = std::chrono::duration_cast(FPSeconds(atof(dbrow[2]))); @@ -280,7 +284,7 @@ bool EventStream::loadEventData(uint64_t event_id) { // Incomplete events might not have any frame data event_data->last_frame_id = last_id; - if ( mysql_errno(&dbconn) ) { + if (mysql_errno(&dbconn)) { Error("Can't fetch row: %s", mysql_error(&dbconn)); exit(mysql_errno(&dbconn)); } @@ -310,7 +314,7 @@ bool EventStream::loadEventData(uint64_t event_id) { else curr_stream_time = event_data->frames[event_data->last_frame_id-1].timestamp; } - Debug(2, "Event: %" PRIu64 ", Frames: %ld, Last Frame ID (%ld, Duration: %.2f s Frames Duration: %.2f s", + Debug(2, "Event: %" PRIu64 ", Frames: %d, Last Frame ID (%d, Duration: %.2f s Frames Duration: %.2f s", event_data->event_id, event_data->frame_count, event_data->last_frame_id, @@ -342,12 +346,12 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( (mode == MODE_SINGLE || mode == MODE_NONE) && - ((unsigned int)curr_frame_id == event_data->last_frame_id) + (curr_frame_id == event_data->last_frame_id) ) { Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame"); curr_frame_id = 1; } else { - Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld", + Debug(1, "mode is %s, current frame is %d, frame count is %d, last frame id is %d", StreamMode_Strings[(int) mode].c_str(), curr_frame_id, event_data->frame_count, @@ -404,9 +408,9 @@ void EventStream::processCommand(const CmdMsg *msg) { paused = true; replay_rate = ZM_RATE_BASE; step = 1; - if ( (unsigned int)curr_frame_id < event_data->last_frame_id ) + if (curr_frame_id < event_data->last_frame_id) curr_frame_id += 1; - Debug(1, "Got SLOWFWD command new frame id %ld", curr_frame_id); + Debug(1, "Got SLOWFWD command new frame id %d", curr_frame_id); break; case CMD_SLOWREV : paused = true; @@ -414,7 +418,7 @@ void EventStream::processCommand(const CmdMsg *msg) { step = -1; curr_frame_id -= 1; if ( curr_frame_id < 1 ) curr_frame_id = 1; - Debug(1, "Got SLOWREV command new frame id %ld", curr_frame_id); + Debug(1, "Got SLOWREV command new frame id %d", curr_frame_id); break; case CMD_FASTREV : Debug(1, "Got FAST REV command"); @@ -538,12 +542,12 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( curr_frame_id < 1 ) { curr_frame_id = 1; - } else if ( (unsigned long)curr_frame_id > event_data->last_frame_id ) { + } else if (curr_frame_id > event_data->last_frame_id) { curr_frame_id = event_data->last_frame_id; } curr_stream_time = event_data->frames[curr_frame_id-1].timestamp; - Debug(1, "Got SEEK command, to %f s (new current frame id: %ld offset %f s)", + Debug(1, "Got SEEK command, to %f s (new current frame id: %d offset %f s)", FPSeconds(offset).count(), curr_frame_id, FPSeconds(event_data->frames[curr_frame_id - 1].offset).count()); @@ -615,11 +619,11 @@ bool EventStream::checkEventLoaded() { sql = stringtf( "SELECT `Id` FROM `Events` WHERE `MonitorId` = %d AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1", event_data->monitor_id, event_data->event_id); - } else if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) { + } else if (curr_frame_id > event_data->last_frame_id) { if (event_data->end_time.time_since_epoch() == Seconds(0)) { // We are viewing an in-process event, so just reload it. loadEventData(event_data->event_id); - if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) + if (curr_frame_id > event_data->last_frame_id) curr_frame_id = event_data->last_frame_id; return false; } @@ -628,7 +632,7 @@ bool EventStream::checkEventLoaded() { event_data->monitor_id, event_data->event_id); } else { // No event change required - Debug(3, "No event change required, as curr frame %ld <=> event frames %lu", + Debug(3, "No event change required, as curr frame %d <=> event frames %d", curr_frame_id, event_data->frame_count); return false; } @@ -662,7 +666,7 @@ bool EventStream::checkEventLoaded() { curr_frame_id = event_data->last_frame_id; else curr_frame_id = 1; - Debug(2, "New frame id = %ld", curr_frame_id); + Debug(2, "New frame id = %d", curr_frame_id); start = std::chrono::steady_clock::now(); return true; } else { @@ -689,13 +693,13 @@ bool EventStream::checkEventLoaded() { Image * EventStream::getImage( ) { std::string path = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id); - Debug(2, "EventStream::getImage path(%s) from %s frame(%ld) ", path.c_str(), event_data->path.c_str(), curr_frame_id); + Debug(2, "EventStream::getImage path(%s) from %s frame(%d) ", path.c_str(), event_data->path.c_str(), curr_frame_id); Image *image = new Image(path.c_str()); return image; } bool EventStream::sendFrame(Microseconds delta_us) { - Debug(2, "Sending frame %ld", curr_frame_id); + Debug(2, "Sending frame %d", curr_frame_id); std::string filepath; struct stat filestat = {}; @@ -881,7 +885,7 @@ void EventStream::runStream() { if ( !paused ) { // Figure out if we should send this frame - Debug(3, "not paused at cur_frame_id (%ld-1) mod frame_mod(%d)", curr_frame_id, frame_mod); + Debug(3, "not paused at cur_frame_id (%d-1) mod frame_mod(%d)", curr_frame_id, frame_mod); // If we are streaming and this frame is due to be sent // frame mod defaults to 1 and if we are going faster than max_fps will get multiplied by 2 // so if it is 2, then we send every other frame, if is it 4 then every fourth frame, etc. @@ -983,7 +987,7 @@ void EventStream::runStream() { if ( (mode == MODE_SINGLE) && ( (curr_frame_id < 1 ) || - ((unsigned int)curr_frame_id >= event_data->frame_count) + (curr_frame_id >= event_data->frame_count) ) ) { Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start"); @@ -992,7 +996,7 @@ void EventStream::runStream() { start = now; } - if ((unsigned int)curr_frame_id <= event_data->frame_count) { + if (curr_frame_id <= event_data->frame_count) { frame_data = &event_data->frames[curr_frame_id-1]; // frame_data->delta is the time since last frame as a float in seconds @@ -1032,7 +1036,7 @@ void EventStream::runStream() { } } // end if need to sleep } else { - Debug(1, "invalid curr_frame_id %ld !< %lu", curr_frame_id, event_data->frame_count); + Debug(1, "invalid curr_frame_id %d !< %d", curr_frame_id, event_data->frame_count); } // end if not at end of event } else { // Paused @@ -1110,12 +1114,12 @@ bool EventStream::send_file(const std::string &filepath) { } if (!filestat.st_size) { fclose(fdj); /* Close the file handle */ - Info("File size is zero. Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); + Info("File size is zero. Unable to send raw frame %d: %s", curr_frame_id, strerror(errno)); return false; } if (0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size)) { fclose(fdj); /* Close the file handle */ - Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); + Info("Unable to send raw frame %d: %s", curr_frame_id, strerror(errno)); return false; } int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size); @@ -1124,7 +1128,7 @@ bool EventStream::send_file(const std::string &filepath) { fclose(fdj); /* Close the file handle */ return true; } - Warning("Unable to send raw frame %ld: %s rc %d != %d", + Warning("Unable to send raw frame %d: %s rc %d != %d", curr_frame_id, strerror(errno), rc, (int)filestat.st_size); #endif @@ -1134,7 +1138,7 @@ bool EventStream::send_file(const std::string &filepath) { int img_buffer_size = fread(img_buffer, 1, sizeof(temp_img_buffer), fdj); fclose(fdj); /* Close the file handle */ if ( !img_buffer_size ) { - Info("Unable to read raw frame %ld: %s", curr_frame_id, strerror(errno)); + Info("Unable to read raw frame %d: %s", curr_frame_id, strerror(errno)); return false; } @@ -1143,13 +1147,13 @@ bool EventStream::send_file(const std::string &filepath) { bool EventStream::send_buffer(uint8_t* buffer, int size) { if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) { - Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); + Info("Unable to send raw frame %d: %s", curr_frame_id, strerror(errno)); return false; } int rc = fwrite(buffer, size, 1, stdout); if ( 1 != rc ) { - Error("Unable to send raw frame %ld: %s %d", curr_frame_id, strerror(errno), rc); + Error("Unable to send raw frame %d: %s %d", curr_frame_id, strerror(errno), rc); return false; } return true; @@ -1157,7 +1161,7 @@ bool EventStream::send_buffer(uint8_t* buffer, int size) { void EventStream::setStreamStart( uint64_t init_event_id, - unsigned int init_frame_id=0) { + int init_frame_id=0) { loadInitialEventData(init_event_id, init_frame_id); } // end void EventStream::setStreamStart(init_event_id,init_frame_id=0) diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 969e6725d..093bcd0eb 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -49,9 +49,9 @@ class EventStream : public StreamBase { struct EventData { uint64_t event_id; unsigned int monitor_id; - unsigned long storage_id; - unsigned long frame_count; // Value of Frames column in Event - unsigned long last_frame_id; // Highest frame id known about. Can be < frame_count in incomplete events + unsigned int storage_id; + int frame_count; // Value of Frames column in Event + int last_frame_id; // Highest frame id known about. Can be < frame_count in incomplete events SystemTimePoint start_time; SystemTimePoint end_time; Microseconds duration; @@ -73,7 +73,7 @@ class EventStream : public StreamBase { StreamMode mode; bool forceEventChange; - long curr_frame_id; + int curr_frame_id; SystemTimePoint curr_stream_time; bool send_frame; TimePoint start; // clock time when started the event @@ -82,7 +82,7 @@ class EventStream : public StreamBase { protected: bool loadEventData(uint64_t event_id); - bool loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id); + bool loadInitialEventData(uint64_t init_event_id, int init_frame_id); bool loadInitialEventData(int monitor_id, SystemTimePoint event_time); bool checkEventLoaded(); @@ -118,7 +118,7 @@ class EventStream : public StreamBase { ffmpeg_input = nullptr; } } - void setStreamStart(uint64_t init_event_id, unsigned int init_frame_id); + void setStreamStart(uint64_t init_event_id, int init_frame_id); void setStreamStart(int monitor_id, time_t event_time); void setStreamMode(StreamMode p_mode) { mode = p_mode; } void runStream() override; From 8b14885541d6fd9ca464249273a5ca981639c09e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 18:51:54 -0500 Subject: [PATCH 451/501] Add handling of NOT IN and =\[\] and \!\[\] --- web/includes/FilterTerm.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/includes/FilterTerm.php b/web/includes/FilterTerm.php index 8a58e2f98..903cce811 100644 --- a/web/includes/FilterTerm.php +++ b/web/includes/FilterTerm.php @@ -174,6 +174,7 @@ class FilterTerm { case 'IN' : return ' IN '; case '![]' : + case 'NOT IN' : return ' NOT IN '; case 'EXISTS' : return ' EXISTS '; @@ -303,7 +304,7 @@ class FilterTerm { } $sql .= $this->sql_operator(); $values = $this->sql_values(); - if ( (count($values) > 1) or ($this->op == 'IN') or ($this->op == 'NOT IN') ) { + if ((count($values) > 1) or ($this->op == 'IN') or ($this->op == 'NOT IN') or ($this->op == '=[]') or ($this->op == '![]')) { $sql .= '('.join(',', $values).')'; } else { $sql .= $values[0]; From b67e7b8111afa3b5691a61ca78abb251f8e878b4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 18:52:27 -0500 Subject: [PATCH 452/501] serialize form instead of just passing filter id because the modal can now handle unsaved filters --- web/skins/classic/views/js/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index 5765a0e76..e7979db15 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -378,7 +378,7 @@ function delTerm( element ) { } function debugFilter() { - getModal('filterdebug', 'fid='+filterid); + getModal('filterdebug', $j(form).serialize()); } function manageModalBtns(id) { From efc1ec770c6fb5205c75b4379eabfc6154ddac10 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Feb 2022 18:52:40 -0500 Subject: [PATCH 453/501] Debug the contents of REQUEST on every hit. --- web/index.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/index.php b/web/index.php index 5ace8e9b6..abca33c9f 100644 --- a/web/index.php +++ b/web/index.php @@ -55,6 +55,7 @@ if ( 0 and ZM\Logger::fetch()->debugOn() ) { ZM\Debug(ob_get_contents()); ob_end_clean(); } +ZM\Debug(print_r($_REQUEST, true)); global $Servers; $Servers = ZM\Server::find(); From 59d283095dfcdd8f7314af97a90ac24f4ddde576 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Wed, 16 Feb 2022 07:54:01 -0600 Subject: [PATCH 454/501] bump rpm specfile rtsp commit anytime the rtsp commit is bumped in packpack, it must also be bumped in the rpm specfile --- distros/redhat/zoneminder.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index f15e60326..37920dcf8 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -9,7 +9,7 @@ %global ceb_version 1.0-zm # RtspServer is configured as a git submodule -%global rtspserver_commit cd7fd49becad6010a1b8466bfebbd93999a39878 +%global rtspserver_commit eab32851421ffe54fec0229c3efc44c642bc8d46 %global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt %global sslkey %{_sysconfdir}/pki/tls/private/localhost.key From a2a4a8e74f0c3d8fd4f09aa715c3f8ae4ef77435 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Wed, 16 Feb 2022 08:22:37 -0600 Subject: [PATCH 455/501] include arp-scan polkit files in rpm --- distros/redhat/zoneminder.spec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 37920dcf8..042eeb7c3 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -352,7 +352,8 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin %config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder %{_unitdir}/zoneminder.service -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy +%{_datadir}/polkit-1/actions/com.zoneminder.* +%{_datadir}/polkit-1/rules.d/com.zoneminder.arp-scan.rules %{_bindir}/zmsystemctl.pl %{_bindir}/zmaudit.pl From 9d400b9f6cf5a04bb490c362696793fe2fdc4dd3 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Wed, 16 Feb 2022 08:33:44 -0600 Subject: [PATCH 456/501] use rockylinux:8 container for ci workflow --- .github/workflows/ci-centos-8.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-centos-8.yml b/.github/workflows/ci-centos-8.yml index f01f9929b..8ad222793 100644 --- a/.github/workflows/ci-centos-8.yml +++ b/.github/workflows/ci-centos-8.yml @@ -19,7 +19,7 @@ jobs: - crypto_backend: gnutls jwt_backend: libjwt runs-on: ubuntu-latest - container: centos:8 + container: rockylinux:8 steps: - name: Enable RPMFusion, EPEL and PowerTools From 93ab345a3a1be5047dffded2ee67f6e8baa9e6c2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 09:28:21 -0500 Subject: [PATCH 457/501] DefaultCodec is for event view, not live --- web/lang/en_gb.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 623d55571..d8d57d1f3 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -294,10 +294,10 @@ $SLANG = array( 'Debug' => 'Debug', 'DefaultRate' => 'Default Rate', 'DefaultScale' => 'Default Scale', - 'DefaultCodec' => 'Default Method For Live View', + 'DefaultCodec' => 'Default Method For Event View', 'DefaultView' => 'Default View', 'Deinterlacing' => 'Deinterlacing', - 'RTSPDescribe' => 'Use RTSP Response Media URL', + 'RTSPDescribe' => 'Use RTSP Response Media URL', 'Delay' => 'Delay', 'DeleteAndNext' => 'Delete & Next', 'DeleteAndPrev' => 'Delete & Prev', From 055414c77822fcb4f18b0dc793263f56904ce8c3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 09:28:58 -0500 Subject: [PATCH 458/501] DefaultCodec is for event view, not live --- web/lang/zh_tw.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/lang/zh_tw.php b/web/lang/zh_tw.php index 642b884e2..601d8609a 100644 --- a/web/lang/zh_tw.php +++ b/web/lang/zh_tw.php @@ -278,7 +278,7 @@ $SLANG = array( 'Debug' => '除錯', 'DefaultRate' => '預設 Rate', 'DefaultScale' => '預設 Scale', - 'DefaultCodec' => '預設 Method For Live View', + 'DefaultCodec' => '預設 Method For Event View', 'DefaultView' => '預設 View', 'Deinterlacing' => 'Deinterlacing', 'RTSPDescribe' => 'Use RTSP Response Media URL', From 2d3f99eabbdb3279ae75903ff7056c0dfb6b7582 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 09:59:23 -0500 Subject: [PATCH 459/501] rework zm_sendfile to try again if not all bytes were sent. According to the docs, this is entirely possible. --- src/zm_sendfile.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/zm_sendfile.h b/src/zm_sendfile.h index fd72e2e61..3568e2fea 100644 --- a/src/zm_sendfile.h +++ b/src/zm_sendfile.h @@ -3,22 +3,25 @@ #ifdef HAVE_SENDFILE4_SUPPORT #include -int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { - int err; +ssize_t zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { + size_t remaining = size; + while (remaining) { + ssize_t err = sendfile(out_fd, in_fd, offset, remaining); + if (err < 0) { + return -errno; + } + remaining -= err; + offset += err; + } - err = sendfile(out_fd, in_fd, offset, size); - if ( err < 0 ) - return -errno; - - return err; + return size-remaining; } #elif HAVE_SENDFILE7_SUPPORT #include #include #include -int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) { - int err; - err = sendfile(in_fd, out_fd, *offset, size, nullptr, &size, 0); +ssize_t zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) { + ssize_t err = sendfile(in_fd, out_fd, *offset, size, nullptr, &size, 0); if (err && errno != EAGAIN) return -errno; From f3d3cad4192725511f32a4c926487e205104e775 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 09:59:39 -0500 Subject: [PATCH 460/501] Remove non-translations --- web/lang/en_gb.php | 254 +++------------------------------------------ 1 file changed, 14 insertions(+), 240 deletions(-) diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index d8d57d1f3..c80a37e85 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -78,18 +78,10 @@ define( "STRF_FMT_DATETIME_SHORTER", "%x %H:%M:%S" ); $SLANG = array( 'SystemLog' => 'System Log', 'DateTime' => 'Date/Time', - 'Component' => 'Component', 'Pid' => 'PID', - 'Level' => 'Level', - 'Message' => 'Message', - 'Line' => 'Line', - 'More' => 'More', - 'Clear' => 'Clear', '24BitColour' => '24 bit colour', '32BitColour' => '32 bit colour', '8BitGrey' => '8 bit greyscale', - 'Action' => 'Action', - 'Actual' => 'Actual', 'AddNewControl' => 'Add New Control', 'AddNewMonitor' => 'Add', 'AddMonitorDisabled' => 'Your user is not allowed to add a new monitor', @@ -97,7 +89,6 @@ $SLANG = array( 'AddNewStorage' => 'Add New Storage', 'AddNewUser' => 'Add New User', 'AddNewZone' => 'Add New Zone', - 'Alarm' => 'Alarm', 'AlarmBrFrames' => 'Alarm
Frames', 'AlarmFrame' => 'Alarm Frame', 'AlarmFrameCount' => 'Alarm Frame Count', @@ -106,20 +97,13 @@ $SLANG = array( 'AlarmPx' => 'Alarm Px', 'AlarmRefImageBlendPct' => 'Alarm Reference Image Blend %ge', 'AlarmRGBUnset' => 'You must set an alarm RGB colour', - 'Alert' => 'Alert', - 'All' => 'All', 'AllTokensRevoked' => 'All Tokens Revoked', 'AnalysisFPS' => 'Analysis FPS', 'AnalysisUpdateDelay' => 'Analysis Update Delay', - 'API' => 'API', 'APIEnabled' => 'API Enabled', - 'Apply' => 'Apply', 'ApplyingStateChange' => 'Applying State Change', 'ArchArchived' => 'Archived Only', - 'Archive' => 'Archive', - 'Archived' => 'Archived', 'ArchUnarchived' => 'Unarchived Only', - 'Area' => 'Area', 'AreaUnits' => 'Area (px/%)', 'AttrAlarmFrames' => 'Alarm Frames', 'AttrAlarmedZone' => 'Alarmed Zone', @@ -154,12 +138,8 @@ $SLANG = array( 'AttrTotalScore' => 'Total Score', 'AttrStartWeekday' => 'Start Weekday', 'AttrEndWeekday' => 'End Weekday', - 'Auto' => 'Auto', 'AutoStopTimeout' => 'Auto Stop Timeout', - 'Available' => 'Available', 'AvgBrScore' => 'Avg.
Score', - 'Available' => 'Available', - 'Background' => 'Background', 'BackgroundFilter' => 'Run filter in background', 'BadAlarmFrameCount' => 'Alarm frame count must be an integer of one or more', 'BadAlarmMaxFPS' => 'Alarm Maximum FPS must be a positive integer or floating point value', @@ -197,20 +177,14 @@ $SLANG = array( 'BadWebColour' => 'Web colour must be a valid web colour string', 'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', 'BadWidth' => 'Width must be set to a valid value', - 'Bandwidth' => 'Bandwidth', 'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing 'BlobPx' => 'Blob Px', - 'Blobs' => 'Blobs', 'BlobSizes' => 'Blob Sizes', - 'Brightness' => 'Brightness', - 'Buffer' => 'Buffer', - 'Buffers' => 'Buffers', 'CanAutoFocus' => 'Can Auto Focus', 'CanAutoGain' => 'Can Auto Gain', 'CanAutoIris' => 'Can Auto Iris', 'CanAutoWhite' => 'Can Auto White Bal.', 'CanAutoZoom' => 'Can Auto Zoom', - 'Cancel' => 'Cancel', 'CancelForcedAlarm' => 'Cancel Forced Alarm', 'CanFocusAbs' => 'Can Focus Absolute', 'CanFocus' => 'Can Focus', @@ -232,7 +206,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', - 'CanReboot' => 'Can Reboot', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', @@ -251,7 +225,6 @@ $SLANG = array( 'CaptureResolution' => 'Capture Resolution', 'CapturePalette' => 'Capture Palette', 'CaptureWidth' => 'Capture Width', - 'Cause' => 'Cause', 'CheckMethod' => 'Alarm Check Method', 'ChooseDetectedCamera' => 'Choose Detected Camera', 'ChooseDetectedProfile' => 'Choose Detected Profile', @@ -260,11 +233,7 @@ $SLANG = array( 'ChooseLogSelection' => 'Choose a log selection', 'ChoosePreset' => 'Choose Preset', 'CloneMonitor' => 'Clone', - 'Close' => 'Close', - 'Colour' => 'Colour', - 'Command' => 'Command', 'ConcurrentFilter' => 'Run filter concurrently', - 'Config' => 'Config', 'ConfigOptions' => 'ConfigOptions', 'ConfigType' => 'Config Type', 'ConfiguredFor' => 'Configured for', @@ -276,47 +245,29 @@ $SLANG = array( 'ConfirmPassword' => 'Confirm Password', 'ConjAnd' => 'and', 'ConjOr' => 'or', - 'Console' => 'Console', 'ContactAdmin' => 'Please contact your adminstrator for details.', - 'Continue' => 'Continue', - 'Contrast' => 'Contrast', 'ControlAddress' => 'Control Address', 'ControlCap' => 'Control Capability', 'ControlCaps' => 'Control Capabilities', - 'Control' => 'Control', 'ControlDevice' => 'Control Device', 'Controllable' => 'Controllable', 'ControlType' => 'Control Type', - 'Current' => 'Current', - 'Cycle' => 'Cycle', 'CycleWatch' => 'Cycle Watch', - 'Day' => 'Day', - 'Debug' => 'Debug', 'DefaultRate' => 'Default Rate', 'DefaultScale' => 'Default Scale', 'DefaultCodec' => 'Default Method For Event View', 'DefaultView' => 'Default View', - 'Deinterlacing' => 'Deinterlacing', 'RTSPDescribe' => 'Use RTSP Response Media URL', - 'Delay' => 'Delay', 'DeleteAndNext' => 'Delete & Next', 'DeleteAndPrev' => 'Delete & Prev', - 'Delete' => 'Delete', 'DeleteSavedFilter' => 'Delete saved filter', - 'Description' => 'Description', 'DetectedCameras' => 'Detected Cameras', 'DetectedProfiles' => 'Detected Profiles', 'DeviceChannel' => 'Device Channel', 'DeviceFormat' => 'Device Format', 'DeviceNumber' => 'Device Number', 'DevicePath' => 'Device Path', - 'Device' => 'Device', - 'Devices' => 'Devices', - 'Dimensions' => 'Dimensions', 'DisableAlarms' => 'Disable Alarms', - 'Disk' => 'Disk', - 'Display' => 'Display', - 'Displaying' => 'Displaying', 'DonateAlready' => 'No, I\'ve already donated', 'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.

If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.

Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.', 'Donate' => 'Please Donate', @@ -327,34 +278,23 @@ $SLANG = array( 'DonateRemindWeek' => 'Not yet, remind again in 1 week', 'DonateYes' => 'Yes, I\'d like to donate now', 'DoNativeMotionDetection'=> 'Do Native Motion Detection', - 'Download' => 'Download', 'DuplicateMonitorName' => 'Duplicate Monitor Name', 'DuplicateRTSPStreamName' => 'Duplicate RTSP Stream Name', - 'Duration' => 'Duration', - 'Edit' => 'Edit', 'EditControl' => 'Edit Control', 'EditLayout' => 'Edit Layout', - 'Email' => 'Email', 'EnableAlarms' => 'Enable Alarms', - 'Enabled' => 'Enabled', 'EnterNewFilterName' => 'Enter new filter name', 'ErrorBrackets' => 'Error, please check you have an equal number of opening and closing brackets', - 'Error' => 'Error', 'ErrorValidValue' => 'Error, please check that all terms have a valid value', 'Etc' => 'etc', - 'Event' => 'Event', 'EventFilter' => 'Event Filter', 'EventId' => 'Event Id', 'EventName' => 'Event Name', 'EventPrefix' => 'Event Prefix', - 'Events' => 'Events', - 'Exclude' => 'Exclude', - 'Execute' => 'Execute', 'ExportCompress' => 'Use Compression', 'ExportDetails' => 'Export Event Details', 'ExportMatches' => 'Export Matches', 'Exif' => 'Embed EXIF data into image', - 'Export' => 'Export', 'DownloadVideo' => 'Download Video', 'GenerateDownload' => 'Generate Download', 'ExistsInFileSystem' => 'Exists In File System', @@ -370,11 +310,7 @@ $SLANG = array( 'ExportOptions' => 'Export Options', 'ExportSucceeded' => 'Export Succeeded', 'ExportVideoFiles' => 'Export Video Files (if present)', - 'Far' => 'Far', 'FastForward' => 'Fast Forward', - 'Feed' => 'Feed', - 'Ffmpeg' => 'Ffmpeg', - 'File' => 'File', 'FilterArchiveEvents' => 'Archive all matches', 'FilterUnarchiveEvents' => 'Unarchive all matches', 'FilterUpdateDiskSpace' => 'Update used disk space', @@ -390,13 +326,10 @@ $SLANG = array( 'FilterLog' => 'Filter log', 'FilterMessageEvents' => 'Message details of all matches', 'FilterPx' => 'Filter Px', - 'Filter' => 'Filter', - 'Filters' => 'Filters', 'FilterUnset' => 'You must specify a filter width and height', 'FilterUploadEvents' => 'Upload all matches', 'FilterUser' => 'User to run filter as', 'FilterVideoEvents' => 'Create video for all matches', - 'First' => 'First', 'FlippedHori' => 'Flipped Horizontally', 'FlippedVert' => 'Flipped Vertically', 'FnNone' => 'None', // Added 2013.08.16. @@ -406,29 +339,17 @@ $SLANG = array( 'FnMocord' => 'Mocord', // Added 2013.08.16. 'FnNodect' => 'Nodect', // Added 2013.08.16. 'FnExtdect' => 'Extdect', // Added 2014.12.14. - 'Focus' => 'Focus', 'ForceAlarm' => 'Force Alarm', - 'Format' => 'Format', 'FPS' => 'fps', 'FPSReportInterval' => 'FPS Report Interval', - 'Frame' => 'Frame', 'FrameId' => 'Frame Id', 'FrameRate' => 'Frame Rate', - 'Frames' => 'Frames', 'FrameSkip' => 'Frame Skip', 'MotionFrameSkip' => 'Motion Frame Skip', - 'FTP' => 'FTP', - 'Func' => 'Func', - 'Function' => 'Function', - 'Gain' => 'Gain', - 'General' => 'General', 'GenerateVideo' => 'Generate Video', 'GeneratingVideo' => 'Generating Video', 'GetCurrentLocation' => 'Get Current Location', 'GoToZoneMinder' => 'Go to ZoneMinder.com', - 'Grey' => 'Grey', - 'Group' => 'Group', - 'Groups' => 'Groups', 'HasFocusSpeed' => 'Has Focus Speed', 'HasGainSpeed' => 'Has Gain Speed', 'HasHomePreset' => 'Has Home Preset', @@ -441,49 +362,17 @@ $SLANG = array( 'HasWhiteSpeed' => 'Has White Bal. Speed', 'HasZoomSpeed' => 'Has Zoom Speed', 'HighBW' => 'High B/W', - 'High' => 'High', - 'Home' => 'Home', - 'Hostname' => 'Hostname', - 'Hour' => 'Hour', - 'Hue' => 'Hue', - 'Id' => 'Id', - 'Idle' => 'Idle', - 'Ignore' => 'Ignore', 'ImageBufferSize' => 'Image Buffer Size (frames)', 'MaxImageBufferCount' => 'Maximum Image Buffer Size (frames)', - 'Image' => 'Image', - 'Images' => 'Images', - 'Include' => 'Include', - 'In' => 'In', 'InvalidateTokens' => 'Invalidate all generated tokens', - 'Inverted' => 'Inverted', - 'Iris' => 'Iris', 'KeyString' => 'Key String', - 'Label' => 'Label', - 'Language' => 'Language', - 'Last' => 'Last', - 'Layout' => 'Layout', - 'Libvlc' => 'Libvlc', 'LimitResultsPost' => 'results only', // This is used at the end of the phrase 'Limit to first N results only' 'LimitResultsPre' => 'Limit to first', // This is used at the beginning of the phrase 'Limit to first N results only' 'LinkedMonitors' => 'Linked Monitors', - 'List' => 'List', 'ListMatches' => 'List Matches', - 'Load' => 'Load', - 'Local' => 'Local', - 'Log' => 'Log', - 'Logs' => 'Logs', - 'Logging' => 'Logging', 'LoggedInAs' => 'Logged in as', 'LoggingIn' => 'Logging In', - 'Login' => 'Login', - 'Logout' => 'Logout', 'LowBW' => 'Low B/W', - 'Low' => 'Low', - 'Main' => 'Main', - 'Man' => 'Man', - 'Manual' => 'Manual', - 'Mark' => 'Mark', 'MaxBandwidth' => 'Max Bandwidth', 'MaxBrScore' => 'Max.
Score', 'MaxFocusRange' => 'Max Focus Range', @@ -496,7 +385,6 @@ $SLANG = array( 'MaxIrisRange' => 'Max Iris Range', 'MaxIrisSpeed' => 'Max Iris Speed', 'MaxIrisStep' => 'Max Iris Step', - 'Max' => 'Max', 'MaxPanRange' => 'Max Pan Range', 'MaxPanSpeed' => 'Max Pan Speed', 'MaxPanStep' => 'Max Pan Step', @@ -510,7 +398,6 @@ $SLANG = array( 'MaxZoomSpeed' => 'Max Zoom Speed', 'MaxZoomStep' => 'Max Zoom Step', 'MediumBW' => 'Medium B/W', - 'Medium' => 'Medium', 'MetaConfig' => 'Meta Config', 'MinAlarmAreaLtMax' => 'Minimum alarm area should be less than maximum', 'MinAlarmAreaUnset' => 'You must specify the minimum alarm pixel count', @@ -546,20 +433,13 @@ $SLANG = array( 'MinZoomRange' => 'Min Zoom Range', 'MinZoomSpeed' => 'Min Zoom Speed', 'MinZoomStep' => 'Min Zoom Step', - 'Misc' => 'Misc', - 'Mode' => 'Mode', 'ModectDuringPTZ' => 'Do motion detection during PTZ motion', 'MonitorIds' => 'Monitor Ids', - 'Monitor' => 'Monitor', 'MonitorPresetIntro' => 'Select an appropriate preset from the list below.

Please note that this may overwrite any values you already have configured for the current monitor.

', 'MonitorPreset' => 'Monitor Preset', 'MonitorProbeIntro' => 'The list below shows detected analog and network cameras and whether they are already being used or available for selection.

Select the desired entry from the list below.

Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', 'MonitorProbe' => 'Monitor Probe', - 'Monitors' => 'Monitors', - 'Montage' => 'Montage', 'MontageReview' => 'Montage Review', - 'Month' => 'Month', - 'Move' => 'Move', 'MtgDefault' => 'Default', // Added 2013.08.15. 'Mtg2widgrd' => '2-wide grid', // Added 2013.08.15. 'Mtg3widgrd' => '3-wide grid', // Added 2013.08.15. @@ -570,35 +450,23 @@ $SLANG = array( 'MustConfirmPassword' => 'You must confirm the password', 'MustSupplyPassword' => 'You must supply a password', 'MustSupplyUsername' => 'You must supply a username', - 'Name' => 'Name', - 'Near' => 'Near', - 'Network' => 'Network', 'NewGroup' => 'New Group', 'NewLabel' => 'New Label', - 'New' => 'New', 'NewPassword' => 'New Password', 'NewState' => 'New State', 'NewUser' => 'New User', - 'Next' => 'Next', 'NextMonitor' => 'Next Monitor', 'NoDetectedCameras' => 'No Detected Cameras', 'NoDetectedProfiles' => 'No Detected Profiles', 'NoFramesRecorded' => 'There are no frames recorded for this event', 'NoGroup' => 'No Group', 'NoneAvailable' => 'None available', - 'None' => 'None', - 'No' => 'No', - 'Normal' => 'Normal', - 'NoSavedFilters' => 'NoSavedFilters', + 'NoSavedFilters' => 'No Saved Filters', 'NoStatisticsRecorded' => 'There are no statistics recorded for this event/frame', - 'Notes' => 'Notes', 'NumPresets' => 'Num Presets', - 'Off' => 'Off', - 'On' => 'On', 'OnvifProbe' => 'ONVIF', 'OnvifProbeIntro' => 'The list below shows detected ONVIF cameras and whether they are already being used or available for selection.

Select the desired entry from the list below.

Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', 'OnvifCredentialsIntro' => 'Please supply user name and password for the selected camera.
If no user has been created for the camera then the user given here will be created with the given password.

', - 'Open' => 'Open', 'OpEq' => 'equal to', 'OpGtEq' => 'greater than or equal to', 'OpGt' => 'greater than', @@ -619,42 +487,26 @@ $SLANG = array( 'Options' => 'Options', 'Order' => 'Order', 'OrEnterNewName' => 'or enter new name', - 'Orientation' => 'Orientation', - 'Out' => 'Out', 'OverwriteExisting' => 'Overwrite Existing', - 'Paged' => 'Paged', 'PanLeft' => 'Pan Left', - 'Pan' => 'Pan', 'PanRight' => 'Pan Right', 'PanTilt' => 'Pan/Tilt', - 'Parameter' => 'Parameter', 'ParentGroup' => 'Parent Group', - 'Password' => 'Password', 'PasswordsDifferent' => 'The new and confirm passwords are different', 'PathToIndex' => 'Path To Index', 'PathToZMS' => 'Path To ZMS', 'PathToApi' => 'Path To Api', - 'Paths' => 'Paths', - 'Pause' => 'Pause', 'PauseCycle' => 'Pause Cycle', 'PhoneBW' => 'Phone B/W', - 'Phone' => 'Phone', 'PixelDiff' => 'Pixel Diff', 'Pixels' => 'pixels', 'PlayAll' => 'Play All', - 'Play' => 'Play', 'PlayCycle' => 'Play Cycle', - 'Plugins' => 'Plugins', 'PleaseWait' => 'Please Wait', - 'Point' => 'Point', 'PostEventImageBuffer' => 'Post Event Image Count', 'PreEventImageBuffer' => 'Pre Event Image Count', 'PreserveAspect' => 'Preserve Aspect Ratio', - 'Preset' => 'Preset', - 'Presets' => 'Presets', - 'Prev' => 'Prev', 'PreviousMonitor' => 'Previous Monitor', - 'Privacy' => 'Privacy', 'PrivacyAbout' => 'About', 'PrivacyAboutText' => 'Since 2002, ZoneMinder has been the premier free and open-source Video Management System (VMS) solution for Linux platforms. ZoneMinder is supported by the community and is managed by those who choose to volunteer their spare time to the project. The best way to improve ZoneMinder is to get involved.', 'PrivacyContact' => 'Contact', @@ -694,15 +546,9 @@ $SLANG = array( 'Probe' => 'Probe', 'ProfileProbe' => 'Stream Probe', 'ProfileProbeIntro' => 'The list below shows the existing stream profiles of the selected camera .

Select the desired entry from the list below.

Please note that ZoneMinder cannot configure additional profiles and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', - 'Progress' => 'Progress', - 'Protocol' => 'Protocol', - 'Rate' => 'Rate', 'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // added Sep 24 2015 - PP - 'RecordAudio' => 'Whether to store the audio stream when saving an event.', - 'Real' => 'Real', - 'Record' => 'Record', + 'RecordAudio' => 'Whether to store the audio stream when saving an event.', 'RefImageBlendPct' => 'Reference Image Blend %ge', - 'Refresh' => 'Refresh', 'RemoteHostName' => 'Host Name', 'RemoteHostPath' => 'Path', 'RemoteHostSubPath' => 'SubPath', @@ -710,30 +556,22 @@ $SLANG = array( 'RemoteImageColours' => 'Image Colours', 'RemoteMethod' => 'Method', 'RemoteProtocol' => 'Protocol', - 'Remote' => 'Remote', - 'Rename' => 'Rename', 'ReplayAll' => 'All Events', 'ReplayGapless' => 'Gapless Events', - 'Replay' => 'Replay', 'ReplaySingle' => 'Single Event', 'ReportEventAudit' => 'Audit Events Report', 'ResetEventCounts' => 'Reset Event Counts', - 'Reset' => 'Reset', - 'Restarting' => 'Restarting', - 'Restart' => 'Restart', 'RestrictedCameraIds' => 'Restricted Camera Ids', 'RestrictedMonitors' => 'Restricted Monitors', 'ReturnDelay' => 'Return Delay', 'ReturnLocation' => 'Return Location', - 'RevokeAllTokens' => 'Revoke All Tokens', - 'Rewind' => 'Rewind', + 'RevokeAllTokens' => 'Revoke All Tokens', 'RotateLeft' => 'Rotate Left', 'RotateRight' => 'Rotate Right', 'RTSPTransport' => 'RTSP Transport Protocol', 'RunAudit' => 'Run Audit Process', 'RunLocalUpdate' => 'Please run zmupdate.pl to update', 'RunMode' => 'Run Mode', - 'Running' => 'Running', 'RunState' => 'Run State', 'RunStats' => 'Run Stats Process', 'RunTrigger' => 'Run Trigger Process', @@ -741,45 +579,29 @@ $SLANG = array( 'SaveAs' => 'Save as', 'SaveFilter' => 'Save Filter', 'SaveJPEGs' => 'Save JPEGs', - 'Save' => 'Save', - 'Scale' => 'Scale', - 'Score' => 'Score', - 'Secs' => 'Secs', 'Sectionlength' => 'Section length', 'SelectMonitors' => 'Select Monitors', - 'Select' => 'Select', 'SelectFormat' => 'Select Format', 'SelectLog' => 'Select Log', 'SelfIntersecting' => 'Polygon edges must not intersect', 'SetNewBandwidth' => 'Set New Bandwidth', 'SetPreset' => 'Set Preset', - 'Set' => 'Set', - 'Settings' => 'Settings', 'ShowFilterWindow' => 'Show Filter Window', 'ShowTimeline' => 'Show Timeline', - 'Shutdown' => 'Shutdown', 'SignalCheckColour' => 'Signal Check Colour', 'SignalCheckPoints' => 'Signal Check Points', - 'Size' => 'Size', 'SkinDescription' => 'Change the skin for this session', 'CSSDescription' => 'Change the css for this session', - 'Sleep' => 'Sleep', 'SortAsc' => 'Asc', 'SortBy' => 'Sort by', 'SortDesc' => 'Desc', - 'Source' => 'Source', 'SourceColours' => 'Source Colours', 'SourcePath' => 'Source Path', 'SourceType' => 'Source Type', 'SpeedHigh' => 'High Speed', 'SpeedLow' => 'Low Speed', 'SpeedMedium' => 'Medium Speed', - 'Speed' => 'Speed', 'SpeedTurbo' => 'Turbo Speed', - 'Start' => 'Start', - 'State' => 'State', - 'Stats' => 'Stats', - 'Status' => 'Status', 'StatusUnknown' => 'Unknown', 'StatusConnected' => 'Capturing', 'StatusNotRunning' => 'Not Running', @@ -790,23 +612,12 @@ $SLANG = array( 'StepMedium' => 'Medium Step', 'StepNone' => 'No Step', 'StepSmall' => 'Small Step', - 'Step' => 'Step', - 'Stills' => 'Stills', - 'Stopped' => 'Stopped', - 'Stop' => 'Stop', 'StorageArea' => 'Storage Area', 'StorageDoDelete' => 'Do Deletes', 'StorageScheme' => 'Scheme', 'StreamReplayBuffer' => 'Stream Replay Image Buffer', - 'Stream' => 'Stream', - 'Submit' => 'Submit', - 'System' => 'System', 'TargetColorspace' => 'Target colorspace', - 'Tele' => 'Tele', - 'Thumbnail' => 'Thumbnail', - 'Tilt' => 'Tilt', 'TimeDelta' => 'Time Delta', - 'Timeline' => 'Timeline', 'TimelineTip1' => 'Pass your mouse over the graph to view a snapshot image and event details.', // Added 2013.08.15. 'TimelineTip2' => 'Click on the coloured sections of the graph, or the image, to view the event.', // Added 2013.08.15. 'TimelineTip3' => 'Click on the background to zoom in to a smaller time period based around your click.', // Added 2013.08.15. @@ -815,44 +626,25 @@ $SLANG = array( 'TimestampLabelX' => 'Timestamp Label X', 'TimestampLabelY' => 'Timestamp Label Y', 'TimestampLabelSize' => 'Font Size', - 'Timestamp' => 'Timestamp', 'TimeStamp' => 'Time Stamp', - 'Time' => 'Time', - 'Today' => 'Today', - 'Tools' => 'Tools', - 'Total' => 'Total', 'TotalBrScore' => 'Total
Score', 'TrackDelay' => 'Track Delay', 'TrackMotion' => 'Track Motion', - 'Triggers' => 'Triggers', 'TurboPanSpeed' => 'Turbo Pan Speed', 'TurboTiltSpeed' => 'Turbo Tilt Speed', - 'Type' => 'Type', 'TZUnset' => 'Unset - use value in php.ini', - 'Unarchive' => 'Unarchive', - 'Undefined' => 'Undefined', - 'Units' => 'Units', - 'Unknown' => 'Unknown', 'UpdateAvailable' => 'An update to ZoneMinder is available.', 'UpdateNotNecessary' => 'No update is necessary.', - 'Update' => 'Update', - 'Upload' => 'Upload', - 'Updated' => 'Updated', - 'UsedPlugins' => 'Used Plugins', + 'UsedPlugins' => 'Used Plugins', 'UseFilterExprsPost' => ' filter expressions', // This is used at the end of the phrase 'use N filter expressions' 'UseFilterExprsPre' => 'Use ', // This is used at the beginning of the phrase 'use N filter expressions' 'UseFilter' => 'Use Filter', - 'Username' => 'Username', - 'Users' => 'Users', - 'User' => 'User', - 'Value' => 'Value', 'VersionIgnore' => 'Ignore this version', 'VersionRemindDay' => 'Remind again in 1 day', 'VersionRemindHour' => 'Remind again in 1 hour', 'VersionRemindNever' => 'Don\'t remind about new versions', 'VersionRemindWeek' => 'Remind again in 1 week', 'VersionRemindMonth' => 'Remind again in 1 month', - 'Version' => 'Version', 'ViewMatches' => 'View Matches', 'VideoFormat' => 'Video Format', 'VideoGenFailed' => 'Video Generation Failed!', @@ -862,31 +654,19 @@ $SLANG = array( 'VideoGenSucceeded' => 'Video Generation Succeeded!', 'VideoSize' => 'Video Size', 'VideoWriter' => 'Video Writer', - 'Video' => 'Video', 'ViewAll' => 'View All', 'ViewEvent' => 'View Event', 'ViewPaged' => 'View Paged', - 'View' => 'View', - 'V4LCapturesPerFrame' => 'Captures Per Frame', - 'V4LMultiBuffer' => 'Multi Buffering', - 'Wake' => 'Wake', + 'V4LCapturesPerFrame' => 'Captures Per Frame', + 'V4LMultiBuffer' => 'Multi Buffering', 'WarmupFrames' => 'Warmup Frames', - 'Watch' => 'Watch', 'WebColour' => 'Web Colour', - 'Web' => 'Web', 'WebSiteUrl' => 'Website URL', - 'Week' => 'Week', 'WhiteBalance' => 'White Balance', - 'White' => 'White', - 'Wide' => 'Wide', 'X10ActivationString' => 'X10 Activation String', 'X10InputAlarmString' => 'X10 Input Alarm String', 'X10OutputAlarmString' => 'X10 Output Alarm String', - 'X10' => 'X10', - 'X' => 'X', - 'Yes' => 'Yes', 'YouNoPerms' => 'You do not have permissions to access this resource.', - 'Y' => 'Y', 'ZoneAlarmColour' => 'Alarm Colour (Red/Green/Blue)', 'ZoneArea' => 'Zone Area', 'ZoneFilterSize' => 'Filter Width/Height (pixels)', @@ -898,11 +678,8 @@ $SLANG = array( 'ZoneMinMaxPixelThres' => 'Min/Max Pixel Threshold (0-255)', 'ZoneOverloadFrames' => 'Overload Frame Ignore Count', 'ZoneExtendAlarmFrames' => 'Extend Alarm Frame Count', - 'Zones' => 'Zones', - 'Zone' => 'Zone', 'ZoomIn' => 'Zoom In', 'ZoomOut' => 'Zoom Out', - 'Zoom' => 'Zoom', ); // Complex replacements with formatting and/or placements, must be passed through sprintf @@ -960,17 +737,14 @@ $VLANG = array( // // In languages such as English this is fairly simple // Note this still has to be used with printf etc to get the right formatting -function zmVlang( $langVarArray, $count ) -{ - krsort( $langVarArray ); - foreach ( $langVarArray as $key=>$value ) - { - if ( abs($count) >= $key ) - { - return( $value ); - } +function zmVlang($langVarArray, $count) { + krsort($langVarArray); + foreach ($langVarArray as $key=>$value) { + if (abs($count) >= $key) { + return $value; } - die( 'Error, unable to correlate variable language string' ); + } + ZM\Error('Unable to correlate variable language string'); } // This is an version that could be used in the Russian example above From 2336926d90f97646119f41e06f614b23768c6669 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 14:16:01 -0500 Subject: [PATCH 461/501] Rework to remove static temp_img_buffer. Is now a class member. Must be allocated as needed. Use new reworked zm_sendfile and handle if not all bytes are sent. Fixes #3437. --- src/zm_eventstream.cpp | 47 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 34f693f36..4521aaf89 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -799,7 +799,13 @@ bool EventStream::sendFrame(Microseconds delta_us) { } Image *send_image = prepareImage(image); - static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; + if (temp_img_buffer_size < send_image->Size()) { + Debug(1, "Resizing image buffer from %zu to %u", + temp_img_buffer_size, send_image->Size()); + delete[] temp_img_buffer; + temp_img_buffer = new uint8_t[send_image->Size()]; + temp_img_buffer_size = send_image->Size(); + } int img_buffer_size = 0; uint8_t *img_buffer = temp_img_buffer; @@ -1098,14 +1104,12 @@ void EventStream::runStream() { } // end void EventStream::runStream() bool EventStream::send_file(const std::string &filepath) { - FILE *fdj = nullptr; - fdj = fopen(filepath.c_str(), "rb"); + FILE *fdj = fopen(filepath.c_str(), "rb"); if (!fdj) { Error("Can't open %s: %s", filepath.c_str(), strerror(errno)); std::string error_message = stringtf("Can't open %s: %s", filepath.c_str(), strerror(errno)); return sendTextFrame(error_message.c_str()); } -#if HAVE_SENDFILE static struct stat filestat; if (fstat(fileno(fdj), &filestat) < 0) { fclose(fdj); /* Close the file handle */ @@ -1117,33 +1121,30 @@ bool EventStream::send_file(const std::string &filepath) { Info("File size is zero. Unable to send raw frame %d: %s", curr_frame_id, strerror(errno)); return false; } - if (0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size)) { + if (0 > fprintf(stdout, "Content-Length: %jd\r\n\r\n", filestat.st_size)) { fclose(fdj); /* Close the file handle */ Info("Unable to send raw frame %d: %s", curr_frame_id, strerror(errno)); return false; } - int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size); - if (rc == (int)filestat.st_size) { + ssize_t remaining = filestat.st_size; + + while (remaining > 0) { + ssize_t rc = zm_sendfile(fileno(stdout), fileno(fdj), nullptr, remaining); + if (rc < 0) break; + if (rc > 0) { + remaining -= rc; + } + } // end while remaining + + if (!remaining) { // Success fclose(fdj); /* Close the file handle */ return true; } - Warning("Unable to send raw frame %d: %s rc %d != %d", - curr_frame_id, strerror(errno), rc, (int)filestat.st_size); -#endif - - static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; - - uint8_t *img_buffer = temp_img_buffer; - int img_buffer_size = fread(img_buffer, 1, sizeof(temp_img_buffer), fdj); - fclose(fdj); /* Close the file handle */ - if ( !img_buffer_size ) { - Info("Unable to read raw frame %d: %s", curr_frame_id, strerror(errno)); - return false; - } - - return send_buffer(img_buffer, img_buffer_size); -} + Warning("Unable to send raw frame %d: %s %zu remaining", + curr_frame_id, strerror(errno), remaining); + return false; +} // end bool EventStream::send_file(const std::string &filepath) bool EventStream::send_buffer(uint8_t* buffer, int size) { if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) { From 243c780a6a22e1af7aa3a23ee4a3964effed485f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 14:16:25 -0500 Subject: [PATCH 462/501] cleanup and add fallback to read/write if no sendfile support. --- src/zm_sendfile.h | 51 +++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/zm_sendfile.h b/src/zm_sendfile.h index 3568e2fea..4edabccab 100644 --- a/src/zm_sendfile.h +++ b/src/zm_sendfile.h @@ -3,37 +3,44 @@ #ifdef HAVE_SENDFILE4_SUPPORT #include -ssize_t zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { - size_t remaining = size; - while (remaining) { - ssize_t err = sendfile(out_fd, in_fd, offset, remaining); - if (err < 0) { - return -errno; - } - remaining -= err; - offset += err; - } - - return size-remaining; -} #elif HAVE_SENDFILE7_SUPPORT #include #include #include -ssize_t zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) { +#else +#include +#endif + +/* Function to send the contents of a file. Will use sendfile or fall back to reading/writing */ + +ssize_t zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { +#ifdef HAVE_SENDFILE4_SUPPORT + ssize_t err = sendfile(out_fd, in_fd, offset, size); + if (err < 0) { + return -errno; + } + return err; + +#elif HAVE_SENDFILE7_SUPPORT ssize_t err = sendfile(in_fd, out_fd, *offset, size, nullptr, &size, 0); if (err && errno != EAGAIN) return -errno; - - if (size) { - *offset += size; - return size; + return size; +#else + uint8_t buffer[size]; + ssize_t err = read(in_fd, buffer, size); + if (err < 0) { + Error("Unable to read %zu bytes: %s", size, strerror(errno)); + return -errno; } - return -EAGAIN; -} -#else -#error "Your platform does not support sendfile. Sorry." + err = fwrite(out_fd, buffer, size); + if (err < 0) { + Error("Unable to write %zu bytes: %s", size, strerror(errno)); + return -errno; + } + return err; #endif +} #endif // ZM_SENDFILE_H From 67563737c3fdc58e972cbeb298c6316f905bf0a7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 14:16:46 -0500 Subject: [PATCH 463/501] Rework to remove static temp_img_buffer. Is now a class member. Must be allocated as needed. --- src/zm_stream.cpp | 6 ++---- src/zm_stream.h | 7 ++++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 084bd6754..b3b3aa120 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -31,10 +31,8 @@ constexpr Seconds StreamBase::MAX_STREAM_DELAY; constexpr Milliseconds StreamBase::MAX_SLEEP; StreamBase::~StreamBase() { - if (vid_stream) { - delete vid_stream; - vid_stream = nullptr; - } + delete vid_stream; + delete temp_img_buffer; closeComms(); } diff --git a/src/zm_stream.h b/src/zm_stream.h index d89d97e7f..de58fcf7b 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -144,6 +144,9 @@ protected: CmdMsg msg; + unsigned char *temp_img_buffer; // Used when encoding or sending file data + size_t temp_img_buffer_size; + protected: bool loadMonitor(int monitor_id); bool checkInitialised(); @@ -182,7 +185,9 @@ public: actual_fps(0.0), frame_count(0), last_frame_count(0), - frame_mod(1) + frame_mod(1), + temp_img_buffer(nullptr), + temp_img_buffer_size(0) { memset(&loc_sock_path, 0, sizeof(loc_sock_path)); memset(&loc_addr, 0, sizeof(loc_addr)); From c9de883a7c1d9a61b76ccaaca62677b9e7c6581a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 14:18:37 -0500 Subject: [PATCH 464/501] Rework to remove static temp_img_buffer. Is now a class member. Must be allocated as needed. --- src/zm_monitorstream.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index a6232a23e..2bb82d360 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -422,12 +422,17 @@ bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) { /* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.count()); } else { - static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; + if (temp_img_buffer_size < send_image->Size()) { + Debug(1, "Resizing image buffer from %zu to %u", + temp_img_buffer_size, send_image->Size()); + delete[] temp_img_buffer; + temp_img_buffer = new uint8_t[send_image->Size()]; + temp_img_buffer_size = send_image->Size(); + } int img_buffer_size = 0; unsigned char *img_buffer = temp_img_buffer; - switch ( type ) { case STREAM_JPEG : send_image->EncodeJpeg(img_buffer, &img_buffer_size); From b9e6cf803871a758be5a0b408f126e70db541389 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 16 Feb 2022 17:27:12 -0500 Subject: [PATCH 465/501] Spacing, remove dead code, add comments. When implementing CLOSE_ALARM mode, check Alarm Frames against alarm_frame_count instead of 0. --- src/zm_monitor.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index c094aecff..026fad766 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1747,7 +1747,7 @@ bool Monitor::Analyse() { Event::StringSetMap noteSetMap; #ifdef WITH_GSOAP - if (onvif_event_listener && Event_Poller_Healthy) { + if (onvif_event_listener && Event_Poller_Healthy) { if (Poll_Trigger_State) { score += 9; Debug(1, "Triggered on ONVIF"); @@ -1856,9 +1856,6 @@ bool Monitor::Analyse() { // So... Debug(1, "Waiting for decode"); packet_lock->wait(); - //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all - //packetqueue.wait(); - ////packet_lock->lock(); } // end while ! decoded if (zm_terminate or analysis_thread->Stopped()) { delete packet_lock; @@ -1890,7 +1887,7 @@ bool Monitor::Analyse() { Image v_image(snap->in_frame->width, snap->in_frame->height, 1, ZM_SUBPIX_ORDER_NONE, snap->in_frame->data[0], 0); ref_image.Assign(v_image); } else { - Debug(1, "assigning refimage from snap->image"); + Debug(1, "assigning refimage from snap->image"); ref_image.Assign(*(snap->image)); } alarm_image.Assign(*(snap->image)); @@ -1956,7 +1953,7 @@ bool Monitor::Analyse() { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() - && !event->AlarmFrames() + && (event->AlarmFrames() < alarm_frame_count) && (event_close_mode == CLOSE_ALARM) // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead && (event->Duration() >= min_section_length) @@ -2016,7 +2013,7 @@ bool Monitor::Analyse() { if ( ((analysis_image_count - last_alarm_count) > post_event_count) && - (event->Duration() >= min_section_length)) { + (event->Duration() >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); if ( @@ -2051,6 +2048,7 @@ bool Monitor::Analyse() { Event::EmptyPreAlarmFrames(); } // end if score or not + // At this point, snap ONLY has motion score, so this adds other sources if (score > snap->score) snap->score = score; From 909c0e903f8660ed9ede5e23052156327912a738 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 17 Feb 2022 13:30:01 -0500 Subject: [PATCH 466/501] Include EndDateTimeShort in event ajax response --- web/ajax/status.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/ajax/status.php b/web/ajax/status.php index 0d17213a5..b37a3bda6 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -113,6 +113,7 @@ $statusData = array( 'StartTimeShort' => array( 'sql' => 'date_format( StartDateTime, \''.MYSQL_FMT_DATETIME_SHORT.'\' )' ), 'StartDateTimeShort' => array( 'sql' => 'date_format( StartDateTime, \''.MYSQL_FMT_DATETIME_SHORT.'\' )' ), 'EndDateTime' => true, + 'EndDateTimeShort' => array( 'sql' => 'date_format( EndDateTime, \''.MYSQL_FMT_DATETIME_SHORT.'\' )' ), 'Width' => true, 'Height' => true, 'Length' => true, @@ -141,6 +142,7 @@ $statusData = array( 'StartTimeShort' => array( 'sql' => 'date_format( StartDateTime, \''.MYSQL_FMT_DATETIME_SHORT.'\' )' ), 'StartDateTimeShort' => array( 'sql' => 'date_format( StartDateTime, \''.MYSQL_FMT_DATETIME_SHORT.'\' )' ), 'EndDateTime' => true, + 'EndDateTimeShort' => array( 'sql' => 'date_format( EndDateTime, \''.MYSQL_FMT_DATETIME_SHORT.'\' )' ), 'Width' => true, 'Height' => true, 'Length' => true, From 8dedac1a216fe5f78c8a170f759b53d14b559c11 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 17 Feb 2022 13:30:50 -0500 Subject: [PATCH 467/501] Handle empty endtime more gracefully. If there is a next event just jump to it. --- web/skins/classic/views/js/event.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 31363e154..1c7a11954 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -52,14 +52,19 @@ function vjsReplay() { var overLaid = $j("#videoobj"); overLaid.append('

No more events

'); } else { - var endTime = (Date.parse(eventData.EndDateTime)).getTime(); + if (!eventData.EndDateTime) { + // No EndTime but have a next event, just go to it. + streamNext(true); + return; + } + var endTime = Date.parse(eventData.EndDateTime).getTime(); var nextStartTime = nextEventStartTime.getTime(); //nextEventStartTime.getTime() is a mootools workaround, highjacks Date.parse if ( nextStartTime <= endTime ) { streamNext(true); return; } - var overLaid = $j("#videoobj"); vid.pause(); + var overLaid = $j("#videoobj"); overLaid.append('

'); var gapDuration = (new Date().getTime()) + (nextStartTime - endTime); var messageP = $j('.vjsMessage'); From adf84133336133915eec031ee8a8009eaac443fb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 17 Feb 2022 13:31:25 -0500 Subject: [PATCH 468/501] Include EndDateTimeShort in event stats --- web/skins/classic/views/js/event.js.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/skins/classic/views/js/event.js.php b/web/skins/classic/views/js/event.js.php index c2b61f395..700558cca 100644 --- a/web/skins/classic/views/js/event.js.php +++ b/web/skins/classic/views/js/event.js.php @@ -52,6 +52,7 @@ var eventData = { StartDateTime: 'StartDateTime() ?>', StartDateTimeShort: 'StartDateTime())) ?>', EndDateTime: 'EndDateTime() ?>', + EndDateTimeShort: 'EndDateTime()? strftime(STRF_FMT_DATETIME_SHORT, strtotime($Event->EndDateTime())) : '' ?>', Frames: 'Frames() ?>', AlarmFrames: 'AlarmFrames() ?>', TotScore: 'TotScore() ?>', @@ -75,6 +76,7 @@ var eventDataStrings = { Cause: '', Notes: '', StartDateTimeShort: '', + EndDateTimeShort: '', Length: '', Frames: '', AlarmFrames: '', From 28f3cb18e4458385e6d67e449bd66bdcc05eca1d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 18 Feb 2022 15:59:00 -0500 Subject: [PATCH 469/501] Make std function warning into a debug. Warning will happen if they are actually used --- src/zm_image.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 35efa8774..7a2048102 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -98,7 +98,7 @@ void Image::update_function_pointers() { delta8_abgr = &std_delta8_abgr; delta8_gray8 = &std_delta8_gray8; blend = &std_blend; - Warning("Using slow std functions because pixels %d mod 4=%d", pixels, pixels%4); + Debug(1, "Using slow std functions because pixels %d mod 4=%d", pixels, pixels%4); } else { // Use either sse or neon, or loop unrolled version delta8_rgb = fptr_delta8_rgb; @@ -1167,7 +1167,7 @@ bool Image::WriteJpeg(const std::string &filename, } else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) { cinfo->in_color_space = JCS_EXT_XBGR; } else { - Warning("Unknwon subpixelorder %d", subpixelorder); + Warning("Unknown subpixelorder %d", subpixelorder); /* Assume RGBA */ cinfo->in_color_space = JCS_EXT_RGBX; } From 002b2c39aa3f97e5200b82405bb5306dfb24c63d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 18 Feb 2022 17:08:40 -0500 Subject: [PATCH 470/501] fix button assignments. Don't abort ajax, as it might be important --- web/js/MonitorStream.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index 86b9b97e0..110f1665d 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -26,7 +26,7 @@ function MonitorStream(monitorData) { this.buttons = {}; // index by name this.setButton = function(name, element) { - this.buttons.name = element; + this.buttons[name] = element; }; this.element = null; @@ -380,6 +380,8 @@ function MonitorStream(monitorData) { if ('enableAlarmButton' in this.buttons) { this.buttons.enableAlarmButton.addClass('disabled'); this.buttons.enableAlarmButton.prop('title', disableAlarmsStr); + } else { + console.log('enableAlarmButton not found in buttons'); } if ('forceAlarmButton' in this.buttons) { if (streamStatus.forced) { @@ -390,8 +392,11 @@ function MonitorStream(monitorData) { this.buttons.forceAlarmButton.prop('title', forceAlarmStr); } this.buttons.forceAlarmButton.prop('disabled', false); + } else { + console.log('forceAlarmButton not found in buttons'); } } else { + console.log("streamStatus not enabled"); if ('enableAlarmButton' in this.buttons) { this.buttons.enableAlarmButton.removeClass('disabled'); this.buttons.enableAlarmButton.prop('title', enableAlarmsStr); @@ -462,6 +467,8 @@ function MonitorStream(monitorData) { this.alarmCommand = function(command) { if (this.ajaxQueue) { + console.log("Aborting in progress ajax for alarm"); + // Doing this for responsiveness, but we could be aborting something important. Need smarter logic this.ajaxQueue.abort(); } const alarmCmdParms = Object.assign({}, this.streamCmdParms); @@ -485,9 +492,6 @@ function MonitorStream(monitorData) { } this.streamCmdReq = function(streamCmdParms) { - if (this.ajaxQueue) { - this.ajaxQueue.abort(); - } this.ajaxQueue = jQuery.ajaxQueue({url: this.url, data: streamCmdParms, dataType: "json"}) .done(this.getStreamCmdResponse.bind(this)) .fail(this.onFailure.bind(this)); From 1a54a96c0479537411205a6e6c84961409520833 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 18 Feb 2022 17:09:24 -0500 Subject: [PATCH 471/501] Improve title on alarm buttons when we don't have permission. Move onclick from data-on-click to setup in initPage only if permitted --- web/skins/classic/views/js/watch.js | 8 ++++++++ web/skins/classic/views/watch.php | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index 21026c3c8..4af160389 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -833,6 +833,14 @@ function initPage() { monitorStream.setButton('enableAlarmButton', enableAlmBtn); monitorStream.setButton('forceAlarmButton', forceAlmBtn); monitorStream.setButton('zoomOutButton', $j('zoomOutBtn')); + if (canEdit.Monitors) { + // Will be enabled by streamStatus ajax + enableAlmBtn.on('click', cmdAlarm); + forceAlmBtn.on('click', cmdForce); + } else { + forceAlmBtn.prop('title', forceAlmBtn.prop('title') + ': disabled because cannot edit Monitors'); + enableAlmBtn.prop('title', enableAlmBtn.prop('title') + ': disabled because cannot edit Monitors'); + } /* if (streamMode == 'single') { diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index fdcdae06d..8ceb06ab2 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -159,8 +159,8 @@ xhtmlHeaders(__FILE__, $monitor->Name().' - '.translate('Feed')); - - + +
translate('Enabled'), '0'=>'Disabled'), $monitor->ONVIF_Event_Listener()); ?>
translate('Enabled'), '0'=>'Disabled'), $monitor->use_Amcrest_API()); ?>