Merge branch 'master' into add_manufacturer_model_to_monitors

This commit is contained in:
Isaac Connor 2021-11-05 10:13:41 -04:00
commit 1fd99424bb
97 changed files with 2416 additions and 1647 deletions

View File

@ -541,7 +541,7 @@ CREATE TABLE `Monitors` (
`Longitude` DECIMAL(11,8),
`RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE,
`RTSPStreamName` varchar(255) NOT NULL default '',
`Importance` enum('Not','Less','Normal'),
`Importance` enum('Normal','Less','Not') NOT NULL default 'Normal',
PRIMARY KEY (`Id`)
) ENGINE=@ZM_MYSQL_ENGINE@;

View File

@ -28,8 +28,8 @@ SET @s = (SELECT IF(
AND table_name = 'Monitors'
AND column_name = 'TotalEvents'
) > 0,
"SELECT 'Column TotalEvents is already removed from Monitors'",
"ALTER TABLE `Monitors` DROP `TotalEvents`"
"ALTER TABLE `Monitors` DROP `TotalEvents`",
"SELECT 'Column TotalEvents is already removed from Monitors'"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
@ -50,8 +50,8 @@ SET @s = (SELECT IF(
AND table_name = 'Monitors'
AND column_name = 'TotalEventDiskSpace'
) > 0,
"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'",
"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`"
"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`",
"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

View File

@ -2,3 +2,6 @@ UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left","left":"0px
UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='2 Wide';
UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='4 Wide';
UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='5 Wide';
UPDATE Monitors set Importance = 'Normal' where Importance IS NULL;
ALTER TABLE `Monitors` MODIFY `Importance` enum('Normal','Less','Not') NOT NULL default 'Normal';

View File

@ -5,6 +5,12 @@ set +e
create_db () {
echo "Checking for db"
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
if [ $? -ne 0 ]; then
echo "Cannot talk to database. You will have to create the db manually with something like:";
echo "cat /usr/share/zoneminder/db/zm_create.sql | mysql -u root";
return;
fi
# test if database if already present...
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
echo "Creating zm db"

View File

@ -3,6 +3,50 @@ Debian
.. contents::
Easy Way: Debian 11 (Bullseye)
------------------------
This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye).
**Step 1:** Setup Sudo (optional but recommended)
By default Debian does not come with sudo, so you have to install it and configure it manually.
This step is optional but recommended and the following instructions assume that you have setup sudo.
If you prefer to setup ZoneMinder as root, do it at your own risk and adapt the following instructions accordingly.
::
apt install sudo
usermod -a -G sudo <username>
exit
Now your terminal session is back under your normal user. You can check that
you are now part of the sudo group with the command ``groups``, "sudo" should
appear in the list. If not, run ``newgrp sudo`` and check again with ``groups``.
**Step 2:** Update system and install zoneminder
Run the following commands.
::
sudo apt update
sudo apt upgrade
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``.
**Step 3:** Setup permissions for zm.conf
To make sure zoneminder can read the configuration file, run the following command.
::
sudo chgrp -c www-data /etc/zm/zm.conf
Congratulations! You should now be able to access zoneminder at ``http://yourhostname/zm``
Easy Way: Debian Buster
------------------------

View File

@ -2,7 +2,7 @@ An Easy To Use Docker Image
===========================
If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories:
* If you want to run the latest stable release, please use his `zoneminder repository <https://github.com/dlandon/zoneminder>`__.
* If you want to run the latest stable release, please use his `zoneminder machine learning repository <https://github.com/dlandon/zoneminder.machine.learning>`__.
* If you want to run the latest zoneminder master, please use his `zoneminder master repository <https://github.com/dlandon/zoneminder.master-docker>`__.
In both cases, instructions are provided in the repo README files.

View File

@ -86,7 +86,7 @@ Source Path
Use this field to enter the full URL of the stream or file your camera supports. This is usually an RTSP url. There are several methods to learn this:
* Check the documentation that came with your camera
* Look for your camera in the hardware compatibilty list in the `hardware compatibility wiki <https://wiki.zoneminder.com/Hardware_Compatibility_List>`__
* Look for your camera in the hardware compatibility list in the `hardware compatibility wiki <https://wiki.zoneminder.com/Hardware_Compatibility_List>`__
* Try ZoneMinder's new ONVIF probe feature
* Download and install the `ONVIF Device Manager <https://sourceforge.net/projects/onvifdm/>`__ onto a Windows machine
* Use Google to find third party sites, such as ispy, which document this information
@ -179,12 +179,12 @@ Storage Tab
The storage section allows for each monitor to configure if and how video and audio are recorded.
Save JPEGs
Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows to view an event anytime while it is being recorded.
Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows one to view an event anytime while it is being recorded.
* Disabled video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all.
* Frames only video is recorded in individual JPEG frames.
* Analysis images only (if available) video is recorded in invidual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames.
* Frames + Analysis images (if available) video is recorded twice, once as normal individual JPEG frames and once in invidual JPEG frames with analysis information overlaid.
* Analysis images only (if available) video is recorded in individual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames.
* Frames + Analysis images (if available) video is recorded twice, once as normal individual JPEG frames and once in individual JPEG frames with analysis information overlaid.
Video Writer
Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored.

View File

@ -34,7 +34,7 @@ Here is what the filter window looks like
* Update used disk space: calculates how much disk space is currently taken by the event and updates the db record.
* Create video for all matches: creates a video file of all the events that match
* Create video for all matches: ffmpeg will be used to create a video file (mp4) out of all the stored jpgs if using jpeg storage.
* Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%".
* Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceded by replacement arguents. eg: /usr/bin/script.sh %MN% will execute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%".
* Delete all matches: Deletes all the matched events.
* Email details of all matches: Sends an email to the configured address with details about the event.
* Copy all matches: copies the event files to another location, specified in the Copy To dropdown. The other location must be setup in the Storage Tab under options.

View File

@ -53,7 +53,7 @@ This screen is called the "console" screen in ZoneMinder and shows a summary of
* **B**: This brings up a color coded log window that shows various system and component level logs. This window is useful if you are trying to diagnose issues. Refer to :doc:`logging`.
* **C**: ZoneMinder allows you to group monitors for logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups.
* **D**: Filters are a powerful mechanism to perform actions when certain conditions are met. ZoneMinder comes with some preset filters that keep a tab of disk space and others. Many users create their own filters for more advanced actions like sending emails when certain events occur and more. Refer to :doc:`filterevents`.
* **E**: The Cycle option allows you to rotate between live views of each cofigured monitor.
* **E**: The Cycle option allows you to rotate between live views of each configured monitor.
* **F**: The Montage option shows a collage of your monitors. You can customize them including moving them around.
* **G**: Montage Review allows you to simultaneously view past events for different monitors. Note that this is a very resource intensive page and its performance will vary based on your system capabilities.
* **H**: Audit Events Report is more of a power user feature. This option looks for recording gaps in events and recording issues in mp4 files.

View File

@ -6,7 +6,7 @@ Here are some options for using ZoneMinder on Mobile devices:
Third party mobile clients
^^^^^^^^^^^^^^^^^^^^^^^^^^^
* zmNinja (`source code <https://github.com/pliablepixels/zmNinja>`__, needs APIs to be installed to work)
* Available in App Store, Play Store and for Desktops - `website <http://pliablepixels.github.io/zmNinja/>`__
* Available in App Store, Play Store and for Desktops - `website <http://pliablepixels.github.io/>`__
Using the existing web console
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -30,7 +30,7 @@ This screen allows you to configure various permissions on a per user basis. The
.. note:: if you are using zmNinja, users are required to have 'View' access to system because multi-server information is only available as part of this permission
- Bandwidth
- Specifies the maximum bandwith that this user can configure (Low, Medium or High)
- Specifies the maximum bandwidth that this user can configure (Low, Medium or High)
- API enabled
- Specifies if the ZoneMinder API is enabled for this user (needs to be on, if you are using a mobile app such as zmNinja)

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).
A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -2709,7 +2709,7 @@ Returns a L<ONVIF::Device::Elements::GetNetworkInterfacesResponse|ONVIF::Device:
=head3 SetNetworkInterfaces
For interoperability with a client unaware of the IEEE 802.11 extension a device shall retain its IEEE 802.11 configuration if the IEEE 802.11 configuration element isnt present in the request.
For interoperability with a client unaware of the IEEE 802.11 extension a device shall retain its IEEE 802.11 configuration if the IEEE 802.11 configuration element isn't present in the request.
Returns a L<ONVIF::Device::Elements::SetNetworkInterfacesResponse|ONVIF::Device::Elements::SetNetworkInterfacesResponse> object.
@ -3093,7 +3093,7 @@ Returns a L<ONVIF::Device::Elements::SetRelayOutputStateResponse|ONVIF::Device::
=head3 SendAuxiliaryCommand
tt:IRLamp|Auto Request to configure an IR illuminator attached to the unit so that it automatically turns ON and OFF. A device that indicates auxiliary service capability shall support this command.
tt:IRLamp|Auto - Request to configure an IR illuminator attached to the unit so that it automatically turns ON and OFF. A device that indicates auxiliary service capability shall support this command.
Returns a L<ONVIF::Device::Elements::SendAuxiliaryCommandResponse|ONVIF::Device::Elements::SendAuxiliaryCommandResponse> object.
@ -3288,7 +3288,7 @@ Returns a L<ONVIF::Device::Elements::GetSystemUrisResponse|ONVIF::Device::Elemen
=head3 StartFirmwareUpgrade
The value of the Content-Type header in the HTTP POST request shall be application/octetstream.
The value of the Content-Type header in the HTTP POST request shall be "application/octetstream".
Returns a L<ONVIF::Device::Elements::StartFirmwareUpgradeResponse|ONVIF::Device::Elements::StartFirmwareUpgradeResponse> object.
@ -3298,7 +3298,7 @@ Returns a L<ONVIF::Device::Elements::StartFirmwareUpgradeResponse|ONVIF::Device:
=head3 StartSystemRestore
The value of the Content-Type header in the HTTP POST request shall be application/octetstream.
The value of the Content-Type header in the HTTP POST request shall be "application/octetstream".
Returns a L<ONVIF::Device::Elements::StartSystemRestoreResponse|ONVIF::Device::Elements::StartSystemRestoreResponse> object.

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).
A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -2147,7 +2147,7 @@ Returns a L<ONVIF::Media::Elements::GetAudioOutputsResponse|ONVIF::Media::Elemen
=head3 CreateProfile
This operation creates a new empty media profile. The media profile shall be created in the device and shall be persistent (remain after reboot). A created profile shall be deletable and a device shall set the fixed attribute to false in the returned Profile.
This operation creates a new empty media profile. The media profile shall be created in the device and shall be persistent (remain after reboot). A created profile shall be deletable and a device shall set the "fixed" attribute to false in the returned Profile.
Returns a L<ONVIF::Media::Elements::CreateProfileResponse|ONVIF::Media::Elements::CreateProfileResponse> object.

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).
A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -987,7 +987,7 @@ Returns a L<ONVIF::PTZ::Elements::GotoHomePositionResponse|ONVIF::PTZ::Elements:
=head3 SetHomePosition
Operation to save current position as the home position. The SetHomePosition command returns with a failure if the home position is fixed and cannot be overwritten. If the SetHomePosition is successful, it is possible to recall the Home Position with the GotoHomePosition command.
Operation to save current position as the home position. The SetHomePosition command returns with a failure if the "home" position is fixed and cannot be overwritten. If the SetHomePosition is successful, it is possible to recall the Home Position with the GotoHomePosition command.
Returns a L<ONVIF::PTZ::Elements::SetHomePositionResponse|ONVIF::PTZ::Elements::SetHomePositionResponse> object.

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).
A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -100,7 +100,7 @@ of the corresponding class can be passed instead of the marked hash ref.
You may pass any combination of objects, hash and list refs to these
methods, as long as you meet the structure.
List items (i.e. multiple occurences) are not displayed in the synopsis.
List items (i.e. multiple occurrences) are not displayed in the synopsis.
You may generally pass a list ref of hash refs (or objects) instead of a hash
ref - this may result in invalid XML if used improperly, though. Note that
SOAP::WSDL always expects list references at maximum depth position.

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·."
alternative is chosen, then the simple ur-type definition."

View File

