diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 61922771b..08ae4abf2 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -21,28 +21,6 @@ # # ========================================================================== -=head1 NAME - -zmfilter.pl - ZoneMinder tool to filter events - -=head1 SYNOPSIS - -zmfilter.pl [-f ,--filter=] [--filter_id=] | -v, --version - -=head1 DESCRIPTION - -This script continuously monitors the recorded events for the given -monitor and applies any filters which would delete and/or upload -matching events. - -=head1 OPTIONS - - --f{filter name}, --filter={filter name} - The name of a specific filter to run ---filter_id={filter id} - The id of a specific filter to run --v, --version - Print ZoneMinder version - -=cut use strict; use bytes; @@ -163,7 +141,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; -if ( !EVENT_PATH ) { +if (!EVENT_PATH) { Error('No event path defined. Config was '.$Config{ZM_DIR_EVENTS}); die; } @@ -195,22 +173,22 @@ if ( ! ( $filter_name or $filter_id ) ) { my @filters; my $last_action = 0; -while( !$zm_terminate ) { +while (!$zm_terminate) { my $now = time; - if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { + if (($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY}) { Debug('Reloading filters'); $last_action = $now; @filters = getFilters({ Name=>$filter_name, Id=>$filter_id }); } - foreach my $filter ( @filters ) { + foreach my $filter (@filters) { last if $zm_terminate; - if ( $$filter{Concurrent} and ! ( $filter_id or $filter_name ) ) { + if ($$filter{Concurrent} and !($filter_id or $filter_name)) { my ( $proc ) = $0 =~ /(\S+)/; my ( $id ) = $$filter{Id} =~ /(\d+)/; Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}"); - system(qq`$proc --filter "$$filter{Name}" &`); + system(qq`$proc --filter_id $id &`); } else { checkFilter($filter); } @@ -1051,9 +1029,7 @@ sub executeCommand { my $filter = shift; my $Event = shift; - my $event_path = $Event->Path(); - - my $command = $filter->{AutoExecuteCmd}.' '.$event_path; + my $command = $filter->{AutoExecuteCmd}.' '.$Event->Path(); $command = substituteTags($command, $filter, $Event); Info("Executing '$command'"); @@ -1063,15 +1039,37 @@ sub executeCommand { chomp($output); Debug("Output: $output"); } - if ( $status ) { + if ($status) { Error("Command '$command' exited with status: $status"); return 0; } else { - my $sql = 'UPDATE `Events` SET `Executed` = 1 WHERE `Id` = ?'; - my $sth = $dbh->prepare_cached($sql) - or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute( $Event->{Id} ) - or Fatal("Unable to execute '$sql': ".$dbh->errstr()); + zmSQLExecute('UPDATE `Events` SET `Executed` = 1 WHERE `Id` = ?', $Event->{Id}); } - return( 1 ); + return 1; } + +1; +__END__ + +=head1 NAME + +zmfilter.pl - ZoneMinder tool to select events and perform actions on them + +=head1 SYNOPSIS + +zmfilter.pl [-f ,--filter=] [--filter_id=] [--daemon] | -v, --version + +=head1 DESCRIPTION + +This script performs a specified database query to select recorded events and performs specified actions on them +such as email reporting, deleting, moving, etc. If the --daemon option is given it will remain resident, repeating +the query and applying actions. This is normally managed by zmdc.pl however it can be used manually as well. + +=head1 OPTIONS + + -f{filter name}, --filter={filter name} - The name of a specific filter to run + --filter_id={filter id} - The id of a specific filter to run + --daemon - Causes zmfilter.pl to stay running endlessly repeating the filter(s). + -v, --version - Print ZoneMinder version + +=cut diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 0410d39bc..fff655177 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3047,9 +3047,6 @@ int Monitor::PrimeCapture() { int Monitor::PreCapture() const { return camera->PreCapture(); } int Monitor::PostCapture() const { return camera->PostCapture(); } int Monitor::Close() { - if (close_event_thread.joinable()) { - close_event_thread.join(); - } // Because the stream indexes may change we have to clear out the packetqueue if (decoder) { decoder->Stop(); @@ -3067,10 +3064,14 @@ int Monitor::Close() { video_fifo = nullptr; } + if (close_event_thread.joinable()) { + close_event_thread.join(); + } std::lock_guard lck(event_mutex); if (event) { Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name.c_str(), image_count, event->Id()); closeEvent(); + close_event_thread.join(); } if (camera) camera->Close(); return 1; 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 ?> diff --git a/web/includes/config.php.in b/web/includes/config.php.in index e044df58f..d285d8616 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -87,11 +87,12 @@ define('ZM_PCRE', '@ZM_PCRE@'); // PCRE support enabled // // Alarm states // -define('STATE_IDLE', 0); -define('STATE_PREALARM', 1); -define('STATE_ALARM', 2); -define('STATE_ALERT', 3); -define('STATE_TAPE', 4); +define('STATE_UNKNOWN', 0); +define('STATE_IDLE', 1); +define('STATE_PREALARM', 2); +define('STATE_ALARM', 3); +define('STATE_ALERT', 4); +define('STATE_TAPE', 5); // // DVR Control Commands 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%; +} 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; +} diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 87e08310e..2b7980c3a 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -90,3 +90,18 @@ unset($user_without_password['Password']); echo json_encode($user_without_password); ?>; var running = ; + +var STATE_UNKNOWN = ; +var STATE_IDLE = ; +var STATE_PREALARM = ; +var STATE_ALARM = ; +var STATE_ALERT = ; +var STATE_TAPE = ; + +var stateStrings = new Array(); +stateStrings[STATE_UNKNOWN] = ""; +stateStrings[STATE_IDLE] = ""; +stateStrings[STATE_PREALARM] = ""; +stateStrings[STATE_ALARM] = ""; +stateStrings[STATE_ALERT] = ""; +stateStrings[STATE_TAPE] = ""; diff --git a/web/skins/classic/views/console.php b/web/skins/classic/views/console.php index f0f654f56..e49b2c467 100644 --- a/web/skins/classic/views/console.php +++ b/web/skins/classic/views/console.php @@ -260,31 +260,37 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { ?> @@ -317,7 +323,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { } ?> - lens + lens ' : '>') . validHtmlStr($monitor['Name']) ?>
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 = ''; diff --git a/web/skins/classic/views/js/montage.js.php b/web/skins/classic/views/js/montage.js.php index 9b6962119..1c1c4130e 100644 --- a/web/skins/classic/views/js/montage.js.php +++ b/web/skins/classic/views/js/montage.js.php @@ -1,18 +1,6 @@ // // Import constants // -var STATE_IDLE = ; -var STATE_PREALARM = ; -var STATE_ALARM = ; -var STATE_ALERT = ; -var STATE_TAPE = ; - -var stateStrings = new Array(); -stateStrings[STATE_IDLE] = ""; -stateStrings[STATE_PREALARM] = ""; -stateStrings[STATE_ALARM] = ""; -stateStrings[STATE_ALERT] = ""; -stateStrings[STATE_TAPE] = ""; var CMD_QUERY = ; diff --git a/web/skins/classic/views/js/watch.js.php b/web/skins/classic/views/js/watch.js.php index 5b4568dfc..74a86100c 100644 --- a/web/skins/classic/views/js/watch.js.php +++ b/web/skins/classic/views/js/watch.js.php @@ -9,18 +9,6 @@ // // Import constants // -var STATE_IDLE = ; -var STATE_PREALARM = ; -var STATE_ALARM = ; -var STATE_ALERT = ; -var STATE_TAPE = ; - -var stateStrings = new Array(); -stateStrings[STATE_IDLE] = ""; -stateStrings[STATE_PREALARM] = ""; -stateStrings[STATE_ALARM] = ""; -stateStrings[STATE_ALERT] = ""; -stateStrings[STATE_TAPE] = ""; var deleteString = ""; diff --git a/web/skins/classic/views/js/zone.js.php b/web/skins/classic/views/js/zone.js.php index 37eb6db82..2ba286d55 100644 --- a/web/skins/classic/views/js/zone.js.php +++ b/web/skins/classic/views/js/zone.js.php @@ -94,20 +94,6 @@ var deleteString = ""; // Imported from watch.js.php and modified for new zone edit view // -var STATE_IDLE = ; -var STATE_PREALARM = ; -var STATE_ALARM = ; -var STATE_ALERT = ; -var STATE_TAPE = ; - -var stateStrings = new Array(); -stateStrings[STATE_IDLE] = ""; -stateStrings[STATE_PREALARM] = ""; -stateStrings[STATE_ALARM] = ""; -stateStrings[STATE_ALERT] = ""; -stateStrings[STATE_TAPE] = ""; - - var CMD_PAUSE = ; var CMD_PLAY = ; var CMD_STOP = ; diff --git a/web/skins/classic/views/js/zones.js.php b/web/skins/classic/views/js/zones.js.php index 338095552..414231d12 100644 --- a/web/skins/classic/views/js/zones.js.php +++ b/web/skins/classic/views/js/zones.js.php @@ -17,25 +17,10 @@ monitorData[monitorData.length] = { } ?> -var STATE_IDLE = ; -var STATE_PREALARM = ; -var STATE_ALARM = ; -var STATE_ALERT = ; -var STATE_TAPE = ; - -var stateStrings = new Array(); -stateStrings[STATE_IDLE] = ""; -stateStrings[STATE_PREALARM] = ""; -stateStrings[STATE_ALARM] = ""; -stateStrings[STATE_ALERT] = ""; -stateStrings[STATE_TAPE] = ""; - - var CMD_PAUSE = ; var CMD_PLAY = ; var CMD_STOP = ; var CMD_QUERY = ; var CMD_QUIT = ; - var statusRefreshTimeout = ; 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 .= '