Merge branch 'master' into add_manufacturer_model_to_monitors

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

View File

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

View File

@ -28,8 +28,8 @@ SET @s = (SELECT IF(
AND table_name = 'Monitors' AND table_name = 'Monitors'
AND column_name = 'TotalEvents' AND column_name = 'TotalEvents'
) > 0, ) > 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; PREPARE stmt FROM @s;
EXECUTE stmt; EXECUTE stmt;
@ -50,8 +50,8 @@ SET @s = (SELECT IF(
AND table_name = 'Monitors' AND table_name = 'Monitors'
AND column_name = 'TotalEventDiskSpace' AND column_name = 'TotalEventDiskSpace'
) > 0, ) > 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; PREPARE stmt FROM @s;
EXECUTE stmt; EXECUTE stmt;

View File

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

View File

@ -5,6 +5,12 @@ set +e
create_db () { create_db () {
echo "Checking for db" echo "Checking for db"
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload 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... # test if database if already present...
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
echo "Creating zm db" echo "Creating zm db"

View File

@ -225,7 +225,7 @@ change the 3 to a 1
I can't see more than 6 monitors in montage on my browser I can't see more than 6 monitors in montage on my browser
--------------------------------------------------------- ---------------------------------------------------------
Browsers such a Chrome and Safari only support upto 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network` Browsers such a Chrome and Safari only support up to 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network`
Why is ZoneMinder using so much CPU? Why is ZoneMinder using so much CPU?
--------------------------------------- ---------------------------------------

View File

@ -3,6 +3,50 @@ Debian
.. contents:: .. 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 Easy Way: Debian Buster
------------------------ ------------------------

View File

@ -2,7 +2,7 @@ An Easy To Use Docker Image
=========================== ===========================
If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories: If you 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>`__. * 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. In both cases, instructions are provided in the repo README files.

View File

@ -86,7 +86,7 @@ Source Path
Use this field to enter the full URL of the stream or file your camera supports. This is usually an RTSP url. There are several methods to learn this: 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 * 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 * Try ZoneMinder's new ONVIF probe feature
* Download and install the `ONVIF Device Manager <https://sourceforge.net/projects/onvifdm/>`__ onto a Windows machine * 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 * 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. The storage section allows for each monitor to configure if and how video and audio are recorded.
Save JPEGs 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. * 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. * 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. * 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 invidual JPEG frames with analysis information overlaid. * 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 Video Writer
Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored. Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored.

View File

@ -34,7 +34,7 @@ Here is what the filter window looks like
* Update used disk space: calculates how much disk space is currently taken by the event and updates the db record. * 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: 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. * 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. * 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. * 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. * Copy all matches: copies the event files to another location, specified in the Copy To dropdown. The other location must be setup in the Storage Tab under options.

View File

@ -53,7 +53,7 @@ This screen is called the "console" screen in ZoneMinder and shows a summary of
* **B**: This brings up a color coded log window that shows various system and component level logs. This window is useful if you are trying to diagnose issues. Refer to :doc:`logging`. * **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. * **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`. * **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. * **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. * **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. * **H**: Audit Events Report is more of a power user feature. This option looks for recording gaps in events and recording issues in mp4 files.

View File

@ -6,7 +6,7 @@ Here are some options for using ZoneMinder on Mobile devices:
Third party mobile clients Third party mobile clients
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
* zmNinja (`source code <https://github.com/pliablepixels/zmNinja>`__, needs APIs to be installed to work) * 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 Using the existing web console
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -30,7 +30,7 @@ This screen allows you to configure various permissions on a per user basis. The
.. note:: if you are using zmNinja, users are required to have 'View' access to system because multi-server information is only available as part of this permission .. 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 - 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 - 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) - Specifies if the ZoneMinder API is enabled for this user (needs to be on, if you are using a mobile app such as zmNinja)

View File

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

View File

@ -2709,7 +2709,7 @@ Returns a L<ONVIF::Device::Elements::GetNetworkInterfacesResponse|ONVIF::Device:
=head3 SetNetworkInterfaces =head3 SetNetworkInterfaces
For interoperability with a client unaware of the IEEE 802.11 extension a device shall retain its IEEE 802.11 configuration if the IEEE 802.11 configuration element isnt present in the request. For interoperability with a client unaware of the IEEE 802.11 extension a device shall retain its IEEE 802.11 configuration if the IEEE 802.11 configuration element isn't present in the request.
Returns a L<ONVIF::Device::Elements::SetNetworkInterfacesResponse|ONVIF::Device::Elements::SetNetworkInterfacesResponse> object. 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 =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. 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 =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. 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 =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. Returns a L<ONVIF::Device::Elements::StartSystemRestoreResponse|ONVIF::Device::Elements::StartSystemRestoreResponse> object.

View File

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

View File

@ -2147,7 +2147,7 @@ Returns a L<ONVIF::Media::Elements::GetAudioOutputsResponse|ONVIF::Media::Elemen
=head3 CreateProfile =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. Returns a L<ONVIF::Media::Elements::CreateProfileResponse|ONVIF::Media::Elements::CreateProfileResponse> object.

View File

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

View File

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

View File

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

View File

@ -987,7 +987,7 @@ Returns a L<ONVIF::PTZ::Elements::GotoHomePositionResponse|ONVIF::PTZ::Elements:
=head3 SetHomePosition =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. Returns a L<ONVIF::PTZ::Elements::SetHomePositionResponse|ONVIF::PTZ::Elements::SetHomePositionResponse> object.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -100,7 +100,7 @@ of the corresponding class can be passed instead of the marked hash ref.
You may pass any combination of objects, hash and list refs to these You may pass any combination of objects, hash and list refs to these
methods, as long as you meet the structure. 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 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 ref - this may result in invalid XML if used improperly, though. Note that
SOAP::WSDL always expects list references at maximum depth position. SOAP::WSDL always expects list references at maximum depth position.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1064,7 +1064,7 @@ our @options = (
}, },
{ {
name => 'ZM_FFMPEG_FORMATS', 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', description => 'Formats to allow for ffmpeg video generation',
help => q` help => q`
Ffmpeg can generate video in many different formats. This Ffmpeg can generate video in many different formats. This

View File

@ -29,6 +29,7 @@ use strict;
use warnings; use warnings;
require ZoneMinder::Base; require ZoneMinder::Base;
require ZoneMinder::Object;
require ZoneMinder::Monitor; require ZoneMinder::Monitor;
our $VERSION = $ZoneMinder::Base::VERSION; our $VERSION = $ZoneMinder::Base::VERSION;
@ -42,24 +43,116 @@ our $VERSION = $ZoneMinder::Base::VERSION;
use ZoneMinder::Logger qw(:all); use ZoneMinder::Logger qw(:all);
use ZoneMinder::Database 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; 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 { sub AUTOLOAD {
my $self = shift; my $self = shift;
my $class = ref($self); my $class = ref($self);
@ -79,24 +172,24 @@ sub AUTOLOAD {
sub getKey { sub getKey {
my $self = shift; my $self = shift;
return $self->{id}; return $self->{Id};
} }
sub open { sub open {
my $self = shift; my $self = shift;
Fatal('No open method defined for protocol '.$self->{name}); Fatal('No open method defined for protocol '.$self->{Protocol});
} }
sub close { sub close {
my $self = shift; my $self = shift;
$self->{state} = 'closed'; $self->{state} = 'closed';
Debug('No close method defined for protocol '.$self->{name}); Debug('No close method defined for protocol '.$self->{Protocol});
} }
sub loadMonitor { sub loadMonitor {
my $self = shift; my $self = shift;
if ( !$self->{Monitor} ) { 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'); Fatal('Monitor id '.$self->{id}.' not found');
} }
if ( defined($self->{Monitor}->{AutoStopTimeout}) ) { if ( defined($self->{Monitor}->{AutoStopTimeout}) ) {

View File

@ -283,7 +283,7 @@ sub presetSet
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset = $self->getParam( $params, 'preset' ); 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 ); $self->sendCmd( $cmd );
} }
@ -294,7 +294,7 @@ sub presetGoto
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset = $self->getParam( $params, 'preset' ); 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 ); $self->sendCmd( $cmd );
} }

View File

@ -41,122 +41,135 @@ our @ISA = qw(ZoneMinder::Control);
use ZoneMinder::Logger qw(:all); use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all); use ZoneMinder::Config qw(:all);
use ZoneMinder::General qw(:all);
use Time::HiRes qw( usleep ); 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; my $self = shift;
$self->loadMonitor(); $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; use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new; $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'; $self->{state} = 'open';
} }
sub close sub close {
{
my $self = shift; my $self = shift;
$self->{state} = 'closed'; $self->{state} = 'closed';
} }
sub printMsg sub sendCmd {
{
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
my ($self, $cmd, $speedcmd) = @_; my ($self, $cmd, $speedcmd) = @_;
my $result = undef; $self->printMsg( $speedcmd, 'Tx' );
$self->printMsg( $cmd, 'Tx' );
printMsg( $speedcmd, "Tx" ); my $req = HTTP::Request->new( GET => $BASE_URL."/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd");
printMsg( $cmd, "Tx" );
my $req = HTTP::Request->new( GET => "http://" . $self->{Monitor}->{ControlAddress} . "/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd" );
my $res = $self->{ua}->request($req); my $res = $self->{ua}->request($req);
if ( $res->is_success ) if (!$res->is_success) {
{ Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')');
$result = !undef;
} }
else return $res->is_success;
{
Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" );
}
return( $result );
} }
sub moveConUp sub moveConUp {
{
my ($self, $params) = @_; my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
Debug( "Move Up" );
$self->sendCmd( 'move=up', $speed ); $self->sendCmd( 'move=up', $speed );
} }
sub moveConDown sub moveConDown {
{
my ($self, $params) = @_; my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
Debug( "Move Down" );
$self->sendCmd( 'move=down', $speed ); $self->sendCmd( 'move=down', $speed );
} }
sub moveConLeft sub moveConLeft {
{
my ($self, $params) = @_; my ($self, $params) = @_;
my $speed = 'speedpan=-' . $params->{panspeed}; my $speed = 'speedpan=-' . $params->{panspeed};
Debug( "Move Left" );
$self->sendCmd( 'move=left', $speed ); $self->sendCmd( 'move=left', $speed );
} }
sub moveConRight sub moveConRight {
{
my ($self, $params) = @_; my ($self, $params) = @_;
my $speed = 'speedpan=' . ($params->{panspeed} - 6); my $speed = 'speedpan=' . ($params->{panspeed} - 6);
Debug( "Move Right" );
$self->sendCmd( 'move=right', $speed ); $self->sendCmd( 'move=right', $speed );
} }
sub moveStop sub moveStop {
{
my $self = shift; my $self = shift;
Debug( "Move Stop" ); Debug( "Move Stop: not implemented" );
# not implemented # not implemented
} }
sub zoomConTele sub zoomConTele {
{
my ($self, $params) = @_; my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6); my $speed = 'speedzoom=' . ($params->{speed} - 6);
Debug( "Zoom In" );
$self->sendCmd( 'zoom=tele', $speed ); $self->sendCmd( 'zoom=tele', $speed );
} }
sub zoomConWide sub zoomConWide {
{
my ($self, $params) = @_; my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6); my $speed = 'speedzoom=' . ($params->{speed} - 6);
Debug( "Zoom Out" );
$self->sendCmd( 'zoom=wide', $speed ); $self->sendCmd( 'zoom=wide', $speed );
} }
sub reset sub reset {
{
my $self = shift; my $self = shift;
Debug( "Camera Reset" );
$self->sendCmd( 'move=home' ); $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; 1;
__END__ __END__

View File

@ -584,6 +584,7 @@ sub DiskSpace {
return $_[0]{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 { sub CopyTo {
my ( $self, $NewStorage ) = @_; my ( $self, $NewStorage ) = @_;
@ -614,16 +615,12 @@ sub CopyTo {
Debug("$NewPath is good"); 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. # data is reloaded, so need to check that the move hasn't already happened.
if ( $$self{StorageId} == $$NewStorage{Id} ) { if ( $$self{StorageId} == $$NewStorage{Id} ) {
$ZoneMinder::Database::dbh->commit();
return 'Event has already been moved by someone else.'; return 'Event has already been moved by someone else.';
} }
if ( $$OldStorage{Id} != $$self{StorageId} ) { if ( $$OldStorage{Id} != $$self{StorageId} ) {
$ZoneMinder::Database::dbh->commit();
return 'Old Storage path changed, Event has moved somewhere else.'; return 'Old Storage path changed, Event has moved somewhere else.';
} }
@ -661,40 +658,22 @@ sub CopyTo {
} }
my $event_path = $subpath.$self->RelativePath(); 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/*"); my @files = glob("$OldPath/*");
Debug("Files to move @files"); Debug("Files to move @files");
foreach my $file ( @files ) { foreach my $file (@files) {
next if $file =~ /^\./; next if $file =~ /^\./;
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint ($file) = ($file =~ /^(.*)$/); # De-taint
my $starttime = [gettimeofday]; my $starttime = [gettimeofday];
Debug("Moving file $file to $NewPath"); Debug("Moving file $file to $NewPath");
my $size = -s $file; my $size = -s $file;
if ( ! $size ) { if (!$size) {
Info('Not moving file with 0 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); my $filename = $event_path.'/'.File::Basename::basename($file);
if ( ! $bucket->add_key($filename, $file_contents) ) { if (!$bucket->add_key_filename($filename, $file)) {
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; die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr;
} }
}
my $duration = tv_interval($starttime); 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'); Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
@ -704,16 +683,15 @@ sub CopyTo {
}; };
Error($@) if $@; Error($@) if $@;
} else { } 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 Url
} # end if s3 } # end if s3
my $error = ''; my $error = '';
if ( !$moved ) { if (!$moved) {
File::Path::make_path($NewPath, {error => \my $err}); File::Path::make_path($NewPath, {error => \my $err});
if ( @$err ) { if (@$err) {
for my $diag (@$err) { for my $diag (@$err) {
my ($file, $message) = %$diag; my ($file, $message) = %$diag;
next if $message eq 'File exists'; next if $message eq 'File exists';
@ -724,23 +702,16 @@ sub CopyTo {
} }
} }
} }
if ( $error ) { return $error if $error;
$ZoneMinder::Database::dbh->commit();
return $error;
}
my @files = glob("$OldPath/*"); my @files = glob("$OldPath/*");
if ( ! @files ) { return 'No files to move.' if !@files;
$ZoneMinder::Database::dbh->commit();
return 'No files to move.';
}
for my $file (@files) { for my $file (@files) {
next if $file =~ /^\./; next if $file =~ /^\./;
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint ($file) = ($file =~ /^(.*)$/); # De-taint
my $starttime = [gettimeofday]; my $starttime = [gettimeofday];
Debug("Moving file $file to $NewPath");
my $size = -s $file; my $size = -s $file;
if ( ! File::Copy::copy( $file, $NewPath ) ) { if (!File::Copy::copy($file, $NewPath)) {
$error .= "Copy failed: for $file to $NewPath: $!"; $error .= "Copy failed: for $file to $NewPath: $!";
last; last;
} }
@ -749,20 +720,21 @@ sub CopyTo {
} # end foreach file. } # end foreach file.
} # end if ! moved } # end if ! moved
if ( $error ) {
$ZoneMinder::Database::dbh->commit();
return $error; return $error;
}
} # end sub CopyTo } # end sub CopyTo
sub MoveTo { sub MoveTo {
my ( $self, $NewStorage ) = @_; my ($self, $NewStorage) = @_;
if ( !$self->canEdit() ) { if (!$self->canEdit()) {
Warning('No permission to move event.'); Warning('No permission to move event.');
return 'No permission to move event.'; 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 $OldStorage = $self->Storage(undef);
my $error = $self->CopyTo($NewStorage); my $error = $self->CopyTo($NewStorage);
@ -772,11 +744,11 @@ sub MoveTo {
$$self{StorageId} = $$NewStorage{Id}; $$self{StorageId} = $$NewStorage{Id};
$self->Storage($NewStorage); $self->Storage($NewStorage);
$error .= $self->save(); $error .= $self->save();
if ( $error ) {
$ZoneMinder::Database::dbh->commit(); # Going to leave it to upper layer as to whether we rollback or not
return $error; $ZoneMinder::Database::dbh->commit() if !$was_in_transaction;
} return $error if $error;
$ZoneMinder::Database::dbh->commit();
$self->delete_files($OldStorage); $self->delete_files($OldStorage);
return $error; return $error;
} # end sub MoveTo } # end sub MoveTo

View File

@ -0,0 +1,99 @@
# ==========================================================================
#
# ZoneMinder Event_Summary Module
# Copyright (C) 2020 ZoneMinder
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#
# This module contains the common definitions and functions used by the rest
# of the ZoneMinder scripts
#
package ZoneMinder::Event_Summary;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Object;
#our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object);
use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
$table = 'Event_Summaries';
$serial = $primary_key = 'MonitorId';
%fields = map { $_ => $_ } qw(
MonitorId
TotalEvents
TotalEventDiskSpace
HourEvents
HourEventDiskSpace
DayEvents
DayEventDiskSpace
WeekEvents
WeekEventDiskSpace
MonthEvents
MonthEventDiskSpace
ArchivedEvents
ArchivedEventDiskSpace
);
%defaults = (
TotalEvents => undef,
TotalEventDiskSpace => undef,
HourEvents => undef,
HourEventDiskSpace => undef,
DayEvents => undef,
DayEventDiskSpace => undef,
WeekEvents => undef,
WeekEventDiskSpace => undef,
MonthEvents => undef,
MonthEventDiskSpace => undef,
ArchivedEvents => undef,
ArchivedEventDiskSpace => undef,
);
sub Monitor {
return new ZoneMinder::Monitor( $_[0]{MonitorId} );
} # end sub Monitor
1;
__END__
=head1 NAME
ZoneMinder::Event_Summary - Perl Class for Event Summaries
=head1 SYNOPSIS
use ZoneMinder::Event_Summary;
=head1 AUTHOR
Isaac Connor, E<lt>isaac@zoneminder.comE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2001-2017 ZoneMinder LLC
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.3 or,
at your option, any later version of Perl 5 you may have available.
=cut

View File

@ -31,6 +31,8 @@ our %EXPORT_TAGS = (
systemStatus systemStatus
packageControl packageControl
daemonControl daemonControl
parseNameEqualsValueToHash
hash_diff
) ] ) ]
); );
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
@ -534,6 +536,42 @@ sub jsonDecode {
return $result; 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 { sub packageControl {
my $command = shift; my $command = shift;
my $string = $Config{ZM_PATH_BIN}.'/zmpkg.pl '.$command; my $string = $Config{ZM_PATH_BIN}.'/zmpkg.pl '.$command;
@ -598,6 +636,8 @@ of the ZoneMinder scripts
packageControl packageControl
daemonControl daemonControl
systemStatus systemStatus
parseNameEqualsValueToHash
hash_diff
) ] ) ]

View File

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

View File

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

View File

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

View File

@ -30,7 +30,6 @@ use autouse 'Pod::Usage'=>qw(pod2usage);
use POSIX qw/strftime EPIPE EINTR/; use POSIX qw/strftime EPIPE EINTR/;
use Socket; use Socket;
use Data::Dumper; use Data::Dumper;
use Module::Load::Conditional qw{can_load};
use constant MAX_CONNECT_DELAY => 15; use constant MAX_CONNECT_DELAY => 15;
use constant MAX_COMMAND_WAIT => 1800; use constant MAX_COMMAND_WAIT => 1800;
@ -102,40 +101,21 @@ if ($options{command}) {
} }
} else { } else {
# The server isn't there # 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; 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) { 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'); 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"); Info("Starting control server $id/$protocol");
close(CLIENT); 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; my $zm_terminate = 0;
sub TermHandler { sub TermHandler {
Info('Received TERM, exiting'); Info('Received TERM, exiting');
@ -150,7 +130,6 @@ if ($options{command}) {
$0 = $0.' --id '.$id; $0 = $0.' --id '.$id;
my $control = ('ZoneMinder::Control::'.$protocol)->new($id);
my $control_key = $control->getKey(); my $control_key = $control->getKey();
$control->loadMonitor(); $control->loadMonitor();

View File

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

View File

@ -107,6 +107,7 @@ endif()
add_executable(zmc zmc.cpp) add_executable(zmc zmc.cpp)
add_executable(zms zms.cpp) add_executable(zms zms.cpp)
add_executable(zmu zmu.cpp) add_executable(zmu zmu.cpp)
add_executable(zmbenchmark zmbenchmark.cpp)
target_link_libraries(zmc target_link_libraries(zmc
PRIVATE PRIVATE
@ -129,6 +130,13 @@ target_link_libraries(zmu
${ZM_EXTRA_LIBS} ${ZM_EXTRA_LIBS}
${CMAKE_DL_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 # Generate man files for the binaries destined for the bin folder
if(BUILD_MAN) if(BUILD_MAN)
foreach(CBINARY zmc zmu) foreach(CBINARY zmc zmu)

View File

@ -23,14 +23,6 @@ void AnalysisThread::Start() {
void AnalysisThread::Run() { void AnalysisThread::Run() {
while (!(terminate_ or zm_terminate)) { while (!(terminate_ or zm_terminate)) {
// Some periodic updates are required for variable capturing framerate monitor_->Analyse();
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);
}
}
} }
} }

View File

@ -73,19 +73,35 @@ void bind_libcurl_symbols() {
*(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup"); *(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(
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 ), const Monitor* monitor,
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) 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)
{ {
if (capture) {
if ( capture ) {
Initialise(); Initialise();
} }
} }
cURLCamera::~cURLCamera() { cURLCamera::~cURLCamera() {
if ( capture ) { if (capture) {
Terminate(); Terminate();
} }
} }
@ -99,36 +115,36 @@ void cURLCamera::Initialise() {
bind_libcurl_symbols(); bind_libcurl_symbols();
/* cURL initialization */ /* cURL initialization */
CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL); CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet)); Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet));
dlclose(curl_lib); dlclose(curl_lib);
return; return;
} }
Debug(2,"libcurl version: %s", (*curl_version_f)()); Debug(2, "libcurl version: %s", (*curl_version_f)());
/* Create the shared data mutex */ /* Create the shared data mutex */
int nRet = pthread_mutex_init(&shareddata_mutex, nullptr); int nRet = pthread_mutex_init(&shareddata_mutex, nullptr);
if(nRet != 0) { if (nRet != 0) {
Error("Shared data mutex creation failed: %s",strerror(nRet)); Error("Shared data mutex creation failed: %s",strerror(nRet));
return; return;
} }
/* Create the data available condition variable */ /* Create the data available condition variable */
nRet = pthread_cond_init(&data_available_cond, nullptr); nRet = pthread_cond_init(&data_available_cond, nullptr);
if(nRet != 0) { if (nRet != 0) {
Error("Data available condition variable creation failed: %s",strerror(nRet)); Error("Data available condition variable creation failed: %s",strerror(nRet));
return; return;
} }
/* Create the request complete condition variable */ /* Create the request complete condition variable */
nRet = pthread_cond_init(&request_complete_cond, nullptr); nRet = pthread_cond_init(&request_complete_cond, nullptr);
if(nRet != 0) { if (nRet != 0) {
Error("Request complete condition variable creation failed: %s",strerror(nRet)); Error("Request complete condition variable creation failed: %s",strerror(nRet));
return; return;
} }
/* Create the thread */ /* Create the thread */
nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this); nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this);
if(nRet != 0) { if (nRet != 0) {
Error("Thread creation failed: %s",strerror(nRet)); Error("Thread creation failed: %s",strerror(nRet));
return; return;
} }
@ -151,18 +167,19 @@ void cURLCamera::Terminate() {
/* cURL cleanup */ /* cURL cleanup */
(*curl_global_cleanup_f)(); (*curl_global_cleanup_f)();
if(curl_lib) if (curl_lib)
dlclose(curl_lib); dlclose(curl_lib);
} }
int cURLCamera::PrimeCapture() { int cURLCamera::PrimeCapture() {
getVideoStream();
//Info( "Priming capture from %s", mPath.c_str() ); //Info( "Priming capture from %s", mPath.c_str() );
return 0; return 1;
} }
int cURLCamera::PreCapture() { int cURLCamera::PreCapture() {
// Nothing to do here // Nothing to do here
return( 0 ); return 1;
} }
int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) { int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
@ -178,10 +195,9 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
/* Grab the mutex to ensure exclusive access to the shared data */ /* Grab the mutex to ensure exclusive access to the shared data */
lock(); lock();
while ( !frameComplete ) { while (!frameComplete) {
/* If the work thread did a reset, reset our local variables */ /* If the work thread did a reset, reset our local variables */
if ( bReset ) { if (bReset) {
SubHeadersParsingComplete = false; SubHeadersParsingComplete = false;
frame_content_length = 0; frame_content_length = 0;
frame_content_type.clear(); frame_content_type.clear();
@ -189,25 +205,25 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
bReset = false; bReset = false;
} }
if ( mode == MODE_UNSET ) { if (mode == MODE_UNSET) {
/* Don't have a mode yet. Sleep while waiting for data */ /* Don't have a mode yet. Sleep while waiting for data */
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
if ( nRet != 0 ) { if (nRet != 0) {
Error("Failed waiting for available data condition variable: %s",strerror(nRet)); Error("Failed waiting for available data condition variable: %s",strerror(nRet));
return -1; return -1;
} }
} }
if ( mode == MODE_STREAM ) { if (mode == MODE_STREAM) {
/* Subheader parsing */ /* Subheader parsing */
while( !SubHeadersParsingComplete && !need_more_data ) { while (!SubHeadersParsingComplete && !need_more_data) {
size_t crlf_start, crlf_end, crlf_size; size_t crlf_start, crlf_end, crlf_size;
std::string subheader; std::string subheader;
/* Check if the buffer contains something */ /* Check if the buffer contains something */
if ( databuffer.empty() ) { if (databuffer.empty()) {
/* Empty buffer, wait for data */ /* Empty buffer, wait for data */
need_more_data = true; need_more_data = true;
break; break;
@ -215,14 +231,14 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
/* Find crlf start */ /* Find crlf start */
crlf_start = memcspn(reinterpret_cast<const char *>(databuffer.head()),"\r\n",databuffer.size()); crlf_start = memcspn(reinterpret_cast<const char *>(databuffer.head()),"\r\n",databuffer.size());
if ( crlf_start == databuffer.size() ) { if (crlf_start == databuffer.size()) {
/* Not found, wait for more data */ /* Not found, wait for more data */
need_more_data = true; need_more_data = true;
break; break;
} }
/* See if we have enough data for determining crlf length */ /* See if we have enough data for determining crlf length */
if ( databuffer.size() < crlf_start+5 ) { if (databuffer.size() < crlf_start+5) {
/* Need more data */ /* Need more data */
need_more_data = true; need_more_data = true;
break; break;
@ -233,18 +249,22 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
crlf_size = (crlf_start + crlf_end) - crlf_start; crlf_size = (crlf_start + crlf_end) - crlf_start;
/* Is this the end of a previous stream? (This is just before the boundary) */ /* Is this the end of a previous stream? (This is just before the boundary) */
if ( crlf_start == 0 ) { if (crlf_start == 0) {
databuffer.consume(crlf_size); databuffer.consume(crlf_size);
continue; continue;
} }
/* Check for invalid CRLF size */ /* Check for invalid CRLF size */
if ( crlf_size > 4 ) { if (crlf_size > 4) {
Error("Invalid CRLF length"); Error("Invalid CRLF length");
} }
/* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */ /* 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 */ /* This is the last header */
SubHeadersParsingComplete = true; SubHeadersParsingComplete = true;
} }
@ -255,48 +275,56 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
/* Advance the buffer past this one */ /* Advance the buffer past this one */
databuffer.consume(crlf_start+crlf_size); databuffer.consume(crlf_start+crlf_size);
Debug(7,"Got subheader: %s",subheader.c_str()); Debug(7, "Got subheader: %s",subheader.c_str());
/* Find where the data in this header starts */ /* Find where the data in this header starts */
size_t subheader_data_start = subheader.rfind(' '); size_t subheader_data_start = subheader.rfind(' ');
if ( subheader_data_start == std::string::npos ) { if (subheader_data_start == std::string::npos) {
subheader_data_start = subheader.find(':'); subheader_data_start = subheader.find(':');
} }
/* Extract the data into a string */ /* Extract the data into a string */
std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos); std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos);
Debug(8,"Got subheader data: %s",subheader_data.c_str()); Debug(8, "Got subheader data: %s", subheader_data.c_str());
/* Check the header */ /* Check the header */
if(strncasecmp(subheader.c_str(),content_length_match,content_length_match_len) == 0) { if (strncasecmp(subheader.c_str(), content_length_match, content_length_match_len) == 0) {
/* Found the content-length header */ /* Found the content-length header */
frame_content_length = atoi(subheader_data.c_str()); frame_content_length = atoi(subheader_data.c_str());
Debug(6,"Got content-length subheader: %d",frame_content_length); Debug(6,"Got content-length subheader: %d",frame_content_length);
} else if(strncasecmp(subheader.c_str(),content_type_match,content_type_match_len) == 0) { } else if (strncasecmp(subheader.c_str(), content_type_match, content_type_match_len) == 0) {
/* Found the content-type header */ /* Found the content-type header */
frame_content_type = subheader_data; frame_content_type = subheader_data;
Debug(6,"Got content-type subheader: %s",frame_content_type.c_str()); Debug(6,"Got content-type subheader: %s", frame_content_type.c_str());
} }
} }
/* Attempt to extract the frame */ /* Attempt to extract the frame */
if(!need_more_data) { if (!need_more_data) {
if(!SubHeadersParsingComplete) { if (!SubHeadersParsingComplete) {
/* We haven't parsed all headers yet */ /* We haven't parsed all headers yet */
need_more_data = true; need_more_data = true;
} else if ( ! frame_content_length ) { } else if (!frame_content_length) {
/* Invalid frame */ /* Invalid frame */
Error("Invalid frame: invalid content length"); Error("Invalid frame: invalid content length");
} else if ( frame_content_type != "image/jpeg" ) { } else if (frame_content_type != "image/jpeg") {
/* Unsupported frame type */ /* Unsupported frame type */
Error("Unsupported frame: %s",frame_content_type.c_str()); Error("Unsupported frame: %s", frame_content_type.c_str());
} else if(frame_content_length > databuffer.size()) { } else if (frame_content_length > databuffer.size()) {
/* Incomplete frame, wait for more data */ /* Incomplete frame, wait for more data */
need_more_data = true; need_more_data = true;
} else { } else {
/* All good. decode the image */ /* 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); zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder);
frameComplete = true; frameComplete = true;
} }
@ -317,6 +345,14 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
if (!single_offsets.empty()) { if (!single_offsets.empty()) {
if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) { if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) {
/* Extract frame */ /* 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); zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder);
single_offsets.pop_front(); single_offsets.pop_front();
frameComplete = true; frameComplete = true;
@ -345,7 +381,7 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
/* Release the mutex */ /* Release the mutex */
unlock(); unlock();
if(!frameComplete) if (!frameComplete)
return 0; return 0;
return 1; return 1;
@ -353,7 +389,7 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
int cURLCamera::PostCapture() { int cURLCamera::PostCapture() {
// Nothing to do here // Nothing to do here
return( 0 ); return 1;
} }
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {
@ -364,7 +400,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *
/* Signal data available */ /* Signal data available */
int nRet = pthread_cond_signal(&data_available_cond); int nRet = pthread_cond_signal(&data_available_cond);
if ( nRet != 0 ) { if (nRet != 0) {
Error("Failed signaling data available condition variable: %s",strerror(nRet)); Error("Failed signaling data available condition variable: %s",strerror(nRet));
unlock(); unlock();
return -16; return -16;
@ -380,17 +416,17 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi
std::string header; std::string header;
header.assign((const char*)buffer, size*nmemb); header.assign((const char*)buffer, size*nmemb);
Debug(4,"Got header: %s",header.c_str()); Debug(4, "Got header: %s", header.c_str());
/* Check Content-Type header */ /* Check Content-Type header */
if(strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) { if (strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) {
size_t pos = header.find(';'); size_t pos = header.find(';');
if(pos != std::string::npos) { if (pos != std::string::npos) {
header.erase(pos, std::string::npos); header.erase(pos, std::string::npos);
} }
pos = header.rfind(' '); pos = header.rfind(' ');
if(pos == std::string::npos) { if (pos == std::string::npos) {
pos = header.find(':'); pos = header.find(':');
} }
@ -401,11 +437,11 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi
const char* multipart_match = "multipart/x-mixed-replace"; const char* multipart_match = "multipart/x-mixed-replace";
const char* image_jpeg_match = "image/jpeg"; const char* image_jpeg_match = "image/jpeg";
if(strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) { if (strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) {
Debug(7,"Content type matched as multipart/x-mixed-replace"); Debug(7, "Content type matched as multipart/x-mixed-replace");
mode = MODE_STREAM; mode = MODE_STREAM;
} else if(strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) { } else if (strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) {
Debug(7,"Content type matched as image/jpeg"); Debug(7, "Content type matched as image/jpeg");
mode = MODE_SINGLE; mode = MODE_SINGLE;
} }
@ -421,7 +457,7 @@ void* cURLCamera::thread_func() {
double dSize; double dSize;
c = (*curl_easy_init_f)(); c = (*curl_easy_init_f)();
if(c == nullptr) { if (c == nullptr) {
dlclose(curl_lib); dlclose(curl_lib);
Error("Failed getting easy handle from libcurl"); Error("Failed getting easy handle from libcurl");
tRet = -51; tRet = -51;
@ -431,7 +467,7 @@ void* cURLCamera::thread_func() {
CURLcode cRet; CURLcode cRet;
/* Set URL */ /* Set URL */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_URL, mPath.c_str()); cRet = (*curl_easy_setopt_f)(c, CURLOPT_URL, mPath.c_str());
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet));
tRet = -52; tRet = -52;
return (void*)tRet; return (void*)tRet;
@ -439,91 +475,91 @@ void* cURLCamera::thread_func() {
/* Header callback */ /* Header callback */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher); cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl header callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl header callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -53; tRet = -53;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERDATA, this); cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERDATA, this);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl header callback object: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl header callback object: %s", (*curl_easy_strerror_f)(cRet));
tRet = -54; tRet = -54;
return (void*)tRet; return (void*)tRet;
} }
/* Data callback */ /* Data callback */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher); cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl data callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl data callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -55; tRet = -55;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEDATA, this); cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEDATA, this);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl data callback object: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl data callback object: %s", (*curl_easy_strerror_f)(cRet));
tRet = -56; tRet = -56;
return (void*)tRet; return (void*)tRet;
} }
/* Progress callback */ /* Progress callback */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_NOPROGRESS, 0); cRet = (*curl_easy_setopt_f)(c, CURLOPT_NOPROGRESS, 0);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -57; tRet = -57;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher); cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -58; tRet = -58;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSDATA, this); cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSDATA, this);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl progress callback object: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl progress callback object: %s", (*curl_easy_strerror_f)(cRet));
tRet = -59; tRet = -59;
return (void*)tRet; return (void*)tRet;
} }
/* Set username and password */ /* Set username and password */
if(!mUser.empty()) { if (!mUser.empty()) {
cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str()); cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str());
if(cRet != CURLE_OK) if (cRet != CURLE_OK)
Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet));
} }
if(!mPass.empty()) { if (!mPass.empty()) {
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str()); cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str());
if(cRet != CURLE_OK) if (cRet != CURLE_OK)
Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet));
} }
/* Authenication preference */ /* Authenication preference */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
if(cRet != CURLE_OK) if (cRet != CURLE_OK)
Warning("Failed setting libcurl acceptable http authenication methods: %s", (*curl_easy_strerror_f)(cRet)); Warning("Failed setting libcurl acceptable http authenication methods: %s", (*curl_easy_strerror_f)(cRet));
/* Work loop */ /* Work loop */
for(int attempt=1;attempt<=CURL_MAXRETRY;attempt++) { for (int attempt=1;attempt<=CURL_MAXRETRY;attempt++) {
tRet = 0; tRet = 0;
while(!bTerminate) { while (!bTerminate) {
/* Do the work */ /* Do the work */
cRet = (*curl_easy_perform_f)(c); cRet = (*curl_easy_perform_f)(c);
if(mode == MODE_SINGLE) { if (mode == MODE_SINGLE) {
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
break; break;
} }
/* Attempt to get the size of the file */ /* Attempt to get the size of the file */
cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize); cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
break; break;
} }
/* We need to lock for the offsets array and the condition variable */ /* We need to lock for the offsets array and the condition variable */
lock(); lock();
/* Push the size into our offsets array */ /* Push the size into our offsets array */
if(dSize > 0) { if (dSize > 0) {
single_offsets.push_back(dSize); single_offsets.push_back(dSize);
} else { } else {
Error("Unable to get the size of the image"); Error("Unable to get the size of the image");
@ -532,7 +568,7 @@ void* cURLCamera::thread_func() {
} }
/* Signal the request complete condition variable */ /* Signal the request complete condition variable */
tRet = pthread_cond_signal(&request_complete_cond); tRet = pthread_cond_signal(&request_complete_cond);
if(tRet != 0) { if (tRet != 0) {
Error("Failed signaling request completed condition variable: %s",strerror(tRet)); Error("Failed signaling request completed condition variable: %s",strerror(tRet));
tRet = -61; tRet = -61;
return (void*)tRet; return (void*)tRet;
@ -546,13 +582,13 @@ void* cURLCamera::thread_func() {
} }
/* Return value checking */ /* Return value checking */
if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) { if (cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) {
/* Aborted */ /* Aborted */
break; break;
} else if (cRet != CURLE_OK) { } else if (cRet != CURLE_OK) {
/* Some error */ /* Some error */
Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet)); Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet));
if(attempt < CURL_MAXRETRY) { if (attempt < CURL_MAXRETRY) {
Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY); Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY);
/* Do a reset */ /* Do a reset */
lock(); lock();
@ -597,7 +633,7 @@ int cURLCamera::unlock() {
int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
/* Signal the curl thread to terminate */ /* Signal the curl thread to terminate */
if(bTerminate) if (bTerminate)
return -10; return -10;
return 0; return 0;

View File

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

View File

@ -60,8 +60,8 @@ Event::Event(
//snapshit_file(), //snapshit_file(),
//alarm_file(""), //alarm_file(""),
videoStore(nullptr), videoStore(nullptr),
//video_name(""),
//video_file(""), //video_file(""),
//video_path(""),
last_db_frame(0), last_db_frame(0),
have_video_keyframe(false), have_video_keyframe(false),
//scheme //scheme
@ -104,6 +104,13 @@ Event::Event(
// Copy it in case opening the mp4 doesn't work we can set it to another value // Copy it in case opening the mp4 doesn't work we can set it to another value
save_jpegs = monitor->GetOptSaveJPEGs(); save_jpegs = monitor->GetOptSaveJPEGs();
Storage * storage = monitor->getStorage(); 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( std::string sql = stringtf(
"INSERT INTO `Events` " "INSERT INTO `Events` "
@ -120,7 +127,7 @@ Event::Event(
state_id, state_id,
monitor->getOrientation(), monitor->getOrientation(),
0, 0,
"", video_incomplete_file.c_str(),
save_jpegs, save_jpegs,
storage->SchemeString().c_str() storage->SchemeString().c_str()
); );
@ -178,24 +185,16 @@ Event::Event(
} // end if ! setPath(Storage) } // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str()); Debug(1, "Using storage area at %s", path.c_str());
video_name = "";
snapshot_file = path + "/snapshot.jpg"; snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg"; alarm_file = path + "/alarm.jpg";
video_incomplete_path = path + "/" + video_incomplete_file;
if (monitor->GetOptVideoWriter() != 0) {
/* Save as video */ /* Save as video */
if ( monitor->GetOptVideoWriter() != 0 ) {
std::string container = monitor->OutputContainer();
if ( container == "auto" || container == "" ) {
container = "mp4";
}
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( videoStore = new VideoStore(
video_file.c_str(), video_incomplete_path.c_str(),
container.c_str(), container.c_str(),
monitor->GetVideoStream(), monitor->GetVideoStream(),
monitor->GetVideoCodecContext(), monitor->GetVideoCodecContext(),
@ -213,8 +212,10 @@ Event::Event(
zmDbDo(sql); zmDbDo(sql);
} }
} else { } else {
sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); std::string codec = videoStore->get_codec();
zmDbDo(sql); 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 } // end if GetOptVideoWriter
} }
@ -223,10 +224,18 @@ Event::~Event() {
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
/* Close the video file */ /* Close the video file */
if ( videoStore != nullptr ) { if (videoStore != nullptr) {
Debug(4, "Deleting video store"); Debug(4, "Deleting video store");
delete videoStore; delete videoStore;
videoStore = nullptr; 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. // 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( 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), monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time),
delta_time.count(), delta_time.count(),
frames, alarm_frames, frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id); id);
if (!zmDbDoUpdate(sql)) { if (!zmDbDoUpdate(sql)) {
// Name might have been changed during recording, so just do the update without changing the name. // Name might have been changed during recording, so just do the update without changing the name.
sql = stringtf( 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), std::chrono::system_clock::to_time_t(end_time),
delta_time.count(), delta_time.count(),
frames, alarm_frames, frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id); id);
zmDbDoUpdate(sql); zmDbDoUpdate(sql);
} // end if no changed rows due to Name change during recording } // end if no changed rows due to Name change during recording
@ -479,7 +490,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing snapshot"); Debug(1, "Writing snapshot");
WriteFrameImage(image, timestamp, snapshot_file.c_str()); WriteFrameImage(image, timestamp, snapshot_file.c_str());
} else { } 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 // We are writing an Alarm frame
@ -491,7 +502,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing alarm image"); Debug(1, "Writing alarm image");
WriteFrameImage(image, timestamp, alarm_file.c_str()); WriteFrameImage(image, timestamp, alarm_file.c_str());
} else { } 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)) { if (alarm_image and (save_jpegs & 2)) {

View File

@ -84,8 +84,13 @@ class Event {
std::string alarm_file; std::string alarm_file;
VideoStore *videoStore; VideoStore *videoStore;
std::string video_name; std::string container;
std::string codec;
std::string video_file; std::string video_file;
std::string video_path;
std::string video_incomplete_file;
std::string video_incomplete_path;
int last_db_frame; 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. 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; Storage::Schemes scheme;

View File

@ -663,6 +663,7 @@ bool EventStream::checkEventLoaded() {
else else
curr_frame_id = 1; curr_frame_id = 1;
Debug(2, "New frame id = %ld", curr_frame_id); Debug(2, "New frame id = %ld", curr_frame_id);
start = std::chrono::system_clock::now();
return true; return true;
} else { } else {
Debug(2, "No next event loaded using %s. Pausing", sql.c_str()); 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); fputs("Content-Type: image/x-rgbz\r\n", stdout);
break; break;
case STREAM_RAW : case STREAM_RAW :
img_buffer = (uint8_t*)(send_image->Buffer()); img_buffer = send_image->Buffer();
img_buffer_size = send_image->Size(); img_buffer_size = send_image->Size();
fputs("Content-Type: image/x-rgb\r\n", stdout); fputs("Content-Type: image/x-rgb\r\n", stdout);
break; break;
@ -957,6 +958,7 @@ void EventStream::runStream() {
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count())); static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()));
// if effective > base we should speed up frame delivery // 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); delta = std::chrono::duration_cast<Microseconds>((delta * base_fps) / effective_fps);
Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f)", Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f)",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()), static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()),
@ -965,10 +967,11 @@ void EventStream::runStream() {
// but must not exceed maxfps // but must not exceed maxfps
delta = std::max(delta, Microseconds(lround(Microseconds::period::den / maxfps))); delta = std::max(delta, Microseconds(lround(Microseconds::period::den / maxfps)));
Debug(3, "delta %" PRIi64 " us = base_fps (%f) /effective_fps (%f) from 30fps", Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f) from 30fps",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()), static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()),
base_fps, base_fps,
effective_fps); effective_fps);
}
// +/- 1? What if we are skipping frames? // +/- 1? What if we are skipping frames?
curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod; curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod;