@ -1064,7 +1064,7 @@ our @options = (
},
{
name => 'ZM_FFMPEG_FORMATS',
default => 'mpg mpeg wmv asf avi* mov swf 3gp**',
default => 'mp4* mpg mpeg wmv asf avi mov swf 3gp**',
description => 'Formats to allow for ffmpeg video generation',
help => q`
Ffmpeg can generate video in many different formats. This

View File

@ -29,6 +29,7 @@ use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Object;
require ZoneMinder::Monitor;
our $VERSION = $ZoneMinder::Base::VERSION;
@ -42,24 +43,116 @@ our $VERSION = $ZoneMinder::Base::VERSION;
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Database qw(:all);
use parent qw(ZoneMinder::Object);
use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
$table = 'Controls';
$serial = $primary_key = 'Id';
%fields = map { $_ => $_ } qw(
Id
Name
Type
Protocol
CanWake
CanSleep
CanReset
CanReboot
CanZoom
CanAutoZoom
CanZoomAbs
CanZoomRel
CanZoomCon
MinZoomRange
MaxZoomRange
MinZoomStep
MaxZoomStep
HasZoomSpeed
MinZoomSpeed
MaxZoomSpeed
CanFocus
CanAutoFocus
CanFocusAbs
CanFocusRel
CanFocusCon
MinFocusRange
MaxFocusRange
MinFocusStep
MaxFocusStep
HasFocusSpeed
MinFocusSpeed
MaxFocusSpeed
CanIris
CanAutoIris
CanIrisAbs
CanIrisRel
CanIrisCon
MinIrisRange
MaxIrisRange
MinIrisStep
MaxIrisStep
HasIrisSpeed
MinIrisSpeed
MaxIrisSpeed
CanGain
CanAutoGain
CanGainAbs
CanGainRel
CanGainCon
MinGainRange
MaxGainRange
MinGainStep
MaxGainStep
HasGainSpeed
MinGainSpeed
MaxGainSpeed
CanWhite
CanAutoWhite
CanWhiteAbs
CanWhiteRel
CanWhiteCon
MinWhiteRange
MaxWhiteRange
MinWhiteStep
MaxWhiteStep
HasWhiteSpeed
MinWhiteSpeed
MaxWhiteSpeed
HasPresets
NumPresets
HasHomePreset
CanSetPresets
CanMove
CanMoveDiag
CanMoveMap
CanMoveAbs
CanMoveRel
CanMoveCon
CanPan
MinPanRange
MaxPanRange
MinPanStep
MaxPanStep
HasPanSpeed
MinPanSpeed
MaxPanSpeed
HasTurboPan
TurboPanSpeed
CanTilt
MinTiltRange
MaxTiltRange
MinTiltStep
MaxTiltStep
HasTiltSpeed
MinTiltSpeed
MaxTiltSpeed
HasTurboTilt
TurboTiltSpeed
CanAutoScan
NumScanPaths
);
our $AUTOLOAD;
sub new {
my $class = shift;
my $id = shift;
if ( !defined($id) ) {
Fatal('No monitor defined when invoking protocol '.$class);
}
my $self = {};
$self->{name} = $class;
$self->{id} = $id;
bless($self, $class);
return $self;
}
sub DESTROY {
}
sub AUTOLOAD {
my $self = shift;
my $class = ref($self);
@ -79,24 +172,24 @@ sub AUTOLOAD {
sub getKey {
my $self = shift;
return $self->{id};
return $self->{Id};
}
sub open {
my $self = shift;
Fatal('No open method defined for protocol '.$self->{name});
Fatal('No open method defined for protocol '.$self->{Protocol});
}
sub close {
my $self = shift;
$self->{state} = 'closed';
Debug('No close method defined for protocol '.$self->{name});
Debug('No close method defined for protocol '.$self->{Protocol});
}
sub loadMonitor {
my $self = shift;
if ( !$self->{Monitor} ) {
if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{id})) ) {
if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{MonitorId})) ) {
Fatal('Monitor id '.$self->{id}.' not found');
}
if ( defined($self->{Monitor}->{AutoStopTimeout}) ) {

View File

@ -283,7 +283,7 @@ sub presetSet
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=0";
my $cmd = 'form/presetSet?flag=3&existFlag=1&language=cn&presetNum='.$preset;
$self->sendCmd( $cmd );
}
@ -294,7 +294,7 @@ sub presetGoto
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=1";
my $cmd = 'form/presetSet?flag=4&existFlag=1&language=cn&presetNum='.$preset;
$self->sendCmd( $cmd );
}

View File

@ -41,122 +41,135 @@ our @ISA = qw(ZoneMinder::Control);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use ZoneMinder::General qw(:all);
use Time::HiRes qw( usleep );
use URI::Encode qw(uri_encode);
sub open
{
our $REALM = '';
our $PROTOCOL = 'http://';
our $USERNAME = 'admin';
our $PASSWORD = '';
our $ADDRESS = '';
our $BASE_URL = '';
sub open {
my $self = shift;
$self->loadMonitor();
Debug( "Camera open" );
if (($self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/)) {
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
$USERNAME = $+{USERNAME} if $+{USERNAME};
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
} else {
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( !($ADDRESS =~ /:/) ) {
Error('You generally need to also specify the port. I will append :80');
$ADDRESS .= ':80';
}
$BASE_URL = $PROTOCOL.($USERNAME?$USERNAME.':'.$PASSWORD.'@':'').$ADDRESS;
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{ua}->agent( 'ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION );
$self->{state} = 'open';
}
sub close
{
sub close {
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg
{
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
sub sendCmd {
my ($self, $cmd, $speedcmd) = @_;
my $result = undef;
$self->printMsg( $speedcmd, 'Tx' );
$self->printMsg( $cmd, 'Tx' );
printMsg( $speedcmd, "Tx" );
printMsg( $cmd, "Tx" );
my $req = HTTP::Request->new( GET => "http://" . $self->{Monitor}->{ControlAddress} . "/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd" );
my $req = HTTP::Request->new( GET => $BASE_URL."/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd");
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
if (!$res->is_success) {
Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')');
}
else
{
Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" );
return $res->is_success;
}
return( $result );
}
sub moveConUp
{
sub moveConUp {
my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
Debug( "Move Up" );
$self->sendCmd( 'move=up', $speed );
}
sub moveConDown
{
sub moveConDown {
my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
Debug( "Move Down" );
$self->sendCmd( 'move=down', $speed );
}
sub moveConLeft
{
sub moveConLeft {
my ($self, $params) = @_;
my $speed = 'speedpan=-' . $params->{panspeed};
Debug( "Move Left" );
$self->sendCmd( 'move=left', $speed );
}
sub moveConRight
{
sub moveConRight {
my ($self, $params) = @_;
my $speed = 'speedpan=' . ($params->{panspeed} - 6);
Debug( "Move Right" );
$self->sendCmd( 'move=right', $speed );
}
sub moveStop
{
sub moveStop {
my $self = shift;
Debug( "Move Stop" );
Debug( "Move Stop: not implemented" );
# not implemented
}
sub zoomConTele
{
sub zoomConTele {
my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6);
Debug( "Zoom In" );
$self->sendCmd( 'zoom=tele', $speed );
}
sub zoomConWide
{
sub zoomConWide {
my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6);
Debug( "Zoom Out" );
$self->sendCmd( 'zoom=wide', $speed );
}
sub reset
{
sub reset {
my $self = shift;
Debug( "Camera Reset" );
$self->sendCmd( 'move=home' );
}
sub get_config {
my $self = shift;
my $url = $BASE_URL.'/cgi-bin/admin/lsctrl.cgi?cmd=queryStatus&retType=javascript';
my $req = new HTTP::Request(GET => $url);
my $response = $self->{ua}->request($req);
if ( $response->is_success() ) {
my $resp = $response->decoded_content;
return ZoneMinder::General::parseNameEqualsValueToHash($resp);
}
Warn("Failed to get config from $url: " . $response->status_line());
return;
} # end sub get_config
sub set_config {
my $self = shift;
my $diff = shift;
my $url = $BASE_URL.'/cgi-bin/'.$USERNAME.'/setparam.cgi?'.
join('&', map { $_.'='.uri_encode($$diff{$_}) } keys %$diff);
my $response = $self->{ua}->get($url);
Debug($response->content);
return $response->is_success();
}
1;
__END__

View File

@ -584,6 +584,7 @@ sub DiskSpace {
return $_[0]{DiskSpace};
}
# Icon: I removed the locking from this. So we now have an assumption that the Event object is up to date.
sub CopyTo {
my ( $self, $NewStorage ) = @_;
@ -614,16 +615,12 @@ sub CopyTo {
Debug("$NewPath is good");
}
$ZoneMinder::Database::dbh->begin_work();
$self->lock_and_load();
# data is reloaded, so need to check that the move hasn't already happened.
if ( $$self{StorageId} == $$NewStorage{Id} ) {
$ZoneMinder::Database::dbh->commit();
return 'Event has already been moved by someone else.';
}
if ( $$OldStorage{Id} != $$self{StorageId} ) {
$ZoneMinder::Database::dbh->commit();
return 'Old Storage path changed, Event has moved somewhere else.';
}
@ -661,12 +658,6 @@ sub CopyTo {
}
my $event_path = $subpath.$self->RelativePath();
if ( 0 ) { # Not neccessary
Debug("Making directory $event_path/");
if ( !$bucket->add_key($event_path.'/', '') ) {
Warning("Unable to add key for $event_path/ :". $s3->err . ': '. $s3->errstr());
}
}
my @files = glob("$OldPath/*");
Debug("Files to move @files");
@ -679,22 +670,10 @@ sub CopyTo {
if (!$size) {
Info('Not moving file with 0 size');
}
if ( 0 ) {
my $file_contents = File::Slurp::read_file($file);
if ( ! $file_contents ) {
die 'Loaded empty file, but it had a size. Giving up';
}
my $filename = $event_path.'/'.File::Basename::basename($file);
if ( ! $bucket->add_key($filename, $file_contents) ) {
die "Unable to add key for $filename : ".$s3->err . ': ' . $s3->errstr;
}
} else {
my $filename = $event_path.'/'.File::Basename::basename($file);
if (!$bucket->add_key_filename($filename, $file)) {
die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr;
}
}
my $duration = tv_interval($starttime);
Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
@ -704,9 +683,8 @@ sub CopyTo {
};
Error($@) if $@;
} else {
Error("Unable to parse S3 Url into it's component parts.");
Error('Unable to parse S3 Url into it\'s component parts.');
}
#die $@ if $@;
} # end if Url
} # end if s3
@ -724,21 +702,14 @@ sub CopyTo {
}
}
}
if ( $error ) {
$ZoneMinder::Database::dbh->commit();
return $error;
}
return $error if $error;
my @files = glob("$OldPath/*");
if ( ! @files ) {
$ZoneMinder::Database::dbh->commit();
return 'No files to move.';
}
return 'No files to move.' if !@files;
for my $file (@files) {
next if $file =~ /^\./;
($file) = ($file =~ /^(.*)$/); # De-taint
my $starttime = [gettimeofday];
Debug("Moving file $file to $NewPath");
my $size = -s $file;
if (!File::Copy::copy($file, $NewPath)) {
$error .= "Copy failed: for $file to $NewPath: $!";
@ -749,10 +720,7 @@ sub CopyTo {
} # end foreach file.
} # end if ! moved
if ( $error ) {
$ZoneMinder::Database::dbh->commit();
return $error;
}
} # end sub CopyTo
sub MoveTo {
@ -763,6 +731,10 @@ sub MoveTo {
return 'No permission to move event.';
}
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
my $OldStorage = $self->Storage(undef);
my $error = $self->CopyTo($NewStorage);
@ -772,11 +744,11 @@ sub MoveTo {
$$self{StorageId} = $$NewStorage{Id};
$self->Storage($NewStorage);
$error .= $self->save();
if ( $error ) {
$ZoneMinder::Database::dbh->commit();
return $error;
}
$ZoneMinder::Database::dbh->commit();
# 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;
$self->delete_files($OldStorage);
return $error;
} # end sub MoveTo

View File

@ -0,0 +1,99 @@
# ==========================================================================
#
# ZoneMinder Event_Summary Module
# Copyright (C) 2020 ZoneMinder
#
# 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.
#
# ==========================================================================
#
# This module contains the common definitions and functions used by the rest
# of the ZoneMinder scripts
#
package ZoneMinder::Event_Summary;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Object;
#our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object);
use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
$table = 'Event_Summaries';
$serial = $primary_key = 'MonitorId';
%fields = map { $_ => $_ } qw(
MonitorId
TotalEvents
TotalEventDiskSpace
HourEvents
HourEventDiskSpace
DayEvents
DayEventDiskSpace
WeekEvents
WeekEventDiskSpace
MonthEvents
MonthEventDiskSpace
ArchivedEvents
ArchivedEventDiskSpace
);
%defaults = (
TotalEvents => undef,
TotalEventDiskSpace => undef,
HourEvents => undef,
HourEventDiskSpace => undef,
DayEvents => undef,
DayEventDiskSpace => undef,
WeekEvents => undef,
WeekEventDiskSpace => undef,
MonthEvents => undef,
MonthEventDiskSpace => undef,
ArchivedEvents => undef,
ArchivedEventDiskSpace => undef,
);
sub Monitor {
return new ZoneMinder::Monitor( $_[0]{MonitorId} );
} # end sub Monitor
1;
__END__
=head1 NAME
ZoneMinder::Event_Summary - Perl Class for Event Summaries
=head1 SYNOPSIS
use ZoneMinder::Event_Summary;
=head1 AUTHOR
Isaac Connor, E<lt>isaac@zoneminder.comE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2001-2017 ZoneMinder LLC
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.3 or,
at your option, any later version of Perl 5 you may have available.
=cut

View File

@ -31,6 +31,8 @@ our %EXPORT_TAGS = (
systemStatus
packageControl
daemonControl
parseNameEqualsValueToHash
hash_diff
) ]
);
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
@ -534,6 +536,42 @@ sub jsonDecode {
return $result;
}
sub parseNameEqualsValueToHash {
my %settings;
foreach my $line ( split ( /\r?\n/, $_[0] ) ) {
next if ! $line;
next if ! ( $line =~ /=/ );
my ($name, $value ) = split('=', $line);
$value =~ s/^'//;
$value =~ s/'$//;
$settings{$name} = defined $value ? $value : '';
}
return %settings;
}
sub hash_diff {
# assumes keys of second hash are all in the first hash
my ( $settings, $defaults ) = @_;
my %updates;
foreach my $setting ( keys %{$settings} ) {
next if ! exists $$defaults{$setting};
if (
($$settings{$setting} and ! $$defaults{$setting})
or
(!$$settings{$setting} and $$defaults{$setting})
or
(
($$settings{$setting} and $$defaults{$setting} and (
$$settings{$setting} ne $$defaults{$setting}))
)
) {
$updates{$setting} = $$defaults{$setting};
}
} # end foreach setting
return %updates;
}
sub packageControl {
my $command = shift;
my $string = $Config{ZM_PATH_BIN}.'/zmpkg.pl '.$command;
@ -598,6 +636,8 @@ of the ZoneMinder scripts
packageControl
daemonControl
systemStatus
parseNameEqualsValueToHash
hash_diff
) ]

View File

@ -35,7 +35,9 @@ require ZoneMinder::Storage;
require ZoneMinder::Server;
require ZoneMinder::Memory;
require ZoneMinder::Monitor_Status;
require ZoneMinder::Event_Summary;
require ZoneMinder::Zone;
use ZoneMinder::Logger qw(:all);
#our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object);
@ -266,6 +268,15 @@ sub Status {
return $$self{Status};
}
sub Event_Summary {
my $self = shift;
$$self{Event_Summary} = shift if @_;
if ( ! $$self{Event_Summary} ) {
$$self{Event_Summary} = ZoneMinder::Event_Summary->find_one(MonitorId=>$$self{Id});
}
return $$self{Event_Summary};
}
sub connect {
my $self = shift;
return ZoneMinder::Memory::zmMemVerify($self);
@ -313,6 +324,25 @@ sub resumeMotionDetection {
return 1;
}
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;
}
bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol};
$$Control{MonitorId} = $$self{Id};
$$self{Control} = $Control;
}
}
return $$self{Control};
}
1;
__END__

View File

@ -43,18 +43,6 @@ $serial = $primary_key = 'MonitorId';
CaptureFPS
AnalysisFPS
CaptureBandwidth
TotalEvents
TotalEventDiskSpace
HourEvents
HourEventDiskSpace
DayEvents
DayEventDiskSpace
WeekEvents
WeekEventDiskSpace
MonthEvents
MonthEventDiskSpace
ArchivedEvents
ArchivedEventDiskSpace
);
%defaults = (
@ -62,18 +50,6 @@ $serial = $primary_key = 'MonitorId';
CaptureFPS => undef,
AnalysisFPS => undef,
CaptureBandwidth => undef,
TotalEvents => undef,
TotalEventDiskSpace => undef,
HourEvents => undef,
HourEventDiskSpace => undef,
DayEvents => undef,
DayEventDiskSpace => undef,
WeekEvents => undef,
WeekEventDiskSpace => undef,
MonthEvents => undef,
MonthEventDiskSpace => undef,
ArchivedEvents => undef,
ArchivedEventDiskSpace => undef,
);
sub Monitor {

View File

@ -218,7 +218,7 @@ sub save {
my $serial = eval '$'.$type.'::serial';
my @identified_by = eval '@'.$type.'::identified_by';
my $ac = ZoneMinder::Database::start_transaction( $local_dbh );
my $ac = ZoneMinder::Database::start_transaction( $local_dbh ) if $local_dbh->{AutoCommit};
if ( ! $serial ) {
my $insert = $force_insert;
my %serial = eval '%'.$type.'::serial';
@ -234,8 +234,8 @@ $log->debug("No serial") if $debug;
if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM `$table` WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) {
$where =~ s/\?/\%s/g;
$log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr);
$local_dbh->rollback();
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
$local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $local_dbh->errstr;
} elsif ( $debug ) {
$log->debug("SQL succesful DELETE FROM $table WHERE $where");
@ -267,8 +267,8 @@ $log->debug("No serial") if $debug;
my $error = $local_dbh->errstr;
$command =~ s/\?/\%s/g;
$log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr);
$local_dbh->rollback();
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
$local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
@ -282,8 +282,8 @@ $log->debug("No serial") if $debug;
my $error = $local_dbh->errstr;
$command =~ s/\?/\%s/g;
$log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr);
$local_dbh->rollback();
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
$local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
@ -321,8 +321,8 @@ $log->debug("No serial") if $debug;
$command =~ s/\?/\%s/g;
my $error = $local_dbh->errstr;
$log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error);
$local_dbh->rollback();
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
$local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
@ -340,8 +340,8 @@ $log->debug("No serial") if $debug;
my $error = $local_dbh->errstr;
$command =~ s/\?/\%s/g;
$log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log;
$local_dbh->rollback();
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
$local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error;
} # end if
if ( $debug or DEBUG_ALL ) {
@ -350,7 +350,7 @@ $log->debug("No serial") if $debug;
} # end if
} # end if
} # end if
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
#$self->load();
#if ( $$fields{id} ) {
#if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) {

View File

@ -30,7 +30,6 @@ use autouse 'Pod::Usage'=>qw(pod2usage);
use POSIX qw/strftime EPIPE EINTR/;
use Socket;
use Data::Dumper;
use Module::Load::Conditional qw{can_load};
use constant MAX_CONNECT_DELAY => 15;
use constant MAX_COMMAND_WAIT => 1800;
@ -102,40 +101,21 @@ if ($options{command}) {
}
} else {
# The server isn't there
my $monitor = zmDbGetMonitorAndControl($id);
require ZoneMinder::Monitor;
my $monitor = ZoneMinder::Monitor->find_one(Id=>$id);
Fatal("Unable to load control data for monitor $id") if !$monitor;
my $protocol = $monitor->{Protocol};
my $control = $monitor->Control();
my $protocol = $control->{Protocol};
if (!$protocol) {
Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field');
}
if (-x $protocol) {
# Protocol is actually a script!
# Holdover from previous versions
my $command .= $protocol.' '.$arg_string;
Debug($command);
my $output = qx($command);
my $status = $? >> 8;
if ($status || logDebugging()) {
chomp($output);
Debug("Output: $output");
}
if ($status) {
Error("Command '$command' exited with status: $status");
exit($status);
}
exit(0);
}
Info("Starting control server $id/$protocol");
close(CLIENT);
if (!can_load(modules => {'ZoneMinder::Control::'.$protocol => undef})) {
Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR");
}
my $zm_terminate = 0;
sub TermHandler {
Info('Received TERM, exiting');
@ -150,7 +130,6 @@ if ($options{command}) {
$0 = $0.' --id '.$id;
my $control = ('ZoneMinder::Control::'.$protocol)->new($id);
my $control_key = $control->getKey();
$control->loadMonitor();

View File

@ -665,10 +665,10 @@ sub substituteTags {
# We have a filter and an event, do we need any more
# monitor information?
my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/;
my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
my $need_summary = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
my $Monitor = $Event->Monitor() if $need_monitor;
my $Status = $Monitor->Status() if $need_status;
my $Summary = $Monitor->Event_Summary() if $need_summary;
# Do we need the image information too?
my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD|EIMODG)%/;
@ -692,19 +692,19 @@ sub substituteTags {
}
$rows ++;
}
Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alaarm_frame: $max_alarm_frame, score: $max_alarm_score");
Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alarm_frame: $max_alarm_frame, score: $max_alarm_score");
$sth->finish();
}
my $url = $Config{ZM_URL};
$text =~ s/%ZP%/$url/g;
$text =~ s/%MN%/$Monitor->{Name}/g;
$text =~ s/%MET%/$Status->{TotalEvents}/g;
$text =~ s/%MEH%/$Status->{HourEvents}/g;
$text =~ s/%MED%/$Status->{DayEvents}/g;
$text =~ s/%MEW%/$Status->{WeekEvents}/g;
$text =~ s/%MEM%/$Status->{MonthEvents}/g;
$text =~ s/%MEA%/$Status->{ArchivedEvents}/g;
$text =~ s/%MET%/$Summary->{TotalEvents}/g;
$text =~ s/%MEH%/$Summary->{HourEvents}/g;
$text =~ s/%MED%/$Summary->{DayEvents}/g;
$text =~ s/%MEW%/$Summary->{WeekEvents}/g;
$text =~ s/%MEM%/$Summary->{MonthEvents}/g;
$text =~ s/%MEA%/$Summary->{ArchivedEvents}/g;
$text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g;
$text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g;
$text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g;

View File

@ -107,6 +107,7 @@ endif()
add_executable(zmc zmc.cpp)
add_executable(zms zms.cpp)
add_executable(zmu zmu.cpp)
add_executable(zmbenchmark zmbenchmark.cpp)
target_link_libraries(zmc
PRIVATE
@ -129,6 +130,13 @@ target_link_libraries(zmu
${ZM_EXTRA_LIBS}
${CMAKE_DL_LIBS})
target_link_libraries(zmbenchmark
PRIVATE
zm-core-interface
zm
${ZM_EXTRA_LIBS}
${CMAKE_DL_LIBS})
# Generate man files for the binaries destined for the bin folder
if(BUILD_MAN)
foreach(CBINARY zmc zmu)

View File

@ -23,14 +23,6 @@ void AnalysisThread::Start() {
void AnalysisThread::Run() {
while (!(terminate_ or zm_terminate)) {
// Some periodic updates are required for variable capturing framerate
Debug(2, "Analyzing");
if (!monitor_->Analyse()) {
if (!(terminate_ or zm_terminate)) {
Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE);
Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count()));
std::this_thread::sleep_for(sleep_for);
}
}
monitor_->Analyse();
}
}

View File

@ -73,11 +73,28 @@ void bind_libcurl_symbols() {
*(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup");
}
cURLCamera::cURLCamera( const Monitor* monitor, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
cURLCamera::cURLCamera(
const Monitor* monitor,
const std::string &p_path,
const std::string &p_user,
const std::string &p_pass,
unsigned int p_width,
unsigned int p_height,
int p_colours,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio) :
Camera(monitor, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio),
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET )
mPath(p_path),
mUser(p_user),
mPass(p_pass),
bTerminate(false),
bReset(false),
mode(MODE_UNSET)
{
if (capture) {
Initialise();
}
@ -85,7 +102,6 @@ cURLCamera::cURLCamera( const Monitor* monitor, const std::string &p_path, const
cURLCamera::~cURLCamera() {
if (capture) {
Terminate();
}
}
@ -156,13 +172,14 @@ void cURLCamera::Terminate() {
}
int cURLCamera::PrimeCapture() {
getVideoStream();
//Info( "Priming capture from %s", mPath.c_str() );
return 0;
return 1;
}
int cURLCamera::PreCapture() {
// Nothing to do here
return( 0 );
return 1;
}
int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
@ -179,7 +196,6 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
lock();
while (!frameComplete) {
/* If the work thread did a reset, reset our local variables */
if (bReset) {
SubHeadersParsingComplete = false;
@ -244,7 +260,11 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
}
/* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */
if( (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) || (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) ) {
if (
(crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0)
||
(crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0)
) {
/* This is the last header */
SubHeadersParsingComplete = true;
}
@ -297,6 +317,14 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
need_more_data = true;
} else {
/* All good. decode the image */
if (!zm_packet->image) {
Debug(4, "Allocating image");
zm_packet->image = new Image(width, height, colours, subpixelorder);
}
zm_packet->keyframe = 1;
zm_packet->codec_type = AVMEDIA_TYPE_VIDEO;
zm_packet->packet.stream_index = mVideoStreamId;
zm_packet->stream = mVideoStream;
zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder);
frameComplete = true;
}
@ -317,6 +345,14 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
if (!single_offsets.empty()) {
if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) {
/* Extract frame */
if (!zm_packet->image) {
Debug(4, "Allocating image");
zm_packet->image = new Image(width, height, colours, subpixelorder);
}
zm_packet->keyframe = 1;
zm_packet->codec_type = AVMEDIA_TYPE_VIDEO;
zm_packet->packet.stream_index = mVideoStreamId;
zm_packet->stream = mVideoStream;
zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder);
single_offsets.pop_front();
frameComplete = true;
@ -353,7 +389,7 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
int cURLCamera::PostCapture() {
// Nothing to do here
return( 0 );
return 1;
}
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {

View File

@ -251,6 +251,13 @@ void zmDbQueue::process() {
mCondition.wait(lock);
}
while (!mQueue.empty()) {
if (mQueue.size() > 10) {
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());
log->databaseLevel(db_level);
}
std::string sql = mQueue.front();
mQueue.pop();
// My idea for leaving the locking around each sql statement is to allow

View File

@ -60,8 +60,8 @@ Event::Event(
//snapshit_file(),
//alarm_file(""),
videoStore(nullptr),
//video_name(""),
//video_file(""),
//video_path(""),
last_db_frame(0),
have_video_keyframe(false),
//scheme
@ -104,6 +104,13 @@ Event::Event(
// Copy it in case opening the mp4 doesn't work we can set it to another value
save_jpegs = monitor->GetOptSaveJPEGs();
Storage * storage = monitor->getStorage();
if (monitor->GetOptVideoWriter() != 0) {
container = monitor->OutputContainer();
if ( container == "auto" || container == "" ) {
container = "mp4";
}
video_incomplete_file = "incomplete."+container;
}
std::string sql = stringtf(
"INSERT INTO `Events` "
@ -120,7 +127,7 @@ Event::Event(
state_id,
monitor->getOrientation(),
0,
"",
video_incomplete_file.c_str(),
save_jpegs,
storage->SchemeString().c_str()
);
@ -178,24 +185,16 @@ Event::Event(
} // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str());
video_name = "";
snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg";
/* Save as video */
video_incomplete_path = path + "/" + video_incomplete_file;
if (monitor->GetOptVideoWriter() != 0) {
std::string container = monitor->OutputContainer();
if ( container == "auto" || container == "" ) {
container = "mp4";
}
/* Save as video */
video_name = stringtf("%" PRIu64 "-%s.%s", id, "video", container.c_str());
video_file = path + "/" + video_name;
Debug(1, "Writing video file to %s", video_file.c_str());
videoStore = new VideoStore(
video_file.c_str(),
video_incomplete_path.c_str(),
container.c_str(),
monitor->GetVideoStream(),
monitor->GetVideoCodecContext(),
@ -213,8 +212,10 @@ Event::Event(
zmDbDo(sql);
}
} else {
sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id);
zmDbDo(sql);
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
}
@ -227,6 +228,14 @@ Event::~Event() {
Debug(4, "Deleting video store");
delete videoStore;
videoStore = nullptr;
int result = rename(video_incomplete_path.c_str(), video_path.c_str());
if (result == 0) {
Debug(1, "File successfully renamed");
} else {
Error("Failed renaming %s to %s", video_incomplete_path.c_str(), video_path.c_str());
// So that we don't update the event record
video_file = video_incomplete_file;
}
}
// endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp.
@ -245,21 +254,23 @@ Event::~Event() {
}
std::string sql = stringtf(
"UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64 " AND Name='New Event'",
"UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64 " AND Name='New Event'",
monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time),
delta_time.count(),
frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id);
if (!zmDbDoUpdate(sql)) {
// Name might have been changed during recording, so just do the update without changing the name.
sql = stringtf(
"UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64,
"UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64,
std::chrono::system_clock::to_time_t(end_time),
delta_time.count(),
frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id);
zmDbDoUpdate(sql);
} // end if no changed rows due to Name change during recording
@ -479,7 +490,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing snapshot");
WriteFrameImage(image, timestamp, snapshot_file.c_str());
} else {
Debug(1, "Not Writing snapshot");
Debug(1, "Not Writing snapshot because score %d > max %d", score, max_score);
}
// We are writing an Alarm frame
@ -491,7 +502,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing alarm image");
WriteFrameImage(image, timestamp, alarm_file.c_str());
} else {
Debug(1, "Not Writing alarm image");
Debug(3, "Not Writing alarm image because alarm frame already written");
}
if (alarm_image and (save_jpegs & 2)) {

View File

@ -84,8 +84,13 @@ class Event {
std::string alarm_file;
VideoStore *videoStore;
std::string video_name;
std::string container;
std::string codec;
std::string video_file;
std::string video_path;
std::string video_incomplete_file;
std::string video_incomplete_path;
int last_db_frame;
bool have_video_keyframe; // a flag to tell us if we have had a video keyframe when writing an mp4. The first frame SHOULD be a video keyframe.
Storage::Schemes scheme;

View File

@ -663,6 +663,7 @@ bool EventStream::checkEventLoaded() {
else
curr_frame_id = 1;
Debug(2, "New frame id = %ld", curr_frame_id);
start = std::chrono::system_clock::now();
return true;
} else {
Debug(2, "No next event loaded using %s. Pausing", sql.c_str());
@ -810,7 +811,7 @@ bool EventStream::sendFrame(Microseconds delta_us) {
fputs("Content-Type: image/x-rgbz\r\n", stdout);
break;
case STREAM_RAW :
img_buffer = (uint8_t*)(send_image->Buffer());
img_buffer = send_image->Buffer();
img_buffer_size = send_image->Size();
fputs("Content-Type: image/x-rgb\r\n", stdout);
break;
@ -957,6 +958,7 @@ void EventStream::runStream() {
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()));
// if effective > base we should speed up frame delivery
if (base_fps < effective_fps) {
delta = std::chrono::duration_cast<Microseconds>((delta * base_fps) / effective_fps);
Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f)",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()),
@ -969,6 +971,7 @@ void EventStream::runStream() {
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()),
base_fps,
effective_fps);
}
// +/- 1? What if we are skipping frames?
curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod;

View File

@ -270,7 +270,6 @@ int Image::PopulateFrame(AVFrame *frame) {
frame->width = width;
frame->height = height;
frame->format = imagePixFormat;
Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d", width, height, linesize, colours, size);
zm_dump_video_frame(frame, "Image.Populate(frame)");
return 1;
} // int Image::PopulateFrame(AVFrame *frame)

View File

@ -179,9 +179,11 @@ class Image {
}
}
/* Internal buffer should not be modified from functions outside of this class */
inline uint8_t* Buffer() { return buffer; }
inline const uint8_t* Buffer() const { return buffer; }
inline uint8_t* Buffer(unsigned int x, unsigned int y=0) { return &buffer[(y*linesize) + x*colours]; }
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

View File

@ -49,7 +49,6 @@ static int vidioctl(int fd, int request, void *arg) {
static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) {
_AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE;
if ( v4l_version == 2 ) {
switch (palette) {
#if defined(V4L2_PIX_FMT_RGB444) && defined(AV_PIX_FMT_RGB444)
case V4L2_PIX_FMT_RGB444 :
@ -172,7 +171,6 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette)
#endif
}
} // end switch palette
} // end if v4l2
return pixFormat;
} // end getFfPixFormatFromV4lPalette
@ -289,7 +287,7 @@ LocalCamera::LocalCamera(
BigEndian = 0;
}
if ( v4l_version == 2 && palette == 0 ) {
if (palette == 0) {
/* Use automatic format selection */
Debug(2,"Using automatic format selection");
palette = AutoSelectFormat(colours);
@ -310,9 +308,6 @@ LocalCamera::LocalCamera(
if (capture) {
if (last_camera) {
if ((p_method == "v4l2" && v4l_version != 2) || (p_method == "v4l1" && v4l_version != 1))
Fatal("Different Video For Linux version used for monitors sharing same device");
if (standard != last_camera->standard)
Warning("Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong");
@ -328,8 +323,6 @@ LocalCamera::LocalCamera(
imagePixFormat = AV_PIX_FMT_NONE;
}
/* V4L2 format matching */
if ( v4l_version == 2 ) {
/* Try to find a match for the selected palette and target colourspace */
/* RGB32 palette and 32bit target colourspace */
@ -437,7 +430,6 @@ LocalCamera::LocalCamera(
}
} // end if conversion_type == 2
} // end if needs conversion
} // end if v4l2
last_camera = this;
Debug(3, "Selected subpixelorder: %u", subpixelorder);
@ -492,7 +484,6 @@ int LocalCamera::Close() {
void LocalCamera::Initialise() {
Debug(3, "Opening video device %s", device.c_str());
//if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 )
if ((vid_fd = open(device.c_str(), O_RDWR, 0)) < 0)
Fatal("Failed to open video device %s: %s", device.c_str(), strerror(errno));
@ -503,8 +494,6 @@ void LocalCamera::Initialise() {
if (!S_ISCHR(st.st_mode))
Fatal("File %s is not device file: %s", device.c_str(), strerror(errno));
Debug(2, "V4L2 support enabled, using V4L%d api", v4l_version);
if ( v4l_version == 2 ) {
struct v4l2_capability vid_cap;
Debug(3, "Checking video device capabilities");
@ -736,7 +725,6 @@ void LocalCamera::Initialise() {
Brightness(brightness);
Hue(hue);
Colour(colour);
}
} // end LocalCamera::Initialize
void LocalCamera::Terminate() {
@ -1170,126 +1158,47 @@ bool LocalCamera::GetCurrentSettings(
return true;
}
int LocalCamera::Brightness(int p_brightness) {
if ( v4l_version == 2 ) {
int LocalCamera::Control(int vid_id, int newvalue) {
struct v4l2_control vid_control;
memset(&vid_control, 0, sizeof(vid_control));
vid_control.id = V4L2_CID_BRIGHTNESS;
vid_control.id = vid_id;
if (vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0) {
if (errno != EINVAL) {
Error("Unable to query brightness: %s", strerror(errno));
Error("Unable to query control: %s", strerror(errno));
} else {
Warning("Brightness control is not supported");
Warning("Control is not supported");
}
//Info( "Brightness 1 %d", vid_control.value );
} else if ( p_brightness >= 0 ) {
vid_control.value = p_brightness;
} else if (newvalue >= 0) {
vid_control.value = newvalue;
//Info( "Brightness 2 %d", vid_control.value );
/* The driver may clamp the value or return ERANGE, ignored here */
if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) {
if (errno != ERANGE) {
Error("Unable to set brightness: %s", strerror(errno));
Error("Unable to set control: %s", strerror(errno));
} else {
Warning("Given brightness value (%d) may be out-of-range", p_brightness);
Warning("Given control value (%d) may be out-of-range", newvalue);
}
}
//Info( "Brightness 3 %d", vid_control.value );
}
return vid_control.value;
}
return -1;
int LocalCamera::Brightness(int p_brightness) {
return Control(V4L2_CID_BRIGHTNESS, p_brightness);
}
int LocalCamera::Hue(int p_hue) {
if ( v4l_version == 2 ) {
struct v4l2_control vid_control;
memset( &vid_control, 0, sizeof(vid_control) );
vid_control.id = V4L2_CID_HUE;
if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) {
if ( errno != EINVAL )
Error("Unable to query hue: %s", strerror(errno));
else
Warning("Hue control is not supported");
} else if ( p_hue >= 0 ) {
vid_control.value = p_hue;
/* The driver may clamp the value or return ERANGE, ignored here */
if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) {
if ( errno != ERANGE ) {
Error("Unable to set hue: %s", strerror(errno));
} else {
Warning("Given hue value (%d) may be out-of-range", p_hue);
}
}
}
return vid_control.value;
}
return -1;
return Control(V4L2_CID_HUE, p_hue);
}
int LocalCamera::Colour( int p_colour ) {
if ( v4l_version == 2 ) {
struct v4l2_control vid_control;
memset(&vid_control, 0, sizeof(vid_control));
vid_control.id = V4L2_CID_SATURATION;
if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) {
if ( errno != EINVAL ) {
Error("Unable to query saturation: %s", strerror(errno));
} else {
Warning("Saturation control is not supported");
}
} else if ( p_colour >= 0 ) {
vid_control.value = p_colour;
/* The driver may clamp the value or return ERANGE, ignored here */
if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) {
if ( errno != ERANGE ) {
Error("Unable to set saturation: %s", strerror(errno));
} else {
Warning("Given saturation value (%d) may be out-of-range", p_colour);
}
}
}
return vid_control.value;
}
return -1;
return Control(V4L2_CID_SATURATION, p_colour);
}
int LocalCamera::Contrast(int p_contrast) {
if ( v4l_version == 2 ) {
struct v4l2_control vid_control;
memset(&vid_control, 0, sizeof(vid_control));
vid_control.id = V4L2_CID_CONTRAST;
if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) {
if ( errno != EINVAL ) {
Error("Unable to query contrast: %s", strerror(errno));
} else {
Warning("Contrast control is not supported");
}
} else if ( p_contrast >= 0 ) {
vid_control.value = p_contrast;
/* The driver may clamp the value or return ERANGE, ignored here */
if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) {
if ( errno != ERANGE ) {
Error("Unable to set contrast: %s", strerror(errno));
} else {
Warning("Given contrast value (%d) may be out-of-range", p_contrast);
}
}
}
return vid_control.value;
}
return -1;
return Control(V4L2_CID_CONTRAST, p_contrast);
}
int LocalCamera::PrimeCapture() {
@ -1297,8 +1206,6 @@ int LocalCamera::PrimeCapture() {
if (!device_prime)
return 1;
Debug(2, "Priming capture");
if ( v4l_version == 2 ) {
Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count);
for (unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++) {
struct v4l2_buffer vid_buf;
@ -1327,13 +1234,11 @@ int LocalCamera::PrimeCapture() {
Error("Failed to start capture stream: %s", strerror(errno));
return -1;
}
} // end if v4l_version == 2
return 1;
} // end LocalCamera::PrimeCapture
int LocalCamera::PreCapture() {
//Debug(5, "Pre-capturing");
return 1;
}
@ -1353,7 +1258,6 @@ int LocalCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
// Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer
if (channel_prime) {
if ( v4l_version == 2 ) {
static struct v4l2_buffer vid_buf;
memset(&vid_buf, 0, sizeof(vid_buf));
@ -1398,9 +1302,7 @@ int LocalCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
Error("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",
v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height);
}
} // end if v4l2
if ( v4l_version == 2 ) {
if (channel_count > 1) {
int next_channel = (channel_index+1)%channel_count;
Debug(3, "Switching video source to %d", channels[next_channel]);
@ -1423,7 +1325,6 @@ int LocalCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
} else {
Error("Unable to requeue buffer due to not v4l2_data");
}
}
} /* prime capture */
if (!zm_packet->image) {

View File

@ -113,6 +113,7 @@ public:
int Palette() const { return palette; }
int Extras() const { return extras; }
int Control(int vid_id, int newvalue=-1 );
int Brightness( int p_brightness=-1 ) override;
int Hue( int p_hue=-1 ) override;
int Colour( int p_colour=-1 ) override;

View File

@ -83,9 +83,10 @@ std::string load_monitor_sql =
"`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, "
"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`,"
"`RTSPServer`, `RTSPStreamName`,"
"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-2 FROM `Monitors`";
"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`";
std::string CameraType_Strings[] = {
"Unknown",
"Local",
"Remote",
"File",
@ -93,10 +94,21 @@ std::string CameraType_Strings[] = {
"LibVLC",
"NVSOCKET",
"CURL",
"VNC",
"VNC"
};
std::string Function_Strings[] = {
"Unknown",
"None",
"Monitor",
"Modect",
"Record",
"Mocord",
"Nodect"
};
std::string State_Strings[] = {
"Unknown",
"IDLE",
"PREALARM",
"ALARM",
@ -435,7 +447,7 @@ Monitor::Monitor()
"SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, "
"FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif,"
"`RTSPServer`,`RTSPStreamName`,
"SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors";
"SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors";
*/
void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
@ -474,6 +486,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
function = (Function)atoi(dbrow[col]); col++;
enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
// See below after save_jpegs for a recalculation of decoding_enabled
ReloadLinkedMonitors(dbrow[col]); col++;
@ -542,6 +555,17 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
videowriter = (VideoWriter)atoi(dbrow[col]); col++;
encoderparams = dbrow[col] ? dbrow[col] : ""; col++;
decoding_enabled = !(
( function == RECORD or function == NODECT )
and
( savejpegs == 0 )
and
( videowriter == PASSTHROUGH )
and
!decoding_enabled
);
Debug(3, "Decoding enabled: %d function %d %s savejpegs %d videowriter %d", decoding_enabled, function, Function_Strings[function].c_str(), savejpegs, videowriter);
/*"`OutputCodec`, `Encoder`, `OutputContainer`, " */
output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
encoder = dbrow[col] ? dbrow[col] : ""; col++;
@ -591,7 +615,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
rtsp_server = (*dbrow[col] != '0'); col++;
rtsp_streamname = dbrow[col]; col++;
/*"SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors"; */
/*"SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */
signal_check_points = atoi(dbrow[col]); col++;
signal_check_colour = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++;
@ -603,6 +627,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */
importance = dbrow[col] ? atoi(dbrow[col]) : 0;// col++;
if (importance < 0) importance = 0; // Should only be >= 0
// How many frames we need to have before we start analysing
ready_count = std::max(warmup_count, pre_event_count);
@ -650,18 +675,6 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno));
}
// Do this here to save a few cycles with all the comparisons
decoding_enabled = !(
( function == RECORD or function == NODECT )
and
( savejpegs == 0 )
and
( videowriter == PASSTHROUGH )
and
!decoding_enabled
);
Debug(1, "Decoding enabled: %d", decoding_enabled);
if ( config.record_diag_images ) {
if ( config.record_diag_images_fifo ) {
diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id);
@ -1304,8 +1317,12 @@ void Monitor::actionResume() {
}
int Monitor::actionBrightness(int p_brightness) {
if (purpose != CAPTURE) {
if (p_brightness >= 0) {
if (purpose == CAPTURE) {
// We are the capture process, so take the action
return camera->Brightness(p_brightness);
}
// If we are an outside actor, sending the command
shared_data->brightness = p_brightness;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
@ -1317,7 +1334,16 @@ int Monitor::actionBrightness(int p_brightness) {
return -1;
}
}
} else {
return shared_data->brightness;
}
int Monitor::actionBrightness() {
if (purpose == CAPTURE) {
// We are the capture process, so take the action
return camera->Brightness();
}
// If we are an outside actor, sending the command
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) {
@ -1328,15 +1354,14 @@ int Monitor::actionBrightness(int p_brightness) {
return -1;
}
}
}
return shared_data->brightness;
}
return camera->Brightness(p_brightness);
} // end int Monitor::actionBrightness(int p_brightness)
} // end int Monitor::actionBrightness()
int Monitor::actionContrast(int p_contrast) {
if (purpose != CAPTURE) {
if (p_contrast >= 0) {
if (purpose == CAPTURE) {
return camera->Contrast(p_contrast);
}
shared_data->contrast = p_contrast;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
@ -1348,7 +1373,15 @@ int Monitor::actionContrast(int p_contrast) {
return -1;
}
}
} else {
return shared_data->contrast;
}
int Monitor::actionContrast() {
if (purpose == CAPTURE) {
// We are the capture process, so take the action
return camera->Contrast();
}
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) {
@ -1359,15 +1392,14 @@ int Monitor::actionContrast(int p_contrast) {
return -1;
}
}
}
return shared_data->contrast;
}
return camera->Contrast(p_contrast);
} // end int Monitor::actionContrast(int p_contrast)
} // end int Monitor::actionContrast()
int Monitor::actionHue(int p_hue) {
if (purpose != CAPTURE) {
if (p_hue >= 0) {
if (purpose == CAPTURE) {
return camera->Hue(p_hue);
}
shared_data->hue = p_hue;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
@ -1379,7 +1411,13 @@ int Monitor::actionHue(int p_hue) {
return -1;
}
}
} else {
return shared_data->hue;
}
int Monitor::actionHue() {
if (purpose == CAPTURE) {
return camera->Hue();
}
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) {
@ -1390,15 +1428,13 @@ int Monitor::actionHue(int p_hue) {
return -1;
}
}
}
return shared_data->hue;
}
return camera->Hue(p_hue);
} // end int Monitor::actionHue(int p_hue)
int Monitor::actionColour(int p_colour) {
if (purpose != CAPTURE) {
if (p_colour >= 0) {
if (purpose == CAPTURE) {
return camera->Colour(p_colour);
}
shared_data->colour = p_colour;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
@ -1410,7 +1446,13 @@ int Monitor::actionColour(int p_colour) {
return -1;
}
}
} else {
return shared_data->colour;
}
int Monitor::actionColour() {
if (purpose == CAPTURE) {
return camera->Colour();
}
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) {
@ -1421,10 +1463,7 @@ int Monitor::actionColour(int p_colour) {
return -1;
}
}
}
return shared_data->colour;
}
return camera->Colour(p_colour);
} // end int Monitor::actionColour(int p_colour)
void Monitor::DumpZoneImage(const char *zone_string) {
@ -1617,7 +1656,7 @@ void Monitor::CheckAction() {
}
}
void Monitor::UpdateCaptureFPS() {
void Monitor::UpdateFPS() {
if ( fps_report_interval and
(
!(image_count%fps_report_interval)
@ -1636,82 +1675,35 @@ void Monitor::UpdateCaptureFPS() {
uint32 new_camera_bytes = camera->Bytes();
uint32 new_capture_bandwidth =
static_cast<uint32>((new_camera_bytes - last_camera_bytes) / elapsed.count());
last_camera_bytes = new_camera_bytes;
double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count();
Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps",
"Capturing",
Debug(4, "FPS: capture count %d - last capture count %d = %d now:%lf, last %lf, elapsed %lf = capture: %lf fps analysis: %lf fps",
image_count,
last_capture_image_count,
image_count - last_capture_image_count,
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
FPSeconds(last_fps_time.time_since_epoch()).count(),
elapsed.count(),
new_capture_fps);
new_capture_fps,
new_analysis_fps);
Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec",
name.c_str(), image_count, new_capture_fps, new_capture_bandwidth);
Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec Analysing at %.2lf fps",
name.c_str(), image_count, new_capture_fps, new_capture_bandwidth, new_analysis_fps);
shared_data->capture_fps = new_capture_fps;
last_fps_time = now;
last_capture_image_count = image_count;
shared_data->analysis_fps = new_analysis_fps;
last_motion_frame_count = motion_frame_count;
last_camera_bytes = new_camera_bytes;
std::string sql = stringtf(
"UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u WHERE MonitorId=%u",
new_capture_fps, new_capture_bandwidth, id);
"UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u, AnalysisFPS = %.2lf WHERE MonitorId=%u",
new_capture_fps, new_capture_bandwidth, new_analysis_fps, id);
dbQueue.push(std::move(sql));
} // now != last_fps_time
} // end if report fps
} // void Monitor::UpdateCaptureFPS()
void Monitor::UpdateAnalysisFPS() {
Debug(1, "analysis_image_count(%d) motion_count(%d) fps_report_interval(%d) mod%d",
analysis_image_count, motion_frame_count, fps_report_interval,
((analysis_image_count && fps_report_interval) ? !(analysis_image_count%fps_report_interval) : -1 ) );
if (
( analysis_image_count and fps_report_interval and !(analysis_image_count%fps_report_interval) )
or
// In startup do faster updates
( (analysis_image_count < fps_report_interval) and !(analysis_image_count%10) )
) {
SystemTimePoint now = std::chrono::system_clock::now();
FPSeconds elapsed = now - last_analysis_fps_time;
Debug(4, "%s: %d - now: %.2f, last %lf, diff %lf",
name.c_str(),
analysis_image_count,
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
elapsed.count());
if (elapsed > Seconds(1)) {
double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count();
Info("%s: %d - Analysing at %.2lf fps from %d - %d=%d / %lf - %lf = %lf",
name.c_str(),
analysis_image_count,
new_analysis_fps,
motion_frame_count,
last_motion_frame_count,
(motion_frame_count - last_motion_frame_count),
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
elapsed.count());
if (new_analysis_fps != shared_data->analysis_fps) {
shared_data->analysis_fps = new_analysis_fps;
std::string sql = stringtf("UPDATE LOW_PRIORITY Monitor_Status SET AnalysisFPS = %.2lf WHERE MonitorId=%u",
new_analysis_fps, id);
dbQueue.push(std::move(sql));
last_analysis_fps_time = now;
last_motion_frame_count = motion_frame_count;
} else {
Debug(4, "No change in fps");
} // end if change in fps
} // end if at least 1 second has passed since last update
} // end if time to do an update
} // end void Monitor::UpdateAnalysisFPS
} // void Monitor::UpdateFPS()
// Would be nice if this JUST did analysis
// This idea is that we should be analysing as close to the capture frame as possible.
@ -2015,8 +2007,7 @@ bool Monitor::Analyse() {
} // end if ! event
} // end if RECORDING
if (score) {
if (score and (function == MODECT or function == NODECT)) {
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()
@ -2261,8 +2252,6 @@ bool Monitor::Analyse() {
// Only do these if it's a video packet.
shared_data->last_read_index = snap->image_index;
analysis_image_count++;
if (function == MODECT or function == MOCORD)
UpdateAnalysisFPS();
}
packetqueue.increment_it(analysis_it);
packetqueue.unlock(packet_lock);
@ -2547,7 +2536,6 @@ int Monitor::Capture() {
// Will only be queued if there are iterators allocated in the queue.
packetqueue.queuePacket(packet);
UpdateCaptureFPS();
} else { // result == 0
// Question is, do we update last_write_index etc?
return 0;
@ -2587,7 +2575,7 @@ bool Monitor::Decode() {
//
//capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
int ret = packet->decode(camera->getVideoCodecContext());
if (ret > 0) {
if (ret > 0 and !zm_terminate) {
if (packet->in_frame and !packet->image) {
packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder());
AVFrame *input_frame = packet->in_frame;

View File

@ -64,7 +64,7 @@ public:
} Function;
typedef enum {
LOCAL,
LOCAL=1,
REMOTE,
FILE,
FFMPEG,
@ -546,8 +546,7 @@ public:
unsigned int GetLastWriteIndex() const;
uint64_t GetLastEventId() const;
double GetFPS() const;
void UpdateAnalysisFPS();
void UpdateCaptureFPS();
void UpdateFPS();
void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" );
void ForceAlarmOff();
void CancelForced();
@ -567,10 +566,14 @@ public:
void actionSuspend();
void actionResume();
int actionBrightness( int p_brightness=-1 );
int actionHue( int p_hue=-1 );
int actionColour( int p_colour=-1 );
int actionContrast( int p_contrast=-1 );
int actionBrightness(int p_brightness);
int actionBrightness();
int actionHue(int p_hue);
int actionHue();
int actionColour(int p_colour);
int actionColour();
int actionContrast(int p_contrast);
int actionContrast();
int PrimeCapture();
int PreCapture() const;

View File

@ -422,7 +422,7 @@ bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) {
break;
case STREAM_RAW :
fputs("Content-Type: image/x-rgb\r\n", stdout);
img_buffer = (uint8_t*)send_image->Buffer();
img_buffer = send_image->Buffer();
img_buffer_size = send_image->Size();
break;
case STREAM_ZIP :

View File

@ -86,38 +86,63 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
{
std::unique_lock<std::mutex> lck(mutex);
if (add_packet->packet.stream_index == video_stream_id) {
if ((max_video_packet_count > 0) and (packet_counts[video_stream_id] > max_video_packet_count)) {
Warning("You have set the max video packets in the queue to %u."
" The queue is full. Either Analysis is not keeping up or"
" your camera's keyframe interval is larger than this setting."
" We are dropping packets.", max_video_packet_count);
if (add_packet->keyframe) {
// Have a new keyframe, so delete everything
while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > max_video_packet_count)) {
std::shared_ptr <ZMPacket>zm_packet = *pktQueue.begin();
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
if (!lp->trylock()) {
Debug(1, "Found locked packet when trying to free up video packets. Can't continue");
delete lp;
break;
}
delete lp;
pktQueue.push_back(add_packet);
packet_counts[add_packet->packet.stream_index] += 1;
Debug(2, "packet counts for %d is %d",
add_packet->packet.stream_index,
packet_counts[add_packet->packet.stream_index]);
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
auto iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *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) == zm_packet ) {
Debug(1, "Bumping IT because it is at the front that we are deleting");
++(*iterators_it);
if (*iterator_it == pktQueue.end()) {
--(*iterator_it);
}
} // end foreach iterator
pktQueue.pop_front();
if (
(add_packet->packet.stream_index == video_stream_id)
and
(max_video_packet_count > 0)
and
(packet_counts[video_stream_id] > max_video_packet_count)
) {
Warning("You have set the max video packets in the queue to %u."
" The queue is full. Either Analysis is not keeping up or"
" your camera's keyframe interval is larger than this setting."
, max_video_packet_count);
for (
auto it = ++pktQueue.begin();
it != pktQueue.end() and *it != add_packet;
) {
std::shared_ptr <ZMPacket>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");
delete lp;
++it;
continue;
}
for (
auto iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
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)) {
Debug(1, "Bumping IT because it is at the front that we are deleting");
++(*iterator_it);
}
} // end foreach iterator
it = pktQueue.erase(it);
packet_counts[zm_packet->packet.stream_index] -= 1;
Debug(1,
"Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu",
@ -127,39 +152,13 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
packet_counts[video_stream_id],
max_video_packet_count,
pktQueue.size());
delete lp;
if (zm_packet->packet.stream_index == video_stream_id)
break;
} // end while
}
} // end if too many video packets
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;
}
}
} // end if this packet is a video packet
pktQueue.push_back(add_packet);
packet_counts[add_packet->packet.stream_index] += 1;
Debug(2, "packet counts for %d is %d",
add_packet->packet.stream_index,
packet_counts[add_packet->packet.stream_index]);
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
if (*iterator_it == pktQueue.end()) {
Debug(4, "pointing it %p to back", iterator_it);
--(*iterator_it);
} else {
Debug(4, "it %p not at end", iterator_it);
}
} // end foreach iterator
} // end if not able catch up
} // end lock scope
// We signal on every packet because someday we may analyze sound
Debug(4, "packetqueue queuepacket, unlocked signalling");
@ -195,7 +194,6 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, keep_keyframes, packet_counts[video_stream_id], pre_event_video_packet_count,
( *(pktQueue.begin()) != add_packet )
);
Warning("Keyframe interval may be larger than MaxImageBufferCount and PreEventCount. Please increase MaxImageBufferCount");
return;
}
std::unique_lock<std::mutex> lck(mutex);
@ -241,8 +239,8 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return;
}
packetqueue_iterator it = pktQueue.begin();
packetqueue_iterator next_front = pktQueue.begin();
auto it = pktQueue.begin();
auto next_front = pktQueue.begin();
// First packet is special because we know it is a video keyframe and only need to check for lock
std::shared_ptr<ZMPacket> zm_packet = *it;
@ -250,32 +248,32 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return;
}
Debug(1, "trying lock on first packet");
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
if (lp->trylock()) {
int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking
Debug(1, "Have lock on first packet");
Debug(4, "Have lock on first packet");
++it;
delete lp;
if (it == pktQueue.end()) {
Debug(1, "Hit end already");
it = pktQueue.begin();
} else {
// Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that
while (*it != add_packet) {
zm_packet = *it;
lp = new ZMLockedPacket(zm_packet);
if (!lp->trylock()) {
Debug(3, "Failed locking packet %d", zm_packet->image_index);
delete lp;
break;
}
delete lp;
if (is_there_an_iterator_pointing_to_packet(zm_packet) and (pktQueue.begin() == next_front)) {
#if 0
// There are no threads that follow analysis thread. So there cannot be an it pointing here
if (is_there_an_iterator_pointing_to_packet(zm_packet)) {
if (pktQueue.begin() == next_front)
Warning("Found iterator at beginning of queue. Some thread isn't keeping up");
break;
}
#endif
if (zm_packet->packet.stream_index == video_stream_id) {
if (zm_packet->keyframe) {
@ -283,7 +281,7 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
next_front = it;
}
++video_packets_to_delete;
Debug(4, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d",
Debug(3, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d",
video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count);
if (packet_counts[video_stream_id] - video_packets_to_delete <= pre_event_video_packet_count + tail_count) {
break;
@ -291,9 +289,8 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
}
++it;
} // end while
}
} // end if first packet not locked
Debug(1, "Resulting pointing at latest packet? %d, next front points to begin? %d",
Debug(1, "Resulting it pointing at latest packet? %d, next front points to begin? %d",
( *it == add_packet ),
( next_front == pktQueue.begin() )
);

View File

@ -74,8 +74,6 @@ void RemoteCamera::Initialise() {
if (port.empty())
Fatal("No port specified for remote camera");
//if( path.empty() )
//Fatal( "No path specified for remote camera" );
// Cache as much as we can to speed things up
std::string::size_type authIndex = host.rfind('@');

View File

@ -46,6 +46,7 @@ RETSIGTYPE zm_die_handler(int signal, siginfo_t * info, void *context)
RETSIGTYPE zm_die_handler(int signal)
#endif
{
zm_terminate = true;
Error("Got signal %d (%s), crashing", signal, strsignal(signal));
#if (defined(__i386__) || defined(__x86_64__))
// Get more information if available

View File

@ -83,4 +83,41 @@ Duration duration_cast(timeval const &tv) {
}
}
//
// This can be used for benchmarking. It will measure the time in between
// its constructor and destructor (or when you call Finish()) and add that
// duration to a microseconds clock.
//
class TimeSegmentAdder {
public:
TimeSegmentAdder(Microseconds &in_target) :
target_(in_target),
start_time_(std::chrono::steady_clock::now()),
finished_(false) {
}
~TimeSegmentAdder() {
Finish();
}
// Call this to stop the timer and add the timed duration to `target`.
void Finish() {
if (!finished_) {
const TimePoint end_time = std::chrono::steady_clock::now();
target_ += (std::chrono::duration_cast<Microseconds>(end_time - start_time_));
}
finished_ = true;
}
private:
// This is where we will add our duration to.
Microseconds &target_;
// The time we started.
const TimePoint start_time_;
// True when it has finished timing.
bool finished_;
};
#endif // ZM_TIME_H

View File

@ -151,6 +151,7 @@ bool VideoStore::open() {
Debug(3, "Encoder Option %s=%s", e->key, e->value);
}
}
av_dict_free(&opts);
if (video_in_stream) {
zm_dump_codecpar(video_in_stream->codecpar);
@ -184,6 +185,7 @@ bool VideoStore::open() {
}
} // end if orientation
av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0);
if (av_dict_get(opts, "new_extradata", nullptr, AV_DICT_MATCH_CASE)) {
av_dict_set(&opts, "new_extradata", nullptr, 0);
// Special flag to tell us to open a codec to get new extraflags to fix weird h265
@ -230,8 +232,8 @@ bool VideoStore::open() {
if (ret < 0) {
Error("Could not initialize stream parameteres");
}
} // end if extradata_entry
av_dict_free(&opts);
} // end if extradata_entry
} else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) {
int wanted_codec = monitor->OutputCodec();
if (!wanted_codec) {
@ -485,6 +487,7 @@ bool VideoStore::open() {
zm_dump_stream_format(oc, 0, 0, 1);
if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1);
av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0);
const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE);
if (!movflags_entry) {
Debug(1, "setting movflags to frag_keyframe+empty_moov");
@ -616,7 +619,8 @@ VideoStore::~VideoStore() {
Debug(1, "Writing trailer");
/* Write the trailer before close */
if (int rc = av_write_trailer(oc)) {
int rc;
if ((rc = av_write_trailer(oc)) < 0) {
Error("Error writing trailer %s", av_err2str(rc));
} else {
Debug(3, "Success Writing trailer");
@ -626,7 +630,7 @@ VideoStore::~VideoStore() {
if (!(out_format->flags & AVFMT_NOFILE)) {
/* Close the out file. */
Debug(4, "Closing");
if (int rc = avio_close(oc->pb)) {
if ((rc = avio_close(oc->pb)) < 0) {
Error("Error closing avio %s", av_err2str(rc));
}
} else {

View File

@ -111,6 +111,13 @@ class VideoStore {
int writePacket(const std::shared_ptr<ZMPacket> &pkt);
int write_packets(PacketQueue &queue);
void flush_codecs();
const char *get_codec() {
if (chosen_codec_data)
return chosen_codec_data->codec_codec;
if (video_out_stream)
return avcodec_get_name(video_out_stream->codecpar->codec_id);
return "";
}
};
#endif // ZM_VIDEOSTORE_H

View File

@ -206,7 +206,7 @@ bool Zone::CheckAlarms(const Image *delta_image) {
// Get the difference image
Image *diff_image = image = new Image(*delta_image);
int diff_width = diff_image->Width();
uint8_t* diff_buff = (uint8_t*)diff_image->Buffer();
uint8_t* diff_buff = diff_image->Buffer();
uint8_t* pdiff;
unsigned int pixel_diff_count = 0;
@ -283,7 +283,7 @@ bool Zone::CheckAlarms(const Image *delta_image) {
int lo_x = ranges[y].lo_x;
int hi_x = ranges[y].hi_x;
pdiff = (uint8_t*)diff_image->Buffer(lo_x, y);
pdiff = diff_image->Buffer(lo_x, y);
for (int x = lo_x; x <= hi_x; x++, pdiff++) {
if (*pdiff == kWhite) {
@ -366,7 +366,7 @@ bool Zone::CheckAlarms(const Image *delta_image) {
int lo_x = ranges[y].lo_x;
int hi_x = ranges[y].hi_x;
pdiff = (uint8_t*)diff_image->Buffer(lo_x, y);
pdiff = diff_image->Buffer(lo_x, y);
for (int x = lo_x; x <= hi_x; x++, pdiff++) {
if (*pdiff == kWhite) {
Debug(9, "Got white pixel at %d,%d (%p)", x, y, pdiff);
@ -980,7 +980,7 @@ void Zone::std_alarmedpixels(
unsigned int hi_x = ranges[y].hi_x;
Debug(7, "Checking line %d from %d -> %d", y, lo_x, hi_x);
uint8_t *pdiff = (uint8_t*)pdiff_image->Buffer(lo_x, y);
uint8_t *pdiff = pdiff_image->Buffer(lo_x, y);
const uint8_t *ppoly = ppoly_image->Buffer(lo_x, y);
for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) {

326
src/zmbenchmark.cpp Normal file
View File

@ -0,0 +1,326 @@
//
// ZoneMinder Benchmark, $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 <algorithm>
#include <cassert>
#include <cstdlib>
#include <memory>
#include <random>
#include <utility>
#include "zm_config.h"
#include "zm_image.h"
#include "zm_monitor.h"
#include "zm_time.h"
#include "zm_utils.h"
#include "zm_zone.h"
static std::mt19937 mt_rand(111);
//
// This allows you to feed in a set of columns and timing rows, and print it
// out in a nice-looking table.
//
class TimingsTable {
public:
explicit TimingsTable(std::vector<std::string> in_columns) : columns_(std::move(in_columns)) {}
//
// Add a row to the end of the table.
//
// Args:
// label: The name of the row (printed in the first column).
// timings: The values for all the other columns in this row.
void AddRow(const std::string &label, const std::vector<Microseconds> &timings) {
assert(timings.size() == columns_.size());
Row row;
row.label = label;
row.timings = timings;
rows_.push_back(row);
}
//
// Print out the table.
//
// Args:
// columnPad: # characters between table columns
//
void Print(const int column_pad = 5) {
// Figure out column widths.
std::vector<size_t> widths(columns_.size() + 1);
// The first width is the max of the row labels.
auto result = std::max_element(rows_.begin(),
rows_.end(),
[](const Row &a, const Row &b) -> bool {
return a.label.length() < b.label.length();
});
widths[0] = result->label.length() + column_pad;
// Calculate the rest of the column widths.
for (size_t i = 0 ; i < columns_.size() ; i++)
widths[i + 1] = columns_[i].length() + column_pad;
auto PrintColStr = [&](size_t icol, const std::string &str) {
printf("%s", str.c_str());
PrintPadding(widths[icol] - str.length());
};
// Print the header.
PrintColStr(0, "");
for (size_t i = 0 ; i < columns_.size() ; i++) {
PrintColStr(i + 1, columns_[i]);
}
printf("\n");
// Print the timings rows.
for (const Row &row : rows_) {
PrintColStr(0, row.label);
for (size_t i = 0 ; i < row.timings.size() ; i++) {
std::string num = stringtf("%.2f", std::chrono::duration_cast<FPSeconds>(row.timings[i]).count());
PrintColStr(i + 1, num);
}
printf("\n");
}
}
private:
static void PrintPadding(size_t count) {
std::string str(count, ' ');
printf("%s", str.c_str());
}
struct Row {
std::string label;
std::vector<Microseconds> timings;
};
std::vector<std::string> columns_;
std::vector<Row> rows_;
};
//
// Generate a greyscale image that simulates a delta that can be fed to
// Zone::CheckAlarms. This first creates a black image, and then it fills
// a box of a certain size inside the image with random data. This is to simulate
// a typical scene where most of the scene doesn't change except a specific region.
//
// Args:
// changeBoxPercent: 0-100 value telling how large the box with random data should be.
// Set to 0 to leave the whole thing black.
// width: The width of the new image.
// height: The height of the new image.
//
// Return:
// An image with all pixels initialized to values in the [minVal,maxVal] range.
//
std::shared_ptr<Image> GenerateRandomImage(
const int change_box_percent,
const int width = 3840,
const int height = 2160) {
// Create the image.
Image *image = new Image(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE);
// Set it to black initially.
memset(image->Buffer(0, 0), 0, (size_t) image->LineSize() * (size_t) image->Height());
// Now randomize the pixels inside a box.
const int box_width = (width * change_box_percent) / 100;
const int box_height = (height * change_box_percent) / 100;
const int box_x = (int) ((uint64_t) mt_rand() * (width - box_width) / RAND_MAX);
const int box_y = (int) ((uint64_t) mt_rand() * (height - box_height) / RAND_MAX);
for (int y = 0 ; y < box_height ; y++) {
uint8_t *row = image->Buffer(box_x, box_y + y);
for (int x = 0 ; x < box_width ; x++) {
row[x] = (uint8_t) mt_rand();
}
}
return std::shared_ptr<Image>(image);
}
//
// This is used to help rig up Monitor benchmarks.
//
class TestMonitor : public Monitor {
public:
TestMonitor(int width, int height) : cur_zone_id(111) {
this->width = width;
this->height = height;
// Create a dummy ref_image.
std::shared_ptr<Image> tempImage = GenerateRandomImage(0, width, height);
ref_image = *tempImage;
shared_data = &temp_shared_data;
}
//
// Add a new zone to this monitor.
//
// Args:
// checkMethod: This controls how this zone will actually do motion detection.
//
// p_filter_box: The size of the filter to use.
//
void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box = Vector2(5, 5)) {
const int p_min_pixel_threshold = 50;
const int p_max_pixel_threshold = 255;
const int p_min_alarm_pixels = 1000;
const int p_max_alarm_pixels = 10000000;
const int zone_id = cur_zone_id++;
const std::string zone_label = std::string("zone_") + std::to_string(zone_id);
const Zone::ZoneType zone_type = Zone::ZoneType::ACTIVE;
const Polygon poly({Vector2(0, 0),
Vector2(width - 1, 0),
Vector2(width - 1, height - 1),
Vector2(0, height - 1)});
Zone zone(this,
zone_id,
zone_label.c_str(),
zone_type,
poly,
kRGBGreen,
Zone::CheckMethod::FILTERED_PIXELS,
p_min_pixel_threshold,
p_max_pixel_threshold,
p_min_alarm_pixels,
p_max_alarm_pixels,
p_filter_box);
zones.push_back(zone);
}
void SetRefImage(const Image *image) {
ref_image = *image;
}
private:
SharedData temp_shared_data;
int cur_zone_id;
};
//
// Run zone benchmarks on the given image.
//
// Args:
// label: A label to be printed before the output.
//
// image: The image to run the tests on.
//
// p_filter_box: The size of the filter to use for alarm detection.
//
// Return:
// The average time taken for each DetectMotion call.
//
Microseconds RunDetectMotionBenchmark(const std::string &label,
const std::shared_ptr<Image>& image,
const Vector2 &p_filter_box) {
// Create a monitor to use for the benchmark. Give it 1 zone that uses
// a 5x5 filter.
TestMonitor testMonitor(image->Width(), image->Height());
testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, p_filter_box);
// Generate a black image to use as the reference image.
std::shared_ptr<Image> blackImage = GenerateRandomImage(
0, image->Width(), image->Height());
testMonitor.SetRefImage(blackImage.get());
Microseconds totalTimeTaken(0);
// Run a series of passes over DetectMotion.
const int numPasses = 10;
for (int i = 0 ; i < numPasses ; i++) {
printf("\r%s - pass %2d / %2d ", label.c_str(), i + 1, numPasses);
fflush(stdout);
TimeSegmentAdder adder(totalTimeTaken);
Event::StringSet zoneSet;
testMonitor.DetectMotion(*image, zoneSet);
}
printf("\n");
return totalTimeTaken / numPasses;
}
//
// This runs a set of Monitor::DetectMotion benchmarks, one for each of the
// "delta box percents" that are passed in. This adds one row to the
// TimingsTable specified.
//
// Args:
// table: The table to add timings into.
//
// deltaBoxPercents: Each of these defines a box size in the delta images
// passed to DetectMotion (larger boxes make it slower, sometimes significantly so).
//
// p_filter_box: Defines the filter size used in DetectMotion.
//
void RunDetectMotionBenchmarks(
TimingsTable &table,
const std::vector<int> &delta_box_percents,
const Vector2 &p_filter_box) {
std::vector<Microseconds> timings;
for (int percent : delta_box_percents) {
Microseconds timing = RunDetectMotionBenchmark(
std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_)
+ " box, " + std::to_string(percent) + "% delta",
GenerateRandomImage(percent),
p_filter_box);
timings.push_back(timing);
}
table.AddRow(
std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter",
timings);
}
int main(int argc, char *argv[]) {
// Init global stuff that we need.
config.font_file_location = "../fonts/default.zmfnt";
config.event_close_mode = "time";
config.cpu_extensions = true;
// Detect SSE version.
HwCapsDetect();
// Setup the column titles for the TimingsTable we'll generate.
// Each column represents how large the box in the image is with delta pixels.
// Each row represents a different filter size.
const std::vector<int> percents = {0, 10, 50, 100};
std::vector<std::string> columns(percents.size());
std::transform(percents.begin(), percents.end(), columns.begin(),
[](const int percent) { return std::to_string(percent) + "% delta (ms)"; });
TimingsTable table(columns);
std::vector<Vector2> filterSizes = {Vector2(3, 3), Vector2(5, 5), Vector2(13, 13)};
for (const auto filterSize : filterSizes) {
RunDetectMotionBenchmarks(table, percents, filterSize);
}
table.Print();
return 0;
}

View File

@ -308,6 +308,7 @@ int main(int argc, char *argv[]) {
result = -1;
break;
}
monitors[i]->UpdateFPS();
// capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate.
Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay()

View File

@ -257,9 +257,16 @@ int main(int argc, char *argv[]) {
int image_idx = -1;
int scale = -1;
int brightness = -1;
bool have_brightness = false;
int contrast = -1;
bool have_contrast = false;
int hue = -1;
bool have_hue = false;
int colour = -1;
bool have_colour = false;
char *zoneString = nullptr;
char *username = nullptr;
char *password = nullptr;
@ -349,23 +356,31 @@ int main(int argc, char *argv[]) {
break;
case 'B':
function |= ZMU_BRIGHTNESS;
if ( optarg )
if (optarg) {
have_brightness = true;
brightness = atoi(optarg);
}
break;
case 'C':
function |= ZMU_CONTRAST;
if ( optarg )
if (optarg) {
have_contrast = true;
contrast = atoi(optarg);
}
break;
case 'H':
function |= ZMU_HUE;
if ( optarg )
if (optarg) {
have_hue = true;
hue = atoi(optarg);
}
break;
case 'O':
function |= ZMU_COLOUR;
if ( optarg )
if (optarg) {
have_colour = true;
colour = atoi(optarg);
}
break;
case 'U':
username = optarg;
@ -652,13 +667,13 @@ int main(int argc, char *argv[]) {
}
if (function & ZMU_BRIGHTNESS) {
if (verbose) {
if ( brightness >= 0 )
if (have_brightness)
printf("New brightness: %d\n", monitor->actionBrightness(brightness));
else
printf("Current brightness: %d\n", monitor->actionBrightness());
} else {
if (have_output) fputc(separator, stdout);
if ( brightness >= 0 )
if (have_brightness)
printf("%d", monitor->actionBrightness(brightness));
else
printf("%d", monitor->actionBrightness());
@ -667,13 +682,13 @@ int main(int argc, char *argv[]) {
}
if (function & ZMU_CONTRAST) {
if (verbose) {
if ( contrast >= 0 )
printf("New brightness: %d\n", monitor->actionContrast(contrast));
if (have_contrast)
printf("New contrast: %d\n", monitor->actionContrast(contrast));
else
printf("Current contrast: %d\n", monitor->actionContrast());
} else {
if (have_output) fputc(separator, stdout);
if ( contrast >= 0 )
if (have_contrast)
printf("%d", monitor->actionContrast(contrast));
else
printf("%d", monitor->actionContrast());
@ -682,13 +697,13 @@ int main(int argc, char *argv[]) {
}
if (function & ZMU_HUE) {
if (verbose) {
if ( hue >= 0 )
if (have_hue)
printf("New hue: %d\n", monitor->actionHue(hue));
else
printf("Current hue: %d\n", monitor->actionHue());
} else {
if (have_output) fputc(separator, stdout);
if ( hue >= 0 )
if (have_hue)
printf("%d", monitor->actionHue(hue));
else
printf("%d", monitor->actionHue());
@ -697,13 +712,13 @@ int main(int argc, char *argv[]) {
}
if (function & ZMU_COLOUR) {
if (verbose) {
if ( colour >= 0 )
if (have_colour)
printf("New colour: %d\n", monitor->actionColour(colour));
else
printf("Current colour: %d\n", monitor->actionColour());
} else {
if (have_output) fputc(separator, stdout);
if ( colour >= 0 )
if (have_colour)
printf("%d", monitor->actionColour(colour));
else
printf("%d", monitor->actionColour());

View File

@ -231,8 +231,12 @@ 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;
fi;
IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
echo "Generating package for $DISTRO";
@ -358,7 +362,7 @@ EOF
dput="Y";
if [ "$INTERACTIVE" != "no" ]; then
read -p "Ready to dput $SC to $PPA ? Y/n...";
if [[ "$REPLY" == [yY] ]]; then
if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
dput $PPA $SC
fi;
else

View File

@ -6,16 +6,18 @@ $data = array();
// INITIALIZE AND CHECK SANITY
//
if ( !canView('Events') ) $message = 'Insufficient permissions for user '.$user['Username'];
if (!canView('Events'))
$message = 'Insufficient permissions for user '.$user['Username'].'<br/>';
if (empty($_REQUEST['task'])) {
$message = 'Must specify a task';
$message = 'Must specify a task<br/>';
} else {
$task = $_REQUEST['task'];
}
if (empty($_REQUEST['eids'])) {
if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No event id(s) supplied';
if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query')
$message = 'No event id(s) supplied<br/>';
} else {
$eids = $_REQUEST['eids'];
}
@ -39,10 +41,19 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array();
// Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
$order = $filter->sort_asc() ? 'ASC' : 'DESC';
if (isset($_REQUEST['order'])) {
if (strtolower($_REQUEST['order']) == 'asc') {
$order = 'ASC';
} else if (strtolower($_REQUEST['order']) == 'desc') {
$order = 'DESC';
} else {
Warning("Invalid value for order " . $_REQUEST['order']);
}
}
// Sort specifies the name of the column to sort on
$sort = 'StartDateTime';
$sort = $filter->sort_field();
if (isset($_REQUEST['sort'])) {
$sort = $_REQUEST['sort'];
if ($sort == 'EndDateTime') {
@ -228,15 +239,17 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
} # end if search
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
ZM\Debug('Calling the following sql query: ' .$sql);
$filtered_rows = dbFetchAll($sql);
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter.');
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql);
} else {
$filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1
if ($limit)
$filtered_rows = array_slice($filtered_rows, $offset, $limit);
$returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) {
foreach ($filtered_rows as $row) {
$event = new ZM\Event($row);
$scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());

View File

@ -13,7 +13,6 @@ if ( $zmuOutput ) {
$monitor->Hue($hue);
$monitor->Colour($colour);
}
?>
<div class="modal" id="settingsModal" tabindex="-1">
<div class="modal-dialog">
@ -35,22 +34,89 @@ if ( $zmuOutput ) {
<input type="hidden" name="mid" value="<?php echo validInt($_REQUEST['mid']) ?>"/>
<table id="contentTable" class="major">
<tbody>
<?php
$ctls = shell_exec('v4l2-ctl -d '.$monitor->Device().' --list-ctrls');
if (!$ctls) {
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
saturation 0x00980902 (int) : min=0 max=10 step=1 default=7 value=6
hue 0x00980903 (int) : min=-5000 max=5000 step=1000 default=0 value=2000
';
}
$ctls = trim($ctls);
$ctls = explode("\n", $ctls);
foreach ($ctls as $line) {
$ctl = explode(':', $line);
$type_info = explode(' ', trim($ctl[0]));
$setting = trim($type_info[0]);
if ($setting == 'saturation')
$setting = 'colour';
$setting_uc = ucwords($setting);
$type = $type[2];
$min = '';
$max = '';
$step = '';
$value = '';
$default = '';
# The purpose is security
foreach (explode(' ', trim($ctl[1])) as $index=>$prop) {
list($key,$val) = explode('=', $prop);
// get current value
if ($key == 'value') {
$value = validInt($val);
} else if ($key == 'default') {
$default = validInt($val);
} else if ($key == 'min') {
$min = validInt($val);
} else if ($key == 'max') {
$max = validInt($val);
} else if ($key == 'step') {
$step = validInt($val);
}
}
if ($setting == 'brightness' or $setting == 'colour' or $setting == 'contrast' or $setting == 'hue') {
echo '
<tr>
<th scope="row"><?php echo translate('Brightness') ?></th>
<td><input type="number" name="newBrightness" value="<?php echo $monitor->Brightness() ?>" <?php if ( !canView( 'Control' ) ) { ?> disabled="disabled"<?php } ?> /></td>
<th scope="row">'.translate($setting_uc).'</th>
<td>'.$min.'</td><td><input type="range" title="'.$value.'" min="'.$min.'" max="'.$max.'" step="'.$step.'" default="'.$default.'" value="'.$value.'" id="new'.$setting_uc.'" name="new'.$setting_uc.'" '.(canEdit('Control') ? '' : 'disabled="disabled"') .'/></td><td>'.$max.'</td>
</tr>
';
} else {
if ($type == '(bool)') {
echo '
<tr>
<th scope="row"><?php echo translate('Contrast') ?></th>
<td><input type="number" name="newContrast" value="<?php echo $monitor->Contrast() ?>" <?php echo canView('Control') ? '' : ' disabled="disabled"' ?> /></td>
<th scope="row">'.translate($setting_uc).'</th>
<td></td><td>'.html_radio('new'.$setting_uc, array('0'=>translate('True'), '1', translate('False')), $value, array('disabled'=>'disabled')).'
</td><td></td>
</tr>
';
} else if ($type == '(int)') {
echo '
<tr>
<th scope="row"><?php echo translate('Hue') ?></th>
<td><input type="number" name="newHue" value="<?php echo $monitor->Hue() ?>" <?php echo canView('Control') ? '' : ' disabled="disabled"' ?> /></td>
<th scope="row">'.translate($setting_uc).'</th>
<td></td><td><input type="range" '.$ctl[1].' disabled="disabled"/></td><td></td>
</tr>
';
} else {
echo '
<tr>
<th scope="row"><?php echo translate('Colour') ?></th>
<td><input type="number" name="newColour" value="<?php echo $monitor->Colour() ?>" <?php echo canView('Control') ? '' : ' disabled="disabled"' ?> /></td>
<th scope="row">'.translate($setting_uc).'</th>
<td></td><td>'.$value.'</td><td></td>
</tr>
';
}
}
} # end foreach ctrl
?>
</tbody>
</table>
</div>

View File

@ -1,5 +1,4 @@
<?php
if (empty($_REQUEST['eid'])) ajaxError('Event Id Not Provided');
if (empty($_REQUEST['fid'])) ajaxError('Frame Id Not Provided');
@ -9,32 +8,26 @@ $row = ( isset($_REQUEST['row']) ) ? $_REQUEST['row'] : '';
$raw = isset($_REQUEST['raw']);
$data = array();
// Not sure if this is required
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) {
$data['auth'] = $auth_hash;
}
}
if ($raw) {
$sql = 'SELECT S.*,E.*,Z.Name AS ZoneName,Z.Units,Z.Area,M.Name AS MonitorName FROM Stats AS S LEFT JOIN Events AS E ON S.EventId = E.Id LEFT JOIN Zones AS Z ON S.ZoneId = Z.Id LEFT JOIN Monitors AS M ON E.MonitorId = M.Id WHERE S.EventId = ? AND S.FrameId = ? ORDER BY S.ZoneId';
$stat = dbFetchOne( $sql, NULL, array( $eid, $fid ) );
if ( $stat ) {
$sql = 'SELECT S.*,E.*,Z.Name AS ZoneName,Z.Units,Z.Area,M.Name AS MonitorName
FROM Stats AS S LEFT JOIN Events AS E ON S.EventId = E.Id LEFT JOIN Zones AS Z ON S.ZoneId = Z.Id LEFT JOIN Monitors AS M ON E.MonitorId = M.Id
WHERE S.EventId = ? AND S.FrameId = ? ORDER BY S.ZoneId';
$stats = dbFetchAll($sql, NULL, array($eid, $fid));
foreach ($stats as $stat) {
$stat['ZoneName'] = validHtmlStr($stat['ZoneName']);
$stat['PixelDiff'] = validHtmlStr($stat['PixelDiff']);
$stat['AlarmPixels'] = sprintf( "%d (%d%%)", $stat['AlarmPixels'], (100*$stat['AlarmPixels']/$stat['Area']) );
$stat['FilterPixels'] = sprintf( "%d (%d%%)", $stat['FilterPixels'], (100*$stat['FilterPixels']/$stat['Area']) );
$stat['BlobPixels'] = sprintf( "%d (%d%%)", $stat['BlobPixels'], (100*$stat['BlobPixels']/$stat['Area']) );
$stat['AlarmPixels'] = sprintf('%d (%d%%)', $stat['AlarmPixels'], (100*$stat['AlarmPixels']/$stat['Area']));
$stat['FilterPixels'] = sprintf('%d (%d%%)', $stat['FilterPixels'], (100*$stat['FilterPixels']/$stat['Area']));
$stat['BlobPixels'] = sprintf('%d (%d%%)', $stat['BlobPixels'], (100*$stat['BlobPixels']/$stat['Area']));
$stat['Blobs'] = validHtmlStr($stat['Blobs']);
if ($stat['Blobs'] > 1) {
$stat['BlobSizes'] = sprintf( "%d-%d (%d%%-%d%%)", $stat['MinBlobSize'], $stat['MaxBlobSize'], (100*$stat['MinBlobSize']/$stat['Area']), (100*$stat['MaxBlobSize']/$stat['Area']) );
$stat['BlobSizes'] = sprintf('%d-%d (%d%%-%d%%)', $stat['MinBlobSize'], $stat['MaxBlobSize'], (100*$stat['MinBlobSize']/$stat['Area']), (100*$stat['MaxBlobSize']/$stat['Area']));
} else {
$stat['BlobSizes'] = sprintf( "%d (%d%%)", $stat['MinBlobSize'], 100*$stat['MinBlobSize']/$stat['Area'] );
$stat['BlobSizes'] = sprintf('%d (%d%%)', $stat['MinBlobSize'], 100*$stat['MinBlobSize']/$stat['Area']);
}
$stat['AlarmLimits'] = validHtmlStr($stat['MinX'].",".$stat['MinY']."-".$stat['MaxX'].",".$stat['MaxY']);
}
$data['raw'] = $stat;
$stat['AlarmLimits'] = validHtmlStr($stat['MinX'].','.$stat['MinY'].'-'.$stat['MaxX'].','.$stat['MaxY']);
$data['raw'][] = $stat;
} # end foreach stat/zone
} else {
$data['html'] = getStatsTableHTML($eid, $fid, $row);
$data['id'] = '#contentStatsTable' .$row;

View File

@ -13,7 +13,8 @@ if ( $_REQUEST['entity'] == 'navBar' ) {
$data['getSysLoadHTML'] = getSysLoadHTML();
$data['getDbConHTML'] = getDbConHTML();
$data['getStorageHTML'] = getStorageHTML();
$data['getShmHTML'] = getShmHTML();
//$data['getShmHTML'] = getShmHTML();
$data['getRamHTML'] = getRamHTML();
ajaxResponse($data);
return;

View File

@ -1,6 +1,12 @@
ZoneMinder uses certain 3rd party media assets/libraries for UI display purposes. Their licenses are listed in this file
### Font Awesome icons
Origin: http://fontawesome.io
License: Font: SIL OFL 1.1, CSS: MIT License (http://fontawesome.io/license)
### Material Design icons
Origin: https://github.com/google/material-design-icons

View File

@ -60,6 +60,9 @@ class Filter extends ZM_Object {
foreach ( $this->FilterTerms() as $term ) {
$this->_querystring .= $term->querystring($objectname, $separator);
} # end foreach term
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc();
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field();
$this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit();
if ( $this->Id() ) {
$this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id();
}

View File

@ -39,6 +39,7 @@ class Server extends ZM_Object {
} else if ( $this->Id() ) {
return $this->{'Name'};
}
if (isset($_SERVER['HTTP_HOST'])) {
# This theoretically will match ipv6 addresses as well
if ( preg_match( '/^(\[[[:xdigit:]:]+\]|[^:]+)(:[[:digit:]]+)?$/', $_SERVER['HTTP_HOST'], $matches ) ) {
return $matches[1];
@ -47,6 +48,8 @@ class Server extends ZM_Object {
$result = explode(':', $_SERVER['HTTP_HOST']);
return $result[0];
}
return '';
}
public function Protocol( $new = null ) {
if ( $new != null )

View File

@ -231,7 +231,7 @@ if ( $action == 'save' ) {
} // end if changes in width or height
} else {
global $error_message;
$error_message = dbError();
$error_message = dbError('unknown');
} // end if successful save
$restart = true;
} else { // new monitor

View File

@ -45,5 +45,7 @@ if ( $action == 'settings' ) {
dbQuery(
'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?',
array($brightness, $contrast, $hue, $colour, $mid));
global $redirect;
$redirect = '?view=watch&mid='.$mid;
}
?>

View File

@ -1990,6 +1990,10 @@ function requestVar($name, $default='') {
// For numbers etc in javascript or tags etc
function validInt($input) {
return preg_replace('/[^\-\d]/', '', $input);
}
function validCardinal($input) {
return preg_replace('/\D/', '', $input);
}

View File

@ -348,16 +348,16 @@ $SLANG = array(
'Ffmpeg' => 'Ffmpeg', // Added - 2009-02-08
'File' => 'Fichier',
'Filter' => 'Filtre', // Added - 2015-04-18
'FilterArchiveEvents' => 'Archiver',
'FilterDeleteEvents' => 'Effacer',
'FilterEmailEvents' => 'Envoyer les détails par email',
'FilterArchiveEvents' => 'Archiver les évènements',
'FilterDeleteEvents' => 'Effacer les évènements',
'FilterEmailEvents' => 'Envoyer les évènements par email',
'FilterExecuteEvents' => 'Exécuter une commande',
'FilterLog' => 'Filtre', // Added - 2015-04-18
'FilterMessageEvents' => 'Envoyer les détails par message',
'FilterMessageEvents' => 'Envoyer les évènements par message',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filtre Px',
'FilterUnset' => 'Vous devez spécifier une largeur et une hauteur de filtre',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUpdateDiskSpace'=> 'Mies à jour de l\'espace disque utilisé', // Added - 2018-08-30
'FilterUploadEvents' => 'Transférer',
'FilterVideoEvents' => 'Créer vidéo',
'Filters' => 'Filtres',

View File

@ -63,7 +63,7 @@ select {
input[name="filter[EmailSubject]"],
input[name="filter[EmailTo]"],
textarea[name="filter[EmailBody]"] {
width: 500px;
width: 100%;
}
select#Id {
min-width: 500px;
@ -71,3 +71,16 @@ min-width: 500px;
.Name input {
min-width: 500px;
}
#ActionsAndOptions {
padding-top: 5px;
border-top: 1px solid #7f7fb2;
border-bottom: 1px solid #7f7fb2;
}
#ActionsAndOptions:after {
content: ".";
display: block;
height: 0;
font-size: 0;
clear: both;
visibility: hidden;
}

View File

@ -50,6 +50,7 @@ select.chosen {
}
tr td:first-child {
min-width: 300px;
vertical-align: top;
}
.OutputContainer {
display: none;

View File

@ -242,7 +242,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
width="<?php echo $event->Width() ?>"
height="<?php echo $event->Height() ?>"
data-setup='{ "controls": true, "autoplay": true, "preload": "auto", "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}'>
<source src="<?php echo $event->getStreamSrc(array('mode'=>'mpeg','format'=>'h264')); ?>" type="video/mp4">
<source src="<?php echo $event->DefaultVideo(); ?>" type="video/mp4">
<track id="monitorCaption" kind="captions" label="English" srclang="en" src='data:plain/text;charset=utf-8,"WEBVTT\n\n 00:00:00.000 --> 00:00:01.000 ZoneMinder"' default>
Your browser does not support the video tag.
</video>
@ -250,7 +250,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
<?php
} else { // end if DefaultVideo
?>
<ilayer id="slidensmain" width=&{slidewidth}; height=&{slideheight}; bgColor=&{slidebgcolor}; visibility=hide>
<ilayer id="slidensmain" width="&{slidewidth};" height="&{slideheight};" bgColor="&{slidebgcolor};" visibility="hide">
<layer id="slidenssub" width="&{slidewidth};" left="auto" top="auto"></layer>
</ilayer>
<div id="imagevideo" align="center"></div>
@ -571,27 +571,21 @@ else if (document.layers) window.onload=start_slider;
} # end function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist)
function eventlist_html($Event, $exportDetail, $exportFrames, $exportStructure) {
$html = '<div class="event">
';
$html = '';
if ($Event->SaveJPEGs()) {
$html .= '<a href="#" onclick="switchevent(\''.$Event->Id().'/zmEventImages.html\');return false;">
';
if ( ZM_WEB_LIST_THUMBS ) {
$html .= '<img width="'.ZM_WEB_LIST_THUMB_WIDTH.'" src="'. $Event->Id().($exportStructure=='flat'?'_':'/').'snapshot.jpg" alt="'.$Event->Id().'"/>
';
} else {
$html .= $Event->Id();
}
$html .= '</a><br/>
';
} # end if has jpegs
if ($Event->DefaultVideo()) {
if ( ZM_WEB_LIST_THUMBS ) {
$html .= '<a href="'.$Event->Id().'/'.$Event->DefaultVideo() .'">';
$html .= '<img width="'.ZM_WEB_LIST_THUMB_WIDTH.'" src="'. $Event->Id().($exportStructure=='flat'?'_':'/').'snapshot.jpg" alt="'.$Event->Id().'"/>';
$html .= '</a><br/>
';
}
$html .= '</a><br/>'.PHP_EOL;
}
if ($exportDetail) {
$html .= '<a href="#" onclick="switchevent(\''.$Event->Id().'/zmEventDetail.html\');return false;">Detail</a>
@ -601,8 +595,8 @@ function eventlist_html($Event, $exportDetail, $exportFrames, $exportStructure)
$html .= '<a href="#" onclick="switchevent(\''.$Event->Id().'/zmEventFrames.html\');return false;">Frames</a>
';
}
$html .= '</div><!--event-->
';
if (!$html) $html = $Event->Id();
$html = '<div class="event">'.PHP_EOL.$html.PHP_EOL.'</div><!--event-->'.PHP_EOL;
return $html;
} // end function eventlist_html
@ -641,7 +635,7 @@ function exportEventImagesMaster($eids, $exportDetail, $exportFrames, $exportStr
?>
</ul>
</div>
<table>
<table style="width: 100%;">
<tr>
<td valign="top" bgcolor="#dddddd" style="padding:10px;">
<div class="tab_content" id="all">
@ -665,7 +659,7 @@ function exportEventImagesMaster($eids, $exportDetail, $exportFrames, $exportStr
} # end foreach monitor
?>
</td><td valign="top">
</td><td valign="top" style="height: 100%;">
<iframe id="myframe" onload="resizeCaller();" name="myframe" src="about:blank"
scrolling="no" marginwidth="0" marginheight="0" frameborder="0"
vspace="0" hspace="0" style="overflow:visible; width:100%; display:none">
@ -828,6 +822,8 @@ function exportFileList(
foreach ($files as $file) {
if (preg_match('/-(?:capture|analyse).jpg$/', $file)) {
$myfilelist[$file] = $exportFileList[$file] = $file;
} else if ($exportVideo and preg_match('/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file)) {
$exportFileList[$file] = $file;
} else {
$filesLeft[$file] = $file;
}
@ -835,7 +831,6 @@ function exportFileList(
$files = $filesLeft;
// create an image slider
if (!empty($myfilelist)) {
$file = 'zmEventImages.html';
if ($fp = fopen($eventPath.'/'.$file, 'w')) {
fwrite($fp, exportEventImages($event, $exportDetail, $exportFrames, $myfilelist));
@ -844,23 +839,10 @@ function exportFileList(
} else {
ZM\Error("Can't open event images export file '$file'");
}
}
} else {
ZM\Debug('Not including frame images');
} # end if exportImages
$filesLeft = array();
foreach ($files as $file) {
if (preg_match('/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file)) {
if ($exportVideo) {
$exportFileList[$file] = $file;
}
} else {
$filesLeft[$file] = $file;
}
}
$files = $filesLeft;
if ($exportMisc) {
foreach ($files as $file) {
$exportFileList[$file] = $file;
@ -901,14 +883,14 @@ function exportEvents(
}
# Ensure that we are going to be able to do this.
if (!(mkdir(ZM_DIR_EXPORTS) or file_exists(ZM_DIR_EXPORTS))) {
if (!(@mkdir(ZM_DIR_EXPORTS) or file_exists(ZM_DIR_EXPORTS))) {
ZM\Fatal('Can\'t create exports dir at \''.ZM_DIR_EXPORTS.'\'');
}
chmod(ZM_DIR_EXPORTS, 0700);
$export_dir = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'');
# Ensure that we are going to be able to do this.
if (!(mkdir($export_dir) or file_exists($export_dir))) {
if (!(@mkdir($export_dir) or file_exists($export_dir))) {
ZM\Error("Can't create exports dir at '$export_dir'");
return false;
}
@ -933,7 +915,7 @@ function exportEvents(
continue;
}
$event_dir = $export_dir.'/'.$event->Id();
if (!(mkdir($event_dir) or file_exists($event_dir))) {
if (!(@mkdir($event_dir) or file_exists($event_dir))) {
ZM\Error("Can't mkdir $event_dir");
}
$event_exportFileList = exportFileList($event, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc);
@ -952,7 +934,7 @@ function exportEvents(
} # end foreach event
if (!(
symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/jquery.min.js', $export_dir.'/jquery.min.js')
@symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/jquery.min.js', $export_dir.'/jquery.min.js')
or
file_exists($export_dir.'/jquery.min.js')
)) {
@ -988,7 +970,8 @@ function exportEvents(
$archive = '';
if ($exportFormat == 'tar') {
$archive = $export_root.($connkey?'_'.$connkey:'').'.tar';
$version = shell_exec('tar -v');
$version = @shell_exec('tar --version');
ZM\Debug("Version $version");
$command = 'tar --create --dereference';
if ($exportCompressed) {
@ -1015,6 +998,7 @@ function exportEvents(
@unlink($archive_path);
$command .= ' '.$export_root.($connkey?'_'.$connkey:'').'/';
ZM\Debug($command);
exec($command, $output, $status);
if ($status) {
ZM\Error("Command '$command' returned with status $status");

View File

@ -265,7 +265,8 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
echo getSysLoadHTML();
echo getDbConHTML();
echo getStorageHTML();
echo getShmHTML();
echo getRamHTML();
#echo getShmHTML();
#echo getLogIconHTML();
?>
</ul>
@ -323,7 +324,8 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
echo getSysLoadHTML();
echo getDbConHTML();
echo getStorageHTML();
echo getShmHTML();
echo getRamHTML();
#echo getShmHTML();
echo getLogIconHTML();
?>
</ul>
@ -456,6 +458,37 @@ function getStorageHTML() {
return $result;
}
function getRamHTML() {
$result = '';
if ( !canView('System') ) return $result;
$contents = file_get_contents('/proc/meminfo');
preg_match_all('/(\w+):\s+(\d+)\s/', $contents, $matches);
$meminfo = array_combine($matches[1], array_map(function($v){return 1024*$v;}, $matches[2]));
$mem_used = $meminfo['MemTotal'] - $meminfo['MemFree'] - $meminfo['Buffers'] - $meminfo['Cached'];
$mem_used_percent = (int)(100*$mem_used/$meminfo['MemTotal']);
$used_class = '';
if ($mem_used_percent > 95) {
$used_class = 'text-danger';
} 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 .= ' <li id="getRamHTML" class="nav-item dropdown mx-2">'.
'<span class="'.$used_class.'" title="' .human_filesize($mem_used). ' of ' .human_filesize($meminfo['MemTotal']). '">'.translate('Memory').': '.$mem_used_percent.'%</span> '.
'<span class="'.$swap_class.'" title="' .human_filesize($swap_used). ' of ' .human_filesize($meminfo['SwapTotal']). '">'.translate('Swap').': '.$swap_used_percent.'%</span> '.
'</li>'.PHP_EOL;
return $result;
}
// Returns the html representing the current capacity of mapped memory filesystem (usually /dev/shm)
function getShmHTML() {
$result = '';
@ -883,7 +916,7 @@ function xhtmlFooter() {
?>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/jquery.min.js'); ?>"></script>
<script src="skins/<?php echo $skin; ?>/js/jquery-ui-1.12.1/jquery-ui.min.js"></script>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/bootstrap.min.js'); ?>"></script>
<script src="skins/<?php echo $skin; ?>/js/bootstrap-4.5.0.min.js"></script>
<?php echo output_script_if_exists(array(
'js/tableExport.min.js',
'js/bootstrap-table.min.js',

View File

@ -153,6 +153,7 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
download
<?php echo $Event->DefaultVideo() ? '' : 'style="display:none;"' ?>
><i class="fa fa-download"></i></a>
<button id="videoBtn" class="btn btn-normal" data-toggle="tooltip" data-toggle="tooltip" data-placement="top" title="<?php echo translate('GenerateVideo') ?>"><i class="fa fa-file-video-o"></i></button>
<button id="statsBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Stats') ?>" ><i class="fa fa-info"></i></button>
<button id="framesBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Frames') ?>" ><i class="fa fa-picture-o"></i></button>
<button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>"><i class="fa fa-trash"></i></button>

View File

@ -79,11 +79,14 @@ getBodyTopHTML();
data-cookie-id-table="zmEventsTable"
data-cookie-expire="2y"
data-click-to-select="true"
data-remember-order="true"
data-remember-order="false"
data-show-columns="true"
data-show-export="true"
data-uncheckAll="true"
data-toolbar="#toolbar"
data-sort-name="<?php echo $filter->sort_field() ?>"
data-sort-order="<?php echo $filter->sort_asc() ? 'asc' : 'desc' ?>"
data-server-sort="true"
data-show-fullscreen="true"
data-click-to-select="true"
data-maintain-meta-data="true"

View File

@ -396,7 +396,7 @@ echo htmlSelect( 'filter[Query][sort_asc]', $sort_dirns, $filter->sort_asc() );
</tr>
</tbody>
</table>
<hr/>
<div id="ActionsAndOptions">
<div id="actionsTable" class="filterTable">
<fieldset><legend><?php echo translate('Actions') ?></legend>
<p>
@ -501,21 +501,21 @@ if ( ZM_OPT_EMAIL ) {
?>
</fieldset>
</div>
<hr/>
</div><!--ActionsAndOptions-->
<div id="contentButtons">
<button type="button" data-on-click-this="submitToEvents"><?php echo translate('ListMatches') ?></button>
<button type="button" data-on-click-this="submitToMontageReview"><?php echo translate('ViewMatches') ?></button>
<button type="button" data-on-click-this="submitToExport"><?php echo translate('ExportMatches') ?></button>
<button type="submit" name="action" value="execute" id="executeButton"><?php echo translate('Execute') ?></button>
<button type="button" data-on-click-this="submitAction" value="execute" id="executeButton"><?php echo translate('Execute') ?></button>
<?php
if ( canEdit('Events') ) {
?>
<button type="submit" name="action" value="Save" id="Save"><?php echo translate('Save') ?></button>
<button type="submit" name="action" value="SaveAs" id="SaveAs"><?php echo translate('SaveAs') ?></button>
<button type="button" data-on-click-this="submitAction" value="Save" id="Save"><?php echo translate('Save') ?></button>
<button type="button" data-on-click-this="submitAction" value="SaveAs" id="SaveAs"><?php echo translate('SaveAs') ?></button>
<?php
if ( $filter->Id() ) {
?>
<button type="button" value="Delete" data-on-click-this="deleteFilter"><?php echo translate('Delete') ?></button>
<button type="button" value="delete" data-on-click-this="deleteFilter"><?php echo translate('Delete') ?></button>
<?php
}
}

View File

@ -18,17 +18,16 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canView('Events') ) {
$view = 'error';
return;
}
require_once('includes/Frame.php');
$eid = validInt($_REQUEST['eid']);
$fid = empty($_REQUEST['fid']) ? 0 : validInt($_REQUEST['fid']);
$Event = new ZM\Event($eid);
if (!$Event->canView()) {
$view = 'error';
return;
}
$Monitor = $Event->Monitor();
# This is kinda weird.. so if we pass fid=0 or some other non-integer, then it loads max score
@ -41,7 +40,6 @@ if ( !empty($fid) ) {
$frame = dbFetchOne('SELECT * FROM Frames WHERE EventId=? AND Score=?', NULL, array($eid, $Event->MaxScore()));
}
$Frame = new ZM\Frame($frame);
$maxFid = $Event->Frames();
$firstFid = 1;
@ -92,33 +90,35 @@ xhtmlHeaders(__FILE__, translate('Frame').' - '.$Event->Id().' - '.$Frame->Frame
<div id="page p-0">
<div class="d-flex flex-row justify-content-between px-3 pt-1">
<div id="toolbar" >
<button id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
<button id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
<button id="statsBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Stats') ?>" ><i class="fa fa-info"></i></button>
<button id="statsViewBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Stats').' '.translate('View') ?>" ><i class="fa fa-table"></i></button>
<button type="button" id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
<button type="button" id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
<button type="button" id="statsBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Stats') ?>" ><i class="fa fa-info"></i></button>
<button type="button" id="statsViewBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Stats').' '.translate('View') ?>" ><i class="fa fa-table"></i></button>
</div>
<h2><?php echo translate('Frame') ?> <?php echo $Event->Id().'-'.$Frame->FrameId().' ('.$Frame->Score().')' ?></h2>
<form>
<div id="scaleControl"><label for="scale"><?php echo translate('Scale') ?></label><?php echo htmlSelect('scale', $scales, $scale, array('data-on-change'=>'changeScale','id'=>'scale')); ?></div>
<div id="scaleControl">
<label for="scale"><?php echo translate('Scale') ?></label>
<?php echo htmlSelect('scale', $scales, $scale, array('data-on-change'=>'changeScale','id'=>'scale')); ?>
</div>
<input type="hidden" name="base_width" id="base_width" value="<?php echo $Event->Width(); ?>"/>
<input type="hidden" name="base_height" id="base_height" value="<?php echo $Event->Height(); ?>"/>
</form>
</div>
<div id="content" class="d-flex flex-row justify-content-center">
<table id="frameStatsTable" class="table-sm table-borderless pr-3">
<!-- FRAME STATISTICS POPULATED BY AJAX -->
</table>
<div>
<p id="image">
<?php if ( $imageData['hasAnalImage'] ) {
<?php
if ( $imageData['hasAnalImage'] ) {
echo sprintf('<a href="?view=frame&amp;eid=%d&amp;fid=%d&scale=%d&amp;show=%s">', $Event->Id(), $Frame->FrameId(), $scale, ( $show=='anal'?'capt':'anal' ) );
} ?>
}
?>
<img id="frameImg"
src="<?php echo validHtmlStr($Frame->getImageSrc($show=='anal'?'analyse':'capture')) ?>"
width="<?php echo reScale($Event->Width(), $Monitor->DefaultScale(), $scale) ?>"
@ -126,8 +126,8 @@ xhtmlHeaders(__FILE__, translate('Frame').' - '.$Event->Id().' - '.$Frame->Frame
alt="<?php echo $Frame->EventId().'-'.$Frame->FrameId() ?>"
class="<?php echo $imageData['imageClass'] ?>"
/>
<?php if ( $imageData['hasAnalImage'] ) { ?></a><?php } ?>
<?php
if ($imageData['hasAnalImage']) { ?></a><?php } ?>
</p>
<?php
$frame_url_base = '?view=frame&amp;eid='.$Event->Id().'&amp;scale='.$scale.'&amp;show='.$show.'&amp;fid=';

View File

@ -34,7 +34,7 @@ function streamReq(data) {
data.view = 'request';
data.request = 'stream';
$j.getJSON(thisUrl, data)
$j.getJSON(monitorUrl, data)
.done(getCmdResponse)
.fail(logAjaxFail);
}
@ -300,7 +300,7 @@ function getCmdResponse(respObj, respText) {
if (streamStatus.auth) {
// Try to reload the image stream.
var streamImg = $j('#evtStream');
var streamImg = document.getElementById('evtStream');
if (streamImg) {
streamImg.src = streamImg.src.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
}
@ -708,7 +708,7 @@ function renameEvent() {
}
function exportEvent() {
window.location.assign('?view=export&eid='+eventData.Id);
window.location.assign('?view=export&eids[]='+eventData.Id);
}
function showEventFrames() {
@ -773,8 +773,9 @@ function manageDelConfirmModalBtns() {
}
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+eventData.Id)
$j.getJSON(thisUrl + '?request=event&task=delete&id='+eventData.Id)
.done(function(data) {
$j('#deleteConfirm').modal('hide');
streamNext(true);
})
.fail(logAjaxFail);
@ -1015,7 +1016,13 @@ function initPage() {
// Manage the EXPORT button
bindButton('#exportBtn', 'click', null, function onExportClick(evt) {
evt.preventDefault();
window.location.assign('?view=export&eids[]='+eventData.Id);
exportEvent();
});
// Manage the generateVideo button
bindButton('#videoBtn', 'click', null, function onExportClick(evt) {
evt.preventDefault();
videoEvent();
});
// Manage the Event STATISTICS Button

View File

@ -41,6 +41,8 @@ function exportProgress() {
} else {
$j('#exportProgressTicker').append('.');
}
} else {
console.log("No timer");
}
}
@ -53,35 +55,32 @@ function exportResponse(respObj, respText) {
setTimeout(startDownload, 1500, decodeURIComponent(respObj.exportFile));
}
return;
if ( 0 ) {
var eids = new Array();
for (var i = 0, len=form.elements.length; i < len; i++) {
if ( form.elements[i].name == 'eids[]' ) {
eids[eids.length] = 'eids[]='+form.elements[i].value;
}
}
}
form.submit();
//window.location.replace( thisUrl+'?view='+currentView+'&'+eids.join('&')+'&exportFile='+respObj.exportFile+'&generated='+((respObj.result=='Ok')?1:0) );
}
function exportEvents( ) {
var formData = $j('#contentForm').serialize();
$j.ajaxSetup({
timeout: 0
});
$j.getJSON(thisUrl + '?view=event&request=event&action=export', formData)
.done(exportResponse)
.fail(logAjaxFail);
.fail(exportFail);
$j('#exportProgress').removeClass('hidden');
$j('#exportProgress').addClass('warnText');
$j('#exportProgress').text(exportProgressString);
$j('#exportProgressText').text(exportProgressString);
//exportProgress();
exportTimer = setInterval(exportProgress, 500);
}
function exportFail() {
clearInterval(exportTimer);
$j('#exportProgress').addClass('errorText');
$j('#exportProgressTicker').text('Failed export');
logAjaxFail();
}
function getEventDetailModal(eid) {
$j.getJSON(thisUrl + '?request=modal&modal=eventdetail&eids[]=' + eid)
.done(function(data) {

View File

@ -57,8 +57,8 @@ function validateForm(form) {
form.elements['filter[AutoUnarchive]'].checked ||
form.elements['filter[UpdateDiskSpace]'].checked ||
form.elements['filter[AutoVideo]'].checked ||
form.elements['filter[AutoEmail]'].checked ||
form.elements['filter[AutoMessage]'].checked ||
(form.elements['filter[AutoEmail]'] && form.elements['filter[AutoEmail]'].checked) ||
(form.elements['filter[AutoMessage]'] && form.elements['filter[AutoMessage]'].checked) ||
form.elements['filter[AutoExecute]'].checked ||
form.elements['filter[AutoDelete]'].checked ||
form.elements['filter[AutoCopy]'].checked ||
@ -166,6 +166,12 @@ function submitToExport(element) {
window.location.assign('?view=export&'+$j(form).serialize());
}
function submitAction(button) {
var form = button.form;
form.elements['action'].value = button.value;
form.submit();
}
function deleteFilter(element) {
var form = element.form;
if (confirm(deleteSavedFilterString+" '"+form.elements['filter[Name]'].value+"'?")) {
@ -376,7 +382,6 @@ function debugFilter() {
}
function manageModalBtns(id) {
console.log(id);
// Manage the CANCEL modal button
var cancelBtn = document.getElementById(id+"CancelBtn");
if ( cancelBtn ) {

View File

@ -39,7 +39,7 @@ function changeScale() {
onStatsResize(newWidth);
}
function getFrmStatsCookie() {
function getFrameStatsCookie() {
var cookie = 'zmFrameStats';
var stats = getCookie(cookie);
@ -53,16 +53,19 @@ function getFrmStatsCookie() {
function getStat(params) {
$j.getJSON(thisUrl + '?view=request&request=stats&raw=true', params)
.done(function(data) {
var stat = data.raw;
var stats = data.raw;
$j('#frameStatsTable').empty().append('<tbody>');
for (const stat of stats) {
$j.each(statHeaderStrings, function(key) {
var th = $j('<th>').addClass('text-right').text(statHeaderStrings[key]);
var tdString;
switch (stat ? key : 'n/a') {
case 'FrameId':
tdString = '<a href="?view=stats&amp;eid=' + params.eid + '&amp;fid=' + params.fid + '">' + stat[key] + '</a>';
case 'EventId':
//tdString = '<a href="?view=stats&amp;eid=' + params.eid + '&amp;fid=' + params.fid + '">' + stat[key] + '</a>';
break;
case 'n/a':
tdString = 'n/a';
@ -76,6 +79,7 @@ function getStat(params) {
$j('#frameStatsTable tbody').append(row);
});
} // end foreach stat
})
.fail(logAjaxFail);
}
@ -143,7 +147,7 @@ function initPage() {
// Load the frame stats
getStat({eid: eid, fid: fid});
if ( getFrmStatsCookie() != 'on' ) {
if (getFrameStatsCookie() != 'on') {
table.toggle(false);
} else {
onStatsResize($j('#base_width').val() * scale / SCALE_BASE);

View File

@ -98,6 +98,7 @@ function changeScale() {
var scale = $j('#scale').val();
var newWidth;
var newHeight;
var autoScale;
// Always turn it off, we will re-add it below. I don't know if you can add a callback multiple
// times and what the consequences would be
@ -118,6 +119,10 @@ function changeScale() {
var streamImg = $j('#liveStream'+monitorId);
if (streamImg) {
var oldSrc = streamImg.attr('src');
streamImg.attr('src', '');
// This is so that we don't waste bandwidth and let the browser do all the scaling.
if (autoScale > 100) autoScale = 100;
if (scale > 100) scale = 100;
var newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+((scale == 'auto' || scale == '0') ? autoScale : scale));
streamImg.width(newWidth);
@ -783,6 +788,25 @@ function getCtrlPresetModal() {
.fail(logAjaxFail);
}
function changeControl(e) {
const input = e.target;
$j.getJSON(monitorUrl+'?request=v4l2_settings&mid='+monitorId+'&'+input.name+'='+input.value)
.done(function(evt) {
if (evt.result == 'Ok') {
evt.controls.forEach(function(control) {
const element = $j('#new'+control.control.charAt(0).toUpperCase() + control.control.slice(1));
if (element.length) {
element.val(control.value);
element.attr('title', control.value);
} else {
console.err('Element not found for #new'+control.control.charAt(0).toUpperCase() + control.control.slice(1));
}
});
}
})
.fail(logAjaxFail);
}
function getSettingsModal() {
$j.getJSON(monitorUrl + '?request=modal&modal=settings&mid=' + monitorId)
.done(function(data) {
@ -792,6 +816,10 @@ function getSettingsModal() {
evt.preventDefault();
$j('#settingsForm').submit();
});
$j('#newBrightness').change(changeControl);
$j('#newContrast').change(changeControl);
$j('#newHue').change(changeControl);
$j('#newColour').change(changeControl);
})
.fail(logAjaxFail);
}
@ -917,7 +945,7 @@ function initPage() {
});
// Only enable the settings button for local cameras
settingsBtn.prop('disabled', !(canView.Control && monitorType == 'Local'));
settingsBtn.prop('disabled', !(canView.Control && (monitorType == 'Local')));
// Init the bootstrap-table
if (monitorType != 'WebSite') table.bootstrapTable({icons: icons});

View File

@ -102,7 +102,7 @@ $focusWindow = true;
xhtmlHeaders(__FILE__, translate('Video'));
?>
<body>
<?php if ( !$popup ) echo getNavBarHTML() ?>
<?php echo getNavBarHTML() ?>
<div id="page">
<div class="w-100 py-1">
<div class="float-left pl-3">

View File

@ -50,7 +50,6 @@ echo $error_message;
var countdown = 30; // seconds
function timerCountdown() {
console.log('hello');
document.getElementById('countdown').innerHTML=countdown;
countdown --;
if ( countdown <= 0 ) {