Merge branch 'master' into add_manufacturer_model_to_monitors
This commit is contained in:
commit
1fd99424bb
|
@ -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@;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
------------------------
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 isn’t 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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}) ) {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
) ]
|
||||
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}} ) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 :
|
||||
|
|
|
@ -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() )
|
||||
);
|
||||
|
|
|
@ -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('@');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++ ) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
41
src/zmu.cpp
41
src/zmu.cpp
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ select.chosen {
|
|||
}
|
||||
tr td:first-child {
|
||||
min-width: 300px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.OutputContainer {
|
||||
display: none;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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&eid=%d&fid=%d&scale=%d&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&eid='.$Event->Id().'&scale='.$scale.'&show='.$show.'&fid=';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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&eid=' + params.eid + '&fid=' + params.fid + '">' + stat[key] + '</a>';
|
||||
case 'EventId':
|
||||
//tdString = '<a href="?view=stats&eid=' + params.eid + '&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);
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
Loading…
Reference in New Issue