View File

@ -270,7 +270,6 @@ int Image::PopulateFrame(AVFrame *frame) {
frame->width = width; frame->width = width;
frame->height = height; frame->height = height;
frame->format = imagePixFormat; 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)"); zm_dump_video_frame(frame, "Image.Populate(frame)");
return 1; return 1;
} // int Image::PopulateFrame(AVFrame *frame) } // int Image::PopulateFrame(AVFrame *frame)

View File

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

View File

@ -49,8 +49,7 @@ static int vidioctl(int fd, int request, void *arg) {
static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) { static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) {
_AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE;
if ( v4l_version == 2 ) { switch (palette) {
switch ( palette ) {
#if defined(V4L2_PIX_FMT_RGB444) && defined(AV_PIX_FMT_RGB444) #if defined(V4L2_PIX_FMT_RGB444) && defined(AV_PIX_FMT_RGB444)
case V4L2_PIX_FMT_RGB444 : case V4L2_PIX_FMT_RGB444 :
pixFormat = AV_PIX_FMT_RGB444; pixFormat = AV_PIX_FMT_RGB444;
@ -172,7 +171,6 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette)
#endif #endif
} }
} // end switch palette } // end switch palette
} // end if v4l2
return pixFormat; return pixFormat;
} // end getFfPixFormatFromV4lPalette } // end getFfPixFormatFromV4lPalette
@ -259,12 +257,12 @@ LocalCamera::LocalCamera(
v4l_multi_buffer = p_v4l_multi_buffer; v4l_multi_buffer = p_v4l_multi_buffer;
v4l_captures_per_frame = p_v4l_captures_per_frame; v4l_captures_per_frame = p_v4l_captures_per_frame;
if ( capture ) { if (capture) {
if ( device_prime ) { if (device_prime) {
Debug(2, "V4L support enabled, using V4L%d api", v4l_version); Debug(2, "V4L support enabled, using V4L%d api", v4l_version);
} }
if ( (!last_camera) || (channel != last_camera->channel) ) { if ((!last_camera) || (channel != last_camera->channel)) {
// We are the first, or only, input that uses this channel // We are the first, or only, input that uses this channel
channel_prime = true; channel_prime = true;
channel_index = channel_count++; channel_index = channel_count++;
@ -278,10 +276,10 @@ LocalCamera::LocalCamera(
/* The V4L1 API doesn't care about endianness, we need to check the endianness of the machine */ /* The V4L1 API doesn't care about endianness, we need to check the endianness of the machine */
uint32_t checkval = 0xAABBCCDD; uint32_t checkval = 0xAABBCCDD;
if ( *(unsigned char*)&checkval == 0xDD ) { if (*(unsigned char*)&checkval == 0xDD) {
BigEndian = 0; BigEndian = 0;
Debug(2, "little-endian processor detected"); Debug(2, "little-endian processor detected");
} else if ( *(unsigned char*)&checkval == 0xAA ) { } else if (*(unsigned char*)&checkval == 0xAA) {
BigEndian = 1; BigEndian = 1;
Debug(2, "Big-endian processor detected"); Debug(2, "Big-endian processor detected");
} else { } else {
@ -289,15 +287,15 @@ LocalCamera::LocalCamera(
BigEndian = 0; BigEndian = 0;
} }
if ( v4l_version == 2 && palette == 0 ) { if (palette == 0) {
/* Use automatic format selection */ /* Use automatic format selection */
Debug(2,"Using automatic format selection"); Debug(2,"Using automatic format selection");
palette = AutoSelectFormat(colours); palette = AutoSelectFormat(colours);
if ( palette == 0 ) { if (palette == 0) {
Error("Automatic format selection failed. Falling back to YUYV"); Error("Automatic format selection failed. Falling back to YUYV");
palette = V4L2_PIX_FMT_YUYV; palette = V4L2_PIX_FMT_YUYV;
} else { } else {
if ( capture ) { if (capture) {
Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)",
palette_desc, palette_desc,
static_cast<uint8>((palette >> 24) & 0xff), static_cast<uint8>((palette >> 24) & 0xff),
@ -310,9 +308,6 @@ LocalCamera::LocalCamera(
if (capture) { if (capture) {
if (last_camera) { 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) if (standard != last_camera->standard)
Warning("Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong"); Warning("Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong");
@ -324,37 +319,35 @@ LocalCamera::LocalCamera(
} }
/* Get ffmpeg pixel format based on capture palette and endianness */ /* Get ffmpeg pixel format based on capture palette and endianness */
capturePixFormat = getFfPixFormatFromV4lPalette( v4l_version, palette ); capturePixFormat = getFfPixFormatFromV4lPalette(v4l_version, palette);
imagePixFormat = AV_PIX_FMT_NONE; imagePixFormat = AV_PIX_FMT_NONE;
} }
/* V4L2 format matching */
if ( v4l_version == 2 ) {
/* Try to find a match for the selected palette and target colourspace */ /* Try to find a match for the selected palette and target colourspace */
/* RGB32 palette and 32bit target colourspace */ /* RGB32 palette and 32bit target colourspace */
if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32 ) { if (palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32) {
conversion_type = 0; conversion_type = 0;
subpixelorder = ZM_SUBPIX_ORDER_ARGB; subpixelorder = ZM_SUBPIX_ORDER_ARGB;
/* BGR32 palette and 32bit target colourspace */ /* BGR32 palette and 32bit target colourspace */
} else if ( palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32 ) { } else if (palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32) {
conversion_type = 0; conversion_type = 0;
subpixelorder = ZM_SUBPIX_ORDER_BGRA; subpixelorder = ZM_SUBPIX_ORDER_BGRA;
/* RGB24 palette and 24bit target colourspace */ /* RGB24 palette and 24bit target colourspace */
} else if ( palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24 ) { } else if (palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24) {
conversion_type = 0; conversion_type = 0;
conversion_type = 0; conversion_type = 0;
subpixelorder = ZM_SUBPIX_ORDER_BGR; subpixelorder = ZM_SUBPIX_ORDER_BGR;
/* Grayscale palette and grayscale target colourspace */ /* Grayscale palette and grayscale target colourspace */
} else if ( palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8 ) { } else if (palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8) {
conversion_type = 0; conversion_type = 0;
subpixelorder = ZM_SUBPIX_ORDER_NONE; subpixelorder = ZM_SUBPIX_ORDER_NONE;
/* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */
} else { } else {
if ( capture ) { if (capture) {
Info( Info(
"No direct match for the selected palette (%d) and target colorspace (%02u). Format conversion is required, performance penalty expected", "No direct match for the selected palette (%d) and target colorspace (%02u). Format conversion is required, performance penalty expected",
capturePixFormat, capturePixFormat,
@ -363,50 +356,50 @@ LocalCamera::LocalCamera(
/* Try using swscale for the conversion */ /* Try using swscale for the conversion */
conversion_type = 1; conversion_type = 1;
Debug(2, "Using swscale for image conversion"); Debug(2, "Using swscale for image conversion");
if ( colours == ZM_COLOUR_RGB32 ) { if (colours == ZM_COLOUR_RGB32) {
subpixelorder = ZM_SUBPIX_ORDER_RGBA; subpixelorder = ZM_SUBPIX_ORDER_RGBA;
imagePixFormat = AV_PIX_FMT_RGBA; imagePixFormat = AV_PIX_FMT_RGBA;
} else if ( colours == ZM_COLOUR_RGB24 ) { } else if (colours == ZM_COLOUR_RGB24) {
subpixelorder = ZM_SUBPIX_ORDER_RGB; subpixelorder = ZM_SUBPIX_ORDER_RGB;
imagePixFormat = AV_PIX_FMT_RGB24; imagePixFormat = AV_PIX_FMT_RGB24;
} else if ( colours == ZM_COLOUR_GRAY8 ) { } else if (colours == ZM_COLOUR_GRAY8) {
subpixelorder = ZM_SUBPIX_ORDER_NONE; subpixelorder = ZM_SUBPIX_ORDER_NONE;
imagePixFormat = AV_PIX_FMT_GRAY8; imagePixFormat = AV_PIX_FMT_GRAY8;
} else { } else {
Panic("Unexpected colours: %u",colours); Panic("Unexpected colours: %u",colours);
} }
if ( capture ) { if (capture) {
if ( !sws_isSupportedInput(capturePixFormat) ) { if (!sws_isSupportedInput(capturePixFormat)) {
Error("swscale does not support the used capture format: %d", capturePixFormat); Error("swscale does not support the used capture format: %d", capturePixFormat);
conversion_type = 2; /* Try ZM format conversions */ conversion_type = 2; /* Try ZM format conversions */
} }
if ( !sws_isSupportedOutput(imagePixFormat) ) { if (!sws_isSupportedOutput(imagePixFormat)) {
Error("swscale does not support the target format: 0x%d", imagePixFormat); Error("swscale does not support the target format: 0x%d", imagePixFormat);
conversion_type = 2; /* Try ZM format conversions */ conversion_type = 2; /* Try ZM format conversions */
} }
} }
/* Our YUYV->Grayscale conversion is a lot faster than swscale's */ /* Our YUYV->Grayscale conversion is a lot faster than swscale's */
if ( colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV ) { if (colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV) {
conversion_type = 2; conversion_type = 2;
} }
/* JPEG */ /* JPEG */
if ( palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG ) { if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) {
Debug(2,"Using JPEG image decoding"); Debug(2,"Using JPEG image decoding");
conversion_type = 3; conversion_type = 3;
} }
if ( conversion_type == 2 ) { if (conversion_type == 2) {
Debug(2,"Using ZM for image conversion"); Debug(2,"Using ZM for image conversion");
if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8 ) { if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8 ) {
conversion_fptr = &std_convert_argb_gray8; conversion_fptr = &std_convert_argb_gray8;
subpixelorder = ZM_SUBPIX_ORDER_NONE; subpixelorder = ZM_SUBPIX_ORDER_NONE;
} else if ( palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_GRAY8 ) { } else if (palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_GRAY8) {
conversion_fptr = &std_convert_bgra_gray8; conversion_fptr = &std_convert_bgra_gray8;
subpixelorder = ZM_SUBPIX_ORDER_NONE; subpixelorder = ZM_SUBPIX_ORDER_NONE;
} else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_GRAY8 ) { } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_GRAY8) {
/* Fast YUYV->Grayscale conversion by extracting the Y channel */ /* Fast YUYV->Grayscale conversion by extracting the Y channel */
if ( config.cpu_extensions && sse_version >= 35 ) { if (config.cpu_extensions && sse_version >= 35) {
conversion_fptr = &ssse3_convert_yuyv_gray8; conversion_fptr = &ssse3_convert_yuyv_gray8;
Debug(2,"Using SSSE3 YUYV->grayscale fast conversion"); Debug(2,"Using SSSE3 YUYV->grayscale fast conversion");
} else { } else {
@ -414,22 +407,22 @@ LocalCamera::LocalCamera(
Debug(2,"Using standard YUYV->grayscale fast conversion"); Debug(2,"Using standard YUYV->grayscale fast conversion");
} }
subpixelorder = ZM_SUBPIX_ORDER_NONE; subpixelorder = ZM_SUBPIX_ORDER_NONE;
} else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB24 ) { } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB24) {
conversion_fptr = &zm_convert_yuyv_rgb; conversion_fptr = &zm_convert_yuyv_rgb;
subpixelorder = ZM_SUBPIX_ORDER_RGB; subpixelorder = ZM_SUBPIX_ORDER_RGB;
} else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB32 ) { } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB32) {
conversion_fptr = &zm_convert_yuyv_rgba; conversion_fptr = &zm_convert_yuyv_rgba;
subpixelorder = ZM_SUBPIX_ORDER_RGBA; subpixelorder = ZM_SUBPIX_ORDER_RGBA;
} else if ( palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB24 ) { } else if (palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB24) {
conversion_fptr = &zm_convert_rgb555_rgb; conversion_fptr = &zm_convert_rgb555_rgb;
subpixelorder = ZM_SUBPIX_ORDER_RGB; subpixelorder = ZM_SUBPIX_ORDER_RGB;
} else if ( palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB32 ) { } else if (palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB32) {
conversion_fptr = &zm_convert_rgb555_rgba; conversion_fptr = &zm_convert_rgb555_rgba;
subpixelorder = ZM_SUBPIX_ORDER_RGBA; subpixelorder = ZM_SUBPIX_ORDER_RGBA;
} else if ( palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB24 ) { } else if (palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB24) {
conversion_fptr = &zm_convert_rgb565_rgb; conversion_fptr = &zm_convert_rgb565_rgb;
subpixelorder = ZM_SUBPIX_ORDER_RGB; subpixelorder = ZM_SUBPIX_ORDER_RGB;
} else if ( palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB32 ) { } else if (palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB32) {
conversion_fptr = &zm_convert_rgb565_rgba; conversion_fptr = &zm_convert_rgb565_rgba;
subpixelorder = ZM_SUBPIX_ORDER_RGBA; subpixelorder = ZM_SUBPIX_ORDER_RGBA;
} else { } else {
@ -437,21 +430,20 @@ LocalCamera::LocalCamera(
} }
} // end if conversion_type == 2 } // end if conversion_type == 2
} // end if needs conversion } // end if needs conversion
} // end if v4l2
last_camera = this; last_camera = this;
Debug(3, "Selected subpixelorder: %u", subpixelorder); Debug(3, "Selected subpixelorder: %u", subpixelorder);
/* Initialize swscale stuff */ /* Initialize swscale stuff */
if ( capture and (conversion_type == 1) ) { if (capture and (conversion_type == 1)) {
tmpPicture = av_frame_alloc(); tmpPicture = av_frame_alloc();
if ( !tmpPicture ) if (!tmpPicture)
Fatal("Could not allocate temporary picture"); Fatal("Could not allocate temporary picture");
unsigned int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); unsigned int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1);
if ( pSize != imagesize ) { if (pSize != imagesize) {
Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize);
} }
@ -460,23 +452,23 @@ LocalCamera::LocalCamera(
width, height, imagePixFormat, SWS_BICUBIC, width, height, imagePixFormat, SWS_BICUBIC,
nullptr, nullptr, nullptr); nullptr, nullptr, nullptr);
if ( !imgConversionContext ) { if (!imgConversionContext) {
Fatal("Unable to initialise image scaling context"); Fatal("Unable to initialise image scaling context");
} }
} else { } else {
tmpPicture = nullptr; tmpPicture = nullptr;
imgConversionContext = nullptr; imgConversionContext = nullptr;
} // end if capture and conversion_tye == swscale } // end if capture and conversion_tye == swscale
if ( capture and device_prime ) if (capture and device_prime)
Initialise(); Initialise();
} // end LocalCamera::LocalCamera } // end LocalCamera::LocalCamera
LocalCamera::~LocalCamera() { LocalCamera::~LocalCamera() {
if ( device_prime && capture ) if (device_prime && capture)
Terminate(); Terminate();
/* Clean up swscale stuff */ /* Clean up swscale stuff */
if ( capture && (conversion_type == 1) ) { if (capture && (conversion_type == 1)) {
sws_freeContext(imgConversionContext); sws_freeContext(imgConversionContext);
imgConversionContext = nullptr; imgConversionContext = nullptr;
@ -492,19 +484,16 @@ int LocalCamera::Close() {
void LocalCamera::Initialise() { void LocalCamera::Initialise() {
Debug(3, "Opening video device %s", device.c_str()); 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)
if ( (vid_fd = open(device.c_str(), O_RDWR, 0)) < 0 )
Fatal("Failed to open video device %s: %s", device.c_str(), strerror(errno)); Fatal("Failed to open video device %s: %s", device.c_str(), strerror(errno));
struct stat st; struct stat st;
if ( stat(device.c_str(), &st) < 0 ) if (stat(device.c_str(), &st) < 0)
Fatal("Failed to stat video device %s: %s", device.c_str(), strerror(errno)); Fatal("Failed to stat video device %s: %s", device.c_str(), strerror(errno));
if ( !S_ISCHR(st.st_mode) ) if (!S_ISCHR(st.st_mode))
Fatal("File %s is not device file: %s", device.c_str(), strerror(errno)); 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; struct v4l2_capability vid_cap;
Debug(3, "Checking video device capabilities"); Debug(3, "Checking video device capabilities");
@ -551,18 +540,18 @@ void LocalCamera::Initialise() {
v4l2_data.fmt.fmt.pix.height = height; v4l2_data.fmt.fmt.pix.height = height;
v4l2_data.fmt.fmt.pix.pixelformat = palette; v4l2_data.fmt.fmt.pix.pixelformat = palette;
if ( (extras & 0xff) != 0 ) { if ((extras & 0xff) != 0) {
v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff);
if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) {
Warning("Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff)); Warning("Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff));
v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY;
if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) {
Fatal("Failed to set video format: %s", strerror(errno)); Fatal("Failed to set video format: %s", strerror(errno));
} }
} }
} else { } else {
if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) {
Error("Failed to set video format: %s", strerror(errno)); Error("Failed to set video format: %s", strerror(errno));
} }
} }
@ -589,33 +578,33 @@ void LocalCamera::Initialise() {
, v4l2_data.fmt.fmt.pix.priv , v4l2_data.fmt.fmt.pix.priv
); );
if ( v4l2_data.fmt.fmt.pix.width != width ) { if (v4l2_data.fmt.fmt.pix.width != width) {
Warning("Failed to set requested width"); Warning("Failed to set requested width");
} }
if ( v4l2_data.fmt.fmt.pix.height != height ) { if (v4l2_data.fmt.fmt.pix.height != height) {
Warning("Failed to set requested height"); Warning("Failed to set requested height");
} }
/* Buggy driver paranoia. */ /* Buggy driver paranoia. */
unsigned int min; unsigned int min;
min = v4l2_data.fmt.fmt.pix.width * 2; min = v4l2_data.fmt.fmt.pix.width * 2;
if ( v4l2_data.fmt.fmt.pix.bytesperline < min ) if (v4l2_data.fmt.fmt.pix.bytesperline < min)
v4l2_data.fmt.fmt.pix.bytesperline = min; v4l2_data.fmt.fmt.pix.bytesperline = min;
min = v4l2_data.fmt.fmt.pix.bytesperline * v4l2_data.fmt.fmt.pix.height; min = v4l2_data.fmt.fmt.pix.bytesperline * v4l2_data.fmt.fmt.pix.height;
if ( v4l2_data.fmt.fmt.pix.sizeimage < min ) if (v4l2_data.fmt.fmt.pix.sizeimage < min)
v4l2_data.fmt.fmt.pix.sizeimage = min; v4l2_data.fmt.fmt.pix.sizeimage = min;
if ( palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG ) { if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) {
v4l2_jpegcompression jpeg_comp; v4l2_jpegcompression jpeg_comp;
if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { if (vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0) {
if ( errno == EINVAL ) { if (errno == EINVAL) {
Debug(2, "JPEG compression options are not available"); Debug(2, "JPEG compression options are not available");
} else { } else {
Warning("Failed to get JPEG compression options: %s", strerror(errno)); Warning("Failed to get JPEG compression options: %s", strerror(errno));
} }
} else { } else {
/* Set flags and quality. MJPEG should not have the huffman tables defined */ /* Set flags and quality. MJPEG should not have the huffman tables defined */
if ( palette == V4L2_PIX_FMT_MJPEG ) { if (palette == V4L2_PIX_FMT_MJPEG) {
jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI;
} else { } else {
jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT;
@ -623,10 +612,10 @@ void LocalCamera::Initialise() {
jpeg_comp.quality = 85; jpeg_comp.quality = 85;
/* Update the JPEG options */ /* Update the JPEG options */
if ( vidioctl(vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp) < 0 ) { if (vidioctl(vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp) < 0) {
Warning("Failed to set JPEG compression options: %s", strerror(errno)); Warning("Failed to set JPEG compression options: %s", strerror(errno));
} else { } else {
if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { if (vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0) {
Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno)); Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno));
} else { } else {
Debug(4, "JPEG quality: %d, markers: %#x", Debug(4, "JPEG quality: %d, markers: %#x",
@ -639,9 +628,9 @@ void LocalCamera::Initialise() {
Debug(3, "Setting up request buffers"); Debug(3, "Setting up request buffers");
memset(&v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs)); memset(&v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs));
if ( channel_count > 1 ) { if (channel_count > 1) {
Debug(3, "Channel count is %d", channel_count); Debug(3, "Channel count is %d", channel_count);
if ( v4l_multi_buffer ){ if (v4l_multi_buffer){
v4l2_data.reqbufs.count = 2*channel_count; v4l2_data.reqbufs.count = 2*channel_count;
} else { } else {
v4l2_data.reqbufs.count = 1; v4l2_data.reqbufs.count = 1;
@ -654,15 +643,15 @@ void LocalCamera::Initialise() {
v4l2_data.reqbufs.type = v4l2_data.fmt.type; v4l2_data.reqbufs.type = v4l2_data.fmt.type;
v4l2_data.reqbufs.memory = V4L2_MEMORY_MMAP; v4l2_data.reqbufs.memory = V4L2_MEMORY_MMAP;
if ( vidioctl(vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs) < 0 ) { if (vidioctl(vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs) < 0) {
if ( errno == EINVAL ) { if (errno == EINVAL) {
Fatal("Unable to initialise memory mapping, unsupported in device"); Fatal("Unable to initialise memory mapping, unsupported in device");
} else { } else {
Fatal("Unable to initialise memory mapping: %s", strerror(errno)); Fatal("Unable to initialise memory mapping: %s", strerror(errno));
} }
} }
if ( v4l2_data.reqbufs.count < (v4l_multi_buffer?2:1) ) if (v4l2_data.reqbufs.count < (v4l_multi_buffer?2:1))
Fatal("Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count); Fatal("Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count);
Debug(3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d", Debug(3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d",
@ -671,7 +660,7 @@ void LocalCamera::Initialise() {
v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count]; v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count];
capturePictures = new AVFrame *[v4l2_data.reqbufs.count]; capturePictures = new AVFrame *[v4l2_data.reqbufs.count];
for ( unsigned int i = 0; i < v4l2_data.reqbufs.count; i++ ) { for (unsigned int i = 0; i < v4l2_data.reqbufs.count; i++) {
struct v4l2_buffer vid_buf; struct v4l2_buffer vid_buf;
memset(&vid_buf, 0, sizeof(vid_buf)); memset(&vid_buf, 0, sizeof(vid_buf));
@ -682,19 +671,19 @@ void LocalCamera::Initialise() {
vid_buf.memory = v4l2_data.reqbufs.memory; vid_buf.memory = v4l2_data.reqbufs.memory;
vid_buf.index = i; vid_buf.index = i;
if ( vidioctl(vid_fd, VIDIOC_QUERYBUF, &vid_buf) < 0 ) if (vidioctl(vid_fd, VIDIOC_QUERYBUF, &vid_buf) < 0)
Fatal("Unable to query video buffer: %s", strerror(errno)); Fatal("Unable to query video buffer: %s", strerror(errno));
v4l2_data.buffers[i].length = vid_buf.length; v4l2_data.buffers[i].length = vid_buf.length;
v4l2_data.buffers[i].start = mmap(nullptr, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset); v4l2_data.buffers[i].start = mmap(nullptr, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset);
if ( v4l2_data.buffers[i].start == MAP_FAILED ) if (v4l2_data.buffers[i].start == MAP_FAILED)
Fatal("Can't map video buffer %u (%u bytes) to memory: %s(%d)", Fatal("Can't map video buffer %u (%u bytes) to memory: %s(%d)",
i, vid_buf.length, strerror(errno), errno); i, vid_buf.length, strerror(errno), errno);
capturePictures[i] = av_frame_alloc(); capturePictures[i] = av_frame_alloc();
if ( !capturePictures[i] ) if (!capturePictures[i])
Fatal("Could not allocate picture"); Fatal("Could not allocate picture");
av_image_fill_arrays( av_image_fill_arrays(
@ -709,7 +698,7 @@ void LocalCamera::Initialise() {
Debug(3, "Configuring video source"); Debug(3, "Configuring video source");
if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channel) < 0 ) { if (vidioctl(vid_fd, VIDIOC_S_INPUT, &channel) < 0) {
Fatal("Failed to set camera source %d: %s", channel, strerror(errno)); Fatal("Failed to set camera source %d: %s", channel, strerror(errno));
} }
@ -719,11 +708,11 @@ void LocalCamera::Initialise() {
memset(&input, 0, sizeof(input)); memset(&input, 0, sizeof(input));
input.index = channel; input.index = channel;
if ( vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0 ) { if (vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0) {
Fatal("Failed to enumerate input %d: %s", channel, strerror(errno)); Fatal("Failed to enumerate input %d: %s", channel, strerror(errno));
} }
if ( (input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN) ) { if ((input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN)) {
Error("Device does not support video standard %d", standard); Error("Device does not support video standard %d", standard);
} }
@ -736,7 +725,6 @@ void LocalCamera::Initialise() {
Brightness(brightness); Brightness(brightness);
Hue(hue); Hue(hue);
Colour(colour); Colour(colour);
}
} // end LocalCamera::Initialize } // end LocalCamera::Initialize
void LocalCamera::Terminate() { void LocalCamera::Terminate() {
@ -1170,141 +1158,60 @@ bool LocalCamera::GetCurrentSettings(
return true; return true;
} }
int LocalCamera::Brightness(int p_brightness) { int LocalCamera::Control(int vid_id, int newvalue) {
if ( v4l_version == 2 ) {
struct v4l2_control vid_control; struct v4l2_control vid_control;
memset(&vid_control, 0, sizeof(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 (vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0) {
if ( errno != EINVAL ) { if (errno != EINVAL) {
Error("Unable to query brightness: %s", strerror(errno)); Error("Unable to query control: %s", strerror(errno));
} else { } else {
Warning("Brightness control is not supported"); Warning("Control is not supported");
} }
//Info( "Brightness 1 %d", vid_control.value ); } else if (newvalue >= 0) {
} else if ( p_brightness >= 0 ) { vid_control.value = newvalue;
vid_control.value = p_brightness;
//Info( "Brightness 2 %d", vid_control.value );
/* The driver may clamp the value or return ERANGE, ignored here */ /* The driver may clamp the value or return ERANGE, ignored here */
if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) {
if ( errno != ERANGE ) { if (errno != ERANGE) {
Error("Unable to set brightness: %s", strerror(errno)); Error("Unable to set control: %s", strerror(errno));
} else { } 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 vid_control.value;
} }
return -1;
int LocalCamera::Brightness(int p_brightness) {
return Control(V4L2_CID_BRIGHTNESS, p_brightness);
} }
int LocalCamera::Hue(int p_hue) { int LocalCamera::Hue(int p_hue) {
if ( v4l_version == 2 ) { return Control(V4L2_CID_HUE, p_hue);
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;
} }
int LocalCamera::Colour( int p_colour ) { int LocalCamera::Colour( int p_colour ) {
if ( v4l_version == 2 ) { return Control(V4L2_CID_SATURATION, p_colour);
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;
} }
int LocalCamera::Contrast( int p_contrast ) { int LocalCamera::Contrast(int p_contrast) {
if ( v4l_version == 2 ) { return Control(V4L2_CID_CONTRAST, p_contrast);
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;
} }
int LocalCamera::PrimeCapture() { int LocalCamera::PrimeCapture() {
getVideoStream(); getVideoStream();
if ( !device_prime ) if (!device_prime)
return 1; return 1;
Debug(2, "Priming capture");
if ( v4l_version == 2 ) {
Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count); Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count);
for ( unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++ ) { for (unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++) {
struct v4l2_buffer vid_buf; struct v4l2_buffer vid_buf;
memset(&vid_buf, 0, sizeof(vid_buf)); memset(&vid_buf, 0, sizeof(vid_buf));
if ( v4l2_data.fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ) { if (v4l2_data.fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
Warning("Unknown type: (%d)", v4l2_data.fmt.type); Warning("Unknown type: (%d)", v4l2_data.fmt.type);
} }
@ -1327,13 +1234,11 @@ int LocalCamera::PrimeCapture() {
Error("Failed to start capture stream: %s", strerror(errno)); Error("Failed to start capture stream: %s", strerror(errno));
return -1; return -1;
} }
} // end if v4l_version == 2
return 1; return 1;
} // end LocalCamera::PrimeCapture } // end LocalCamera::PrimeCapture
int LocalCamera::PreCapture() { int LocalCamera::PreCapture() {
//Debug(5, "Pre-capturing");
return 1; return 1;
} }
@ -1352,8 +1257,7 @@ 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 // 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 (channel_prime) {
if ( v4l_version == 2 ) {
static struct v4l2_buffer vid_buf; static struct v4l2_buffer vid_buf;
memset(&vid_buf, 0, sizeof(vid_buf)); memset(&vid_buf, 0, sizeof(vid_buf));
@ -1362,9 +1266,9 @@ int LocalCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
vid_buf.memory = v4l2_data.reqbufs.memory; vid_buf.memory = v4l2_data.reqbufs.memory;
Debug(3, "Capturing %d frames", captures_per_frame); Debug(3, "Capturing %d frames", captures_per_frame);
while ( captures_per_frame ) { while (captures_per_frame) {
if ( vidioctl(vid_fd, VIDIOC_DQBUF, &vid_buf) < 0 ) { if (vidioctl(vid_fd, VIDIOC_DQBUF, &vid_buf) < 0) {
if ( errno == EIO ) { if (errno == EIO) {
Warning("Capture failure, possible signal loss?: %s", strerror(errno)); Warning("Capture failure, possible signal loss?: %s", strerror(errno));
} else { } else {
Error("Unable to capture frame %d: %s", vid_buf.index, strerror(errno)); Error("Unable to capture frame %d: %s", vid_buf.index, strerror(errno));
@ -1377,8 +1281,8 @@ int LocalCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
capture_frame = v4l2_data.bufptr->index; capture_frame = v4l2_data.bufptr->index;
bytes += vid_buf.bytesused; bytes += vid_buf.bytesused;
if ( --captures_per_frame ) { if (--captures_per_frame) {
if ( vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0 ) { if (vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0) {
Error("Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno)); Error("Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno));
return -1; return -1;
} }
@ -1391,39 +1295,36 @@ int LocalCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
buffer_bytesused = v4l2_data.bufptr->bytesused; buffer_bytesused = v4l2_data.bufptr->bytesused;
bytes += buffer_bytesused; bytes += buffer_bytesused;
if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) > (width * height) ) { if ((v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) > (width * height)) {
Fatal("Captured image dimensions larger than image buffer: V4L2: %dx%d monitor: %dx%d", Fatal("Captured image dimensions larger than image buffer: V4L2: %dx%d monitor: %dx%d",
v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height);
} else if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { } else if ((v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height)) {
Error("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", 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); 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) {
if ( channel_count > 1 ) {
int next_channel = (channel_index+1)%channel_count; int next_channel = (channel_index+1)%channel_count;
Debug(3, "Switching video source to %d", channels[next_channel]); Debug(3, "Switching video source to %d", channels[next_channel]);
if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel]) < 0 ) { if (vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel]) < 0) {
Error("Failed to set camera source %d: %s", channels[next_channel], strerror(errno)); Error("Failed to set camera source %d: %s", channels[next_channel], strerror(errno));
return -1; return -1;
} }
v4l2_std_id stdId = standards[next_channel]; v4l2_std_id stdId = standards[next_channel];
if ( vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0 ) { if (vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0) {
Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno)); Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno));
} }
} }
if ( v4l2_data.bufptr ) { if (v4l2_data.bufptr) {
Debug(3, "Requeueing buffer %d", v4l2_data.bufptr->index); Debug(3, "Requeueing buffer %d", v4l2_data.bufptr->index);
if ( vidioctl(vid_fd, VIDIOC_QBUF, v4l2_data.bufptr) < 0 ) { if (vidioctl(vid_fd, VIDIOC_QBUF, v4l2_data.bufptr) < 0) {
Error("Unable to requeue buffer %d: %s", v4l2_data.bufptr->index, strerror(errno)); Error("Unable to requeue buffer %d: %s", v4l2_data.bufptr->index, strerror(errno));
return -1; return -1;
} }
} else { } else {
Error("Unable to requeue buffer due to not v4l2_data"); Error("Unable to requeue buffer due to not v4l2_data");
} }
}
} /* prime capture */ } /* prime capture */
if (!zm_packet->image) { if (!zm_packet->image) {

View File

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

View File

@ -83,9 +83,10 @@ std::string load_monitor_sql =
"`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, "
"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`,"
"`RTSPServer`, `RTSPStreamName`," "`RTSPServer`, `RTSPStreamName`,"
"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-2 FROM `Monitors`"; "`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`";
std::string CameraType_Strings[] = { std::string CameraType_Strings[] = {
"Unknown",
"Local", "Local",
"Remote", "Remote",
"File", "File",
@ -93,10 +94,21 @@ std::string CameraType_Strings[] = {
"LibVLC", "LibVLC",
"NVSOCKET", "NVSOCKET",
"CURL", "CURL",
"VNC", "VNC"
};
std::string Function_Strings[] = {
"Unknown",
"None",
"Monitor",
"Modect",
"Record",
"Mocord",
"Nodect"
}; };
std::string State_Strings[] = { std::string State_Strings[] = {
"Unknown",
"IDLE", "IDLE",
"PREALARM", "PREALARM",
"ALARM", "ALARM",
@ -435,7 +447,7 @@ Monitor::Monitor()
"SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, "
"FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif,"
"`RTSPServer`,`RTSPStreamName`, "`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) { 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++; function = (Function)atoi(dbrow[col]); col++;
enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
decoding_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++; 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++; videowriter = (VideoWriter)atoi(dbrow[col]); col++;
encoderparams = dbrow[col] ? 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`, " */ /*"`OutputCodec`, `Encoder`, `OutputContainer`, " */
output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++; output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
encoder = dbrow[col] ? dbrow[col] : ""; 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_server = (*dbrow[col] != '0'); col++;
rtsp_streamname = dbrow[col]; 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_points = atoi(dbrow[col]); col++;
signal_check_colour = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); 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 */ grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */
importance = dbrow[col] ? atoi(dbrow[col]) : 0;// col++; 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 // How many frames we need to have before we start analysing
ready_count = std::max(warmup_count, pre_event_count); 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)); 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 ) {
if ( config.record_diag_images_fifo ) { if ( config.record_diag_images_fifo ) {
diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id); 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) { int Monitor::actionBrightness(int p_brightness) {
if (purpose != CAPTURE) { if (purpose == CAPTURE) {
if (p_brightness >= 0) { // 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->brightness = p_brightness;
shared_data->action |= SET_SETTINGS; shared_data->action |= SET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
@ -1317,7 +1334,16 @@ int Monitor::actionBrightness(int p_brightness) {
return -1; 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; shared_data->action |= GET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) { while (shared_data->action & GET_SETTINGS) {
@ -1328,15 +1354,14 @@ int Monitor::actionBrightness(int p_brightness) {
return -1; return -1;
} }
} }
}
return shared_data->brightness; return shared_data->brightness;
} } // end int Monitor::actionBrightness()
return camera->Brightness(p_brightness);
} // end int Monitor::actionBrightness(int p_brightness)
int Monitor::actionContrast(int p_contrast) { int Monitor::actionContrast(int p_contrast) {
if (purpose != CAPTURE) { if (purpose == CAPTURE) {
if (p_contrast >= 0) { return camera->Contrast(p_contrast);
}
shared_data->contrast = p_contrast; shared_data->contrast = p_contrast;
shared_data->action |= SET_SETTINGS; shared_data->action |= SET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
@ -1348,7 +1373,15 @@ int Monitor::actionContrast(int p_contrast) {
return -1; 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; shared_data->action |= GET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) { while (shared_data->action & GET_SETTINGS) {
@ -1359,15 +1392,14 @@ int Monitor::actionContrast(int p_contrast) {
return -1; return -1;
} }
} }
}
return shared_data->contrast; return shared_data->contrast;
} } // end int Monitor::actionContrast()
return camera->Contrast(p_contrast);
} // end int Monitor::actionContrast(int p_contrast)
int Monitor::actionHue(int p_hue) { int Monitor::actionHue(int p_hue) {
if (purpose != CAPTURE) { if (purpose == CAPTURE) {
if (p_hue >= 0) { return camera->Hue(p_hue);
}
shared_data->hue = p_hue; shared_data->hue = p_hue;
shared_data->action |= SET_SETTINGS; shared_data->action |= SET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
@ -1379,7 +1411,13 @@ int Monitor::actionHue(int p_hue) {
return -1; return -1;
} }
} }
} else { return shared_data->hue;
}
int Monitor::actionHue() {
if (purpose == CAPTURE) {
return camera->Hue();
}
shared_data->action |= GET_SETTINGS; shared_data->action |= GET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) { while (shared_data->action & GET_SETTINGS) {
@ -1390,15 +1428,13 @@ int Monitor::actionHue(int p_hue) {
return -1; return -1;
} }
} }
}
return shared_data->hue; return shared_data->hue;
}
return camera->Hue(p_hue);
} // end int Monitor::actionHue(int p_hue) } // end int Monitor::actionHue(int p_hue)
int Monitor::actionColour(int p_colour) { int Monitor::actionColour(int p_colour) {
if (purpose != CAPTURE) { if (purpose == CAPTURE) {
if (p_colour >= 0) { return camera->Colour(p_colour);
}
shared_data->colour = p_colour; shared_data->colour = p_colour;
shared_data->action |= SET_SETTINGS; shared_data->action |= SET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
@ -1410,7 +1446,13 @@ int Monitor::actionColour(int p_colour) {
return -1; return -1;
} }
} }
} else { return shared_data->colour;
}
int Monitor::actionColour() {
if (purpose == CAPTURE) {
return camera->Colour();
}
shared_data->action |= GET_SETTINGS; shared_data->action |= GET_SETTINGS;
int wait_loops = 10; int wait_loops = 10;
while (shared_data->action & GET_SETTINGS) { while (shared_data->action & GET_SETTINGS) {
@ -1421,10 +1463,7 @@ int Monitor::actionColour(int p_colour) {
return -1; return -1;
} }
} }
}
return shared_data->colour; return shared_data->colour;
}
return camera->Colour(p_colour);
} // end int Monitor::actionColour(int p_colour) } // end int Monitor::actionColour(int p_colour)
void Monitor::DumpZoneImage(const char *zone_string) { 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 if ( fps_report_interval and
( (
!(image_count%fps_report_interval) !(image_count%fps_report_interval)
@ -1636,82 +1675,35 @@ void Monitor::UpdateCaptureFPS() {
uint32 new_camera_bytes = camera->Bytes(); uint32 new_camera_bytes = camera->Bytes();
uint32 new_capture_bandwidth = uint32 new_capture_bandwidth =
static_cast<uint32>((new_camera_bytes - last_camera_bytes) / elapsed.count()); 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", Debug(4, "FPS: capture count %d - last capture count %d = %d now:%lf, last %lf, elapsed %lf = capture: %lf fps analysis: %lf fps",
"Capturing",
image_count, image_count,
last_capture_image_count, last_capture_image_count,
image_count - last_capture_image_count, image_count - last_capture_image_count,
FPSeconds(now.time_since_epoch()).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(), elapsed.count(),
new_capture_fps); new_capture_fps,
new_analysis_fps);
Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", 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); name.c_str(), image_count, new_capture_fps, new_capture_bandwidth, new_analysis_fps);
shared_data->capture_fps = new_capture_fps; shared_data->capture_fps = new_capture_fps;
last_fps_time = now; last_fps_time = now;
last_capture_image_count = image_count; 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( std::string sql = stringtf(
"UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u WHERE MonitorId=%u", "UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u, AnalysisFPS = %.2lf WHERE MonitorId=%u",
new_capture_fps, new_capture_bandwidth, id); new_capture_fps, new_capture_bandwidth, new_analysis_fps, id);
dbQueue.push(std::move(sql)); dbQueue.push(std::move(sql));
} // now != last_fps_time } // now != last_fps_time
} // end if report fps } // end if report fps
} // void Monitor::UpdateCaptureFPS() } // void Monitor::UpdateFPS()
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
// Would be nice if this JUST did analysis // Would be nice if this JUST did analysis
// This idea is that we should be analysing as close to the capture frame as possible. // 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 ! event
} // end if RECORDING } // end if RECORDING
if (score) { if (score and (function == MODECT or function == NODECT)) {
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
// If we should end then previous continuous event and start a new non-continuous event // If we should end then previous continuous event and start a new non-continuous event
if (event && event->Frames() if (event && event->Frames()
@ -2261,8 +2252,6 @@ bool Monitor::Analyse() {
// Only do these if it's a video packet. // Only do these if it's a video packet.
shared_data->last_read_index = snap->image_index; shared_data->last_read_index = snap->image_index;
analysis_image_count++; analysis_image_count++;
if (function == MODECT or function == MOCORD)
UpdateAnalysisFPS();
} }
packetqueue.increment_it(analysis_it); packetqueue.increment_it(analysis_it);
packetqueue.unlock(packet_lock); packetqueue.unlock(packet_lock);
@ -2511,7 +2500,7 @@ int Monitor::Capture() {
if (packet->codec_type == AVMEDIA_TYPE_VIDEO) { if (packet->codec_type == AVMEDIA_TYPE_VIDEO) {
packet->packet.stream_index = video_stream_id; // Convert to packetQueue's index packet->packet.stream_index = video_stream_id; // Convert to packetQueue's index
if (video_fifo) { if (video_fifo) {
if ( packet->keyframe ) { if (packet->keyframe) {
// avcodec strips out important nals that describe the stream and // avcodec strips out important nals that describe the stream and
// stick them in extradata. Need to send them along with keyframes // stick them in extradata. Need to send them along with keyframes
AVStream *stream = camera->getVideoStream(); AVStream *stream = camera->getVideoStream();
@ -2547,7 +2536,6 @@ int Monitor::Capture() {
// Will only be queued if there are iterators allocated in the queue. // Will only be queued if there are iterators allocated in the queue.
packetqueue.queuePacket(packet); packetqueue.queuePacket(packet);
UpdateCaptureFPS();
} else { // result == 0 } else { // result == 0
// Question is, do we update last_write_index etc? // Question is, do we update last_write_index etc?
return 0; return 0;
@ -2587,7 +2575,7 @@ bool Monitor::Decode() {
// //
//capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); //capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
int ret = packet->decode(camera->getVideoCodecContext()); int ret = packet->decode(camera->getVideoCodecContext());
if (ret > 0) { if (ret > 0 and !zm_terminate) {
if (packet->in_frame and !packet->image) { if (packet->in_frame and !packet->image) {
packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder()); packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder());
AVFrame *input_frame = packet->in_frame; AVFrame *input_frame = packet->in_frame;

View File

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

View File

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

View File

@ -86,38 +86,63 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
{ {
std::unique_lock<std::mutex> lck(mutex); std::unique_lock<std::mutex> lck(mutex);
if (add_packet->packet.stream_index == video_stream_id) { pktQueue.push_back(add_packet);
if ((max_video_packet_count > 0) and (packet_counts[video_stream_id] > max_video_packet_count)) { packet_counts[add_packet->packet.stream_index] += 1;
Warning("You have set the max video packets in the queue to %u." Debug(2, "packet counts for %d is %d",
" The queue is full. Either Analysis is not keeping up or" add_packet->packet.stream_index,
" your camera's keyframe interval is larger than this setting." packet_counts[add_packet->packet.stream_index]);
" 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;
for ( for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin(); auto iterators_it = iterators.begin();
iterators_it != iterators.end(); iterators_it != iterators.end();
++iterators_it ++iterators_it
) { ) {
packetqueue_iterator *iterator_it = *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 == pktQueue.end()) {
if ( *(*iterator_it) == zm_packet ) { --(*iterator_it);
Debug(1, "Bumping IT because it is at the front that we are deleting");
++(*iterators_it);
} }
} // end foreach iterator } // 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; packet_counts[zm_packet->packet.stream_index] -= 1;
Debug(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", "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], packet_counts[video_stream_id],
max_video_packet_count, max_video_packet_count,
pktQueue.size()); pktQueue.size());
delete lp;
if (zm_packet->packet.stream_index == video_stream_id)
break;
} // end while } // end while
} } // end if not able catch up
} // 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 lock scope } // end lock scope
// We signal on every packet because someday we may analyze sound // We signal on every packet because someday we may analyze sound
Debug(4, "packetqueue queuepacket, unlocked signalling"); 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, 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 ) ( *(pktQueue.begin()) != add_packet )
); );
Warning("Keyframe interval may be larger than MaxImageBufferCount and PreEventCount. Please increase MaxImageBufferCount");
return; return;
} }
std::unique_lock<std::mutex> lck(mutex); std::unique_lock<std::mutex> lck(mutex);
@ -241,8 +239,8 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return; return;
} }
packetqueue_iterator it = pktQueue.begin(); auto it = pktQueue.begin();
packetqueue_iterator next_front = 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 // 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; std::shared_ptr<ZMPacket> zm_packet = *it;
@ -250,32 +248,32 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return; return;
} }
Debug(1, "trying lock on first packet");
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
if (lp->trylock()) { 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 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; ++it;
delete lp; 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 // 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) { while (*it != add_packet) {
zm_packet = *it; zm_packet = *it;
lp = new ZMLockedPacket(zm_packet); lp = new ZMLockedPacket(zm_packet);
if (!lp->trylock()) { if (!lp->trylock()) {
Debug(3, "Failed locking packet %d", zm_packet->image_index);
delete lp; delete lp;
break; break;
} }
delete lp; 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"); Warning("Found iterator at beginning of queue. Some thread isn't keeping up");
break; break;
} }
#endif
if (zm_packet->packet.stream_index == video_stream_id) { if (zm_packet->packet.stream_index == video_stream_id) {
if (zm_packet->keyframe) { if (zm_packet->keyframe) {
@ -283,7 +281,7 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
next_front = it; next_front = it;
} }
++video_packets_to_delete; ++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); 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) { if (packet_counts[video_stream_id] - video_packets_to_delete <= pre_event_video_packet_count + tail_count) {
break; break;
@ -291,9 +289,8 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
} }
++it; ++it;
} // end while } // end while
}
} // end if first packet not locked } // 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 ), ( *it == add_packet ),
( next_front == pktQueue.begin() ) ( next_front == pktQueue.begin() )
); );

View File

@ -49,45 +49,43 @@ RemoteCamera::RemoteCamera(
mNeedAuth(false), mNeedAuth(false),
mAuthenticator(nullptr) mAuthenticator(nullptr)
{ {
if ( path[0] != '/' ) if (path[0] != '/')
path = '/'+path; path = '/'+path;
} }
RemoteCamera::~RemoteCamera() { RemoteCamera::~RemoteCamera() {
if ( hp != nullptr ) { if (hp != nullptr) {
freeaddrinfo(hp); freeaddrinfo(hp);
hp = nullptr; hp = nullptr;
} }
if ( mAuthenticator ) { if (mAuthenticator) {
delete mAuthenticator; delete mAuthenticator;
mAuthenticator = nullptr; mAuthenticator = nullptr;
} }
} }
void RemoteCamera::Initialise() { void RemoteCamera::Initialise() {
if( protocol.empty() ) if (protocol.empty())
Fatal( "No protocol specified for remote camera" ); Fatal("No protocol specified for remote camera");
if( host.empty() ) if (host.empty())
Fatal( "No host specified for remote camera" ); Fatal("No host specified for remote camera");
if ( port.empty() ) if (port.empty())
Fatal("No port specified for remote camera"); 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 // Cache as much as we can to speed things up
std::string::size_type authIndex = host.rfind( '@' ); std::string::size_type authIndex = host.rfind('@');
if ( authIndex != std::string::npos ) { if (authIndex != std::string::npos) {
auth = host.substr( 0, authIndex ); auth = host.substr(0, authIndex);
host.erase( 0, authIndex+1 ); host.erase(0, authIndex+1);
auth64 = Base64Encode(auth); auth64 = Base64Encode(auth);
authIndex = auth.rfind( ':' ); authIndex = auth.rfind(':');
username = auth.substr(0,authIndex); username = auth.substr(0,authIndex);
password = auth.substr( authIndex+1, auth.length() ); password = auth.substr(authIndex+1, auth.length());
} }
mNeedAuth = false; mNeedAuth = false;
@ -99,13 +97,13 @@ void RemoteCamera::Initialise() {
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp);
if ( ret != 0 ) { if (ret != 0) {
Error( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); Error("Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret));
return; return;
} }
struct addrinfo *p = nullptr; struct addrinfo *p = nullptr;
int addr_count = 0; int addr_count = 0;
for ( p = hp; p != nullptr; p = p->ai_next ) { for (p = hp; p != nullptr; p = p->ai_next) {
addr_count++; addr_count++;
} }
Debug(1, "%d addresses returned", addr_count); Debug(1, "%d addresses returned", addr_count);

View File

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

View File

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

View File

@ -151,6 +151,7 @@ bool VideoStore::open() {
Debug(3, "Encoder Option %s=%s", e->key, e->value); Debug(3, "Encoder Option %s=%s", e->key, e->value);
} }
} }
av_dict_free(&opts);
if (video_in_stream) { if (video_in_stream) {
zm_dump_codecpar(video_in_stream->codecpar); zm_dump_codecpar(video_in_stream->codecpar);
@ -184,6 +185,7 @@ bool VideoStore::open() {
} }
} // end if orientation } // 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)) { if (av_dict_get(opts, "new_extradata", nullptr, AV_DICT_MATCH_CASE)) {
av_dict_set(&opts, "new_extradata", nullptr, 0); 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 // 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) { if (ret < 0) {
Error("Could not initialize stream parameteres"); Error("Could not initialize stream parameteres");
} }
} // end if extradata_entry
av_dict_free(&opts); av_dict_free(&opts);
} // end if extradata_entry
} else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) { } else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) {
int wanted_codec = monitor->OutputCodec(); int wanted_codec = monitor->OutputCodec();
if (!wanted_codec) { if (!wanted_codec) {
@ -485,6 +487,7 @@ bool VideoStore::open() {
zm_dump_stream_format(oc, 0, 0, 1); zm_dump_stream_format(oc, 0, 0, 1);
if (audio_out_stream) zm_dump_stream_format(oc, 1, 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); const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE);
if (!movflags_entry) { if (!movflags_entry) {
Debug(1, "setting movflags to frag_keyframe+empty_moov"); Debug(1, "setting movflags to frag_keyframe+empty_moov");
@ -616,7 +619,8 @@ VideoStore::~VideoStore() {
Debug(1, "Writing trailer"); Debug(1, "Writing trailer");
/* Write the trailer before close */ /* 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)); Error("Error writing trailer %s", av_err2str(rc));
} else { } else {
Debug(3, "Success Writing trailer"); Debug(3, "Success Writing trailer");
@ -626,7 +630,7 @@ VideoStore::~VideoStore() {
if (!(out_format->flags & AVFMT_NOFILE)) { if (!(out_format->flags & AVFMT_NOFILE)) {
/* Close the out file. */ /* Close the out file. */
Debug(4, "Closing"); 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)); Error("Error closing avio %s", av_err2str(rc));
} }
} else { } else {

View File

@ -111,6 +111,13 @@ class VideoStore {
int writePacket(const std::shared_ptr<ZMPacket> &pkt); int writePacket(const std::shared_ptr<ZMPacket> &pkt);
int write_packets(PacketQueue &queue); int write_packets(PacketQueue &queue);
void flush_codecs(); 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 #endif // ZM_VIDEOSTORE_H

View File

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

326
src/zmbenchmark.cpp Normal file
View File

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

View File

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

View File

@ -257,9 +257,16 @@ int main(int argc, char *argv[]) {
int image_idx = -1; int image_idx = -1;
int scale = -1; int scale = -1;
int brightness = -1; int brightness = -1;
bool have_brightness = false;
int contrast = -1; int contrast = -1;
bool have_contrast = false;
int hue = -1; int hue = -1;
bool have_hue = false;
int colour = -1; int colour = -1;
bool have_colour = false;
char *zoneString = nullptr; char *zoneString = nullptr;
char *username = nullptr; char *username = nullptr;
char *password = nullptr; char *password = nullptr;
@ -272,13 +279,13 @@ int main(int argc, char *argv[]) {
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index); int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index);
if ( c == -1 ) { if (c == -1) {
break; break;
} }
switch (c) { switch (c) {
case 'd': case 'd':
if ( optarg ) if (optarg)
device = optarg; device = optarg;
break; break;
case 'm': case 'm':
@ -292,7 +299,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'i': case 'i':
function |= ZMU_IMAGE; function |= ZMU_IMAGE;
if ( optarg ) if (optarg)
image_idx = atoi(optarg); image_idx = atoi(optarg);
break; break;
case 'S': case 'S':
@ -300,7 +307,7 @@ int main(int argc, char *argv[]) {
break; break;
case 't': case 't':
function |= ZMU_TIME; function |= ZMU_TIME;
if ( optarg ) if (optarg)
image_idx = atoi(optarg); image_idx = atoi(optarg);
break; break;
case 'R': case 'R':
@ -317,7 +324,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'z': case 'z':
function |= ZMU_ZONES; function |= ZMU_ZONES;
if ( optarg ) if (optarg)
zoneString = optarg; zoneString = optarg;
break; break;
case 'a': case 'a':
@ -349,23 +356,31 @@ int main(int argc, char *argv[]) {
break; break;
case 'B': case 'B':
function |= ZMU_BRIGHTNESS; function |= ZMU_BRIGHTNESS;
if ( optarg ) if (optarg) {
have_brightness = true;
brightness = atoi(optarg); brightness = atoi(optarg);
}
break; break;
case 'C': case 'C':
function |= ZMU_CONTRAST; function |= ZMU_CONTRAST;
if ( optarg ) if (optarg) {
have_contrast = true;
contrast = atoi(optarg); contrast = atoi(optarg);
}
break; break;
case 'H': case 'H':
function |= ZMU_HUE; function |= ZMU_HUE;
if ( optarg ) if (optarg) {
have_hue = true;
hue = atoi(optarg); hue = atoi(optarg);
}
break; break;
case 'O': case 'O':
function |= ZMU_COLOUR; function |= ZMU_COLOUR;
if ( optarg ) if (optarg) {
have_colour = true;
colour = atoi(optarg); colour = atoi(optarg);
}
break; break;
case 'U': case 'U':
username = optarg; username = optarg;
@ -650,60 +665,60 @@ int main(int argc, char *argv[]) {
monitor->DumpSettings(monString, verbose); monitor->DumpSettings(monString, verbose);
printf("%s\n", monString); printf("%s\n", monString);
} }
if ( function & ZMU_BRIGHTNESS ) { if (function & ZMU_BRIGHTNESS) {
if ( verbose ) { if (verbose) {
if ( brightness >= 0 ) if (have_brightness)
printf("New brightness: %d\n", monitor->actionBrightness(brightness)); printf("New brightness: %d\n", monitor->actionBrightness(brightness));
else else
printf("Current brightness: %d\n", monitor->actionBrightness()); printf("Current brightness: %d\n", monitor->actionBrightness());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( brightness >= 0 ) if (have_brightness)
printf("%d", monitor->actionBrightness(brightness)); printf("%d", monitor->actionBrightness(brightness));
else else
printf("%d", monitor->actionBrightness()); printf("%d", monitor->actionBrightness());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_CONTRAST ) { if (function & ZMU_CONTRAST) {
if ( verbose ) { if (verbose) {
if ( contrast >= 0 ) if (have_contrast)
printf("New brightness: %d\n", monitor->actionContrast(contrast)); printf("New contrast: %d\n", monitor->actionContrast(contrast));
else else
printf("Current contrast: %d\n", monitor->actionContrast()); printf("Current contrast: %d\n", monitor->actionContrast());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( contrast >= 0 ) if (have_contrast)
printf("%d", monitor->actionContrast(contrast)); printf("%d", monitor->actionContrast(contrast));
else else
printf("%d", monitor->actionContrast()); printf("%d", monitor->actionContrast());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_HUE ) { if (function & ZMU_HUE) {
if ( verbose ) { if (verbose) {
if ( hue >= 0 ) if (have_hue)
printf("New hue: %d\n", monitor->actionHue(hue)); printf("New hue: %d\n", monitor->actionHue(hue));
else else
printf("Current hue: %d\n", monitor->actionHue()); printf("Current hue: %d\n", monitor->actionHue());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( hue >= 0 ) if (have_hue)
printf("%d", monitor->actionHue(hue)); printf("%d", monitor->actionHue(hue));
else else
printf("%d", monitor->actionHue()); printf("%d", monitor->actionHue());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_COLOUR ) { if (function & ZMU_COLOUR) {
if ( verbose ) { if (verbose) {
if ( colour >= 0 ) if (have_colour)
printf("New colour: %d\n", monitor->actionColour(colour)); printf("New colour: %d\n", monitor->actionColour(colour));
else else
printf("Current colour: %d\n", monitor->actionColour()); printf("Current colour: %d\n", monitor->actionColour());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( colour >= 0 ) if (have_colour)
printf("%d", monitor->actionColour(colour)); printf("%d", monitor->actionColour(colour));
else else
printf("%d", monitor->actionColour()); printf("%d", monitor->actionColour());
@ -711,7 +726,7 @@ int main(int argc, char *argv[]) {
} }
} }
if ( have_output ) { if (have_output) {
printf("\n"); printf("\n");
} }
if ( !function ) { if ( !function ) {

View File

@ -230,9 +230,13 @@ rm .gitignore
cd ../ cd ../
if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then 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 tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi; fi;
fi;
IFS=',' ;for DISTRO in `echo "$DISTROS"`; do IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
echo "Generating package for $DISTRO"; echo "Generating package for $DISTRO";
@ -358,7 +362,7 @@ EOF
dput="Y"; dput="Y";
if [ "$INTERACTIVE" != "no" ]; then if [ "$INTERACTIVE" != "no" ]; then
read -p "Ready to dput $SC to $PPA ? Y/n..."; read -p "Ready to dput $SC to $PPA ? Y/n...";
if [[ "$REPLY" == [yY] ]]; then if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
dput $PPA $SC dput $PPA $SC
fi; fi;
else else

View File

@ -6,28 +6,30 @@ $data = array();
// INITIALIZE AND CHECK SANITY // 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']) ) { if (empty($_REQUEST['task'])) {
$message = 'Must specify a task'; $message = 'Must specify a task<br/>';
} else { } else {
$task = $_REQUEST['task']; $task = $_REQUEST['task'];
} }
if ( empty($_REQUEST['eids']) ) { 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 { } else {
$eids = $_REQUEST['eids']; $eids = $_REQUEST['eids'];
} }
if ( $message ) { if ($message) {
ajaxError($message); ajaxError($message);
return; return;
} }
require_once('includes/Filter.php'); require_once('includes/Filter.php');
$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter(); $filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
if ( $user['MonitorIds'] ) { if ($user['MonitorIds']) {
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds'])); $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds']));
} }
@ -39,10 +41,19 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array();
// Order specifies the sort direction, either asc or desc // 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 specifies the name of the column to sort on
$sort = 'StartDateTime'; $sort = $filter->sort_field();
if (isset($_REQUEST['sort'])) { if (isset($_REQUEST['sort'])) {
$sort = $_REQUEST['sort']; $sort = $_REQUEST['sort'];
if ($sort == 'EndDateTime') { if ($sort == 'EndDateTime') {
@ -228,15 +239,17 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
} # end if search } # 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; $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); $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 { } else {
$filtered_rows = $unfiltered_rows; $filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1 } # end if search_filter->terms() > 1
if ($limit)
$filtered_rows = array_slice($filtered_rows, $offset, $limit);
$returned_rows = array(); $returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { foreach ($filtered_rows as $row) {
$event = new ZM\Event($row); $event = new ZM\Event($row);
$scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());

View File

@ -1,11 +1,11 @@
<?php <?php
if ( !canView('Control') ) return; if (!canView('Control')) return;
$monitor = ZM\Monitor::find_one(array('Id'=>$_REQUEST['mid'])); $monitor = ZM\Monitor::find_one(array('Id'=>$_REQUEST['mid']));
$zmuCommand = getZmuCommand(' -m '.escapeshellarg($_REQUEST['mid']).' -B -C -H -O'); $zmuCommand = getZmuCommand(' -m '.escapeshellarg($_REQUEST['mid']).' -B -C -H -O');
$zmuOutput = exec( $zmuCommand ); $zmuOutput = exec( $zmuCommand );
if ( $zmuOutput ) { if ($zmuOutput) {
list($brightness, $contrast, $hue, $colour) = explode(' ', $zmuOutput); list($brightness, $contrast, $hue, $colour) = explode(' ', $zmuOutput);
$monitor->Brightness($brightness); $monitor->Brightness($brightness);
@ -13,7 +13,6 @@ if ( $zmuOutput ) {
$monitor->Hue($hue); $monitor->Hue($hue);
$monitor->Colour($colour); $monitor->Colour($colour);
} }
?> ?>
<div class="modal" id="settingsModal" tabindex="-1"> <div class="modal" id="settingsModal" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
@ -35,22 +34,89 @@ if ( $zmuOutput ) {
<input type="hidden" name="mid" value="<?php echo validInt($_REQUEST['mid']) ?>"/> <input type="hidden" name="mid" value="<?php echo validInt($_REQUEST['mid']) ?>"/>
<table id="contentTable" class="major"> <table id="contentTable" class="major">
<tbody> <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> <tr>
<th scope="row"><?php echo translate('Brightness') ?></th> <th scope="row">'.translate($setting_uc).'</th>
<td><input type="number" name="newBrightness" value="<?php echo $monitor->Brightness() ?>" <?php if ( !canView( 'Control' ) ) { ?> disabled="disabled"<?php } ?> /></td> <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> </tr>
';
} else {
if ($type == '(bool)') {
echo '
<tr> <tr>
<th scope="row"><?php echo translate('Contrast') ?></th> <th scope="row">'.translate($setting_uc).'</th>
<td><input type="number" name="newContrast" value="<?php echo $monitor->Contrast() ?>" <?php echo canView('Control') ? '' : ' disabled="disabled"' ?> /></td> <td></td><td>'.html_radio('new'.$setting_uc, array('0'=>translate('True'), '1', translate('False')), $value, array('disabled'=>'disabled')).'
</td><td></td>
</tr> </tr>
';
} else if ($type == '(int)') {
echo '
<tr> <tr>
<th scope="row"><?php echo translate('Hue') ?></th> <th scope="row">'.translate($setting_uc).'</th>
<td><input type="number" name="newHue" value="<?php echo $monitor->Hue() ?>" <?php echo canView('Control') ? '' : ' disabled="disabled"' ?> /></td> <td></td><td><input type="range" '.$ctl[1].' disabled="disabled"/></td><td></td>
</tr> </tr>
';
} else {
echo '
<tr> <tr>
<th scope="row"><?php echo translate('Colour') ?></th> <th scope="row">'.translate($setting_uc).'</th>
<td><input type="number" name="newColour" value="<?php echo $monitor->Colour() ?>" <?php echo canView('Control') ? '' : ' disabled="disabled"' ?> /></td> <td></td><td>'.$value.'</td><td></td>
</tr> </tr>
';
}
}
} # end foreach ctrl
?>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

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

View File

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

View File

@ -1,6 +1,12 @@
ZoneMinder uses certain 3rd party media assets/libraries for UI display purposes. Their licenses are listed in this file 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 ### Material Design icons
Origin: https://github.com/google/material-design-icons Origin: https://github.com/google/material-design-icons

View File

@ -60,6 +60,9 @@ class Filter extends ZM_Object {
foreach ( $this->FilterTerms() as $term ) { foreach ( $this->FilterTerms() as $term ) {
$this->_querystring .= $term->querystring($objectname, $separator); $this->_querystring .= $term->querystring($objectname, $separator);
} # end foreach term } # 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() ) { if ( $this->Id() ) {
$this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id(); $this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id();
} }

View File

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

View File

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

View File

@ -20,18 +20,18 @@
// Monitor control actions, require a monitor id and control view permissions for that monitor // Monitor control actions, require a monitor id and control view permissions for that monitor
if ( empty($_REQUEST['mid']) ) { if (empty($_REQUEST['mid'])) {
ZM\Warning('Settings requires a monitor id'); ZM\Warning('Settings requires a monitor id');
return; return;
} }
if ( ! canView('Control', $_REQUEST['mid']) ) { if (!canView('Control', $_REQUEST['mid'])) {
ZM\Warning('Settings requires the Control permission'); ZM\Warning('Settings requires the Control permission');
return; return;
} }
require_once('includes/Monitor.php'); require_once('includes/Monitor.php');
$mid = validInt($_REQUEST['mid']); $mid = validInt($_REQUEST['mid']);
if ( $action == 'settings' ) { if ($action == 'settings') {
$args = ' -m ' . escapeshellarg($mid); $args = ' -m ' . escapeshellarg($mid);
$args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']); $args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']);
$args .= ' -C' . escapeshellarg($_REQUEST['newContrast']); $args .= ' -C' . escapeshellarg($_REQUEST['newContrast']);
@ -45,5 +45,7 @@ if ( $action == 'settings' ) {
dbQuery( dbQuery(
'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?',
array($brightness, $contrast, $hue, $colour, $mid)); array($brightness, $contrast, $hue, $colour, $mid));
global $redirect;
$redirect = '?view=watch&mid='.$mid;
} }
?> ?>

View File

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

View File

@ -348,16 +348,16 @@ $SLANG = array(
'Ffmpeg' => 'Ffmpeg', // Added - 2009-02-08 'Ffmpeg' => 'Ffmpeg', // Added - 2009-02-08
'File' => 'Fichier', 'File' => 'Fichier',
'Filter' => 'Filtre', // Added - 2015-04-18 'Filter' => 'Filtre', // Added - 2015-04-18
'FilterArchiveEvents' => 'Archiver', 'FilterArchiveEvents' => 'Archiver les évènements',
'FilterDeleteEvents' => 'Effacer', 'FilterDeleteEvents' => 'Effacer les évènements',
'FilterEmailEvents' => 'Envoyer les détails par email', 'FilterEmailEvents' => 'Envoyer les évènements par email',
'FilterExecuteEvents' => 'Exécuter une commande', 'FilterExecuteEvents' => 'Exécuter une commande',
'FilterLog' => 'Filtre', // Added - 2015-04-18 '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 'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filtre Px', 'FilterPx' => 'Filtre Px',
'FilterUnset' => 'Vous devez spécifier une largeur et une hauteur de filtre', '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', 'FilterUploadEvents' => 'Transférer',
'FilterVideoEvents' => 'Créer vidéo', 'FilterVideoEvents' => 'Créer vidéo',
'Filters' => 'Filtres', 'Filters' => 'Filtres',

View File

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

View File

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

View File

@ -225,15 +225,15 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
<h2><?php echo translate('Images').': '.validHtmlStr($event->Name()).( (!empty($otherlinks)) ? ' ('.$otherlinks.') ' : '' ) ?></h2> <h2><?php echo translate('Images').': '.validHtmlStr($event->Name()).( (!empty($otherlinks)) ? ' ('.$otherlinks.') ' : '' ) ?></h2>
<?php <?php
if ( $event->DefaultVideo() ) { if ($event->DefaultVideo()) {
// videojs zoomrotate only when direct recording // videojs zoomrotate only when direct recording
$Zoom = 1; $Zoom = 1;
$Rotation = 0; $Rotation = 0;
$Monitor = $event->Monitor(); $Monitor = $event->Monitor();
if ( $Monitor->VideoWriter() == '2' ) { if ($Monitor->VideoWriter() == '2') {
# Passthrough # Passthrough
$Rotation = $event->Orientation(); $Rotation = $event->Orientation();
if ( in_array($event->Orientation(), array('ROTATE_90','ROTATE_270')) ) if (in_array($event->Orientation(), array('ROTATE_90','ROTATE_270')))
$Zoom = $event->Height()/$event->Width(); $Zoom = $event->Height()/$event->Width();
} # end if passthrough } # end if passthrough
?> ?>
@ -242,7 +242,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
width="<?php echo $event->Width() ?>" width="<?php echo $event->Width() ?>"
height="<?php echo $event->Height() ?>" height="<?php echo $event->Height() ?>"
data-setup='{ "controls": true, "autoplay": true, "preload": "auto", "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}'> 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> <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. Your browser does not support the video tag.
</video> </video>
@ -250,7 +250,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
<?php <?php
} else { // end if DefaultVideo } 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> <layer id="slidenssub" width="&{slidewidth};" left="auto" top="auto"></layer>
</ilayer> </ilayer>
<div id="imagevideo" align="center"></div> <div id="imagevideo" align="center"></div>
@ -571,29 +571,23 @@ else if (document.layers) window.onload=start_slider;
} # end function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) } # end function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist)
function eventlist_html($Event, $exportDetail, $exportFrames, $exportStructure) { function eventlist_html($Event, $exportDetail, $exportFrames, $exportStructure) {
$html = '<div class="event"> $html = '';
'; if ($Event->SaveJPEGs()) {
if ( $Event->SaveJPEGs() ) {
$html .= '<a href="#" onclick="switchevent(\''.$Event->Id().'/zmEventImages.html\');return false;"> $html .= '<a href="#" onclick="switchevent(\''.$Event->Id().'/zmEventImages.html\');return false;">
'; ';
if ( ZM_WEB_LIST_THUMBS ) { if ( ZM_WEB_LIST_THUMBS ) {
$html .= '<img width="'.ZM_WEB_LIST_THUMB_WIDTH.'" src="'. $Event->Id().($exportStructure=='flat'?'_':'/').'snapshot.jpg" alt="'.$Event->Id().'"/> $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/> $html .= '</a><br/>
'; ';
} # end if has jpegs } # end if has jpegs
if ( $Event->DefaultVideo() ) { if ($Event->DefaultVideo()) {
if ( ZM_WEB_LIST_THUMBS ) {
$html .= '<a href="'.$Event->Id().'/'.$Event->DefaultVideo() .'">'; $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 .= '<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) {
if ( $exportDetail ) {
$html .= '<a href="#" onclick="switchevent(\''.$Event->Id().'/zmEventDetail.html\');return false;">Detail</a> $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 .= '<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; return $html;
} // end function eventlist_html } // end function eventlist_html
@ -615,7 +609,7 @@ function exportEventImagesMaster($eids, $exportDetail, $exportFrames, $exportStr
<?php <?php
$events = ZM\Event::find(array('Id'=>$eids)); $events = ZM\Event::find(array('Id'=>$eids));
foreach ( $events as $event ) { foreach ($events as $event) {
//get monitor id and event id //get monitor id and event id
$eventMonitorId[$event->Id()] = $event->MonitorId(); $eventMonitorId[$event->Id()] = $event->MonitorId();
$eventPath[$event->Id()] = $event->Relative_Path(); $eventPath[$event->Id()] = $event->Relative_Path();
@ -625,7 +619,7 @@ function exportEventImagesMaster($eids, $exportDetail, $exportFrames, $exportStr
$monitorNames = array(); $monitorNames = array();
//* //*
if ( !empty($monitors) ) { if (!empty($monitors)) {
$tmp = dbFetchAll('SELECT Id, Name FROM Monitors WHERE Id IN ('.implode(',', $monitors).') '); $tmp = dbFetchAll('SELECT Id, Name FROM Monitors WHERE Id IN ('.implode(',', $monitors).') ');
foreach ( $tmp as $row ) { $monitorNames[$row['Id']] = $row['Name']; } foreach ( $tmp as $row ) { $monitorNames[$row['Id']] = $row['Name']; }
} }
@ -641,23 +635,23 @@ function exportEventImagesMaster($eids, $exportDetail, $exportFrames, $exportStr
?> ?>
</ul> </ul>
</div> </div>
<table> <table style="width: 100%;">
<tr> <tr>
<td valign="top" bgcolor="#dddddd" style="padding:10px;"> <td valign="top" bgcolor="#dddddd" style="padding:10px;">
<div class="tab_content" id="all"> <div class="tab_content" id="all">
<h2> All </h2> <h2> All </h2>
<?php <?php
foreach($events as $event) { foreach ($events as $event) {
echo eventlist_html($event, $exportDetail, $exportFrames, $exportStructure); echo eventlist_html($event, $exportDetail, $exportFrames, $exportStructure);
} # end foreach event } # end foreach event
?> ?>
</div> </div>
<?php <?php
foreach ( $monitors as $monitor_id ) { foreach ($monitors as $monitor_id) {
echo '<div class="tab_content" id="tab'.$monitor_id.'">'; echo '<div class="tab_content" id="tab'.$monitor_id.'">';
echo '<h2>Monitor: '.$monitorNames[$monitor_id].'</h2>'; echo '<h2>Monitor: '.$monitorNames[$monitor_id].'</h2>';
foreach ( $events as $event ) { foreach ($events as $event) {
if ( $event->MonitorId() == $monitor_id ) { if ($event->MonitorId() == $monitor_id) {
echo eventlist_html($event, $exportDetail, $exportFrames, $exportStructure); echo eventlist_html($event, $exportDetail, $exportFrames, $exportStructure);
} # end if its the right monitor } # end if its the right monitor
} # end foreach event } # end foreach event
@ -665,7 +659,7 @@ function exportEventImagesMaster($eids, $exportDetail, $exportFrames, $exportStr
} # end foreach monitor } # end foreach monitor
?> ?>
</td><td valign="top"> </td><td valign="top" style="height: 100%;">
<iframe id="myframe" onload="resizeCaller();" name="myframe" src="about:blank" <iframe id="myframe" onload="resizeCaller();" name="myframe" src="about:blank"
scrolling="no" marginwidth="0" marginheight="0" frameborder="0" scrolling="no" marginwidth="0" marginheight="0" frameborder="0"
vspace="0" hspace="0" style="overflow:visible; width:100%; display:none"> vspace="0" hspace="0" style="overflow:visible; width:100%; display:none">
@ -828,6 +822,8 @@ function exportFileList(
foreach ($files as $file) { foreach ($files as $file) {
if (preg_match('/-(?:capture|analyse).jpg$/', $file)) { if (preg_match('/-(?:capture|analyse).jpg$/', $file)) {
$myfilelist[$file] = $exportFileList[$file] = $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 { } else {
$filesLeft[$file] = $file; $filesLeft[$file] = $file;
} }
@ -835,7 +831,6 @@ function exportFileList(
$files = $filesLeft; $files = $filesLeft;
// create an image slider // create an image slider
if (!empty($myfilelist)) {
$file = 'zmEventImages.html'; $file = 'zmEventImages.html';
if ($fp = fopen($eventPath.'/'.$file, 'w')) { if ($fp = fopen($eventPath.'/'.$file, 'w')) {
fwrite($fp, exportEventImages($event, $exportDetail, $exportFrames, $myfilelist)); fwrite($fp, exportEventImages($event, $exportDetail, $exportFrames, $myfilelist));
@ -844,23 +839,10 @@ function exportFileList(
} else { } else {
ZM\Error("Can't open event images export file '$file'"); ZM\Error("Can't open event images export file '$file'");
} }
}
} else { } else {
ZM\Debug('Not including frame images'); ZM\Debug('Not including frame images');
} # end if exportImages } # 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) { if ($exportMisc) {
foreach ($files as $file) { foreach ($files as $file) {
$exportFileList[$file] = $file; $exportFileList[$file] = $file;
@ -901,14 +883,14 @@ function exportEvents(
} }
# Ensure that we are going to be able to do this. # 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.'\''); ZM\Fatal('Can\'t create exports dir at \''.ZM_DIR_EXPORTS.'\'');
} }
chmod(ZM_DIR_EXPORTS, 0700); chmod(ZM_DIR_EXPORTS, 0700);
$export_dir = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:''); $export_dir = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'');
# Ensure that we are going to be able to do this. # 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'"); ZM\Error("Can't create exports dir at '$export_dir'");
return false; return false;
} }
@ -933,7 +915,7 @@ function exportEvents(
continue; continue;
} }
$event_dir = $export_dir.'/'.$event->Id(); $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"); ZM\Error("Can't mkdir $event_dir");
} }
$event_exportFileList = exportFileList($event, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc); $event_exportFileList = exportFileList($event, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc);
@ -952,7 +934,7 @@ function exportEvents(
} # end foreach event } # end foreach event
if (!( 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 or
file_exists($export_dir.'/jquery.min.js') file_exists($export_dir.'/jquery.min.js')
)) { )) {
@ -988,7 +970,8 @@ function exportEvents(
$archive = ''; $archive = '';
if ($exportFormat == 'tar') { if ($exportFormat == 'tar') {
$archive = $export_root.($connkey?'_'.$connkey:'').'.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'; $command = 'tar --create --dereference';
if ($exportCompressed) { if ($exportCompressed) {
@ -1015,6 +998,7 @@ function exportEvents(
@unlink($archive_path); @unlink($archive_path);
$command .= ' '.$export_root.($connkey?'_'.$connkey:'').'/'; $command .= ' '.$export_root.($connkey?'_'.$connkey:'').'/';
ZM\Debug($command);
exec($command, $output, $status); exec($command, $output, $status);
if ($status) { if ($status) {
ZM\Error("Command '$command' returned with status $status"); ZM\Error("Command '$command' returned with status $status");

View File

@ -265,7 +265,8 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
echo getSysLoadHTML(); echo getSysLoadHTML();
echo getDbConHTML(); echo getDbConHTML();
echo getStorageHTML(); echo getStorageHTML();
echo getShmHTML(); echo getRamHTML();
#echo getShmHTML();
#echo getLogIconHTML(); #echo getLogIconHTML();
?> ?>
</ul> </ul>
@ -323,7 +324,8 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
echo getSysLoadHTML(); echo getSysLoadHTML();
echo getDbConHTML(); echo getDbConHTML();
echo getStorageHTML(); echo getStorageHTML();
echo getShmHTML(); echo getRamHTML();
#echo getShmHTML();
echo getLogIconHTML(); echo getLogIconHTML();
?> ?>
</ul> </ul>
@ -456,6 +458,37 @@ function getStorageHTML() {
return $result; 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) // Returns the html representing the current capacity of mapped memory filesystem (usually /dev/shm)
function getShmHTML() { function getShmHTML() {
$result = ''; $result = '';
@ -883,7 +916,7 @@ function xhtmlFooter() {
?> ?>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/jquery.min.js'); ?>"></script> <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="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( <?php echo output_script_if_exists(array(
'js/tableExport.min.js', 'js/tableExport.min.js',
'js/bootstrap-table.min.js', 'js/bootstrap-table.min.js',

View File

@ -153,6 +153,7 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
download download
<?php echo $Event->DefaultVideo() ? '' : 'style="display:none;"' ?> <?php echo $Event->DefaultVideo() ? '' : 'style="display:none;"' ?>
><i class="fa fa-download"></i></a> ><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="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="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> <button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>"><i class="fa fa-trash"></i></button>

View File

@ -79,11 +79,14 @@ getBodyTopHTML();
data-cookie-id-table="zmEventsTable" data-cookie-id-table="zmEventsTable"
data-cookie-expire="2y" data-cookie-expire="2y"
data-click-to-select="true" data-click-to-select="true"
data-remember-order="true" data-remember-order="false"
data-show-columns="true" data-show-columns="true"
data-show-export="true" data-show-export="true"
data-uncheckAll="true" data-uncheckAll="true"
data-toolbar="#toolbar" 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-show-fullscreen="true"
data-click-to-select="true" data-click-to-select="true"
data-maintain-meta-data="true" data-maintain-meta-data="true"

View File

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

View File

@ -18,30 +18,28 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// //
if ( !canView('Events') ) {
$view = 'error';
return;
}
require_once('includes/Frame.php'); require_once('includes/Frame.php');
$eid = validInt($_REQUEST['eid']); $eid = validInt($_REQUEST['eid']);
$fid = empty($_REQUEST['fid']) ? 0 : validInt($_REQUEST['fid']); $fid = empty($_REQUEST['fid']) ? 0 : validInt($_REQUEST['fid']);
$Event = new ZM\Event($eid); $Event = new ZM\Event($eid);
if (!$Event->canView()) {
$view = 'error';
return;
}
$Monitor = $Event->Monitor(); $Monitor = $Event->Monitor();
# This is kinda weird.. so if we pass fid=0 or some other non-integer, then it loads max score # This is kinda weird.. so if we pass fid=0 or some other non-integer, then it loads max score
# perhaps we should consider being explicit, like fid = maxscore # perhaps we should consider being explicit, like fid = maxscore
if ( !empty($fid) ) { if (!empty($fid)) {
$sql = 'SELECT * FROM Frames WHERE EventId = ? AND FrameId = ?'; $sql = 'SELECT * FROM Frames WHERE EventId=? AND FrameId=?';
if ( !($frame = dbFetchOne($sql, NULL, array($eid, $fid))) ) if (!($frame = dbFetchOne($sql, NULL, array($eid, $fid))))
$frame = array('EventId'=>$eid, 'FrameId'=>$fid, 'Type'=>'Normal', 'Score'=>0); $frame = array('EventId'=>$eid, 'FrameId'=>$fid, 'Type'=>'Normal', 'Score'=>0);
} else { } else {
$frame = dbFetchOne('SELECT * FROM Frames WHERE EventId = ? AND Score = ?', NULL, array($eid, $Event->MaxScore())); $frame = dbFetchOne('SELECT * FROM Frames WHERE EventId=? AND Score=?', NULL, array($eid, $Event->MaxScore()));
} }
$Frame = new ZM\Frame($frame); $Frame = new ZM\Frame($frame);
$maxFid = $Event->Frames(); $maxFid = $Event->Frames();
$firstFid = 1; $firstFid = 1;
@ -51,11 +49,11 @@ $lastFid = $maxFid;
$alarmFrame = ( $Frame->Type() == 'Alarm' ) ? 1 : 0; $alarmFrame = ( $Frame->Type() == 'Alarm' ) ? 1 : 0;
if ( isset($_REQUEST['scale']) ) { if (isset($_REQUEST['scale'])) {
$scale = validNum($_REQUEST['scale']); $scale = validNum($_REQUEST['scale']);
} else if ( isset($_COOKIE['zmWatchScale'.$Monitor->Id()]) ) { } else if (isset($_COOKIE['zmWatchScale'.$Monitor->Id()])) {
$scale = validNum($_COOKIE['zmWatchScale'.$Monitor->Id()]); $scale = validNum($_COOKIE['zmWatchScale'.$Monitor->Id()]);
} else if ( isset($_COOKIE['zmWatchScale']) ) { } else if (isset($_COOKIE['zmWatchScale'])) {
$scale = validNum($_COOKIE['zmWatchScale']); $scale = validNum($_COOKIE['zmWatchScale']);
} else { } else {
$scale = max(reScale(SCALE_BASE, $Monitor->DefaultScale(), ZM_WEB_DEFAULT_SCALE), SCALE_BASE); $scale = max(reScale(SCALE_BASE, $Monitor->DefaultScale(), ZM_WEB_DEFAULT_SCALE), SCALE_BASE);
@ -63,7 +61,7 @@ if ( isset($_REQUEST['scale']) ) {
$scale = $scale ? $scale : 0; $scale = $scale ? $scale : 0;
$imageData = $Event->getImageSrc($frame, $scale, 0); $imageData = $Event->getImageSrc($frame, $scale, 0);
if ( !$imageData ) { if (!$imageData) {
ZM\Error("No data found for Event $eid frame $fid"); ZM\Error("No data found for Event $eid frame $fid");
$imageData = array(); $imageData = array();
} }
@ -92,42 +90,44 @@ xhtmlHeaders(__FILE__, translate('Frame').' - '.$Event->Id().' - '.$Frame->Frame
<div id="page p-0"> <div id="page p-0">
<div class="d-flex flex-row justify-content-between px-3 pt-1"> <div class="d-flex flex-row justify-content-between px-3 pt-1">
<div id="toolbar" > <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 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 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="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 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 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="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> </div>
<h2><?php echo translate('Frame') ?> <?php echo $Event->Id().'-'.$Frame->FrameId().' ('.$Frame->Score().')' ?></h2> <h2><?php echo translate('Frame') ?> <?php echo $Event->Id().'-'.$Frame->FrameId().' ('.$Frame->Score().')' ?></h2>
<form> <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_width" id="base_width" value="<?php echo $Event->Width(); ?>"/>
<input type="hidden" name="base_height" id="base_height" value="<?php echo $Event->Height(); ?>"/> <input type="hidden" name="base_height" id="base_height" value="<?php echo $Event->Height(); ?>"/>
</form> </form>
</div> </div>
<div id="content" class="d-flex flex-row justify-content-center"> <div id="content" class="d-flex flex-row justify-content-center">
<table id="frameStatsTable" class="table-sm table-borderless pr-3"> <table id="frameStatsTable" class="table-sm table-borderless pr-3">
<!-- FRAME STATISTICS POPULATED BY AJAX --> <!-- FRAME STATISTICS POPULATED BY AJAX -->
</table> </table>
<div> <div>
<p id="image"> <p id="image">
<?php if ( $imageData['hasAnalImage'] ) { <?php
if ( $imageData['hasAnalImage'] ) {
echo sprintf('<a href="?view=frame&amp;eid=%d&amp;fid=%d&scale=%d&amp;show=%s">', $Event->Id(), $Frame->FrameId(), $scale, ( $show=='anal'?'capt':'anal' ) ); echo sprintf('<a href="?view=frame&amp;eid=%d&amp;fid=%d&scale=%d&amp;show=%s">', $Event->Id(), $Frame->FrameId(), $scale, ( $show=='anal'?'capt':'anal' ) );
} ?> }
?>
<img id="frameImg" <img id="frameImg"
src="<?php echo validHtmlStr($Frame->getImageSrc($show=='anal'?'analyse':'capture')) ?>" src="<?php echo validHtmlStr($Frame->getImageSrc($show=='anal'?'analyse':'capture')) ?>"
width="<?php echo reScale($Event->Width(), $Monitor->DefaultScale(), $scale) ?>" width="<?php echo reScale($Event->Width(), $Monitor->DefaultScale(), $scale) ?>"
height="<?php echo reScale( $Event->Height(), $Monitor->DefaultScale(), $scale ) ?>" height="<?php echo reScale($Event->Height(), $Monitor->DefaultScale(), $scale) ?>"
alt="<?php echo $Frame->EventId().'-'.$Frame->FrameId() ?>" alt="<?php echo $Frame->EventId().'-'.$Frame->FrameId() ?>"
class="<?php echo $imageData['imageClass'] ?>" class="<?php echo $imageData['imageClass'] ?>"
/> />
<?php if ( $imageData['hasAnalImage'] ) { ?></a><?php } ?> <?php
if ($imageData['hasAnalImage']) { ?></a><?php } ?>
</p> </p>
<?php <?php
$frame_url_base = '?view=frame&amp;eid='.$Event->Id().'&amp;scale='.$scale.'&amp;show='.$show.'&amp;fid='; $frame_url_base = '?view=frame&amp;eid='.$Event->Id().'&amp;scale='.$scale.'&amp;show='.$show.'&amp;fid=';
@ -139,7 +139,7 @@ xhtmlHeaders(__FILE__, translate('Frame').' - '.$Event->Id().' - '.$Frame->Frame
<a id="lastLink" <?php echo ( $Frame->FrameId() < $maxFid ) ? 'href="'.$frame_url_base.$lastFid .'" class="btn-primary"' : 'class="btn-primary disabled"' ?>><?php echo translate('Last') ?></a> <a id="lastLink" <?php echo ( $Frame->FrameId() < $maxFid ) ? 'href="'.$frame_url_base.$lastFid .'" class="btn-primary"' : 'class="btn-primary disabled"' ?>><?php echo translate('Last') ?></a>
</p> </p>
<?php <?php
if ( file_exists($dImagePath) ) { if (file_exists($dImagePath)) {
?> ?>
<p id="diagImagePath"><?php echo $dImagePath ?></p> <p id="diagImagePath"><?php echo $dImagePath ?></p>
<p id="diagImage"> <p id="diagImage">
@ -152,7 +152,7 @@ if ( file_exists($dImagePath) ) {
</p> </p>
<?php <?php
} }
if ( file_exists($rImagePath) ) { if (file_exists($rImagePath)) {
?> ?>
<p id="refImagePath"><?php echo $rImagePath ?></p> <p id="refImagePath"><?php echo $rImagePath ?></p>
<p id="refImage"> <p id="refImage">

View File

@ -34,7 +34,7 @@ function streamReq(data) {
data.view = 'request'; data.view = 'request';
data.request = 'stream'; data.request = 'stream';
$j.getJSON(thisUrl, data) $j.getJSON(monitorUrl, data)
.done(getCmdResponse) .done(getCmdResponse)
.fail(logAjaxFail); .fail(logAjaxFail);
} }
@ -300,7 +300,7 @@ function getCmdResponse(respObj, respText) {
if (streamStatus.auth) { if (streamStatus.auth) {
// Try to reload the image stream. // Try to reload the image stream.
var streamImg = $j('#evtStream'); var streamImg = document.getElementById('evtStream');
if (streamImg) { if (streamImg) {
streamImg.src = streamImg.src.replace(/auth=\w+/i, 'auth='+streamStatus.auth); streamImg.src = streamImg.src.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
} }
@ -708,7 +708,7 @@ function renameEvent() {
} }
function exportEvent() { function exportEvent() {
window.location.assign('?view=export&eid='+eventData.Id); window.location.assign('?view=export&eids[]='+eventData.Id);
} }
function showEventFrames() { function showEventFrames() {
@ -767,14 +767,15 @@ function handleClick(event) {
// Manage the DELETE CONFIRMATION modal button // Manage the DELETE CONFIRMATION modal button
function manageDelConfirmModalBtns() { function manageDelConfirmModalBtns() {
document.getElementById("delConfirmBtn").addEventListener("click", function onDelConfirmClick(evt) { document.getElementById("delConfirmBtn").addEventListener("click", function onDelConfirmClick(evt) {
if ( !canEdit.Events ) { if (!canEdit.Events) {
enoperm(); enoperm();
return; return;
} }
evt.preventDefault(); 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) { .done(function(data) {
$j('#deleteConfirm').modal('hide');
streamNext(true); streamNext(true);
}) })
.fail(logAjaxFail); .fail(logAjaxFail);
@ -1015,7 +1016,13 @@ function initPage() {
// Manage the EXPORT button // Manage the EXPORT button
bindButton('#exportBtn', 'click', null, function onExportClick(evt) { bindButton('#exportBtn', 'click', null, function onExportClick(evt) {
evt.preventDefault(); 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 // Manage the Event STATISTICS Button

View File

@ -34,54 +34,53 @@ function startDownload(file) {
} }
function exportProgress() { function exportProgress() {
if ( exportTimer ) { if (exportTimer) {
var tickerText = $j('#exportProgressTicker').text(); var tickerText = $j('#exportProgressTicker').text();
if ( tickerText.length < 1 || tickerText.length > 4 ) { if ( tickerText.length < 1 || tickerText.length > 4 ) {
$j('#exportProgressTicker').text('.'); $j('#exportProgressTicker').text('.');
} else { } else {
$j('#exportProgressTicker').append('.'); $j('#exportProgressTicker').append('.');
} }
} else {
console.log("No timer");
} }
} }
function exportResponse(respObj, respText) { function exportResponse(respObj, respText) {
clearInterval(exportTimer); clearInterval(exportTimer);
if ( respObj.result != 'Ok' ) { if (respObj.result != 'Ok') {
$j('#exportProgressTicker').text(respObj.message); $j('#exportProgressTicker').text(respObj.message);
} else { } else {
$j('#exportProgressTicker').text(exportSucceededString); $j('#exportProgressTicker').text(exportSucceededString);
setTimeout(startDownload, 1500, decodeURIComponent(respObj.exportFile)); setTimeout(startDownload, 1500, decodeURIComponent(respObj.exportFile));
} }
return; 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( ) { function exportEvents( ) {
var formData = $j('#contentForm').serialize(); var formData = $j('#contentForm').serialize();
$j.ajaxSetup({
timeout: 0
});
$j.getJSON(thisUrl + '?view=event&request=event&action=export', formData) $j.getJSON(thisUrl + '?view=event&request=event&action=export', formData)
.done(exportResponse) .done(exportResponse)
.fail(logAjaxFail); .fail(exportFail);
$j('#exportProgress').removeClass('hidden'); $j('#exportProgress').removeClass('hidden');
$j('#exportProgress').addClass('warnText'); $j('#exportProgress').addClass('warnText');
$j('#exportProgress').text(exportProgressString); $j('#exportProgressText').text(exportProgressString);
//exportProgress();
exportTimer = setInterval(exportProgress, 500); exportTimer = setInterval(exportProgress, 500);
} }
function exportFail() {
clearInterval(exportTimer);
$j('#exportProgress').addClass('errorText');
$j('#exportProgressTicker').text('Failed export');
logAjaxFail();
}
function getEventDetailModal(eid) { function getEventDetailModal(eid) {
$j.getJSON(thisUrl + '?request=modal&modal=eventdetail&eids[]=' + eid) $j.getJSON(thisUrl + '?request=modal&modal=eventdetail&eids[]=' + eid)
.done(function(data) { .done(function(data) {

View File

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

View File

@ -12,10 +12,10 @@ function changeScale() {
last: $j('#lastLink') last: $j('#lastLink')
}; };
if ( img ) { if (img) {
var baseWidth = $j('#base_width').val(); var baseWidth = $j('#base_width').val();
var baseHeight = $j('#base_height').val(); var baseHeight = $j('#base_height').val();
if ( ! parseInt(scale) ) { if (!parseInt(scale)) {
var newSize = scaleToFit(baseWidth, baseHeight, img, $j('#controls')); var newSize = scaleToFit(baseWidth, baseHeight, img, $j('#controls'));
newWidth = newSize.width; newWidth = newSize.width;
newHeight = newSize.height; newHeight = newSize.height;
@ -30,7 +30,7 @@ function changeScale() {
} }
setCookie('zmWatchScale', scale, 3600); setCookie('zmWatchScale', scale, 3600);
$j.each(controlsLinks, function(k, anchor) { //Make frames respect scale choices $j.each(controlsLinks, function(k, anchor) { //Make frames respect scale choices
if ( anchor ) { if (anchor) {
anchor.prop('href', anchor.prop('href').replace(/scale=.*&/, 'scale=' + scale + '&')); anchor.prop('href', anchor.prop('href').replace(/scale=.*&/, 'scale=' + scale + '&'));
} }
}); });
@ -39,11 +39,11 @@ function changeScale() {
onStatsResize(newWidth); onStatsResize(newWidth);
} }
function getFrmStatsCookie() { function getFrameStatsCookie() {
var cookie = 'zmFrameStats'; var cookie = 'zmFrameStats';
var stats = getCookie(cookie); var stats = getCookie(cookie);
if ( !stats ) { if (!stats) {
stats = 'on'; stats = 'on';
setCookie(cookie, stats, 10*365); setCookie(cookie, stats, 10*365);
} }
@ -53,16 +53,19 @@ function getFrmStatsCookie() {
function getStat(params) { function getStat(params) {
$j.getJSON(thisUrl + '?view=request&request=stats&raw=true', params) $j.getJSON(thisUrl + '?view=request&request=stats&raw=true', params)
.done(function(data) { .done(function(data) {
var stat = data.raw; var stats = data.raw;
$j('#frameStatsTable').empty().append('<tbody>'); $j('#frameStatsTable').empty().append('<tbody>');
$j.each( statHeaderStrings, function( key ) { for (const stat of stats) {
$j.each(statHeaderStrings, function(key) {
var th = $j('<th>').addClass('text-right').text(statHeaderStrings[key]); var th = $j('<th>').addClass('text-right').text(statHeaderStrings[key]);
var tdString; var tdString;
switch (stat ? key : 'n/a') { switch (stat ? key : 'n/a') {
case 'FrameId': case 'FrameId':
tdString = '<a href="?view=stats&amp;eid=' + params.eid + '&amp;fid=' + params.fid + '">' + stat[key] + '</a>'; case 'EventId':
//tdString = '<a href="?view=stats&amp;eid=' + params.eid + '&amp;fid=' + params.fid + '">' + stat[key] + '</a>';
break; break;
case 'n/a': case 'n/a':
tdString = 'n/a'; tdString = 'n/a';
@ -76,6 +79,7 @@ function getStat(params) {
$j('#frameStatsTable tbody').append(row); $j('#frameStatsTable tbody').append(row);
}); });
} // end foreach stat
}) })
.fail(logAjaxFail); .fail(logAjaxFail);
} }
@ -85,16 +89,16 @@ function onStatsResize(vidwidth) {
var width = $j(window).width() - vidwidth; var width = $j(window).width() - vidwidth;
// Hide the stats table if we have run out of room to show it properly // Hide the stats table if we have run out of room to show it properly
if ( width < minWidth ) { if (width < minWidth) {
statsBtn.prop('disabled', true); statsBtn.prop('disabled', true);
if ( table.is(':visible') ) { if (table.is(':visible')) {
table.toggle(false); table.toggle(false);
wasHidden = true; wasHidden = true;
} }
// Show the stats table if we hid it previously and sufficient room becomes available // Show the stats table if we hid it previously and sufficient room becomes available
} else if ( width >= minWidth ) { } else if (width >= minWidth) {
statsBtn.prop('disabled', false); statsBtn.prop('disabled', false);
if ( !table.is(':visible') && wasHidden ) { if (!table.is(':visible') && wasHidden) {
table.toggle(true); table.toggle(true);
wasHidden = false; wasHidden = false;
} }
@ -102,7 +106,7 @@ function onStatsResize(vidwidth) {
} }
function initPage() { function initPage() {
if ( scale == '0' || scale == 'auto' ) changeScale(); if (scale == '0' || scale == 'auto') changeScale();
// Don't enable the back button if there is no previous zm page to go back to // Don't enable the back button if there is no previous zm page to go back to
backBtn.prop('disabled', !document.referrer.length); backBtn.prop('disabled', !document.referrer.length);
@ -125,7 +129,7 @@ function initPage() {
var cookie = 'zmFrameStats'; var cookie = 'zmFrameStats';
// Toggle the visiblity of the stats table and write an appropriate cookie // Toggle the visiblity of the stats table and write an appropriate cookie
if ( table.is(':visible') ) { if (table.is(':visible')) {
setCookie(cookie, 'off', 10*365); setCookie(cookie, 'off', 10*365);
table.toggle(false); table.toggle(false);
} else { } else {
@ -143,7 +147,7 @@ function initPage() {
// Load the frame stats // Load the frame stats
getStat({eid: eid, fid: fid}); getStat({eid: eid, fid: fid});
if ( getFrmStatsCookie() != 'on' ) { if (getFrameStatsCookie() != 'on') {
table.toggle(false); table.toggle(false);
} else { } else {
onStatsResize($j('#base_width').val() * scale / SCALE_BASE); onStatsResize($j('#base_width').val() * scale / SCALE_BASE);

View File

@ -98,6 +98,7 @@ function changeScale() {
var scale = $j('#scale').val(); var scale = $j('#scale').val();
var newWidth; var newWidth;
var newHeight; 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 // 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 // times and what the consequences would be
@ -118,6 +119,10 @@ function changeScale() {
var streamImg = $j('#liveStream'+monitorId); var streamImg = $j('#liveStream'+monitorId);
if (streamImg) { if (streamImg) {
var oldSrc = streamImg.attr('src'); 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)); var newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+((scale == 'auto' || scale == '0') ? autoScale : scale));
streamImg.width(newWidth); streamImg.width(newWidth);
@ -783,6 +788,25 @@ function getCtrlPresetModal() {
.fail(logAjaxFail); .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() { function getSettingsModal() {
$j.getJSON(monitorUrl + '?request=modal&modal=settings&mid=' + monitorId) $j.getJSON(monitorUrl + '?request=modal&modal=settings&mid=' + monitorId)
.done(function(data) { .done(function(data) {
@ -792,6 +816,10 @@ function getSettingsModal() {
evt.preventDefault(); evt.preventDefault();
$j('#settingsForm').submit(); $j('#settingsForm').submit();
}); });
$j('#newBrightness').change(changeControl);
$j('#newContrast').change(changeControl);
$j('#newHue').change(changeControl);
$j('#newColour').change(changeControl);
}) })
.fail(logAjaxFail); .fail(logAjaxFail);
} }
@ -917,7 +945,7 @@ function initPage() {
}); });
// Only enable the settings button for local cameras // 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 // Init the bootstrap-table
if (monitorType != 'WebSite') table.bootstrapTable({icons: icons}); if (monitorType != 'WebSite') table.bootstrapTable({icons: icons});

View File

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

View File

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