Merge branch 'h265' into zma_to_thread

This commit is contained in:
Isaac Connor 2018-11-20 12:38:17 -05:00
commit b16cd7ec84
86 changed files with 2806 additions and 1245 deletions

View File

@ -1,23 +1,43 @@
You should only file an issue if you found a bug. Feature and enhancement requests, general discussions and support questions should occur in one of the following areas:
**THIS FORUM IS FOR BUG REPORTS ONLY**
Do not post feature or enhancement requests, general discussions or support questions here.
Feature and enhancement requests, general discussions, and support questions should occur in one of the following areas:
- The [ZoneMinder-Chat Slack channel](https://zoneminder-chat.herokuapp.com/)
- The [ZoneMinder Forum](https://forums.zoneminder.com/)
**Do not post feature or enhancement requests, general discussions or support questions here.**
Docker related issues should be posted here: https://github.com/ZoneMinder/zmdockerfiles
Make sure you are running the latest version of ZoneMinder before reporting an issue.
In order to submit a bug report, please populate the fields below. This is required.
**ZoneMinder Version (`zmaudit.pl -v`):**
**Describe Your Environment**
- Version of ZoneMinder [release version, development version, or commit]
- How you installed ZoneMinder [e.g. PPA, RPMFusion, from-source, etc]
- Full name and version of OS
**Are you using a development snapshot / git checkout? If so, what is the latest commit? (`git rev-parse HEAD`):**
**If the issue concerns a camera**
- Make and Model
- frame rate
- resolution
- ZoneMinder Source Type:
**Linux Distribution and Version (`cat /etc/os-release` or `cat /etc/redhat-release`):**
**Describe the bug**
A clear and concise description of what the bug is.
**If the issue concerns a camera, provide the make, model, frame rate, resolution and ZoneMinder Source Type:**
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Relevant log lines:**
**Expected behavior**
A clear and concise description of what you expected to happen.
**Debug Logs**
```
log lines here
<insert debug logs here, please make sure they are within the ``` quotes so they are formatted properly>
```

21
.github/config.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# Configuration for welcome - https://github.com/behaviorbot/welcome
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Thanks for opening your first issue here! Just a reminder, this forum is for Bug Reports only. Be sure to follow the issue template!
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
#newPRWelcomeComment: >
# Thanks for opening this pull request! Please check out our contributing guidelines.
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
#firstPRMergeComment: >
# Congrats on merging your first pull request! We here at behaviorbot are proud of you!
# It is recommend to include as many gifs and emojis as possible

13
.github/no-response.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 7
# Label requiring a response
responseRequiredLabel: more-information-needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

View File

@ -38,6 +38,7 @@ env:
- OS=el DIST=7
- OS=fedora DIST=27 DOCKER_REPO=knnniggett/packpack
- OS=fedora DIST=28 DOCKER_REPO=knnniggett/packpack
- OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack
- OS=ubuntu DIST=trusty
- OS=ubuntu DIST=xenial
- OS=ubuntu DIST=trusty ARCH=i386

View File

@ -53,9 +53,9 @@ Lastly, if you desire to build a development snapshot from the master branch, it
Please visit our [ReadtheDocs site](https://zoneminder.readthedocs.org/en/stable/installationguide/index.html) for distro specific instructions.
### Package Maintainers
Many of the ZoneMinder configration variable default values are not configurable at build time through autotools or cmake. A new tool called *zmeditconfigdata.sh* has been added to allow package maintainers to manipulate any variable stored in ConfigData.pm without patching the source.
Many of the ZoneMinder configuration variable default values are not configurable at build time through autotools or cmake. A new tool called *zmeditconfigdata.sh* has been added to allow package maintainers to manipulate any variable stored in ConfigData.pm without patching the source.
For example, let's say I have created a new ZoneMinder package that contains the cambozola javascript file. However, by default cambozola support is turned off. To fix that, add this to the pacakging script:
For example, let's say I have created a new ZoneMinder package that contains the cambozola javascript file. However, by default cambozola support is turned off. To fix that, add this to the packaging script:
```bash
./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes
```

View File

@ -500,6 +500,7 @@ CREATE TABLE `Monitors` (
`DefaultView` enum('Events','Control') NOT NULL default 'Events',
`DefaultRate` smallint(5) unsigned NOT NULL default '100',
`DefaultScale` smallint(5) unsigned NOT NULL default '100',
`DefaultCodec` enum('auto','H264','H265','MJPEG') NOT NULL default 'auto',
`SignalCheckPoints` INT UNSIGNED NOT NULL default '0',
`SignalCheckColour` varchar(32) NOT NULL default '#0000BE',
`WebColour` varchar(32) NOT NULL default 'red',

View File

@ -15,11 +15,11 @@ endif((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx"))
# Configure the zoneminder service files
configure_file(systemd/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
configure_file(nginx/zoneminder.php-fpm.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf @ONLY)
configure_file(nginx/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.nginx.conf @ONLY)
if(ZM_WEB_USER STREQUAL "nginx")
configure_file(nginx/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
configure_file(nginx/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
configure_file(nginx/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
configure_file(nginx/zoneminder.php-fpm.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf @ONLY)
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README COPYONLY)
else(ZM_WEB_USER STREQUAL "nginx")
configure_file(systemd/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
@ -55,10 +55,8 @@ install(FILES misc/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INST
# Install zoneminder service files
install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
if(ZM_WEB_USER STREQUAL "nginx")
install(FILES zoneminder.php-fpm.conf DESTINATION /etc/php-fpm.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ RENAME zoneminder.conf)
endif(ZM_WEB_USER STREQUAL "nginx")
install(FILES zoneminder.php-fpm.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.nginx.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.tmpfiles DESTINATION /usr/lib/tmpfiles.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)

View File

@ -1,39 +1,34 @@
What's New
==========
1. This is an *experimental* build of zoneminder which uses the
nginx web server.
1. See the ZoneMinder release notes for a list of new features:
https://github.com/ZoneMinder/zoneminder/releases
2. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to
"/cgi-bin-zm/zms". This has been to done to avoid this bug:
https://bugzilla.redhat.com/show_bug.cgi?id=973067
2. The contents of the ZoneMinder Apache config file have changed. In
addition, this ZoneMinder package now requires you to manually symlink the
ZoneMinder Apache config file. See new install step 6 and upgrade step 3
below for details.
IMPORTANT: You must manually inspect the value for PATH_ZMS under Options
and verify it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result
in a broken system. You have been warned.
3. Due to the active state of the ZoneMinder project, we now recommend granting
ALL permission to the ZoneMinder mysql account. This change must be done
manually before ZoneMinder will run. See the installation steps below.
4. This package uses the HTTPS protocol by default to access the web portal.
Requests using HTTP will auto-redirect to HTTPS. See README.https for
more information.
5. This package ships with the new ZoneMinder API enabled.
3. This is an experimental build of ZoneMinder supporting nginx, rather than
apache web server.
4. If you have installed ZoneMinder from the FedBerry repositories, this build
of ZoneMinder has support for Raspberry Pi hardware acceleration when using
ffmpeg. Unforunately, there is a problem with the same hardware acceleration
when using libvlc. Consequently, libvlc support in this build of ZoneMinder
has been disabled until the problem is resolved. See the following bug
report for details: https://trac.videolan.org/vlc/ticket/18594
New installs
============
1. This package supports either community-mysql-server or mariadb-server with
mariadb being the preferred choice. Unless you are already using MariaDB or
Mysql server, you need to ensure that the server is configured to start
during boot and properly secured by running:
1. Unless you are already using MariaDB server, you need to ensure that the
server is configured to start during boot and properly secured by running:
sudo dnf install mariadb-server
sudo systemctl enable mariadb
sudo systemctl start mariadb.service
mysql_secure_installation
sudo dnf install mariadb-server
sudo systemctl enable mariadb
sudo systemctl start mariadb.service
mysql_secure_installation
2. Assuming the database is local and using the password for the root account
set during the previous step, you will need to create the ZoneMinder
@ -48,13 +43,17 @@ New installs
anything that suits your environment.
3. If you have chosen to change the zoneminder database account credentials to
something other than zmuser/zmpass, you must now edit /etc/zm/zm.conf.
Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous
step.
something other than zmuser/zmpass, you must now create a config file under
/etc/zm/conf.d and set your credentials there. For example, create the file
/etc/zm/conf.d/zm-db-user.conf and add the following content to it:
ZM_DB_USER = {username of the sql account you want to use}
ZM_DB_PASS = {password of the sql account you want to use}
This version of zoneminder no longer requires you to make a similar change
to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php
This now happens dynamically. Do *not* make any changes to this file.
Once the file has been saved, set proper file & ownership permissions on it:
sudo chown root:apache *.conf
sudo chmod 640 *.conf
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local
timezone. PHP will complain loudly if this is not set, or if it is set
@ -80,54 +79,87 @@ New installs
SELINUX line from "enforcing" to "disabled". This change will take
effect after a reboot.
6. This package comes preconfigured for HTTPS using the default self signed
certificate on your system. We recommend you keep this configuration.
6. Configure the web server
If this does not meet your needs, then read README.https to
learn about alternatives.
This package uses the HTTPS protocol by default to access the web portal,
using the default self signed certificate on your system. Requests using
HTTP will auto-redirect to HTTPS.
7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of
Inspect the web server configuration file and verify it meets your needs:
/etc/zm/www/zoneminder.conf
If you are running other web enabled services then you may need to edit
this file to suite. See README.https to learn about other alternatives.
When in doubt, proceed with the default:
sudo ln -s /etc/zm/www/zoneminder.conf /etc/nginx/default.d/
7. Fcgiwrap is required when using ZoneMinder with Nginx. At the time of this
writing, fcgiwrap is not yet available in the Fedora repos. Until it
becomes available, you may install it from my Copr repository:
https://copr.fedorainfracloud.org/coprs/kni/fcgiwrap/
Follow the intructions on that site to enable the repo. Once enabled,
install fcgiwrap:
sudo dnf install fcgiwrap
After fcgiwrap is installed, it must be configured. Edit
/etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of
simulatneous streams the server should support. Generally, a good minimum
value for this equals the total number of cameras you expect to view at the
same time.
8. Now start the web server:
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl start nginx
9. Now start zoneminder:
sudo systemctl enable zoneminder
sudo systemctl start zoneminder
sudo systemctl enable zoneminder
sudo systemctl start zoneminder
10.The Fedora repos have a ZoneMinder package available, but it does not
support ffmpeg or libvlc, which many modern IP cameras require. Most users
will want to prevent the ZoneMinder package in the Fedora repos from
overwriting the ZoneMinder package in zmrepo, during a future dnf update. To
prevent that from happening you must edit /etc/yum.repos.d/fedora.repo
and /etc/yum.repos.d/fedora-updates.repo. Add the line "exclude=zoneminder*"
without the quotes under the [fedora] and [fedora-updates] blocks,
respectively.
10. Optionally configure the firewall
All Redhat distros ship with the firewall enabled. That means you will not
be able to access the ZoneMinder web console from a remote machine until
changes are made to the firewall.
What follows are a set of minimal commands to allow remote access to the
ZoneMinder web console and also allow ZoneMinder's ONVIF discovery to
work. The following commands do not put any restrictions on which remote
machine(s) have access to the listed ports or services.
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --permanent --zone=public --add-port=3702/udp
sudo firewall-cmd --reload
Additional changes to the firewall may be required, depending on your
security requirements and how you use the system. It is up to you to verify
these commands are sufficient.
11. Access the ZoneMinder web console
You may now access the ZoneMinder web console from your web browser using
an appropriate url. Here are some examples:
http://localhost/zm (works from the local machine only)
http://{machine name}/zm (works only if dns is configured for your network)
http://{ip address}/zm
Upgrades
========
1. Verify /etc/zm/zm.conf.
If zm.conf was manually edited before running the upgrade, the installation
may not overwrite it. In this case, it will create the file
/etc/zm/zm.conf.rpmnew.
For example, this will happen if you are using database account credentials
other than zmuser/zmpass.
Compare /etc/zm/zm.conf to /etc/zm/zm.conf.rpmnew. Verify that zm.conf
contains any new config settings that may be in zm.conf.rpmnew.
This version of zoneminder no longer requires you to make a similar change
to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php
This now happens dynamically. Do *not* make any changes to this file.
1. Conf.d folder support has been added to ZoneMinder. Any custom
changes previously made to zm.conf must now be made in one or more custom
config files, created under the conf.d folder. Do this now. See
/etc/zm/conf.d/README for details. Once you recreate any custom config changes
under the conf.d folder, they will remain in place indefinitely.
2. Verify permissions of the zmuser account.
@ -139,12 +171,16 @@ Upgrades
See step 2 of the Installation section to add missing permissions.
3. Verify the ZoneMinder Apache configuration file in the folder
/etc/httpd/conf.d. You will have a file called "zoneminder.conf" and there
3. Verify the ZoneMinder Nginx configuration file in the folder
/etc/zm/www. You will have a file called "zoneminder.conf" and there
may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file
exists, inspect it and merge anything new in that file with zoneminder.conf.
Verify the SSL REquirements meet your needs. Read README.https if necessary.
The contents of this file must be merged into your Nginx configuration.
See step 6 of the installation section if you have not already done this
during a previous upgrade.
4. Upgrade the database before starting ZoneMinder.
Most upgrades can be performed by executing the following command:

View File

@ -22,6 +22,10 @@ location /cgi-bin-zm {
fastcgi_pass unix:/run/fcgiwrap.sock;
}
location /zm/cache {
alias "@ZM_CACHEDIR@";
}
location /zm {
gzip off;
alias "@ZM_WEBDIR@";

View File

@ -1,10 +1,14 @@
# Change the user and group of the default pool to the web server account
; This config file is needed when using ZoneMinder with web servers other
; than Apache. You can ignore this file if you are using Apache web server.
; Change the user and group of the default pool to the web server account
[www]
user = @WEB_USER@
group = @WEB_GROUP@
# Uncomment these on machines with little memory
#pm = ondemand
#pm.max_children = 10
#pm.process_idle_timeout = 10s
; These parameters are typically a tradoff between performance and memory
; consumption. See the contents of www.conf for details.
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s

View File

@ -1,5 +1,8 @@
D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@
D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@
D @ZM_CACHEDIR@ 0755 @WEB_USER@ @WEB_GROUP@
d @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@
D @ZM_DIR_IMAGES@ 0755 @WEB_USER@ @WEB_GROUP@
D /var/lib/php/session 770 root @WEB_GROUP@
D /var/lib/php/wsdlcache 770 root @WEB_GROUP@

View File

@ -2,7 +2,11 @@
missingok
notifempty
sharedscripts
delaycompress
compress
postrotate
@BINDIR@/zmpkg.pl logrot 2> /dev/null > /dev/null || :
@BINDIR@/zmpkg.pl logrot > /dev/null 2>/dev/null || true
endscript
daily
rotate 7
}

View File

@ -84,7 +84,6 @@ BuildRequires: libmp4v2-devel
BuildRequires: x264-devel
%{?with_nginx:Requires: nginx}
%{?with_nginx:Requires: fcgiwrap}
%{?with_nginx:Requires: php-fpm}
%{!?with_nginx:Requires: httpd}
%{!?with_nginx:Requires: php}
@ -131,7 +130,7 @@ designed to support as many cameras as you can attach to your computer without
too much degradation of performance.
%prep
%autosetup -p 1 -a 1 -n ZoneMinder-%{version}
%autosetup -p 1 -a 1
%{__rm} -rf ./web/api/app/Plugin/Crud
%{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud
@ -265,13 +264,10 @@ EOF
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/*.conf
%ghost %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/zmcustom.conf
%config(noreplace) %attr(644,root,root) /etc/zm/www/zoneminder.conf
%config(noreplace) %attr(644,root,root) %{_sysconfdir}/zm/www/zoneminder.conf
%config(noreplace) %{_sysconfdir}/zm/www/zoneminder.php-fpm.conf
%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder
%if 0%{?with_nginx}
%config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.conf
%endif
%{_tmpfilesdir}/zoneminder.conf
%{_unitdir}/zoneminder.service
%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy
@ -306,7 +302,7 @@ EOF
%{_libexecdir}/zoneminder/
%{_datadir}/zoneminder/
%{_datadir}/applications/*%{name}.desktop
%{_datadir}/applications/*zoneminder.desktop
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/events

View File

@ -5,7 +5,6 @@ This document will provide an overview of ZoneMinder's API. This is work in prog
Overview
^^^^^^^^
In an effort to further 'open up' ZoneMinder, an API was needed. This will
allow quick integration with and development of ZoneMinder.
@ -13,6 +12,78 @@ The API is built in CakePHP and lives under the ``/api`` directory. It
provides a RESTful service and supports CRUD (create, retrieve, update, delete)
functions for Monitors, Events, Frames, Zones and Config.
Streaming Interface
^^^^^^^^^^^^^^^^^^^
Developers working on their application often ask if there is an "API" to receive live streams, or recorded event streams.
It is possible to stream both live and recorded streams. This isn't strictly an "API" per-se (that is, it is not integrated
into the Cake PHP based API layer discussed here) and also why we've used the term "Interface" instead of an "API".
Live Streams
~~~~~~~~~~~~~~
What you need to know is that if you want to display "live streams", ZoneMinder sends you streaming JPEG images (MJPEG)
which can easily be rendered in a browser using an ``img src`` tag.
For example:
::
<img src="https://yourserver/zm/cgi-bin/nph-zms?scale=50&width=640p&height=480px&mode=jpeg&maxfps=5&buffer=1000&&monitor=1&auth=b54a589e09f330498f4ae2203&connkey=36139" />
will display a live feed from monitor id 1, scaled down by 50% in quality and resized to 640x480px.
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
* The "auth" token you see above is required if you use ZoneMinder authentication. To understand how to get the auth token, please read the "Login, Logout & API security" section below.
* The "connkey" parameter is essentially a random number which uniquely identifies a stream. If you don't specify a connkey, ZM will generate its own. It is recommended to generate a connkey because you can then use it to "control" the stream (pause/resume etc.)
* Instead of dealing with the "auth" token, you can also use ``&user=username&pass=password`` where "username" and "password" are your ZoneMinder username and password respectively. Note that this is not recommended because you are transmitting them in a URL and even if you use HTTPS, they may show up in web server logs.
PTZ on live streams
-------------------
PTZ commands are pretty cryptic in ZoneMinder. This is not meant to be an exhaustive guide, but just something to whet your appetite:
Lets assume you have a monitor, with ID=6. Let's further assume you want to pan it left.
You'd need to send a:
``POST`` command to ``https://yourserver/zm/index.php`` with the following data payload in the command (NOT in the URL)
``view=request&request=control&id=6&control=moveConLeft&xge=30&yge=30``
Obviously, if you are using authentication, you need to be logged in for this to work.
Like I said, at this stage, this is only meant to get you started. Explore the ZoneMinder code and use "Inspect source" as you use PTZ commands in the ZoneMinder source code.
`control_functions.php <https://github.com/ZoneMinder/zoneminder/blob/10531df54312f52f0f32adec3d4720c063897b62/web/skins/classic/includes/control_functions.php>`__ is a great place to start.
Pre-recorded (past event) streams
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Similar to live playback, if you have chosen to store events in JPEG mode, you can play it back using:
::
<img src="https://yourserver/zm/cgi-bin/nph-zms?mode=jpeg&frame=1&replay=none&source=event&event=293820&connkey=77493&auth=b54a58f5f4ae2203" />
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
* This will playback event 293820, starting from frame 1 as an MJPEG stream
* Like before, you can add more parameters like ``scale`` etc.
* auth and connkey have the same meaning as before, and yes, you can replace auth by ``&user=usename&pass=password`` as before and the same security concerns cited above apply.
If instead, you have chosen to use the MP4 (Video) storage mode for events, you can directly play back the saved video file:
::
<video src="https://yourserver/zm/index.php?view=view_video&eid=294690&auth=33f3d558af84cf08" type="video/mp4"></video>
* This will play back the video recording for event 294690
What other parameters are supported?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The best way to answer this question is to play with ZoneMinder console. Open a browser, play back live or recorded feed, and do an "Inspect Source" to see what parameters
are generated. Change and observe.
Enabling API
^^^^^^^^^^^^
A default ZoneMinder installs with APIs enabled. You can explictly enable/disable the APIs
@ -132,6 +203,22 @@ Return a list of all monitors
curl http://server/zm/api/monitors.json
It is worthwhile to note that starting ZM 1.32.3 and beyond, this API also returns a ``Monitor_Status`` object per monitor. It looks like this:
::
"Monitor_Status": {
"MonitorId": "2",
"Status": "Connected",
"CaptureFPS": "1.67",
"AnalysisFPS": "1.67",
"CaptureBandwidth": "52095"
}
If you don't see this in your API, you are running an older version of ZM. This gives you a very convenient way to check monitor status without calling the ``daemonCheck`` API described later.
Retrieve monitor 1
^^^^^^^^^^^^^^^^^^^
@ -148,6 +235,13 @@ This API changes monitor 1 to Modect and Enabled
::
curl -XPOST http://server/zm/api/monitors/1.json -d "Monitor[Function]=Modect&Monitor[Enabled]=1"
Get Daemon Status of Monitor 1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
::
curl http://server/zm/api/monitors/daemonStatus/id:1/daemon:zmc.json
Add a monitor
^^^^^^^^^^^^^^
@ -384,11 +478,13 @@ Create a Zone
&Zone[MaxBlobs]=\
&Zone[OverloadFrames]=0"
PTZ Control APIs
^^^^^^^^^^^^^^^^
PTZ Control Meta-Data APIs
^^^^^^^^^^^^^^^^^^^^^^^^^^^
PTZ controls associated with a monitor are stored in the Controls table and not the Monitors table inside ZM. What that means is when you get the details of a Monitor, you will only know if it is controllable (isControllable:true) and the control ID.
To be able to retrieve PTZ information related to that Control ID, you need to use the controls API
Note that these APIs only retrieve control data related to PTZ. They don't actually move the camera. See the "PTZ on live streams" section to move the camera.
This returns all the control definitions:
::
@ -406,7 +502,97 @@ ZM APIs have various APIs that help you in determining host (aka ZM) daemon stat
::
curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running
curl -XGET http://server/zm/api/host/getLoad.json # returns current load of ZM
curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is, space taken to store various event related information,images etc. per monitor)
# Note that ZM 1.32.3 onwards has the same information in Monitors.json which is more reliable and works for multi-server too.
curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running
# The API below uses "du" to calculate disk space. We no longer recommend you use it if you have many events. Use the Storage APIs instead, described later
curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is,space taken to store various event related information,images etc. per monitor)
Storage and Server APIs
^^^^^^^^^^^^^^^^^^^^^^^
ZoneMinder introduced many new options that allowed you to configure multiserver/multistorage configurations. While a part of this was available in previous versions, a lot of rework was done as part of ZM 1.31 and 1.32. As part of that work, a lot of new and useful APIs were added. Some of these are part of ZM 1.32 and others will be part of ZM 1.32.3 (of course, if you build from master, you can access them right away, or wait till a stable release is out.
This returns storage data for my single server install. If you are using multi-storage, you'll see many such "Storage" entries, one for each storage defined:
::
curl http://server/zm/api/storage.json
Returns:
::
{
"storage": [
{
"Storage": {
"Id": "0",
"Path": "\/var\/cache\/zoneminder\/events",
"Name": "Default",
"Type": "local",
"Url": null,
"DiskSpace": "364705447651",
"Scheme": "Medium",
"ServerId": null,
"DoDelete": true
}
}
]
}
"DiskSpace" is the disk used in bytes. While this doesn't return disk space data as rich as ``/host/getDiskPercent``, it is much more efficient.
Similarly,
::
curl http://server/zm/api/servers.json
Returns:
::
{
"servers": [
{
"Server": {
"Id": "1",
"Name": "server1",
"Hostname": "server1.mydomain.com",
"State_Id": null,
"Status": "Running",
"CpuLoad": "0.9",
"TotalMem": "6186237952",
"FreeMem": "156102656",
"TotalSwap": "536866816",
"FreeSwap": "525697024",
"zmstats": false,
"zmaudit": false,
"zmtrigger": false
}
}
]
}
This only works if you have a multiserver setup in place. If you don't it will return an empty array.
Further Reading
^^^^^^^^^^^^^^^^
As described earlier, treat this document as an "introduction" to the important parts of the API and streaming interfaces.
There are several details that haven't yet been documented. Till they are, here are some resources:
* zmNinja, the open source mobile app for ZoneMinder is 100% based on ZM APIs. Explore its `source code <https://github.com/pliablepixels/zmNinja>`__ to see how things work.
* Launch up ZM console in a browser, and do an "Inspect source". See how images are being rendered. Go to the networks tab of the inspect source console and look at network requests that are made when you pause/play/forward streams.
* If you still can't find an answer, post your question in the `forums <https://forums.zoneminder.com/index.php>`__ (not the github repo).

View File

@ -49,7 +49,7 @@ guide you with a quick search.
add-apt-repository ppa:iconnor/zoneminder-1.32
If you are on trusty, you may want to add both, as there are some packages for dependencies included in the old ppa.
If you are on Trusty or Xenial, you may want to add both, as there are some packages for dependencies included in the old ppa.
Update repo and upgrade.
@ -138,9 +138,9 @@ Set /etc/zm/zm.conf to root:www-data 740 and www-data access to content
::
a2enconf zoneminder
a2enmod cgi
a2enmod rewrite
a2enconf zoneminder
You may also want to enable to following modules to improve caching performance

View File

@ -164,6 +164,29 @@ Height (pixels)
Web Site Refresh
If the website in question has static content, optionally enter a time period in seconds for ZoneMinder to refresh the content.
Storage Tab
-----------
The storage section allows for each monitor to configure if and how video and audio are recorded.
Save JPEGs
Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows to view an event anytime while it is being recorded.
* Disabled video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all.
* Frames only video is recorded in individual JPEG frames.
* Analysis images only (if available) video is recorded in invidual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames.
* Frames + Analysis images (if available) video is recorded twice, once as normal individual JPEG frames and once in invidual JPEG frames with analysis information overlaid.
Video Writer
Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored.
* Disabled video is not recorded in video format. If this setting is selected, then "Save JPEGs" should be enabled otherwise there is no video recording at all.
* X264 Encode the video or picture frames received from the camera are transcoded into h264 and stored as a video. This option is useful if the camera cannot natively stream h264.
* H264 Camera Passthrough this option assumes that the camera is already sending an h264 stream. Video will be recorded as is, without any post-processing in zoneminder. Video characteristics such as bitrate, encoding mode, etc. should be set directly in the camera.
Recording Audio
Check the box labeled "Whether to store the audio stream when saving an event." in order to save audio (if available) when events are recorded.
Timestamp Tab
-------------

View File

@ -1,15 +1,14 @@
Introduction
============
Welcome to ZoneMinder, the all-in-one Linux GPL'd security camera solution.
Welcome to ZoneMinder, the all-in-one security camera solution for Linux with GPL License.
Most commercial "security systems" are designed as a monitoring system that also records. Recording quality can vary from bad to unusable, locating the relevant video can range from challenging to impractical, and exporting can often only be done with the manual present. ZoneMinder was designed primarily to record, and allow easy searches and exporting. Recordings are of the best possible quality, easy to filter and find, and simple to export using any system with a web browser. It also monitors.
Commercial "security systems" are often designed as a monitoring system with little attention to recording quality. In such a system, locating and exporting relevant video can be challenging and often requires extensive human intervention. ZoneMinder was designed to provide the best possible record quality while allowing easy searching, filtering and exporting of security footage.
ZoneMinder is designed around a series of independent components that only function when necessary limiting any wasted resource and maximising the efficiency of your machine. A fairly ancient Pentium II PC should be able to track one camera per device at up to 25 frames per second with this dropping by half approximately for each additional camera on the same device. Additional cameras on other devices do not interact so can maintain this frame rate. Even monitoring several cameras still will not overload the CPU as frame processing is designed to synchronise with capture and not stall it.
ZoneMinder is designed around a series of independent components that only function when necessary, limiting any wasted resource and maximising the efficiency of your machine. An outdated Pentium II PC can have multiple recording devices connected to it, and it is able to track one camera per device at up to 25 frames per second, which drops by approximately half for each additional camera on the same device. Additional cameras on devices that do not interact with other devices can maintain the 25 frame rate per second. Monitoring several cameras will not overload the CPU as frame processing is designed to synchronise with capture.
As well as being fast ZoneMinder is designed to be friendly and even more than that, actually useful. As well as the fast video interface core it also comes with a user friendly and comprehensive PHP based web interface allowing you to control and monitor your cameras from home, at work, on the road, or even a web enabled cell phone. It supports variable web capabilities based on available bandwidth. The web interface also allows you to view events that your cameras have captured and archive them or review them time and again, or delete the ones you no longer wish to keep. The web pages directly interact with the core daemons ensuring full co-operation at all times. ZoneMinder can even be installed as a system service ensuring it is right there if your computer has to reboot for any reason.
A fast video interface core, a user-friendly and comprehensive PHP based web interface allows ZoneMinder to be efficient, friendly and most importantly useful. You can control and monitor your cameras from home, at work, on the road, or a web-enabled cell phone. It supports variable web capabilities based on available bandwidth. The web interface also allows you to view events that your cameras have captured, which can be archived, reviewed or deleted. The web application directly interacts with the core daemons ensuring full co-operation at all times. ZoneMinder can also be installed as a system service to reboot a system remotely.
The core of ZoneMinder is the capture and analysis of images and there is a highly configurable set of parameters that allow you to ensure that you can eliminate false positives whilst ensuring that anything you don't want to miss will be captured and saved. ZoneMinder allows you to define a set of 'zones' for each camera of varying sensitivity and functionality. This allows you to eliminate regions that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones.
ZoneMinder is free, but if you do find it useful then please feel free to visit http://www.zoneminder.com/donate.html and help to fund future improvements to ZoneMinder.
The core of ZoneMinder is the capture and analysis of images and a highly configurable set of parameters that eliminate false positives whilst ensuring minimum loss of footage. For example, you can define a set of 'zones' for each camera of varying sensitivity and functionality. This eliminates zones that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones.
ZoneMinder is free under GPL License, but if you do find it useful, then please feel free to visit http://www.zoneminder.com/donate.html and help us fund our future improvements.

View File

@ -309,6 +309,8 @@ saving configuration is a convenient way to ensure that the configuration
held in the database corresponds with the most recent definitions and that
all components are using the same set of configuration.
=back
=head2 EXPORT
None by default.

View File

@ -16,7 +16,7 @@
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#

View File

@ -16,7 +16,7 @@
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#

View File

@ -41,17 +41,18 @@ our @ISA = qw(Exporter ZoneMinder::Base);
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = (
'functions' => [ qw(
functions => [ qw(
zmDbConnect
zmDbDisconnect
zmDbGetMonitors
zmDbGetMonitor
zmDbGetMonitorAndControl
zmDbDo
) ]
);
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
our @EXPORT = qw();
@ -66,8 +67,6 @@ our $VERSION = $ZoneMinder::Base::VERSION;
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Carp;
our $dbh = undef;
sub zmDbConnect {
@ -93,7 +92,7 @@ sub zmDbConnect {
my $sslOptions = '';
if ( $Config{ZM_DB_SSL_CA_CERT} ) {
$sslOptions = ';'.join(';',
$sslOptions = join(';','',
'mysql_ssl=1',
'mysql_ssl_ca_file='.$Config{ZM_DB_SSL_CA_CERT},
'mysql_ssl_client_key='.$Config{ZM_DB_SSL_CLIENT_KEY},
@ -102,8 +101,9 @@ sub zmDbConnect {
}
eval {
$dbh = DBI->connect( 'DBI:mysql:database='.$Config{ZM_DB_NAME}
.$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '')
$dbh = DBI->connect(
'DBI:mysql:database='.$Config{ZM_DB_NAME}
.$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '')
, $Config{ZM_DB_USER}
, $Config{ZM_DB_PASS}
);
@ -125,7 +125,7 @@ sub zmDbConnect {
sub zmDbDisconnect {
if ( defined( $dbh ) ) {
$dbh->disconnect();
$dbh->disconnect() or Error('Error disconnecting db? ' . $dbh->errstr());
$dbh = undef;
}
}
@ -141,7 +141,7 @@ sub zmDbGetMonitors {
zmDbConnect();
my $function = shift || DB_MON_ALL;
my $sql = "select * from Monitors";
my $sql = 'SELECT * FROM Monitors';
if ( $function ) {
if ( $function == DB_MON_CAPT ) {
@ -156,26 +156,38 @@ sub zmDbGetMonitors {
$sql .= " where Function = 'Nodect'";
}
}
my $sth = $dbh->prepare_cached( $sql )
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute()
or croak( "Can't execute '$sql': ".$sth->errstr() );
my $sth = $dbh->prepare_cached( $sql );
if ( ! $sth ) {
Error("Can't prepare '$sql': ".$dbh->errstr());
return undef;
}
my $res = $sth->execute();
if ( ! $res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
my @monitors;
while( my $monitor = $sth->fetchrow_hashref() ) {
push( @monitors, $monitor );
}
$sth->finish();
return( \@monitors );
return \@monitors;
}
sub zmSQLExecute {
my $sql = shift;
my $sth = $dbh->prepare_cached( $sql )
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( @_ )
or croak( "Can't execute '$sql': ".$sth->errstr() );
my $sth = $dbh->prepare_cached( $sql );
if ( ! $sth ) {
Error("Can't prepare '$sql': ".$dbh->errstr());
return undef;
}
my $res = $sth->execute( @_ );
if ( ! $res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
return 1;
}
@ -185,17 +197,22 @@ sub zmDbGetMonitor {
my $id = shift;
if ( !defined($id) ) {
croak("Undefined id in zmDbgetMonitor");
Error('Undefined id in zmDbgetMonitor');
return undef ;
}
my $sql = 'SELECT * FROM Monitors WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql)
or croak("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($id)
or croak("Can't execute '$sql': ".$sth->errstr());
my $sth = $dbh->prepare_cached($sql);
if ( !$sth ) {
Error("Can't prepare '$sql': ".$dbh->errstr());
return undef;
}
my $res = $sth->execute($id);
if ( $res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
my $monitor = $sth->fetchrow_hashref();
return $monitor;
}
@ -204,25 +221,28 @@ sub zmDbGetMonitorAndControl {
my $id = shift;
return( undef ) if ( !defined($id) );
return undef if !defined($id);
my $sql = "SELECT C.*,M.*,C.Protocol
my $sql = 'SELECT C.*,M.*,C.Protocol
FROM Monitors as M
INNER JOIN Controls as C on (M.ControlId = C.Id)
WHERE M.Id = ?"
WHERE M.Id = ?'
;
my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( $id )
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
my $sth = $dbh->prepare_cached($sql);
if ( !$sth ) {
Error("Can't prepare '$sql': ".$dbh->errstr());
return undef;
}
my $res = $sth->execute( $id );
if ( !$res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
my $monitor = $sth->fetchrow_hashref();
return( $monitor );
return $monitor;
}
sub start_transaction {
#my ( $caller, undef, $line ) = caller;
#$openprint::log->debug("Called start_transaction from $caller : $line");
my $d = shift;
$d = $dbh if ! $d;
my $ac = $d->{AutoCommit};
@ -231,20 +251,29 @@ sub start_transaction {
} # end sub start_transaction
sub end_transaction {
#my ( $caller, undef, $line ) = caller;
#$openprint::log->debug("Called end_transaction from $caller : $line");
my ( $d, $ac ) = @_;
if ( ! defined $ac ) {
Error("Undefined ac");
}
$d = $dbh if ! $d;
if ( $ac ) {
#$log->debug("Committing");
$d->commit();
} # end if
$d->{AutoCommit} = $ac;
} # end sub end_transaction
# Basic execution of $dbh->do but with some pretty logging of the sql on error.
# Returns 1 on success, 0 on error
sub zmDbDo {
my $sql = shift;
if ( ! $dbh->do($sql, undef, @_) ) {
$sql =~ s/\?/'%s'/;
Error(sprintf("Failed $sql :", @_).$dbh->errstr());
return 0;
}
return 1;
}
1;
__END__
@ -266,6 +295,7 @@ zmDbDisconnect
zmDbGetMonitors
zmDbGetMonitor
zmDbGetMonitorAndControl
zmDbDo
=head1 AUTHOR

View File

@ -70,6 +70,7 @@ $serial = $primary_key = 'Id';
Frames
AlarmFrames
DefaultVideo
SaveJPEGs
TotScore
AvgScore
MaxScore
@ -83,6 +84,7 @@ $serial = $primary_key = 'Id';
StateId
Orientation
DiskSpace
Scheme
);
use POSIX;
@ -168,6 +170,8 @@ sub Path {
sub Scheme {
my $self = shift;
$$self{Scheme} = shift if @_;
if ( ! $$self{Scheme} ) {
if ( $$self{RelativePath} ) {
if ( $$self{RelativePath} =~ /^\d+\/\d{4}\-\d{2}\-\d{2}\/\d+$/ ) {
@ -316,11 +320,11 @@ sub GenerateVideo {
my $file_size = 'S'.$size;
push( @file_parts, $file_size );
}
my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format";
my $video_file = join('-', $video_name, $file_parts[0], $file_parts[1] ).'.'.$format;
if ( $overwrite || !-s $video_file ) {
Info( "Creating video file $video_file for event $self->{Id}\n" );
Info("Creating video file $video_file for event $self->{Id}");
my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} );
my $frame_rate = sprintf('%.2f', $self->{Frames}/$self->{FullLength});
if ( $rate ) {
if ( $rate != 1.0 ) {
$frame_rate *= $rate;
@ -351,19 +355,19 @@ sub GenerateVideo {
.$Config{ZM_FFMPEG_OUTPUT_OPTIONS}
." '$video_file' > ffmpeg.log 2>&1"
;
Debug( $command."\n" );
Debug($command);
my $output = qx($command);
my $status = $? >> 8;
if ( $status ) {
Error( "Unable to generate video, check $event_path/ffmpeg.log for details");
Error("Unable to generate video, check $event_path/ffmpeg.log for details");
return;
}
Info( "Finished $video_file\n" );
Info("Finished $video_file");
return $event_path.'/'.$video_file;
} else {
Info( "Video file $video_file already exists for event $self->{Id}\n" );
Info("Video file $video_file already exists for event $self->{Id}");
return $event_path.'/'.$video_file;
}
return;
@ -371,57 +375,49 @@ sub GenerateVideo {
sub delete {
my $event = $_[0];
if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) {
my ( $caller, undef, $line ) = caller;
Warning("Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n");
return;
}
if ( ! -e $event->Storage()->Path() ) {
Warning("Not deleting event because storage path doesn't exist");
return;
}
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n");
$ZoneMinder::Database::dbh->ping();
$ZoneMinder::Database::dbh->begin_work();
#$event->lock_and_load();
my $in_zmaudit = ( $0 =~ 'zmaudit.pl$');
{
my $sql = 'DELETE FROM Frames WHERE EventId=?';
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
my $res = $sth->execute($event->{Id})
or Error( "Can't execute '$sql': ".$sth->errstr() );
$sth->finish();
if ( ! $in_zmaudit ) {
if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) {
# zmfilter shouldn't delete anything in an odd situation. zmaudit will though.
my ( $caller, undef, $line ) = caller;
Warning("$0 Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:".
(defined($event->{StartTime})?$event->{StartTime}:'undef')." from $caller:$line");
return;
}
if ( !($event->Storage()->Path() and -e $event->Storage()->Path()) ) {
Warning('Not deleting event because storage path doesn\'t exist');
return;
}
}
if ( $$event{Id} ) {
# Need to have an event Id if we are to delete from the db.
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}");
$ZoneMinder::Database::dbh->ping();
$ZoneMinder::Database::dbh->begin_work();
#$event->lock_and_load();
ZoneMinder::Database::zmDbDo('DELETE FROM Frames WHERE EventId=?', $$event{Id});
if ( $ZoneMinder::Database::dbh->errstr() ) {
$ZoneMinder::Database::dbh->commit();
return;
}
ZoneMinder::Database::zmDbDo('DELETE FROM Stats WHERE EventId=?', $$event{Id});
if ( $ZoneMinder::Database::dbh->errstr() ) {
$ZoneMinder::Database::dbh->commit();
return;
}
$sql = 'DELETE FROM Stats WHERE EventId=?';
$sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
$res = $sth->execute($event->{Id})
or Error("Can't execute '$sql': ".$sth->errstr());
$sth->finish();
if ( $ZoneMinder::Database::dbh->errstr() ) {
$ZoneMinder::Database::dbh->commit();
return;
}
# Do it individually to avoid locking up the table for new events
ZoneMinder::Database::zmDbDo('DELETE FROM Events WHERE Id=?', $$event{Id});
$ZoneMinder::Database::dbh->commit();
}
# Do it individually to avoid locking up the table for new events
{
my $sql = 'DELETE FROM Events WHERE Id=?';
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
my $res = $sth->execute($event->{Id})
or Error("Can't execute '$sql': ".$sth->errstr());
$sth->finish();
}
$ZoneMinder::Database::dbh->commit();
if ( (! $Config{ZM_OPT_FAST_DELETE}) and $event->Storage()->DoDelete() ) {
$event->delete_files( );
if ( ( $in_zmaudit or (!$Config{ZM_OPT_FAST_DELETE})) and $event->Storage()->DoDelete() ) {
$event->delete_files();
} else {
Debug('Not deleting event files from '.$event->Path().' for speed.');
}
@ -430,11 +426,11 @@ sub delete {
sub delete_files {
my $event = shift;
my $Storage = @_ ? $_[0] : new ZoneMinder::Storage( $$event{StorageId} );
my $Storage = @_ ? $_[0] : new ZoneMinder::Storage($$event{StorageId});
my $storage_path = $Storage->Path();
if ( ! $storage_path ) {
Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId} ");
Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}");
return;
}
@ -466,14 +462,14 @@ sub delete_files {
if ( $bucket->delete_key($event_path) ) {
$deleted = 1;
} else {
Error("Failed to delete from S3:".$s3->err . ": " . $s3->errstr);
Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr);
}
};
Error($@) if $@;
}
if ( ! $deleted ) {
if ( !$deleted ) {
my $command = "/bin/rm -rf $storage_path/$event_path";
ZoneMinder::General::executeShellCommand( $command );
ZoneMinder::General::executeShellCommand($command);
}
}
@ -482,7 +478,7 @@ sub delete_files {
Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path.");
if ( $link_path ) {
( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint
unlink($storage_path.'/'.$link_path) or Error( "Unable to unlink '$storage_path/$link_path': $!" );
unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!");
}
}
} # end sub delete_files
@ -502,7 +498,7 @@ sub check_for_in_filesystem {
if ( $path ) {
if ( -e $path ) {
my @files = glob "$path/*";
Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files");
Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . ' files');
return 1 if @files;
} else {
Warning("Path not found for Event $_[0]{Id} at $path");
@ -573,7 +569,7 @@ sub MoveTo {
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.';
}
$$self{Storage} = $NewStorage;
@ -617,11 +613,11 @@ Debug("Files to move @files");
Debug("Moving file $file to $NewPath");
my $size = -s $file;
if ( ! $size ) {
Info("Not moving file with 0 size");
Info('Not moving file with 0 size');
}
my $file_contents = File::Slurp::read_file($file);
if ( ! $file_contents ) {
die "Loaded empty file, but it had a size. Giving up";
die 'Loaded empty file, but it had a size. Giving up';
}
my $filename = $event_path.'/'.File::Basename::basename($file);
@ -629,7 +625,7 @@ Debug("Files to move @files");
die "Unable to add key for $filename";
}
my $duration = time - $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');
} # end foreach file.
$moved = 1;

View File

@ -1,27 +1,3 @@
# ==========================================================================
#
# ZoneMinder General Utility Module, $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.
#
# ==========================================================================
#
# This module contains the common definitions and functions used by the rest
# of the ZoneMinder scripts
#
package ZoneMinder::General;
use 5.006;
@ -42,7 +18,7 @@ our @ISA = qw(Exporter ZoneMinder::Base);
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = (
'functions' => [ qw(
functions => [ qw(
executeShellCommand
getCmdFormat
runCommand
@ -56,7 +32,7 @@ our %EXPORT_TAGS = (
);
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
our @EXPORT = qw();
@ -80,74 +56,74 @@ sub executeShellCommand {
my $output = qx( $command );
my $status = $? >> 8;
if ( $status || logDebugging() ) {
Debug( "Command: $command\n" );
Debug("Command: $command");
chomp( $output );
Debug( "Output: $output\n" );
Debug("Output: $output");
}
return( $status );
return $status;
}
sub getCmdFormat {
Debug( "Testing valid shell syntax\n" );
Debug("Testing valid shell syntax");
my ( $name ) = getpwuid( $> );
if ( $name eq $Config{ZM_WEB_USER} ) {
Debug( "Running as '$name', su commands not needed\n" );
return( "" );
Debug("Running as '$name', su commands not needed");
return '';
}
my $null_command = "true";
my $null_command = 'true';
my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." ";
my $suffix = "";
my $prefix = 'sudo -u '.$Config{ZM_WEB_USER}.' ';
my $suffix = '';
my $command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" );
Debug("Testing \"$command\"");
my $output = qx($command 2>&1);
my $status = $? >> 8;
$output //= $!;
if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
Debug("Test ok, using format \"$prefix<command>$suffix\"");
return( $prefix, $suffix );
} else {
chomp( $output );
Debug( "Test failed, '$output'\n" );
Debug("Test failed, '$output'");
$prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='";
$suffix = "'";
$prefix = 'su '.$Config{ZM_WEB_USER}.q` --shell=/bin/sh --command='`;
$suffix = q`'`;
$command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" );
Debug("Testing \"$command\"");
my $output = qx($command 2>&1);
my $status = $? >> 8;
$output //= $!;
if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
Debug("Test ok, using format \"$prefix<command>$suffix\"");
return( $prefix, $suffix );
} else {
chomp( $output );
Debug( "Test failed, '$output'\n" );
chomp($output);
Debug("Test failed, '$output'");
$prefix = "su ".$Config{ZM_WEB_USER}." -c '";
$suffix = "'";
$command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" );
Debug("Testing \"$command\"");
$output = qx($command 2>&1);
$status = $? >> 8;
$output //= $!;
if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
Debug("Test ok, using format \"$prefix<command>$suffix\"");
return( $prefix, $suffix );
} else {
chomp( $output );
Debug( "Test failed, '$output'\n" );
chomp($output);
Debug("Test failed, '$output'");
}
}
}
Error( "Unable to find valid 'su' syntax\n" );
exit( -1 );
}
Error("Unable to find valid 'su' syntax");
exit -1;
} # end sub getCmdFormat
our $testedShellSyntax = 0;
our ( $cmdPrefix, $cmdSuffix );
@ -161,23 +137,23 @@ sub runCommand {
}
my $command = shift;
$command = $Config{ZM_PATH_BIN}."/".$command;
$command = $Config{ZM_PATH_BIN}.'/'.$command;
if ( $cmdPrefix ) {
$command = $cmdPrefix.$command.$cmdSuffix;
}
Debug( "Command: $command\n" );
Debug("Command: $command");
my $output = qx($command);
my $status = $? >> 8;
chomp( $output );
chomp($output);
if ( $status || logDebugging() ) {
if ( $status ) {
Error( "Unable to run \"$command\", output is \"$output\", status is $status\n" );
Error("Unable to run \"$command\", output is \"$output\", status is $status");
} else {
Debug( "Output: $output\n" );
Debug("Output: $output");
}
}
return( $output );
}
return $output;
} # end sub runCommand
sub createEventPath {
my $event = shift;
@ -210,7 +186,7 @@ sub _checkProcessOwner {
$_setFileOwner = 0;
}
}
return( $_setFileOwner );
return $_setFileOwner;
}
sub setFileOwner {
@ -219,7 +195,7 @@ sub setFileOwner {
if ( _checkProcessOwner() ) {
chown( $_ownerUid, $_ownerGid, $file )
or Fatal( "Can't change ownership of file '$file' to '"
.$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!"
.$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!"
);
}
}
@ -234,13 +210,13 @@ sub _checkForImageInfo {
};
$_hasImageInfo = $@?0:1;
}
return( $_hasImageInfo );
return $_hasImageInfo;
}
sub createEvent {
my $event = shift;
Debug( "Creating event" );
Debug('Creating event');
#print( Dumper( $event )."\n" );
_checkForImageInfo();
@ -561,38 +537,33 @@ __END__
=head1 NAME
ZoneMinder::Database - Perl extension for blah blah blah
ZoneMinder::General - Utility Functions for ZoneMinder
=head1 SYNOPSIS
use ZoneMinder::Database;
use ZoneMinder::General;
blah blah blah
=head1 DESCRIPTION
Stub documentation for ZoneMinder, created by h2xs. It looks like the
author of the extension was negligent enough to leave the stub
unedited.
Blah blah blah.
This module contains the common definitions and functions used by the rest
of the ZoneMinder scripts
=head2 EXPORT
None by default.
functions => [ qw(
executeShellCommand
getCmdFormat
runCommand
setFileOwner
createEventPath
createEvent
makePath
jsonEncode
jsonDecode
) ]
=head1 SEE ALSO
Mention other useful documentation such as the documentation of
related modules or operating system documentation (such as man pages
in UNIX), or any relevant external documentation such as RFCs or
standards.
If you have a mailing list set up for your module, mention it here.
If you have a web site set up for your module, mention it here.
=head1 AUTHOR
Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
@ -601,9 +572,18 @@ Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
Copyright (C) 2001-2008 Philip Coombes
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.
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.
=cut

View File

@ -441,11 +441,11 @@ sub databaseLevel {
$databaseLevel = $this->limit($databaseLevel);
if ( $this->{databaseLevel} != $databaseLevel ) {
if ( ( $databaseLevel > NOLOG ) and ( $this->{databaseLevel} <= NOLOG ) ) {
if ( !$this->{dbh} ) {
$this->{dbh} = ZoneMinder::Database::zmDbConnect();
if ( ! ( $ZoneMinder::Database::dbh or ZoneMinder::Database::zmDbConnect() ) ) {
Warning("Failed connecting to db. Not using database logging.");
$this->{databaseLevel} = NOLOG;
return NOLOG;
}
} elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) {
undef($this->{dbh});
}
$this->{databaseLevel} = $databaseLevel;
}
@ -558,12 +558,12 @@ sub logPrint {
}
if ( $level <= $this->{databaseLevel} ) {
if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) {
if ( ! ( $ZoneMinder::Database::dbh and $ZoneMinder::Database::dbh->ping() ) ) {
$this->{sth} = undef;
# Turn this off because zDbConnect will do logging calls.
my $oldlevel = $this->{databaseLevel};
$this->{databaseLevel} = NOLOG;
if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) {
if ( ! ZoneMinder::Database::zmDbConnect() ) {
#print(STDERR "Can't log to database: ");
return;
}
@ -571,10 +571,10 @@ sub logPrint {
}
my $sql = 'INSERT INTO Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, NULL )';
$this->{sth} = $this->{dbh}->prepare_cached($sql) if ! $this->{sth};
$this->{sth} = $ZoneMinder::Database::dbh->prepare_cached($sql) if ! $this->{sth};
if ( !$this->{sth} ) {
$this->{databaseLevel} = NOLOG;
Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr());
Error("Can't prepare log entry '$sql': ".$ZoneMinder::Database::dbh->errstr());
return;
}
@ -590,7 +590,7 @@ sub logPrint {
);
if ( !$res ) {
$this->{databaseLevel} = NOLOG;
Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr());
Error("Can't execute log entry '$sql': ".$ZoneMinder::Database::dbh->errstr());
}
} # end if doing db logging
} # end if level < effectivelevel

View File

@ -161,7 +161,6 @@ our $mem_data = {
format => { type=>'uint8', seq=>$mem_seq++ },
imagesize => { type=>'uint32', seq=>$mem_seq++ },
epadding1 => { type=>'uint32', seq=>$mem_seq++ },
epadding2 => { type=>'uint32', seq=>$mem_seq++ },
startup_time => { type=>'time_t64', seq=>$mem_seq++ },
last_write_time => { type=>'time_t64', seq=>$mem_seq++ },
last_read_time => { type=>'time_t64', seq=>$mem_seq++ },

View File

@ -456,7 +456,7 @@ sub transform {
sub to_string {
my $type = ref($_[0]);
my $fields = eval '\%'.$type.'::fields';
return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } keys %$fields );
return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } sort { $a cmp $b } keys %$fields );
}
1;

View File

@ -15,7 +15,7 @@
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#

View File

@ -384,7 +384,7 @@ MAIN: while( $loop ) {
Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir");
{
my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*");
Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . " entries." );
Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' );
foreach my $event_dir ( @event_dirs ) {
if ( ! -d $event_dir ) {
Debug( "$event_dir is not a dir. Skipping" );
@ -403,6 +403,7 @@ MAIN: while( $loop ) {
$$Event{RelativePath} = $event_dir;
$Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() );
$Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($$Event{Path})) ) );
} # end foreach event
}
@ -428,7 +429,7 @@ MAIN: while( $loop ) {
} # end foreach event
chdir( $Storage->Path() );
} # if USE_DEEP_STORAGE
Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir\n" );
Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" );
delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir);
} # end foreach monitor
@ -446,7 +447,7 @@ MAIN: while( $loop ) {
next;
}
my @event_ids = keys %$fs_events;
Debug("Have " .scalar @event_ids . " events for monitor $monitor_id");
Debug('Have ' .scalar @event_ids . " events for monitor $monitor_id");
foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) {
@ -490,7 +491,11 @@ MAIN: while( $loop ) {
}
}
} # end foreach Storage Area
redo MAIN if ( $cleaned );
if ( $cleaned ) {
Debug("Events were deleted, starting again.");
redo MAIN;
}
$cleaned = 0;
my $deleteMonitorSql = 'DELETE LOW_PRIORITY FROM Monitors WHERE Id = ?';
@ -508,68 +513,88 @@ MAIN: while( $loop ) {
# Foreach database monitor and it's list of events.
while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) {
Debug("Checking db events for monitor $db_monitor");
if ( ! $db_events ) {
Debug("Skipping db events for $db_monitor because there are none");
next;
}
# If we found the monitor in the file system
if ( my $fs_events = $fs_monitors->{$db_monitor} ) {
next if ! $db_events;
my $fs_events = $fs_monitors->{$db_monitor};
while ( my ( $db_event, $age ) = each( %$db_events ) ) {
if ( ! defined( $fs_events->{$db_event} ) ) {
my $Event = ZoneMinder::Event->find_one( Id=>$db_event );
if ( ! $Event ) {
Debug("Event $db_event is no longer in db. Filter probably deleted it while we were auditing.");
next;
while ( my ( $db_event, $age ) = each( %$db_events ) ) {
if ( ! ($fs_events and defined( $fs_events->{$db_event} ) ) ) {
Debug("Don't have an fs event for $db_event");
my $Event = ZoneMinder::Event->find_one( Id=>$db_event );
if ( ! $Event ) {
Debug("Event $db_event is no longer in db. Filter probably deleted it while we were auditing.");
next;
}
Debug("Event $db_event is not in fs. Should have been at ".$Event->Path());
if ( $Event->Archived() ) {
Warning("Event $$Event{Id} is Archived. Taking no further action on it.");
next;
}
if ( ! $Event->StartTime() ) {
Info("Event $$Event{Id} has no start time.");
if ( confirm() ) {
$Event->delete();
$cleaned = 1;
}
Debug("Event $db_event is not in fs. Should have been at ".$Event->Path());
if ( $Event->Archived() ) {
Warning("Event $$Event{Id} is Archived. Taking no further action on it.");
next;
}
if ( ! $Event->StartTime() ) {
Info("Event $$Event{Id} has no start time. deleting it.");
next;
}
if ( ! $Event->EndTime() ) {
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
Info("Event $$Event{Id} has no end time and is $age seconds old. deleting it.");
if ( confirm() ) {
$Event->delete();
$cleaned = 1;
}
next;
}
if ( ! $Event->EndTime() ) {
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
Info("Event $$Event{Id} has no end time and is $age seconds old. deleting it.");
if ( confirm() ) {
$Event->delete();
$cleaned = 1;
}
next;
}
}
if ( $Event->check_for_in_filesystem() ) {
Debug("Database event $$Event{Id} apparently exists at " . $Event->Path() );
} else {
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
aud_print( "Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem, deleting' );
if ( confirm() ) {
$Event->delete();
$cleaned = 1;
}
} else {
aud_print( "Database event '".$Event->Path()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" );
}
if ( $Event->check_for_in_filesystem() ) {
Debug("Database event $$Event{Id} apparently exists at " . $Event->Path() );
} else {
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
aud_print( "Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem, deleting' );
if ( confirm() ) {
$Event->delete();
$cleaned = 1;
}
} # end if exists in filesystem
} # end if ! in fs_events
} # foreach db_event
#} else {
#my $Monitor = new ZoneMinder::Monitor( $db_monitor );
#my $Storage = $Monitor->Storage();
#aud_print( "Database monitor '$db_monitor' does not exist in filesystem, should have been at ".$Storage->Path().'/'.$Monitor->Id()."\n" );
#if ( confirm() )
#{
# We don't actually do this in case it's new
#my $res = $deleteMonitorSth->execute( $db_monitor )
# or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() );
#$cleaned = 1;
#}
}
} else {
aud_print( "Database event '".$Event->Path()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" );
}
} # end if exists in filesystem
} else {
Debug("Found fs event for $db_event, $age at " . $$fs_events{$db_event}->Path());
my $Event = new ZoneMinder::Event( $db_event );
if ( ! $Event->check_for_in_filesystem() ) {
Warning("Not found at " . $Event->Path() . ' was found at ' . $$fs_events{$db_event}->Path() );
Warning($Event->to_string());
Warning($$fs_events{$db_event}->to_string());
if ( $$fs_events{$db_event}->Scheme() ne $Event->Scheme() ) {
Info("Updating scheme on event $$Event{Id} from $$Event{Scheme} to $$fs_events{$db_event}{Scheme}");
$Event->Scheme($$fs_events{$db_event}->Scheme());
}
if ( $$fs_events{$db_event}->StorageId() != $Event->StorageId() ) {
Info("Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}");
$Event->StorageId($$fs_events{$db_event}->StorageId());
}
if ( $$fs_events{$db_event}->StartTime() ne $Event->StartTime() ) {
Info("Updating StartTime on event $$Event{Id} from $$Event{StartTime} to $$fs_events{$db_event}{StartTime}");
if ( $$Event{Scheme} eq 'Deep' ) {
$Event->StartTime($$fs_events{$db_event}->StartTime());
} else {
$Event->StartTime($$fs_events{$db_event}->StartTime());
}
$Event->save();
}
$Event->save();
}
} # end if ! in fs_events
} # foreach db_event
} # end foreach db_monitor
if ( $cleaned ) {
Debug("Have done some cleaning, restarting.");
@ -954,7 +979,7 @@ sub delete_empty_directories {
return;
}
my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR );
Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' ));
#Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' ));
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
if ( @dirs ) {
Debug("Have " . @dirs . " dirs");
@ -975,6 +1000,25 @@ sub delete_empty_directories {
}
} # end sub delete_empty_directories
sub time_of_youngest_file {
my $dir = shift;
if ( ! opendir(DIR, $dir) ) {
Error("Can't open directory '$dir': $!");
return;
}
my $youngest = (stat($dir))[9];
Debug("stat of $dir is $youngest");
foreach my $file ( readdir( DIR ) ) {
next if $file =~ /^\./;
$_ = (stat($dir))[9];
$youngest = $_ if $_ and ( $_ < $youngest );
#Debug("stat of $dir is $_ < $youngest");
}
Debug("stat of $dir is $youngest");
return $youngest;
} # end sub time_of_youngest_file
1;
__END__

View File

@ -441,7 +441,7 @@ sub start {
$dbh = zmDbConnect(1);
# This logReinit is required. Not sure why.
#logReinit();
logReinit();
$process->{pid} = $cpid;
$process->{started} = time();

View File

@ -576,7 +576,8 @@ sub uploadArchFile {
$host .= ':'.$Config{ZM_UPLOAD_PORT} if $Config{ZM_UPLOAD_PORT};
Info('Uploading to '.$host.' using SFTP');
my %sftpOptions = (
host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER}
host=>$Config{ZM_UPLOAD_HOST},
user=>$Config{ZM_UPLOAD_USER},
($Config{ZM_UPLOAD_PASS} ? (password=>$Config{ZM_UPLOAD_PASS}) : ()),
($Config{ZM_UPLOAD_PORT} ? (port=>$Config{ZM_UPLOAD_PORT}) : ()),
($Config{ZM_UPLOAD_TIMEOUT} ? (timeout=>$Config{ZM_UPLOAD_TIMEOUT}) : ()),

View File

@ -310,17 +310,19 @@ sub isActiveSanityCheck {
if ( $sth->rows != 1 ) {
# PP - no row, or too many rows. Either case is an error
Info( 'Fixing States table - either no default state or duplicate default states' );
$sql = "DELETE FROM States WHERE Name='default'";
$sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute()
or Fatal( "Can't execute: ".$sth->errstr() );
$sql = q`"INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`;
if ( $sth->rows ) {
$sql = q`DELETE FROM States WHERE Name='default'`;
$sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute()
or Fatal( "Can't execute: ".$sth->errstr() );
}
$sql = q`INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`;
$sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
$res = $sth->execute()
or Fatal("Can't execute: ".$sth->errstr());
}
}
# PP - Now make sure no two states have IsActive=1
$sql = 'SELECT Name FROM States WHERE IsActive = 1';

View File

@ -1,39 +1,4 @@
#!/usr/bin/perl -wT
#
# ==========================================================================
#
# ZoneMinder WatchDog Script, $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.
#
# ==========================================================================
=head1 NAME
zmwatch.pl - ZoneMinder Stats Updating Script
=head1 SYNOPSIS
zmstats.pl
=head1 DESCRIPTION
This does background updating various stats in the db like event counts, diskspace, etc.
=cut
use strict;
use bytes;
@ -66,8 +31,8 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
logInit();
logSetSignal();
Info( "Stats Daemon starting in ".START_DELAY." seconds\n" );
sleep( START_DELAY );
Info("Stats Daemon starting in ".START_DELAY." seconds");
sleep(START_DELAY);
my $dbh = zmDbConnect();
@ -88,7 +53,43 @@ while( 1 ) {
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
} # end while (1)
Info( "Stats Daemon exiting\n" );
Info("Stats Daemon exiting");
exit();
1;
__END__
#
# ==========================================================================
#
# ZoneMinder WatchDog Script, $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.
#
# ==========================================================================
=head1 NAME
zmstats.pl - ZoneMinder Stats Updating Script
=head1 SYNOPSIS
zmstats.pl
=head1 DESCRIPTION
This does background updating various stats in the db like event counts, diskspace, etc.
=cut

View File

@ -71,8 +71,9 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) {
if ( event_time ) {
curr_stream_time = event_time;
curr_frame_id = 1;
curr_frame_id = 1; // curr_frame_id is 1-based
if ( event_time >= event_data->start_time ) {
Debug(2, "event time is after event start");
for (unsigned int i = 0; i < event_data->frame_count; i++ ) {
//Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time );
if ( event_data->frames[i].timestamp >= event_time ) {
@ -109,7 +110,10 @@ bool EventStream::loadInitialEventData( uint64_t init_event_id, unsigned int ini
bool EventStream::loadEventData(uint64_t event_id) {
static char sql[ZM_SQL_MED_BUFSIZ];
snprintf(sql, sizeof(sql), "SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo, Scheme FROM Events WHERE Id = %" PRIu64, event_id);
snprintf(sql, sizeof(sql), "SELECT MonitorId, StorageId, Frames,"
" unix_timestamp( StartTime ) AS StartTimestamp,"
" (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo, Scheme"
" FROM Events WHERE Id = %" PRIu64, event_id);
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
@ -161,18 +165,25 @@ bool EventStream::loadEventData(uint64_t event_id) {
if ( storage_path[0] == '/' )
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d",
storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
storage_path, event_data->monitor_id,
event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday,
event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
else
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d",
staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id,
event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday,
event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
} else if ( event_data->scheme == Storage::MEDIUM ) {
struct tm *event_time = localtime( &event_data->start_time );
if ( storage_path[0] == '/' )
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%04d-%02d-%02d/%" PRIu64,
storage_path, event_data->monitor_id, event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday, event_data->event_id );
storage_path, event_data->monitor_id,
event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday,
event_data->event_id );
else
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%04d-%02d-%02d/%" PRIu64,
staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday,
staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id,
event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday,
event_data->event_id );
} else {
@ -186,8 +197,9 @@ bool EventStream::loadEventData(uint64_t event_id) {
delete storage; storage = NULL;
updateFrameRate( (double)event_data->frame_count/event_data->duration );
Debug(3,"fps set by frame_count(%d)/duration(%f)", event_data->frame_count, event_data->duration);
snprintf(sql, sizeof(sql), "SELECT FrameId, unix_timestamp( `TimeStamp` ), Delta FROM Frames where EventId = %" PRIu64 " ORDER BY FrameId ASC", event_id);
snprintf(sql, sizeof(sql), "SELECT FrameId, unix_timestamp(`TimeStamp`), Delta FROM Frames WHERE EventId = %" PRIu64 " ORDER BY FrameId ASC", event_id);
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
@ -203,29 +215,46 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->frames = new FrameData[event_data->frame_count];
int last_id = 0;
time_t timestamp, last_timestamp = event_data->start_time;
double last_timestamp = event_data->start_time;
double last_delta = 0.0;
while ( ( dbrow = mysql_fetch_row( result ) ) ) {
int id = atoi(dbrow[0]);
timestamp = atoi(dbrow[1]);
//timestamp = atof(dbrow[1]);
double delta = atof(dbrow[2]);
int id_diff = id - last_id;
double frame_delta = id_diff ? (delta-last_delta)/id_diff : 0;
double frame_delta = id_diff ? (delta-last_delta)/id_diff : (delta - last_delta);
// Fill in data between bulk frames
if ( id_diff > 1 ) {
for ( int i = last_id+1; i < id; i++ ) {
event_data->frames[i-1].timestamp = (time_t)(last_timestamp + ((i-last_id)*frame_delta));
event_data->frames[i-1].offset = (time_t)(event_data->frames[i-1].timestamp-event_data->start_time);
// Delta is the time since last frame, no since beginning of Event
event_data->frames[i-1].delta = frame_delta;
event_data->frames[i-1].timestamp = last_timestamp + ((i-last_id)*frame_delta);
event_data->frames[i-1].offset = event_data->frames[i-1].timestamp - event_data->start_time;
event_data->frames[i-1].in_db = false;
Debug(3,"Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
i,
event_data->frames[i-1].timestamp,
event_data->frames[i-1].offset,
event_data->frames[i-1].delta,
event_data->frames[i-1].in_db
);
}
}
event_data->frames[id-1].timestamp = timestamp;
event_data->frames[id-1].offset = (time_t)(event_data->frames[id-1].timestamp-event_data->start_time);
event_data->frames[id-1].delta = id>1?frame_delta:0.0;
event_data->frames[id-1].timestamp = event_data->start_time + delta;
event_data->frames[id-1].offset = delta;
event_data->frames[id-1].delta = frame_delta;
event_data->frames[id-1].in_db = true;
last_id = id;
last_delta = delta;
last_timestamp = timestamp;
last_timestamp = event_data->frames[id-1].timestamp;
Debug(4,"Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)",
id,
event_data->frames[id-1].timestamp,
event_data->frames[id-1].offset,
event_data->frames[id-1].delta,
event_data->frames[id-1].in_db
);
}
if ( mysql_errno( &dbconn ) ) {
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
@ -636,10 +665,16 @@ Debug(1, "Loading image");
} else if ( ffmpeg_input ) {
// Get the frame from the mp4 input
Debug(1,"Getting frame from ffmpeg");
AVFrame *frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), curr_frame_id );
AVFrame *frame;
if ( curr_frame_id == 1 ) {
// Special case, first frame, we want to send the initial keyframe.
frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), 0 );
}
FrameData *frame_data = &event_data->frames[curr_frame_id-1];
frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), frame_data->offset );
if ( frame ) {
image = new Image(frame);
av_frame_free(&frame);
//av_frame_free(&frame);
} else {
Error("Failed getting a frame.");
return false;
@ -803,12 +838,16 @@ void EventStream::runStream() {
}
// Figure out if we should send this frame
Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod);
// If we are streaming and this frame is due to be sent
if ( ((curr_frame_id-1)%frame_mod) == 0 ) {
// frame mod defaults to 1 and if we are going faster than max_fps will get multiplied by 2
// so if it is 2, then we send every other frame, if is it 4 then every fourth frame, etc.
if ( (frame_mod == 1) || (((curr_frame_id-1)%frame_mod) == 0) ) {
delta_us = (unsigned int)(frame_data->delta * 1000000);
Debug(3,"frame delta %u ", delta_us);
// if effective > base we should speed up frame delivery
delta_us = (unsigned int)((delta_us * base_fps)/effective_fps);
Debug(3,"delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps);
// but must not exceed maxfps
delta_us = max(delta_us, 1000000 / maxfps);
send_frame = true;

View File

@ -47,8 +47,8 @@ class EventStream : public StreamBase {
protected:
struct FrameData {
//unsigned long id;
time_t timestamp;
time_t offset;
double timestamp;
double offset;
double delta;
bool in_db;
};

View File

@ -276,8 +276,8 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output)
if (flags & AVFMT_SHOW_IDS)
Debug(1, "[0x%x]", st->id);
if (lang)
Debug(1, "(%s)", lang->value);
Debug(1, ", frames:%d, timebase: %d/%d", st->codec_info_nb_frames, st->time_base.num, st->time_base.den);
Debug(1, "language (%s)", lang->value);
Debug(1, "frames:%d, timebase: %d/%d", st->nb_frames, st->time_base.num, st->time_base.den);
avcodec_string(buf, sizeof(buf), st->codec, is_output);
Debug(1, ": %s", buf);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
@ -303,9 +303,6 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output)
int tbn = st->time_base.den && st->time_base.num;
int tbc = st->codec->time_base.den && st->codec->time_base.num;
if (fps || tbn || tbc)
Debug(3, "\n" );
if (fps)
zm_log_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps");
if (tbn)
@ -437,12 +434,16 @@ int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet
#endif
return 1;
} // end int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet )
void dumpPacket(AVPacket *pkt, const char *text) {
void dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) {
char b[10240];
double pts_time = (double)av_rescale_q(pkt->pts,
stream->time_base,
AV_TIME_BASE_Q
) / AV_TIME_BASE;
snprintf(b, sizeof(b),
" pts: %" PRIu64 ", dts: %" PRIu64
" pts: %" PRId64 "=%f, dts: %" PRId64
", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64
", duration: %"
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
@ -452,6 +453,7 @@ void dumpPacket(AVPacket *pkt, const char *text) {
#endif
"\n",
pkt->pts,
pts_time,
pkt->dts,
pkt->size,
pkt->stream_index,

View File

@ -306,11 +306,17 @@ void zm_dump_codecpar ( const AVCodecParameters *par );
#define zm_av_packet_unref(packet) av_free_packet(packet)
unsigned int zm_av_packet_ref(AVPacket *dst, AVPacket *src);
#endif
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
#define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) \
avcodec_send_packet( context, packet ); \
avcodec_receive_frame( context, rawFrame );
#else
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
#define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet )
#else
#define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size)
#endif
#endif
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
#define zm_av_frame_alloc() av_frame_alloc()
@ -327,5 +333,5 @@ int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt);
bool is_video_stream( AVStream * stream );
bool is_audio_stream( AVStream * stream );
int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet );
void dumpPacket(AVPacket *,const char *text="DEBUG");
void dumpPacket(AVStream *, AVPacket *,const char *text="");
#endif // ZM_FFMPEG_H

View File

@ -188,7 +188,7 @@ int FfmpegCamera::Capture(ZMPacket &zm_packet) {
}
return -1;
}
dumpPacket(&packet, "ffmpeg_camera in");
dumpPacket(mFormatContext->streams[packet.stream_index], &packet, "ffmpeg_camera in");
if ( 0 && ( packet.dts < 0 ) ) {
zm_av_packet_unref(&packet);
return 0;

View File

@ -10,6 +10,7 @@ FFmpeg_Input::FFmpeg_Input() {
av_register_all();
avcodec_register_all();
streams = NULL;
frame = NULL;
}
FFmpeg_Input::~FFmpeg_Input() {
@ -36,13 +37,16 @@ int FFmpeg_Input::Open( const char *filepath ) {
/** Get information on the input file (number of streams etc.). */
if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) {
Error("Could not open find stream info (error '%s')",
av_make_error_string(error).c_str() );
Error(
"Could not open find stream info (error '%s')",
av_make_error_string(error).c_str()
);
avformat_close_input(&input_format_context);
return error;
}
streams = new stream[input_format_context->nb_streams];
Debug(2,"Have %d streams", input_format_context->nb_streams);
for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) {
if ( is_video_stream( input_format_context->streams[i] ) ) {
@ -55,10 +59,13 @@ int FFmpeg_Input::Open( const char *filepath ) {
}
} else if ( is_audio_stream(input_format_context->streams[i]) ) {
if ( audio_stream_id == -1 ) {
Debug(2,"Audio stream is %d", i);
audio_stream_id = i;
} else {
Warning("Have another audio stream.");
}
} else {
Warning("Unknown stream type");
}
streams[i].frame_count = 0;
@ -118,14 +125,15 @@ int FFmpeg_Input::Close( ) {
return 1;
} // end int FFmpeg_Input::Close()
AVFrame *FFmpeg_Input::get_frame(int stream_id, int frame_number) {
Debug(1, "Getting frame from stream %d, frame_number(%d)", stream_id, frame_number);
AVFrame *FFmpeg_Input::get_frame( int stream_id ) {
Debug(1, "Getting frame from stream %d", stream_id);
int frameComplete = false;
AVPacket packet;
av_init_packet(&packet);
AVFrame *frame = zm_av_frame_alloc();
char errbuf[AV_ERROR_MAX_STRING_SIZE];
while ( frame_number >= streams[stream_id].frame_count ) {
while ( !frameComplete ) {
int ret = av_read_frame(input_format_context, &packet);
if ( ret < 0 ) {
@ -142,25 +150,131 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, int frame_number) {
}
return NULL;
}
dumpPacket(input_format_context->streams[packet.stream_index], &packet, "Received packet");
if ( ( stream_id < 0 ) || ( packet.stream_index != stream_id ) ) {
Warning("Packet is not for our stream (%d)", packet.stream_index);
if ( (stream_id >= 0) && (packet.stream_index != stream_id) ) {
Debug(1,"Packet is not for our stream (%d)", packet.stream_index );
return NULL;
}
Debug(3,"Packet is for our stream (%d)", packet.stream_index );
AVCodecContext *context = streams[packet.stream_index].context;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_send_packet(context, &packet);
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
if ( ! zm_receive_frame(streams[packet.stream_index].context, frame, packet) ) {
Error("Unable to get frame %d, continuing", streams[packet.stream_index].frame_count);
#if HAVE_AVUTIL_HWCONTEXT_H
if ( hwaccel ) {
ret = avcodec_receive_frame( context, hwFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
ret = av_hwframe_transfer_data(frame, hwFrame, 0);
if (ret < 0) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref(&packet);
continue;
}
} else {
#endif
if ( frame ) {
av_frame_free(&frame);
frame = zm_av_frame_alloc();
} else {
frame = zm_av_frame_alloc();
}
//Debug(1,"Getting frame %d", streams[packet.stream_index].frame_count);
ret = avcodec_receive_frame(context, frame);
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
av_frame_free(&frame);
continue;
}
#if HAVE_AVUTIL_HWCONTEXT_H
}
#endif
frameComplete = 1;
# else
if ( frame ) {
av_frame_free(&frame);
frame = zm_av_frame_alloc();
} else {
frame = zm_av_frame_alloc();
}
ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet);
if ( ret < 0 ) {
av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
Error( "Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
av_frame_free(&frame);
continue;
} else {
Debug(1, "Success getting a packet at frame (%d)", streams[packet.stream_index].frame_count);
streams[packet.stream_index].frame_count += 1;
}
#endif
zm_av_packet_unref(&packet);
if ( frame_number == -1 )
break;
} // end while frame_number > streams.frame_count
} // end while !frameComplete
return frame;
} // end AVFrame *FFmpeg_Input::get_frame
AVFrame *FFmpeg_Input::get_frame( int stream_id, double at ) {
Debug(1, "Getting frame from stream %d at %f", stream_id, at);
int64_t seek_target = (int64_t)(at * AV_TIME_BASE);
Debug(1, "Getting frame from stream %d at seektarget: %" PRId64, stream_id, seek_target);
seek_target = av_rescale_q(seek_target, AV_TIME_BASE_Q, input_format_context->streams[stream_id]->time_base);
Debug(1, "Getting frame from stream %d at %" PRId64, stream_id, seek_target);
if ( frame ) {
if ( (frame->pts + frame->pkt_duration) > seek_target ) {
// The current frame is still the valid picture.
Debug(2,"Returning previous frame which is still good");
return frame;
}
if ( frame->pts < seek_target ) {
Debug(2, "Frame pts %" PRId64 " duration %" PRId64, frame->pts, frame->pkt_duration);
while ( frame && (frame->pts < seek_target) ) {
if ( ! get_frame(stream_id) )
return frame;
}
return frame;
}
}
int ret;
if ( frame ) {
if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, AVSEEK_FLAG_ANY) < 0 ) ) {
Error("Unable to seek in stream");
return NULL;
}
} else {
// Must go for a keyframe
if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target,
AVSEEK_FLAG_FRAME
) < 0 ) ) {
Error("Unable to seek in stream");
return NULL;
}
}
return get_frame(stream_id);
} // end AVFrame *FFmpeg_Input::get_frame( int stream_id, struct timeval at)

View File

@ -21,7 +21,8 @@ class FFmpeg_Input {
int Open( const char *filename );
int Close();
AVFrame *get_frame( int stream_id=-1, int frame_number=-1 );
AVFrame *get_frame( int stream_id=-1 );
AVFrame *get_frame( int stream_id, double at );
int get_video_stream_id() {
return video_stream_id;
}
@ -40,6 +41,7 @@ class FFmpeg_Input {
int video_stream_id;
int audio_stream_id;
AVFormatContext *input_format_context;
AVFrame *frame;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -296,6 +296,7 @@ void std_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result,
void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);

View File

@ -546,7 +546,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
+ (image_buffer_count * width * height * colours)
+ 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */
Debug(1, "mem.size SharedData=%d TriggerData=%d VideoStoreData=%d total=%d",
Debug(1, "mem.size SharedData=%d TriggerData=%d VideoStoreData=%d total=%" PRId64,
sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData), mem_size);
mem_ptr = NULL;

View File

@ -132,24 +132,25 @@ protected:
uint8_t format; /* +55 */
uint32_t imagesize; /* +56 */
uint32_t epadding1; /* +60 */
uint32_t epadding2; /* +64 */
/*
/*
** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038.
** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
*/
union { /* +68 */
** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple
** of 8. Add or delete epadding's to achieve this.
*/
union { /* +64 */
time_t startup_time; /* When the zmc process started. zmwatch uses this to see how long the process has been running without getting any images */
uint64_t extrapad1;
};
union { /* +76 */
union { /* +72 */
time_t last_write_time;
uint64_t extrapad2;
};
union { /* +84 */
union { /* +80 */
time_t last_read_time;
uint64_t extrapad3;
};
uint8_t control_state[256]; /* +92 */
uint8_t control_state[256]; /* +88 */
char alarm_cause[256];

View File

@ -195,7 +195,7 @@ AVPacket *ZMPacket::set_packet(AVPacket *p) {
if ( zm_av_packet_ref(&packet, p) < 0 ) {
Error("error refing packet");
}
dumpPacket(&packet, "zmpacket:");
//dumpPacket(&packet, "zmpacket:");
gettimeofday( timestamp, NULL );
keyframe = p->flags & AV_PKT_FLAG_KEY;
return &packet;

View File

@ -240,13 +240,16 @@ unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_
void zm_packetqueue::clearQueue() {
mutex.lock();
ZMPacket *packet = NULL;
while ( !pktQueue.empty() ) {
int delete_count = 0;
while(!pktQueue.empty()) {
packet = pktQueue.front();
packet_counts[packet->packet.stream_index] -= 1;
pktQueue.pop_front();
if ( packet->image_index == -1 )
delete packet;
delete_count += 1;
}
Debug(3, "Deleted (%d) packets", delete_count );
video_packet_count = 0;
first_video_packet_index = -1;
analysis_it = pktQueue.begin();

View File

@ -388,7 +388,7 @@ int RtspThread::run() {
std::string trackUrl = mUrl;
std::string controlUrl;
_AVCODECID codecId;
_AVCODECID codecId = AV_CODEC_ID_NONE;
if ( mFormatContext->nb_streams >= 1 ) {
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {

View File

@ -64,7 +64,7 @@ void StreamBase::updateFrameRate(double fps) {
base_fps = fps;
effective_fps = (base_fps*abs(replay_rate))/ZM_RATE_BASE;
frame_mod = 1;
Debug(3, "FPS:%.2f, MXFPS:%.2f, BFPS:%.2f, EFPS:%.2f, FM:%d", fps, maxfps, base_fps, effective_fps, frame_mod);
Debug(3, "FPS:%.2f, MaxFPS:%.2f, BaseFPS:%.2f, EffectiveFPS:%.2f, FrameMod:%d", fps, maxfps, base_fps, effective_fps, frame_mod);
// Min frame repeat?
while( effective_fps > maxfps ) {
effective_fps /= 2.0;
@ -325,7 +325,7 @@ void StreamBase::openComms() {
strncpy(loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path));
loc_addr.sun_family = AF_UNIX;
Debug(3, "Binding to %s", loc_sock_path);
if ( bind(sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1) < 0 ) {
if ( ::bind(sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1) < 0 ) {
Fatal("Can't bind: %s", strerror(errno));
}

View File

@ -120,7 +120,6 @@ bool VideoStore::open() {
oc->metadata = pmetadata;
out_format = oc->oformat;
if ( video_in_stream ) {
video_in_stream_index = video_in_stream->index;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
@ -408,7 +407,7 @@ bool VideoStore::open() {
audio_out_ctx->codec_tag = 0;
#endif
if ( ret < 0 ) {
Error("Unable to copy audio ctx %s\n",
Error("Unable to copy audio ctx %s",
av_make_error_string(ret).c_str());
audio_out_stream = NULL;
} else {
@ -610,7 +609,7 @@ VideoStore::~VideoStore() {
avcodec_close(video_out_ctx);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
avcodec_free_context(&video_out_ctx);
//avcodec_free_context(&video_out_ctx);
#endif
video_out_ctx = NULL;
Debug(4, "Success freeing video_out_ctx");
@ -695,7 +694,7 @@ bool VideoStore::setup_resampler() {
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
audio_out_ctx = avcodec_alloc_context3(audio_out_codec);
if ( !audio_out_ctx ) {
Error("could not allocate codec ctx for AAC\n");
Error("could not allocate codec ctx for AAC");
audio_out_stream = NULL;
return false;
}
@ -713,6 +712,13 @@ bool VideoStore::setup_resampler() {
#else
audio_out_ctx->refcounted_frames = 1;
#endif
if ( ! audio_out_ctx->channel_layout ) {
Debug(3, "Correcting channel layout from (%d) to (%d)",
audio_out_ctx->channel_layout,
av_get_default_channel_layout(audio_out_ctx->channels)
);
audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels);
}
if ( audio_out_codec->supported_samplerates ) {
int found = 0;
@ -904,7 +910,7 @@ int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) {
if ( ! zm_packet->out_frame ) {
//Debug(3, "Have no out frame");
AVFrame *out_frame = zm_packet->out_frame = zm_av_frame_alloc();
if ( ! out_frame ) {
if ( !out_frame ) {
Error("Unable to allocate a frame");
return 0;
}
@ -942,16 +948,16 @@ int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) {
out_frame->format = video_out_ctx->pix_fmt;
//out_frame->pkt_duration = 0;
if ( ! zm_packet->in_frame ) {
if ( !zm_packet->in_frame ) {
//Debug(2,"Have no in_frame");
if ( zm_packet->packet.size ) {
//Debug(2,"Decoding");
if ( ! zm_packet->decode( video_in_ctx ) ) {
if ( !zm_packet->decode(video_in_ctx) ) {
Debug(2, "unable to decode yet.");
return 0;
}
//Go straight to out frame
swscale.Convert( zm_packet->in_frame, out_frame );
swscale.Convert(zm_packet->in_frame, out_frame);
} else if ( zm_packet->image ) {
//Debug(2,"Have an image, convert it");
//Go straight to out frame
@ -986,18 +992,18 @@ int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) {
uint64_t temp = zm_packet->timestamp->tv_sec*(uint64_t)1000000;
video_start_pts = temp + zm_packet->timestamp->tv_usec;
Debug(2, "No video_lsat_pts, set to (%" PRId64 ") secs(%d=>%" PRId64 ") usecs(%d)",
video_start_pts, zm_packet->timestamp->tv_sec, temp, zm_packet->timestamp->tv_usec );
video_start_pts, zm_packet->timestamp->tv_sec, temp, zm_packet->timestamp->tv_usec);
Debug(2, "No video_lsat_pts, set to (%" PRId64 ") secs(%d) usecs(%d)",
video_start_pts, zm_packet->timestamp->tv_sec, zm_packet->timestamp->tv_usec );
video_start_pts, zm_packet->timestamp->tv_sec, zm_packet->timestamp->tv_usec);
zm_packet->out_frame->pts = 0;
zm_packet->out_frame->coded_picture_number = 0;
} else {
uint64_t seconds = ( zm_packet->timestamp->tv_sec*(uint64_t)1000000 + zm_packet->timestamp->tv_usec ) - video_start_pts;
zm_packet->out_frame->pts = av_rescale_q( seconds, video_in_stream->time_base, video_out_ctx->time_base);
zm_packet->out_frame->pts = av_rescale_q(seconds, video_in_stream->time_base, video_out_ctx->time_base);
//zm_packet->out_frame->pkt_duration = zm_packet->out_frame->pts - video_start_pts;
Debug(2, " Setting pts for frame(%d), set to (%" PRId64 ") from (start %" PRIu64 " - %" PRIu64 " - secs(%d) usecs(%d)",
frame_count, zm_packet->out_frame->pts, video_start_pts, seconds, zm_packet->timestamp->tv_sec, zm_packet->timestamp->tv_usec );
frame_count, zm_packet->out_frame->pts, video_start_pts, seconds, zm_packet->timestamp->tv_sec, zm_packet->timestamp->tv_usec);
}
if ( zm_packet->keyframe ) {
//Debug(2, "Setting keyframe was (%d)", zm_packet->out_frame->key_frame );
@ -1022,7 +1028,7 @@ int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) {
if ( (ret = avcodec_receive_packet(video_out_ctx, &opkt)) < 0 ) {
zm_av_packet_unref(&opkt);
if ( AVERROR(EAGAIN) == ret ) {
// THe codec may need more samples than it has, perfectly valid
// The codec may need more samples than it has, perfectly valid
Debug(3, "Could not recieve packet (error '%s')",
av_make_error_string(ret).c_str());
return 0;
@ -1057,7 +1063,7 @@ int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) {
if ( opkt.dts != AV_NOPTS_VALUE)
opkt.dts = av_rescale_q(opkt.dts, video_out_ctx->time_base, video_out_stream->time_base);
} else {
} else { // codec matches, we are doing passthrough
AVPacket *ipkt = &zm_packet->packet;
Debug(3, "Doing passthrough, just copy packet");
// Just copy it because the codec is the same
@ -1071,7 +1077,7 @@ int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) {
Debug(2, "No video_lsat_pts, set to (%" PRId64 ")", video_start_pts );
opkt.dts = opkt.pts = 0;
} else {
dumpPacket(ipkt);
dumpPacket(video_in_stream, ipkt);
if ( ipkt->pts != AV_NOPTS_VALUE )
opkt.pts = av_rescale_q( ipkt->pts - video_start_pts, video_in_stream->time_base, video_out_stream->time_base );
else
@ -1081,21 +1087,20 @@ int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) {
opkt.dts = av_rescale_q( ipkt->dts - video_start_pts, video_in_stream->time_base, video_out_stream->time_base );
else
opkt.dts = 0;
Debug(2, "out_stream_time_base(%d/%d) in_stream_time_base(%d/%d) video_start_pts(%" PRId64 ")",
video_out_stream->time_base.num, video_out_stream->time_base.den,
video_in_stream->time_base.num, video_in_stream->time_base.den,
video_start_pts
);
dumpPacket(&opkt);
dumpPacket(video_out_stream, &opkt);
opkt.duration = av_rescale_q( opkt.duration, video_in_stream->time_base, video_out_stream->time_base);
}
}
} // end if codec matches
//opkt.duration = 0;
write_video_packet( opkt );
write_video_packet(opkt);
zm_av_packet_unref(&opkt);
return 1;
@ -1114,7 +1119,7 @@ void VideoStore::write_video_packet( AVPacket &opkt ) {
opkt.stream_index = video_out_stream->index;
//av_packet_rescale_ts( &opkt, video_out_ctx->time_base, video_out_stream->time_base );
dumpPacket(&opkt, "writing video packet");
dumpPacket(video_out_stream, &opkt, "writing video packet");
if ( (opkt.data == NULL) || (opkt.size < 1) ) {
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__);
@ -1187,9 +1192,9 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) {
int data_present;
if ( (ret = avcodec_decode_audio4(audio_in_ctx, in_frame,
&data_present, ipkt)) < 0 ) {
Error("Could not decode frame (error '%s')\n",
Error("Could not decode frame (error '%s')",
av_make_error_string(ret).c_str());
dumpPacket(ipkt);
dumpPacket(autdio_in_stream, ipkt);
av_frame_free(&in_frame);
return 0;
}
@ -1346,7 +1351,7 @@ if ( opkt.dts > opkt.pts ) {
//opkt.duration = out_frame ? out_frame->nb_samples : ipkt->duration;
// opkt.duration = av_rescale_q(ipkt->duration, audio_in_stream->time_base,
// audio_out_stream->time_base);
dumpPacket(&opkt);
dumpPacket(audio_out_stream, &opkt);
// pkt.pos: byte position in stream, -1 if unknown
opkt.pos = -1;
@ -1360,7 +1365,7 @@ ret = av_interleaved_write_frame(oc, &opkt);
if ( ret != 0 ) {
Error("Error writing audio frame packet: %s\n",
av_make_error_string(ret).c_str());
dumpPacket(&safepkt);
dumpPacket(audio_out_stream, &safepkt);
} else {
Debug(2, "Success writing audio frame");
}

View File

@ -74,6 +74,11 @@ int audio_in_stream_index;
int64_t audio_last_pts;
int64_t audio_last_dts;
int64_t video_first_pts;
int64_t video_first_dts;
int64_t audio_first_pts;
int64_t audio_first_dts;
// These are for out, should start at zero. We assume they do not wrap because we just aren't going to save files that big.
;
int64_t audio_next_pts;

View File

@ -164,14 +164,23 @@ switch ( $_REQUEST['task'] ) {
$where = array();
$values = array();
if ( $minTime ) {
preg_match('/(.+)(\.\d+)/', $minTime, $matches);
$minTime = strtotime($matches[1]).$matches[2];
Logger::Debug("MinTime: $minTime");
if ( preg_match('/(.+)(\.\d+)/', $minTime, $matches) ) {
# This handles sub second precision
$minTime = strtotime($matches[1]).$matches[2];
Logger::Debug("MinTime: $minTime");
} else {
$minTime = strtotime($minTime);
}
$where[] = 'TimeKey >= ?';
$values[] = $minTime;
}
if ( $maxTime ) {
preg_match('/(.+)(\.\d+)/', $maxTime, $matches);
$maxTime = strtotime($matches[1]).$matches[2];
if ( preg_match('/(.+)(\.\d+)/', $maxTime, $matches) ) {
$maxTime = strtotime($matches[1]).$matches[2];
} else {
$maxTime = strtotime($maxTime);
}
$where[] = 'TimeKey <= ?';
$values[] = $maxTime;
}
@ -209,8 +218,15 @@ switch ( $_REQUEST['task'] ) {
}
$exportKey = substr(md5(rand()),0,8);
$exportFile = "zm-log.$exportExt";
$exportPath = ZM_PATH_SWAP."/zm-log-$exportKey.$exportExt";
if ( !($exportFP = fopen( $exportPath, "w" )) )
if ( ! file_exists(ZM_DIR_EXPORTS) ) {
Logger::Debug('Creating ' . ZM_DIR_EXPORTS);
if ( ! mkdir(ZM_DIR_EXPORTS) ) {
Fatal("Can't create exports dir at '".ZM_DIR_EXPORTS."'");
}
}
$exportPath = ZM_DIR_EXPORTS."/zm-log-$exportKey.$exportExt";
Logger::Debug("Exporting to $exportPath");
if ( !($exportFP = fopen($exportPath, 'w')) )
Fatal("Unable to open log export file $exportPath");
$logs = array();
foreach ( dbFetchAll($sql, NULL, $values) as $log ) {
@ -218,6 +234,8 @@ switch ( $_REQUEST['task'] ) {
$log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : '';
$logs[] = $log;
}
Logger::Debug(count($logs)." lines being exported by $sql " . implode(',',$values));
switch( $format ) {
case 'text' :
{
@ -390,7 +408,7 @@ switch ( $_REQUEST['task'] ) {
}
$exportFile = "zm-log.$exportExt";
$exportPath = ZM_PATH_SWAP."/zm-log-$exportKey.$exportExt";
$exportPath = ZM_DIR_EXPORTS."/zm-log-$exportKey.$exportExt";
header('Pragma: public');
header('Expires: 0');

View File

@ -50,7 +50,7 @@
*/
Configure::write('Error', array(
'handler' => 'ErrorHandler::handleError',
'level' => E_ALL & ~E_DEPRECATED,
'level' => E_ALL & ~E_DEPRECATED & ~E_NOTICE,
'trace' => true
));

View File

@ -69,7 +69,7 @@ class HostController extends AppController {
$this->Session->read('user'); # this is needed for command line/curl to recognize a session
$zmAuthRelay = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY')))['Config']['Value'];
if ( $zmAuthRelay == 'hashed' ) {
$zmAuthHashIps= $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_IPS')))['Config']['Value'];
$zmAuthHashIps = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_IPS')))['Config']['Value'];
$credentials = 'auth='.generateAuthHash($zmAuthHashIps);
} else if ( $zmAuthRelay == 'plain' ) {
// user will need to append the store password here

View File

@ -207,8 +207,10 @@ class MonitorsController extends AppController {
if ( !$this->Monitor->exists() ) {
throw new NotFoundException(__('Invalid monitor'));
}
if ( $this->Session->Read('systemPermission') != 'Edit' ) {
throw new UnauthorizedException(__('Insufficient privileges'));
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->request->allowMethod('post', 'delete');

View File

@ -59,8 +59,9 @@ public function add() {
if ($this->request->is('post')) {
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}

View File

@ -31,7 +31,7 @@ class StorageController extends AppController {
* @return void
*/
public function index() {
$this->Storage->recursive = 0;
$this->Storage->recursive = -1;
$options = '';
$storage_areas = $this->Storage->find('all',$options);

View File

@ -116,8 +116,15 @@ class Monitor extends AppModel {
'OutputCodec' => array('h264','mjpeg','mpeg1','mpeg2'),
'OutputContainer' => array('auto','mp4','mkv'),
'DefaultView' => array('Events','Control'),
'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'),
#'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'),
)
);
public $hasOne = array(
'Monitor_Status' => array(
'className' => 'Monitor_Status',
'foreignKey' => 'MonitorId',
'joinTable' => 'Monitor_Status',
)
);
}

View File

@ -0,0 +1,59 @@
<?php
App::uses('AppModel', 'Model');
/**
* Monitor_Status Model
*
* @property Event $Event
* @property Zone $Zone
*/
class Monitor_Status extends AppModel {
/**
* Use table
*
* @var mixed False or table name
*/
public $useTable = 'Monitor_Status';
/**
* Primary key field
*
* @var string
*/
public $primaryKey = 'MonitorId';
/**
* Display field
*
* @var string
*/
public $displayField = 'Status';
public $recursive = -1;
/**
* Validation rules
*
* @var array
*/
public $validate = array(
'MonitorId' => array(
'numeric' => array(
'rule' => array('numeric'),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
);
public $actsAs = array(
'CakePHP-Enum-Behavior.Enum' => array(
'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'),
)
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
}

View File

@ -1168,6 +1168,9 @@ class CakeResponse {
if ($modifiedSince) {
$timeMatches = strtotime($this->modified()) === strtotime($modifiedSince);
}
if (!isset($etagMatches, $timeMatches)) {
return false;
}
$checks = compact('etagMatches', 'timeMatches');
if (empty($checks)) {
return false;

View File

@ -546,9 +546,11 @@ class Event {
}
$filters = array();
$result = dbQuery($sql, $values);
$results = $result->fetchALL();
foreach ( $results as $row ) {
$filters[] = new Event($row);
if ( $result ) {
$results = $result->fetchALL();
foreach ( $results as $row ) {
$filters[] = new Event($row);
}
}
return $filters;
}

View File

@ -95,13 +95,12 @@ class Group {
}
}
} # end if options
$groups = array();
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Group');
foreach ( $results as $row => $obj ) {
$groups[] = $obj;
$results = dbFetchAll($sql, NULL, $values);
if ( $results ) {
return array_map( function($row){ return new Group($row); }, $results );
}
return $groups;
return array();
} # end find()
public static function find_one($parameters = null, $options = null) {

View File

@ -9,24 +9,100 @@ class Monitor {
private $defaults = array(
'Id' => null,
'Name' => '',
'StorageId' => 0,
'ServerId' => 0,
'StorageId' => 0,
'Type' => 'Ffmpeg',
'Function' => 'None',
'Enabled' => 1,
'LinkedMonitors' => null,
'Triggers' => null,
'Device' => '',
'Channel' => 0,
'Format' => '0',
'V4LMultiBuffer' => null,
'V4LCapturesPerFrame' => null,
'Protocol' => null,
'Method' => '',
'Host' => null,
'Port' => '',
'SubPath' => '',
'Path' => null,
'Options' => null,
'User' => null,
'Pass' => null,
// These are NOT NULL default 0 in the db, but 0 is not a valid value. FIXME
'Width' => null,
'Height' => null,
'Colours' => 1,
'Palette' => '0',
'Orientation' => null,
'Deinterlacing' => 0,
'SaveJPEGs' => 3,
'VideoWriter' => '0',
'OutputCodec' => null,
'OutputContainer' => null,
'EncoderParameters' => null,
'RecordAudio' => 0,
'RTSPDescribe' => null,
'Brightness' => -1,
'Contrast' => -1,
'Hue' => -1,
'Colour' => -1,
'EventPrefix' => 'Event-',
'LabelFormat' => null,
'LabelX' => 0,
'LabelY' => 0,
'LabelSize' => 1,
'ImageBufferCount' => 100,
'WarmupCount' => 0,
'PreEventCount' => 0,
'PostEventCount' => 0,
'StreamReplayBuffer' => 0,
'AlarmFrameCount' => 1,
'SectionLength' => 600,
'FrameSkip' => 0,
'AnalysisFPSLimit' => null,
'OutputCodec' => '0',
'Encoder' => 'auto',
'OutputContainer' => 'auto',
'ZoneCount' => 0,
'Triggers' => null,
'MaxFPS' => null,
'AlarmMaxFPS' => null,
'FPSReportIneterval' => 100,
'RefBlencPerc' => 6,
'AlarmRefBlendPerc' => 6,
'Controllable' => 0,
'ControlId' => null,
'ControlDevice' => null,
'ControlAddress' => null,
'AutoStopTimeout' => null,
'TrackMotion' => 0,
'TrackDelay' => null,
'ReturnLocation' => -1,
'ReturnDelay' => null,
'DefaultView' => 'Events',
'DefaultRate' => 100,
'DefaultScale' => 100,
'SignalCheckPoints' => 0,
'SignalCheckColour' => '#0000BE',
'WebColour' => 'red',
'Exif' => 0,
'Sequence' => null,
'TotalEvents' => null,
'TotalEventDiskSpace' => null,
'HourEvents' => null,
'HourEventDiskSpace' => null,
'DayEvents' => null,
'DayEventDiskSpace' => null,
'WeekEvents' => null,
'WeekEventDiskSpace' => null,
'MonthEvents' => null,
'MonthEventDiskSpace' => null,
'ArchivedEvents' => null,
'ArchivedEventDiskSpace' => null,
'ZoneCount' => 0,
'Refresh' => null,
'DefaultCodec' => 'auto',
);
private $status_fields = array(
'AnalysisFPS' => null,

View File

@ -189,7 +189,7 @@ class Storage {
# This isn't a function like this in php, so we have to add up the space used in each event.
if ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) {
if ( $this->{'Type'} == 's3fs' ) {
$this->{'disk_used_space'} = $this->disk_event_space();
$this->{'disk_used_space'} = $this->event_disk_space();
} else {
$path = $this->Path();
if ( file_exists($path) ) {

View File

@ -155,7 +155,7 @@ function generateAuthHash($useRemoteAddr, $force=false) {
$time = time();
$mintime = $time - ( ZM_AUTH_HASH_TTL * 1800 );
if ( $force or ( !isset($_SESSION['AuthHash']) ) or ( $_SESSION['AuthHashGeneratedAt'] < $mintime ) ) {
if ( $force or ( !isset($_SESSION['AuthHash'.$_SESSION['remoteAddr']]) ) or ( $_SESSION['AuthHashGeneratedAt'] < $mintime ) ) {
# Don't both regenerating Auth Hash if an hour hasn't gone by yet
$local_time = localtime();
$authKey = '';
@ -172,7 +172,7 @@ function generateAuthHash($useRemoteAddr, $force=false) {
session_start();
$close_session = 1;
}
$_SESSION['AuthHash'] = $auth;
$_SESSION['AuthHash'.$_SESSION['remoteAddr']] = $auth;
$_SESSION['AuthHashGeneratedAt'] = $time;
session_write_close();
} else {
@ -182,7 +182,7 @@ function generateAuthHash($useRemoteAddr, $force=false) {
#} else {
#Logger::Debug("Using cached auth " . $_SESSION['AuthHash'] ." beacuse generatedat:" . $_SESSION['AuthHashGeneratedAt'] . ' < now:'. $time . ' - ' . ZM_AUTH_HASH_TTL . ' * 1800 = '. $mintime);
} # end if AuthHash is not cached
return $_SESSION['AuthHash'];
return $_SESSION['AuthHash'.$_SESSION['remoteAddr']];
} # end if using AUTH and AUTH_RELAY
return '';
}

View File

@ -98,7 +98,14 @@ function dbLog( $sql, $update=false ) {
}
function dbError( $sql ) {
Error( "SQL-ERR '".$dbConn->errorInfo()."', statement was '".$sql."'" );
global $dbConn;
$error = $dbConn->errorInfo();
if ( ! $error[0] )
return '';
$message = "SQL-ERR '".implode("\n",$dbConn->errorInfo())."', statement was '".$sql."'";
Error($message);
return $message;
}
function dbEscape( $string ) {
@ -136,6 +143,10 @@ function dbQuery( $sql, $params=NULL ) {
Logger::Debug("SQL: $sql values:" . ($params?implode(',',$params):'') );
}
$result = $dbConn->query($sql);
if ( ! $result ) {
Error("SQL: Error preparing $sql: " . $pdo->errorInfo);
return NULL;
}
}
if ( defined('ZM_DB_DEBUG') ) {
if ( $params )

View File

@ -41,13 +41,17 @@ function CORSHeaders() {
# The following is left for future reference/use.
$valid = false;
$Servers = Server::find();
if ( sizeof($Servers) <= 1 ) {
if ( sizeof($Servers) < 1 ) {
# Only need CORSHeaders in the event that there are multiple servers in use.
# ICON: Might not be true. multi-port?
return;
}
foreach( $Servers as $Server ) {
if ( preg_match('/^(https?:\/\/)?'.preg_quote($Server->Hostname(),'/').'/', $_SERVER['HTTP_ORIGIN']) ) {
if (
preg_match('/^(https?:\/\/)?'.preg_quote($Server->Hostname(),'/').'/i', $_SERVER['HTTP_ORIGIN'])
or
preg_match('/^(https?:\/\/)?'.preg_quote($Server->Name(),'/').'/i', $_SERVER['HTTP_ORIGIN'])
) {
$valid = true;
Logger::Debug("Setting Access-Controll-Allow-Origin from " . $_SERVER['HTTP_ORIGIN']);
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
@ -1909,23 +1913,24 @@ function logState() {
# This is an expensive request, as it has to hit every row of the Logs Table
$sql = 'SELECT Level, COUNT(Level) AS LevelCount FROM Logs WHERE Level < '.Logger::INFO.' AND TimeKey > unix_timestamp(now() - interval '.ZM_LOG_CHECK_PERIOD.' second) GROUP BY Level ORDER BY Level ASC';
$counts = dbFetchAll( $sql );
foreach ( $counts as $count ) {
if ( $count['Level'] <= Logger::PANIC )
$count['Level'] = Logger::FATAL;
if ( !($levelCount = $levelCounts[$count['Level']]) ) {
Error( "Unexpected Log level ".$count['Level'] );
next;
}
if ( $levelCount[1] && $count['LevelCount'] >= $levelCount[1] ) {
$state = 'alarm';
break;
} elseif ( $levelCount[0] && $count['LevelCount'] >= $levelCount[0] ) {
$state = 'alert';
$counts = dbFetchAll($sql);
if ( $counts ) {
foreach ( $counts as $count ) {
if ( $count['Level'] <= Logger::PANIC )
$count['Level'] = Logger::FATAL;
if ( !($levelCount = $levelCounts[$count['Level']]) ) {
Error('Unexpected Log level '.$count['Level']);
next;
}
if ( $levelCount[1] && $count['LevelCount'] >= $levelCount[1] ) {
$state = 'alarm';
break;
} elseif ( $levelCount[0] && $count['LevelCount'] >= $levelCount[0] ) {
$state = 'alert';
}
}
}
return( $state );
return $state;
}
function isVector ( &$array ) {
@ -2319,4 +2324,30 @@ function do_post_request($url, $data, $optional_headers = null) {
}
return $response;
}
// The following works around php not being built with semaphore functions.
if (!function_exists('sem_get')) {
function sem_get($key) {
return fopen(__FILE__ . '.sem.' . $key, 'w+');
}
function sem_acquire($sem_id) {
return flock($sem_id, LOCK_EX);
}
function sem_release($sem_id) {
return flock($sem_id, LOCK_UN);
}
}
if( !function_exists('ftok') ) {
function ftok($filename = "", $proj = "") {
if ( empty($filename) || !file_exists($filename) ) {
return -1;
} else {
$filename = $filename . (string) $proj;
for($key = array(); sizeof($key) < strlen($filename); $key[] = ord(substr($filename, sizeof($key), 1)));
return dechex(array_sum($key));
}
}
}
?>

995
web/lang/ba_ba.php Normal file
View File

@ -0,0 +1,995 @@
<?php
//
// ZoneMinder web UK English language file, $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.
//
// ZoneMinder Bosnian Translation by Damir Merdan (merdan.damir@gmail.com)
// Notes for Translators
// 0. Get some credit, put your name in the line above (optional)
// 1. When composing the language tokens in your language you should try and keep to roughly the
// same length text if possible. Abbreviate where necessary as spacing is quite close in a number of places.
// 2. There are four types of string replacement
// a) Simple replacements are words or short phrases that are static and used directly. This type of
// replacement can be used 'as is'.
// b) Complex replacements involve some dynamic element being included and so may require substitution
// or changing into a different order. The token listed in this file will be passed through sprintf as
// a formatting string. If the dynamic element is a number you will usually need to use a variable
// replacement also as described below.
// c) Variable replacements are used in conjunction with complex replacements and involve the generation
// of a singular or plural noun depending on the number passed into the zmVlang function. See the
// the zmVlang section below for a further description of this.
// d) Optional strings which can be used to replace the prompts and/or help text for the Options section
// of the web interface. These are not listed below as they are quite large and held in the database
// so that they can also be used by the zmconfig.pl script. However you can build up your own list
// quite easily from the Config table in the database if necessary.
// 3. The tokens listed below are not used to build up phrases or sentences from single words. Therefore
// you can safely assume that a single word token will only be used in that context.
// 4. In new language files, or if you are changing only a few words or phrases it makes sense from a
// maintenance point of view to include the original language file and override the old definitions rather
// than copy all the language tokens across. To do this change the line below to whatever your base language
// is and uncomment it.
// require_once( 'zm_lang_en_gb.php' );
// You may need to change the character set here, if your web server does not already
// do this by default, uncomment this if required.
//
// Example
// header( "Content-Type: text/html; charset=iso-8859-1" );
// You may need to change your locale here if your default one is incorrect for the
// language described in this file, or if you have multiple languages supported.
// If you do need to change your locale, be aware that the format of this function
// is subtlely different in versions of PHP before and after 4.3.0, see
// http://uk2.php.net/manual/en/function.setlocale.php for details.
// Also be aware that changing the whole locale may affect some floating point or decimal
// arithmetic in the database, if this is the case change only the individual locale areas
// that don't affect this rather than all at once. See the examples below.
// Finally, depending on your setup, PHP may not enjoy have multiple locales in a shared
// threaded environment, if you get funny errors it may be this.
//
// Examples
// setlocale( 'LC_ALL', 'en_GB' ); All locale settings pre-4.3.0
// setlocale( LC_ALL, 'en_GB' ); All locale settings 4.3.0 and after
// setlocale( LC_CTYPE, 'en_GB' ); Character class settings 4.3.0 and after
// setlocale( LC_TIME, 'en_GB' ); Date and time formatting 4.3.0 and after
// Simple String Replacements
$SLANG = array(
'SystemLog' => 'Dnevnik',
'DateTime' => 'Datum/Vrijeme',
'Component' => 'Komponenta',
'Pid' => 'PID',
'Level' => 'Nivo',
'Message' => 'Poruka',
'Line' => 'Linija',
'More' => 'Više',
'Clear' => 'Očisti',
'24BitColour' => '24 bitne boje',
'32BitColour' => '32 bitne boje',
'8BitGrey' => '8 bit siva nijansa',
'Action' => 'Action',
'Actual' => 'Stvarno',
'AddNewControl' => 'Dodaj kontrolu',
'AddNewMonitor' => 'Dodaj monitor',
'AddNewServer' => 'Dodaj novi server',
'AddNewStorage' => 'Dodaj novi disk',
'AddNewUser' => 'Dodaj novog korisnika',
'AddNewZone' => 'Dodaj novu zonu',
'Alarm' => 'Alarm',
'AlarmBrFrames' => 'Alarm<br/>Sličice',
'AlarmFrame' => 'Alarm sličica',
'AlarmFrameCount' => 'Brzina snimanja alarma (u frejmovima)',
'AlarmLimits' => 'Alarm limiti',
'AlarmMaximumFPS' => 'Alarm Max SPS',
'AlarmPx' => 'Alarm Px',
'AlarmRefImageBlendPct' => 'Alarm Reference Image Blend %ge',
'AlarmRGBUnset' => 'Morate postaviti RGB boju za alarm',
'Alert' => 'Uzbuna',
'All' => 'Sve',
'AnalysisFPS' => 'Analiza frejmova',
'AnalysisUpdateDelay' => 'Analysis Update Delay',
'Apply' => 'Primjeni',
'ApplyingStateChange' => 'Primjenjujem promjenu stanja',
'ArchArchived' => 'Samo arhivirano',
'Archive' => 'Arhiva',
'Archived' => 'Ahivirano',
'ArchUnarchived' => 'Samo nearhivirano',
'Area' => 'Oblast',
'AreaUnits' => 'Oblast (px/%)',
'AttrAlarmFrames' => 'Alarm frejmovi',
'AttrArchiveStatus' => 'Status arhive',
'AttrAvgScore' => 'Prosj. score',
'AttrCause' => 'Uzrok',
'AttrStartDate' => 'Pocetni datum',
'AttrEndDate' => 'Krajnji datum',
'AttrStartDateTime' => 'Pocetni Datum/Vrijeme',
'AttrEndDateTime' => 'Krajnji Datum/Vrijeme',
'AttrDiskSpace' => 'Disk prostor',
'AttrDiskBlocks' => 'Disk blokovi',
'AttrDiskPercent' => 'Disk procentualno',
'AttrDuration' => 'Trajanje',
'AttrFrames' => 'Frejmovi',
'AttrId' => 'Id',
'AttrMaxScore' => 'Max. Score',
'AttrMonitorId' => 'ID Kamere',
'AttrMonitorName' => 'Naziv Kamere',
'AttrStorageArea' => 'Storage Area',
'AttrFilterServer' => 'Server Filter je pokrenut na',
'AttrMonitorServer' => 'Server Monitor je pokrenut na',
'AttrStorageServer' => 'Server Hosting Storage',
'AttrStateId' => 'Status',
'AttrName' => 'Naziv',
'AttrNotes' => 'Bilješke',
'AttrSystemLoad' => 'Opterećenje sistema',
'AttrStartTime' => 'Vrijeme početka',
'AttrEndTime' => 'Vrijeme završetka',
'AttrTotalScore' => 'Ukupan score',
'AttrStartWeekday' => 'Početni dan',
'AttrEndWeekday' => 'Krajnji dan',
'Auto' => 'Automatski',
'AutoStopTimeout' => 'Auto Stop Timeout',
'Available' => 'Dostupno',
'AvgBrScore' => 'Avg.<br/>Score',
'Available' => 'Dostupno',
'Background' => 'Pozadina',
'BackgroundFilter' => 'Pokreni filter u pozadini',
'BadAlarmFrameCount' => 'Brojač alarm frejmova mora biti tipa integer počevši od jedan ili više',
'BadAlarmMaxFPS' => 'Max FPS za alarm mora biti pozitivan cjeli broj ili broj sa pomičnim zarezom',
'BadAnalysisFPS' => 'Broj frejmova za analitiku mora pozitivan cjeli broj ili broj sa pomičnim zarezom',
'BadAnalysisUpdateDelay'=> 'Vrijeme zadrške analitike mora biti broj od nula ili više',
'BadChannel' => 'Kanal mora biti postavljen na cjeli broj nula ili više',
'BadDevice' => 'Uredaj mora biti postavljen na validnu vrijednost',
'BadFormat' => 'Format mora biti postavljen na validnu vrijenost',
'BadFPSReportInterval' => 'FPS report interval buffer count must be an integer of 0 or more',
'BadFrameSkip' => 'Frame skip count must be an integer of zero or more',
'BadMotionFrameSkip' => 'Motion Frame skip count must be an integer of zero or more',
'BadHeight' => 'Height must be set to a valid value',
'BadHost' => 'Host must be set to a valid ip address or hostname, do not include http://',
'BadImageBufferCount' => 'Image buffer size must be an integer of 10 or more',
'BadLabelX' => 'Label X co-ordinate must be set to an integer of zero or more',
'BadLabelY' => 'Label Y co-ordinate must be set to an integer of zero or more',
'BadMaxFPS' => 'Maximum FPS must be a positive integer or floating point value',
'BadNameChars' => 'Names may only contain alphanumeric characters plus spaces, hyphen and underscore',
'BadPalette' => 'Palette must be set to a valid value',
'BadColours' => 'Target colour must be set to a valid value',
'BadPath' => 'Path must be set to a valid value',
'BadPort' => 'Port must be set to a valid number',
'BadPostEventCount' => 'Post event image count must be an integer of zero or more',
'BadPreEventCount' => 'Pre event image count must be at least zero, and less than image buffer size',
'BadRefBlendPerc' => 'Reference blend percentage must be a positive integer',
'BadSectionLength' => 'Section length must be an integer of 30 or more',
'BadSignalCheckColour' => 'Signal check colour must be a valid RGB colour string',
'BadStreamReplayBuffer' => 'Stream replay buffer must be an integer of zero or more',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"',
'BadWarmupCount' => 'Warmup frames must be an integer of zero or more',
'BadWebColour' => 'Web colour must be a valid web colour string',
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.',
'BadWidth' => 'Width must be set to a valid value',
'Bandwidth' => 'Propusnost',
'BandwidthHead' => 'propusnost', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
'BlobPx' => 'Blob Px',
'Blobs' => 'Blobs',
'BlobSizes' => 'Blob velicine',
'Brightness' => 'Svjetloća',
'Buffer' => 'Bufer',
'Buffers' => 'Buferi',
'CanAutoFocus' => 'Podržava Auto fokusiranje',
'CanAutoGain' => 'Podržava Auto pojačanje',
'CanAutoIris' => 'Podržava Auto blenda',
'CanAutoWhite' => 'Podržava Auto balans bijel.',
'CanAutoZoom' => 'Podržava Auto zum',
'Cancel' => 'otkaži',
'CancelForcedAlarm' => 'Otkaži prisilni alarm',
'CanFocusAbs' => 'Podržava Abs fokus',
'CanFocus' => 'Podržava Fokus',
'CanFocusCon' => 'Podržava Kontinuirani fokus',
'CanFocusRel' => 'Podržava Relativni fokus',
'CanGainAbs' => 'Podržava Aps. pojačanje',
'CanGain' => 'Podržava Pojačanje ',
'CanGainCon' => 'Podržava Kontinuirano pojačanje',
'CanGainRel' => 'Podržava Relativno pojačanje',
'CanIrisAbs' => 'Podržava Aps. blenda',
'CanIris' => 'Podržava Blenda',
'CanIrisCon' => 'Podržava kontinuirana blenda',
'CanIrisRel' => 'Podržava Relativna blenda',
'CanMoveAbs' => 'Podržava Aps. kretanje',
'CanMove' => 'Podržava Kretanje',
'CanMoveCon' => 'Podržava Kontinuirano kretanje',
'CanMoveDiag' => 'Podržava Dijagonalno kretanje',
'CanMoveMap' => 'Podržava Mapirano kretanje',
'CanMoveRel' => 'Podržava Relativno kretanje',
'CanPan' => 'Podržava Pomak' ,
'CanReset' => 'PodržavaReset',
'CanSetPresets' => 'Podržava presetove',
'CanSleep' => 'Podržava Sleep',
'CanTilt' => 'Podržava nagib',
'CanWake' => 'Podržava Wake',
'CanWhiteAbs' => 'Podržava Aps. balans bijele boje',
'CanWhiteBal' => 'Podržava balans bijel.',
'CanWhite' => 'Podržava bijelu',
'CanWhiteCon' => 'Podržava kont. balans bijele boje',
'CanWhiteRel' => 'Podržava relativ. balans bijele boje',
'CanZoomAbs' => 'Podržava Aps. zoom',
'CanZoom' => 'Podržava Zoom',
'CanZoomCon' => 'Podržava kontinuirani Zoom',
'CanZoomRel' => 'Podržava Relativni zoom',
'CaptureHeight' => 'Visina slike',
'CaptureMethod' => 'Metoda snimanja',
'CaptureResolution' => 'Snimi rezoluciju',
'CapturePalette' => 'Paleta boja',
'CaptureWidth' => 'Širina slike',
'Cause' => 'Uzrok',
'CheckMethod' => 'Metoda provjere alarma',
'ChooseDetectedCamera' => 'Odaberi otkrivenu kameru',
'ChooseFilter' => 'Odaberi filter',
'ChooseLogFormat' => 'Odaberi dugi format',
'ChooseLogSelection' => 'Odaberi dugu selekciju',
'ChoosePreset' => 'Odaberi preset',
'CloneMonitor' => 'Kloniraj',
'Close' => 'Zatvori',
'Colour' => 'Bojs',
'Command' => 'Komanda',
'ConcurrentFilter' => 'Istovremeno pokreni filter',
'Config' => 'Postavke',
'ConfiguredFor' => 'Podešeno za',
'ConfirmDeleteEvents' => 'Sigurni ste da želite izbrisati odabrane događaje?',
'ConfirmPassword' => 'Potvrdi lozinku',
'ConjAnd' => 'i',
'ConjOr' => 'ili',
'Console' => 'Konzola',
'ContactAdmin' => 'Molimo konkatirajte svog administratora za detalje.',
'Continue' => 'Nastavi',
'Contrast' => 'Kontrast',
'ControlAddress' => 'Kontrolna adresa',
'ControlCap' => 'Control Capability',
'ControlCaps' => 'Control Capabilities',
'Control' => 'PTZ kontole',
'ControlDevice' => 'Kontroliši uređaj',
'Controllable' => 'Moguće kontrolisati',
'ControlType' => 'Tipa kontrole',
'Current' => 'Tekuće',
'Cycle' => 'Kruži',
'CycleWatch' => 'Kružni prikaz',
'Day' => 'Dan',
'Debug' => 'Debug',
'DefaultRate' => 'Podrazumjevana stopa',
'DefaultScale' => 'Podrazumjevani razmjer',
'DefaultView' => 'Podrazumjevani prikaz',
'Deinterlacing' => 'Deinterlacing',
'RTSPDescribe' => 'Use RTSP Response Media URL',
'Delay' => 'Zadrška',
'DeleteAndNext' => 'Izbriši &amp; Sljedeće',
'DeleteAndPrev' => 'Izbriši &amp; Preth',
'Delete' => 'Izbriši',
'DeleteSavedFilter' => 'Izbriši spremljeni filter',
'Description' => 'Opis',
'DetectedCameras' => 'Detektovane kamere:',
'DetectedProfiles' => 'Otkriveni profili',
'DeviceChannel' => 'Kanal',
'DeviceFormat' => 'Sistem boja',
'DeviceNumber' => 'Broj uređaja',
'DevicePath' => 'Putanja uređaja',
'Device' => 'Uređaj',
'Devices' => 'Uređaji',
'Dimensions' => 'Dimenzije',
'DisableAlarms' => 'Onemogući alarme',
'Disk' => 'Disk',
'Display' => 'Prikaz',
'Displaying' => 'Prikazujem',
'DonateAlready' => 'Ne, već sam napravio donaciju.',
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
'Donate' => 'Molimo donirajte',
'DonateRemindDay' => 'Ne još, podsjetime za 1 dan',
'DonateRemindHour' => 'Ne još, podsjetime za 1 sat',
'DonateRemindMonth' => 'Ne još, podsjeti me za jedan mjesec',
'DonateRemindNever' => 'Ne, ne želim donirati, nemoj me više podsjećati.',
'DonateRemindWeek' => 'Ne još, podsjeti me za sedam dana.',
'DonateYes' => 'Da, želim da doniram sada.',
'DoNativeMotionDetection'=> 'Nativna detekcija pokreta',
'Download' => 'Preuzmi',
'DuplicateMonitorName' => 'Dupliciraj ime monitora',
'Duration' => 'Trajanje',
'Edit' => 'Uredi',
'EditLayout' => 'Uredi raspored',
'Email' => 'Email',
'EnableAlarms' => 'Omogući alarme',
'Enabled' => 'Omogućeno',
'EnterNewFilterName' => 'Unesi novo ime za filter',
'ErrorBrackets' => 'Greška, provjerite da li imate jednak broj otvorenih i zatvorenih zagrada.',
'Error' => 'Greška',
'ErrorValidValue' => 'Greška, osigurajte se da svi pojmovi imaju valide vrijednosti',
'Etc' => 'itd',
'Event' => 'Događaj',
'EventFilter' => 'Filter događaja',
'EventId' => 'ID događaja',
'EventName' => 'Naziv događaja',
'EventPrefix' => 'Prefiks događaja',
'Events' => 'Događaji',
'Exclude' => 'Isključi',
'Execute' => 'Izvrši',
'ExportDetails' => 'Izvezi detalje o događaju',
'Exif' => 'Umetni EXIF podatke u sliku',
'Export' => 'Izvezi',
'DownloadVideo' => 'Preuzmi video',
'GenerateDownload' => 'Generiši preuzimanje',
'ExportFailed' => 'Izvoz nije uspio',
'ExportFormat' => 'Format za izvoz',
'ExportFormatTar' => 'Tar',
'ExportFormatZip' => 'Zip',
'ExportFrames' => 'Izvezi detalje frejma',
'ExportImageFiles' => 'Izvezi slike',
'ExportLog' => 'Izvezi zapisnik',
'Exporting' => 'Izvozim',
'ExportMiscFiles' => 'Izvezi druge fajlove (ukoliko postoje)',
'ExportOptions' => 'Opcije izvoženja',
'ExportSucceeded' => 'Izvoz uspio',
'ExportVideoFiles' => 'Izvezi video fileove (ukoliko postoje)',
'Far' => 'Far',
'FastForward' => 'Naprijed',
'Feed' => 'Feed',
'Ffmpeg' => 'Ffmpeg',
'File' => 'File',
'FilterArchiveEvents' => 'Arhiviraj pronađeno',
'FilterUpdateDiskSpace' => 'Ažuriraj korišteni prostor na disku',
'FilterDeleteEvents' => 'Izbriši sve pronađeno',
'FilterMoveEvents' => 'Premjesti pronađeno',
'FilterEmailEvents' => 'Pošalji detalje mailom',
'FilterExecuteEvents' => 'Izvrši sljededeću komandu',
'FilterLog' => 'Filtriraj zapis',
'FilterMessageEvents' => 'Message details of all matches',
'FilterPx' => 'Filter Px',
'Filter' => 'Filter',
'Filters' => 'Filteri',
'FilterUnset' => 'Morate navesti širinu i visinu filtera',
'FilterUploadEvents' => 'Učitaj sve događaje',
'FilterVideoEvents' => 'Napravi video',
'First' => 'Prvi',
'FlippedHori' => 'Zaokrenuto horizontalno',
'FlippedVert' => 'Zaokrenuto vertikalno',
'FnNone' => 'nijedan', // Added 2013.08.16.
'FnMonitor' => 'Monitor', // Added 2013.08.16.
'FnModect' => 'Modect', // Added 2013.08.16.
'FnRecord' => 'Record', // Added 2013.08.16.
'FnMocord' => 'Mocord', // Added 2013.08.16.
'FnNodect' => 'Nodect', // Added 2013.08.16.
'Focus' => 'Fokus',
'ForceAlarm' => 'Prisilni alarm',
'Format' => 'Format',
'FPS' => 'fps',
'FPSReportInterval' => 'FPS Report Interval',
'Frame' => 'Frame',
'FrameId' => 'Frame Id',
'FrameRate' => 'Frame Rate',
'Frames' => 'Frejmovi',
'FrameSkip' => 'Preskoči frejm',
'MotionFrameSkip' => 'Motion Frame Skip',
'FTP' => 'FTP',
'Func' => 'Func',
'Function' => 'Funkcija',
'Gain' => 'Pojačanje',
'General' => 'Opšte',
'GenerateVideo' => 'Generiši video',
'GeneratingVideo' => 'Generiši video',
'GoToZoneMinder' => 'Idi na ZoneMinder.com',
'Grey' => 'Siva',
'Group' => 'Grupa',
'Groups' => 'Grupe',
'HasFocusSpeed' => 'Posjeduje brzo fokusiranja',
'HasGainSpeed' => 'Posjeduje brzo pojačanja',
'HasHomePreset' => 'Has Home Preset',
'HasIrisSpeed' => 'Posjeduje brzu blendu',
'HasPanSpeed' => 'Posjeduje brzi pomak',
'HasPresets' => 'Posjeduje pre-setove',
'HasTiltSpeed' => 'Posjeduje brzi nagiba',
'HasTurboPan' => 'Posjeduje turbo pomak',
'HasTurboTilt' => 'Posjeduje turbo nagib',
'HasWhiteSpeed' => 'Posjeduje brzo podeš.bijele',
'HasZoomSpeed' => 'Posjeduje brzi zoom',
'HighBW' => 'High&nbsp;B/W',
'High' => 'veliku',
'Home' => 'Početna',
'Hostname' => 'Hostname',
'Hour' => 'Sat',
'Hue' => 'Nijansa',
'Id' => 'Id',
'Idle' => 'Na čekanju',
'Ignore' => 'Zanemari',
'ImageBufferSize' => 'Veličina slikovnog bufera (u frejmovima)',
'Image' => 'Slika',
'Images' => 'Slike',
'Include' => 'Uključi',
'In' => 'U',
'Inverted' => 'Invertirano',
'Iris' => 'Blenda',
'KeyString' => 'Key String',
'Label' => 'Oznaka',
'Language' => 'Jezik',
'Last' => 'Zadnje',
'Layout' => 'Raspored',
'Libvlc' => 'Libvlc',
'LimitResultsPost' => 'results only', // This is used at the end of the phrase 'Limit to first N results only'
'LimitResultsPre' => 'Limit to first', // This is used at the beginning of the phrase 'Limit to first N results only'
'LinkedMonitors' => 'Povezani monitori',
'List' => 'Popis',
'ListMatches' => 'Prikaži pronađeno',
'Load' => 'Opterećenje',
'Local' => 'Lokalno',
'Log' => 'Zapis',
'Logs' => 'Zapisi',
'Logging' => 'Dnevnik događaja',
'LoggedInAs' => 'Prijavljen kao',
'LoggingIn' => 'Prijavljujem',
'Login' => 'prijava',
'Logout' => 'odjava',
'LowBW' => 'Low&nbsp;B/W',
'Low' => 'nisku',
'Main' => 'Glavno',
'Man' => 'Man',
'Manual' => 'Ručno',
'Mark' => 'Označi',
'MaxBandwidth' => 'Max propusnost',
'MaxBrScore' => 'Max.<br/>Score',
'MaxFocusRange' => 'Max raspon fokusa',
'MaxFocusSpeed' => 'Max brzina fokusa',
'MaxFocusStep' => 'Max korak fokusa',
'MaxGainRange' => 'Max raspon pojačanja',
'MaxGainSpeed' => 'Max brzina pojačanja',
'MaxGainStep' => 'Max korak pojačanja',
'MaximumFPS' => 'Maximum FPS',
'MaxIrisRange' => 'Max raspon blende',
'MaxIrisSpeed' => 'Max brzina blende',
'MaxIrisStep' => 'Max korak blende',
'Max' => 'Max',
'MaxPanRange' => 'Max raspon pomaka',
'MaxPanSpeed' => 'Max brzina pomaka',
'MaxPanStep' => 'Max korak pomaka',
'MaxTiltRange' => 'Max raspon nagiba',
'MaxTiltSpeed' => 'Max brzina nagiba',
'MaxTiltStep' => 'Max korak nagiba',
'MaxWhiteRange' => 'Max raspon bijele',
'MaxWhiteSpeed' => 'Max brzina bijele',
'MaxWhiteStep' => 'Max korak bijele',
'MaxZoomRange' => 'Max raspon zumiranja',
'MaxZoomSpeed' => 'Max brzina zumiranja',
'MaxZoomStep' => 'Max korak zumiranja',
'MediumBW' => 'Medium&nbsp;B/W',
'Medium' => 'srednju',
'MinAlarmAreaLtMax' => 'Min područje alarma mora biti manje od maksimalnog',
'MinAlarmAreaUnset' => 'Morate zadati minimalni broj alarm piksela',
'MinBlobAreaLtMax' => 'Min blob područje mora biti manje od maksimalnog',
'MinBlobAreaUnset' => 'Morate zadati minimalni broj blob piksela',
'MinBlobLtMinFilter' => 'Min blob oblast mora biti manja ili jednaka minimalnoj oblasti filtera',
'MinBlobsLtMax' => 'Min blob mora biti manji od maksimalne',
'MinBlobsUnset' => 'morate zadati minimalni broj blob-ova',
'MinFilterAreaLtMax' => 'Minimalna oblast filtera mora biti manja od maksimalne',
'MinFilterAreaUnset' => 'Morate zadati minimalni broj filter piksela',
'MinFilterLtMinAlarm' => 'Min oblast filtera mora biti manja ili jednaka minimalnoj oblasti alarmne oblasti',
'MinFocusRange' => 'Min raspon fokusiranja',
'MinFocusSpeed' => 'Min brzina fokusiranja',
'MinFocusStep' => 'Min korak fokusiranja',
'MinGainRange' => 'Min raspon pojačanja',
'MinGainSpeed' => 'Min brzina pojačanja',
'MinGainStep' => 'Min korak pojačanja',
'MinIrisRange' => 'Min raspon blende',
'MinIrisSpeed' => 'Min brzina blende',
'MinIrisStep' => 'Min korak blende',
'MinPanRange' => 'Min raspon pomaka',
'MinPanSpeed' => 'Min brzina pomaka',
'MinPanStep' => 'Min korak pomaka',
'MinPixelThresLtMax' => 'Min prag piksela mora biti manji od maksimalnog',
'MinPixelThresUnset' => 'Morate zadati minimalni prag piksela',
'MinTiltRange' => 'Min Tilt Range',
'MinTiltSpeed' => 'Min Tilt Speed',
'MinTiltStep' => 'Min Tilt Step',
'MinWhiteRange' => 'Min raspon bijelog balansa',
'MinWhiteSpeed' => 'Min brzina bijelog balansa',
'MinWhiteStep' => 'Min White Bal. Step',
'MinZoomRange' => 'Min raspon zumiranja',
'MinZoomSpeed' => 'Min brzina zumiranja',
'MinZoomStep' => 'Min korak zumiranja',
'Misc' => 'Razno',
'Mode' => 'Modus',
'MonitorIds' => 'Monitor&nbsp;Ids',
'Monitor' => 'Monitor',
'MonitorPresetIntro' => 'Odaberite odgovarajuće pre-setove sa popisa.<br/><br/>Imajte u vidu da ovo može prepisati bilo koju vrijednost koja postoji za odabrane monitore.<br/><br/>',
'MonitorPreset' => 'Monitor Preset',
'MonitorProbeIntro' => 'Donji popis prikazuje otkrivene analogne i mrežne kamere, te da li se iste već koriste i da li su dostupne.<br/><br/>Odaberite željenu kameru sa donjeg popisa.<br/><br/>Imajte u vidu da ovo može prepisati bilo koju vrijednost koja postoji za odabrane monitore.<br/><br/>',
'MonitorProbe' => 'Detektuj kameru',
'Monitors' => 'Monitori',
'Montage' => 'Montage',
'MontageReview' => 'Montage pregled',
'Month' => 'Mjesec',
'Move' => 'Pomjeri',
'MtgDefault' => 'Podrazumjevano', // Added 2013.08.15.
'Mtg2widgrd' => '2-struka rešetka', // Added 2013.08.15.
'Mtg3widgrd' => '3-struka rešetka', // Added 2013.08.15.
'Mtg4widgrd' => '4-struka rešetka', // Added 2013.08.15.
'Mtg3widgrx' => '3-wide grid, scaled, enlarge on alarm', // Added 2013.08.15.
'MustBeGe' => 'mora biti veće ili jednako',
'MustBeLe' => 'mora biti manje ili jednako',
'MustConfirmPassword' => 'Morate potvrditi lozinku',
'MustSupplyPassword' => 'Morate unjeti lozinku',
'MustSupplyUsername' => 'Morate unjeti korisničko ime',
'Name' => 'Ime',
'Near' => 'Blizu',
'Network' => 'Mreža',
'NewGroup' => 'Nova grupa',
'NewLabel' => 'Nova oznaka',
'New' => 'Novo',
'NewPassword' => 'Nova lozinka',
'NewState' => 'Novi radni modus',
'NewUser' => 'Novi korisnik',
'Next' => 'Sljedeće',
'NoDetectedCameras' => 'Nema otkrivenih kamera',
'NoDetectedProfiles' => 'Nema otkrivenih profila',
'NoFramesRecorded' => 'Nije ništa snimljeno za ovaj događaj',
'NoGroup' => 'Nema grupe',
'NoneAvailable' => 'Nijedno dostupno',
'None' => 'Nijedno',
'No' => 'Ne',
'Normal' => 'Normalno',
'NoSavedFilters' => 'NemaSnimljenihFiltera',
'NoStatisticsRecorded' => 'Nema snimljenih statistika za ovaj događaj',
'Notes' => 'Bilješke',
'NumPresets' => 'Num Presets',
'Off' => 'Isključeno',
'On' => 'Uključeno',
'OnvifProbe' => 'ONVIF detekcija',
'OnvifProbeIntro' => 'The list below shows detected ONVIF cameras and whether they are already being used or available for selection.<br/><br/>Select the desired entry from the list below.<br/><br/>Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.<br/><br/>',
'OnvifCredentialsIntro' => 'Please supply user name and password for the selected camera.<br/>If no user has been created for the camera then the user given here will be created with the given password.<br/><br/>',
'Open' => 'Otvori',
'OpEq' => 'jednako',
'OpGtEq' => 'veće ili jednako od',
'OpGt' => 'veće ',
'OpIn' => 'in set',
'OpLtEq' => 'manje ili jednako od',
'OpLt' => 'manje od',
'OpMatches' => 'matches',
'OpNe' => 'nije jednako',
'OpNotIn' => 'nije u ',
'OpNotMatches' => 'ne poklapa se',
'OpIs' => 'je',
'OpIsNot' => 'nije',
'OptionalEncoderParam' => 'Opcionalni parametri enkodera',
'OptionHelp' => 'Option Help',
'OptionRestartWarning' => 'These changes may not come into effect fully\nwhile the system is running. When you have\nfinished making your changes please ensure that\nyou restart ZoneMinder.',
'Options' => 'Opcije',
'Order' => 'Redosljed',
'OrEnterNewName' => 'ili unesi novo ime',
'Orientation' => 'Orijentacija',
'Out' => 'Izlaz',
'OverwriteExisting' => 'Prepiši preko postojećeg',
'Paged' => 'stranično',
'PanLeft' => 'Pomak lijevo',
'Pan' => 'Pomak',
'PanRight' => 'Pomak desno',
'PanTilt' => 'Pomak/Nagib',
'Parameter' => 'Parametar',
'Password' => 'Lozinka',
'PasswordsDifferent' => 'Nova i potvrđena lozinka se razlikuju',
'Paths' => 'Putanje',
'Pause' => 'Pauza',
'PhoneBW' => 'Telefon&nbsp;B/W',
'Phone' => 'Telefon',
'PixelDiff' => 'Piksel razli.',
'Pixels' => 'pikseli',
'PlayAll' => 'play all',
'Play' => 'Play',
'Plugins' => 'Plugini',
'PleaseWait' => 'Molim čekati',
'Point' => 'Point',
'PostEventImageBuffer' => 'Br. frejmova poslije događaja',
'PreEventImageBuffer' => 'Br. frejmova prije događaja',
'PreserveAspect' => 'Zadrži omjer',
'Preset' => 'Preset',
'Presets' => 'Presets',
'Prev' => 'Preth',
'Privacy' => 'Privatnost',
'PrivacyAbout' => 'O',
'PrivacyAboutText' => 'Since 2002, ZoneMinder has been the premier free and open-source Video Management System (VMS) solution for Linux platforms. ZoneMinder is supported by the community and is managed by those who choose to volunteer their spare time to the project. The best way to improve ZoneMinder is to get involved.',
'PrivacyContact' => 'Konakt',
'PrivacyContactText' => 'Please contact us <a href="https://zoneminder.com/contact/">here</a> for any questions regarding our privacy policy or to have your information removed.<br><br>For support, there are three primary ways to engage with the community:<ul><li>The ZoneMinder <a href="https://forums.zoneminder.com/">user forum</a></li><li>The ZoneMinder <a href="https://zoneminder-chat.herokuapp.com/">Slack channel</a></li><li>The ZoneMinder <a href="https://github.com/ZoneMinder/zoneminder/issues">Github forum</a></li></ul><p>Our Github forum is only for bug reporting. Please use our user forum or slack channel for all other questions or comments.</p>',
'PrivacyCookies' => 'Kolačići',
'PrivacyCookiesText' => 'Whether you use a web browser or a mobile app to communicate with the ZoneMinder server, a ZMSESSID cookie is created on the client to uniquely identify a session with the ZoneMinder server. ZmCSS and zmSkin cookies are created to remember your style and skin choices.',
'PrivacyTelemetry' => 'Telemetry',
'PrivacyTelemetryText' => 'Because ZoneMinder is open-source, anyone can install it without registering. This makes it difficult to answer questions such as: how many systems are out there, what is the largest system out there, what kind of systems are out there, or where are these systems located? Knowing the answers to these questions, helps users who ask us these questions, and it helps us set priorities based on the majority user base.',
'PrivacyTelemetryList' => 'The ZoneMinder Telemetry daemon collects the following data about your system:<ul><li>A unique identifier (UUID) <li>City based location is gathered by querying <a href="https://ipinfo.io/geo">ipinfo.io</a>. City, region, country, latitude, and longitude parameters are saved. The latitude and longitude coordinates are accurate down to the city or town level only!<li>Current time<li>Total number of monitors<li>Total number of events<li>System architecture<li>Operating system kernel, distro, and distro version<li>Version of ZoneMinder<li>Total amount of memory<li>Number of cpu cores</ul>',
'PrivacyMonitorList' => 'The following configuration parameters from each monitor are collected:<ul><li>Id<li>Name<li>Type<li>Function<li>Width<li>Height<li>Colours<li>MaxFPS<li>AlarmMaxFPS</ul>',
'PrivacyConclusionText' => 'We are <u>NOT</u> collecting any image specific data from your cameras. We don<6F>t know what your cameras are watching. This data will not be sold or used for any purpose not stated herein. By clicking accept, you agree to send us this data to help make ZoneMinder a better product. By clicking decline, you can still freely use ZoneMinder and all its features.',
'Probe' => 'Detektuj kameru',
'ProfileProbe' => 'Stream proba',
'ProfileProbeIntro' => 'The list below shows the existing stream profiles of the selected camera .<br/><br/>Select the desired entry from the list below.<br/><br/>Please note that ZoneMinder cannot configure additional profiles and that choosing a camera here may overwrite any values you already have configured for the current monitor.<br/><br/>',
'Progress' => 'Napredak',
'Protocol' => 'Protkol',
'Rate' => 'Stopa',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // added Sep 24 2015 - PP
'RecordAudio' => 'Whether to store the audio stream when saving an event.',
'Real' => 'Stvarno',
'Record' => 'Snimaj',
'RefImageBlendPct' => 'Reference Image Blend %ge',
'Refresh' => 'Osvježi',
'RemoteHostName' => 'Naziv uređaja',
'RemoteHostPath' => 'Putanja',
'RemoteHostSubPath' => 'Pod-putanja',
'RemoteHostPort' => 'Port',
'RemoteImageColours' => 'Boje slike',
'RemoteMethod' => 'Metoda',
'RemoteProtocol' => 'Protokol',
'Remote' => 'Udaljeno',
'Rename' => 'Preimenuj',
'ReplayAll' => 'Svi događaji',
'ReplayGapless' => 'Gapless Events',
'Replay' => 'Ponovo odigraj',
'ReplaySingle' => 'Jedan događaj',
'ReportEventAudit' => 'Audit Events Report',
'ResetEventCounts' => 'Resetiraj događaje',
'Reset' => 'Reset',
'Restarting' => 'Restartiram',
'Restart' => 'Restaruj',
'RestrictedCameraIds' => 'Restricted Camera Ids',
'RestrictedMonitors' => 'Ograničeni monitori',
'ReturnDelay' => 'Vrati kašnjenje',
'ReturnLocation' => 'Vrati lokaciju',
'Rewind' => 'Premotaj',
'RotateLeft' => 'Rotoraj ulijevo',
'RotateRight' => 'Rotiraj udesno',
'RTSPTransport' => 'RTSP Transport Protocol',
'RunAudit' => 'Run Audit Process',
'RunLocalUpdate' => 'Pokrenite zmupdate.pl za ažuriranje',
'RunMode' => 'Modus rada',
'Running' => 'Pokrenuto',
'RunState' => 'Radni modus',
'RunStats' => 'Pokreni stats proces',
'RunTrigger' => 'Pokreni triger proces',
'SaveAs' => 'Spremi kao',
'SaveFilter' => 'Spremi Filter',
'SaveJPEGs' => 'Spremi JPEGs',
'Save' => 'Spremi',
'Scale' => 'Razmjer',
'Score' => 'Zbir',
'Secs' => 'Secs',
'Sectionlength' => 'Odaberi dužinu',
'SelectMonitors' => 'SOdaberi monitore',
'Select' => 'Odaberi',
'SelectFormat' => 'Odaberi format',
'SelectLog' => 'Odaberi zapis',
'SelfIntersecting' => 'Polygon edges must not intersect',
'SetNewBandwidth' => 'Postavi propusnost na',
'SetPreset' => 'Postavi pozicije',
'Set' => 'Postavi',
'Settings' => 'Postavke',
'ShowFilterWindow' => 'Prikaži prozor za filter',
'ShowTimeline' => 'Prikaži vremensku liniju',
'SignalCheckColour' => 'Signal Check Colour',
'SignalCheckPoints' => 'Signal Check Points',
'Size' => 'Veličina',
'SkinDescription' => 'Izmjeni izgled za ovu sesiju',
'CSSDescription' => 'Izmjeni css za ovu sesiju',
'Sleep' => 'Sleep',
'SortAsc' => 'Rastuće',
'SortBy' => 'Sortiraj po',
'SortDesc' => 'Padajuće',
'Source' => 'Izvor',
'SourceColours' => 'Source Colours',
'SourcePath' => 'Putanja izvora ',
'SourceType' => 'Izvor videa',
'SpeedHigh' => 'Velika brzina',
'SpeedLow' => 'Niska brzina',
'SpeedMedium' => 'Srednja brzina',
'Speed' => 'brzina',
'SpeedTurbo' => 'Turbo brzina',
'Start' => 'Start',
'State' => 'Stanje',
'Stats' => 'Statistka',
'Status' => 'Status',
'StatusUnknown' => 'Nepoznato',
'StatusConnected' => 'Snimam',
'StatusNotRunning' => 'Nije pokrenuto',
'StatusRunning' => 'Ne snima',
'StepBack' => 'Korak nazad',
'StepForward' => 'Korak naprijed',
'StepLarge' => 'Veliki korak',
'StepMedium' => 'Srednji korak',
'StepNone' => 'Bez koraka',
'StepSmall' => 'Mali korak',
'Step' => 'Korak',
'Stills' => 'Stills',
'Stopped' => 'Zaustavljeno',
'Stop' => 'Zaustavi',
'StorageArea' => 'Storage Area',
'StorageDoDelete' => 'Brisanja',
'StorageScheme' => 'Šema',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Stream' => 'Stream',
'Submit' => 'Pošalji',
'System' => 'Sistem',
'TargetColorspace' => 'Rezolucija boja',
'Tele' => 'Udaljeno',
'Thumbnail' => 'Sličica',
'Tilt' => 'Tilt',
'TimeDelta' => 'Vremenska razlika',
'Timeline' => 'Vremenska linija',
'TimelineTip1' => 'Pass your mouse over the graph to view a snapshot image and event details.', // Added 2013.08.15.
'TimelineTip2' => 'Click on the coloured sections of the graph, or the image, to view the event.', // Added 2013.08.15.
'TimelineTip3' => 'Click on the background to zoom in to a smaller time period based around your click.', // Added 2013.08.15.
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'TimestampLabelFormat' => 'Timestamp format oznake',
'TimestampLabelX' => 'Timestamp oznaka X',
'TimestampLabelY' => 'Timestamp oznaka Y',
'TimestampLabelSize' => 'Veličina fonta',
'Timestamp' => 'Timestamp',
'TimeStamp' => 'Vremenski pečat',
'Time' => 'Vrijeme',
'Today' => 'Danas',
'Tools' => 'Alati',
'Total' => 'Ukupno',
'TotalBrScore' => 'Total<br/>Score',
'TrackDelay' => 'Kašnjenje',
'TrackMotion' => 'Prati pokret',
'Triggers' => 'Okidači',
'TurboPanSpeed' => 'Turbo Pan brzina',
'TurboTiltSpeed' => 'Turbo Tilt brzina',
'Type' => 'Tip',
'Unarchive' => 'Dearhiviraj',
'Undefined' => 'Nedefinisano',
'Units' => 'Mjere',
'Unknown' => 'Nepoznato',
'UpdateAvailable' => 'Dostupno je novo ažurranje za Zoneminder .',
'UpdateNotNecessary' => 'Ažuriranje nije potrebno.',
'Update' => 'Ažuiriaj',
'Upload' => 'Upload',
'Updated' => 'Ažurirano',
'UsedPlugins' => 'Korišteni plugini ',
'UseFilterExprsPost' => '&nbsp;filter&nbsp;expressions', // This is used at the end of the phrase 'use N filter expressions'
'UseFilterExprsPre' => 'Use&nbsp;', // This is used at the beginning of the phrase 'use N filter expressions'
'UseFilter' => 'Koristi filter',
'Username' => 'Korisničko ime',
'Users' => 'Korisnici',
'User' => 'Korisnik',
'Value' => 'Vrijednost',
'VersionIgnore' => 'Ignoriši ovu verziju',
'VersionRemindDay' => 'Podsjeti me za jedan dan',
'VersionRemindHour' => 'Podsjeti me za jedan sat',
'VersionRemindNever' => 'Ne podsjecaj me na nove verzije',
'VersionRemindWeek' => 'Podsjeti me za sedam dana',
'Version' => 'Verzija',
'VideoFormat' => 'Video Format',
'VideoGenFailed' => 'Generisanje videa nije uspjelo!',
'VideoGenFiles' => 'Postojece video datoteke',
'VideoGenNoFiles' => 'Video datoteke nisu pronadjene',
'VideoGenParms' => 'Parametri za generisanje videa',
'VideoGenSucceeded' => 'Generisanje videa uspjelo!',
'VideoSize' => 'Velicina videa',
'VideoWriter' => 'Video pisac',
'Video' => 'Video',
'ViewAll' => 'Pregledaj sve',
'ViewEvent' => 'Pregled događaja',
'ViewPaged' => 'Stanični pregled',
'View' => 'Pregled',
'V4L' => 'V4L',
'V4LCapturesPerFrame' => 'Snimci po frejmu',
'V4LMultiBuffer' => 'Višestr. bafer',
'Wake' => 'Budi',
'WarmupFrames' => 'Warmup frejmovi',
'Watch' => 'Gledaj',
'WebColour' => 'Web boja',
'Web' => 'Web',
'WebSiteUrl' => 'URL web stranice',
'Week' => 'Sedmica',
'WhiteBalance' => 'Balans bijele',
'White' => 'Bijelo',
'Wide' => 'Široko',
'X10ActivationString' => 'X10 znakovni niz za aktiviranje',
'X10InputAlarmString' => 'X10 ulazni znakovni niz za alarm',
'X10OutputAlarmString' => 'X10 izlazni znakovni niz za alarm',
'X10' => 'X10',
'X' => 'X',
'Yes' => 'Da',
'YouNoPerms' => 'Nemate potrebne dozvole za pristup ovom resursu.',
'Y' => 'Y',
'ZoneAlarmColour' => 'Boja alarma (Red/Green/Blue)',
'ZoneArea' => 'Oblast zone',
'ZoneFilterSize' => 'Filter Width/Height (pixels)',
'ZoneMinderLog' => 'ZoneMinder zapisnik',
'ZoneMinMaxAlarmArea' => 'Min/Max alarmirana oblast',
'ZoneMinMaxBlobArea' => 'Min/Max blob oblast',
'ZoneMinMaxBlobs' => 'Min/Max Blobovi',
'ZoneMinMaxFiltArea' => 'Min/Max filtrirane oblasti',
'ZoneMinMaxPixelThres' => 'Min/Max Pixel Threshold (0-255)',
'ZoneOverloadFrames' => 'Overload Frame Ignore Count',
'ZoneExtendAlarmFrames' => 'Extend Alarm Frame Count',
'Zones' => 'Zone',
'Zone' => 'Zona',
'ZoomIn' => 'Zoom In',
'ZoomOut' => 'Zoom Out',
'Zoom' => 'Zumiranje',
);
// Complex replacements with formatting and/or placements, must be passed through sprintf
$CLANG = array(
'CurrentLogin' => 'Prijavljeni ste kao \'%1$s\'',
'EventCount' => '%1$s %2$s', // For example '37 Events' (from Vlang below)
'LastEvents' => 'Last %1$s %2$s', // For example 'Last 37 Events' (from Vlang below)
'LatestRelease' => 'Zadnja verzija servera je v%1$s, vi imate v%2$s.',
'MonitorCount' => '%1$s %2$s', // For example '4 Monitors' (from Vlang below)
'MonitorFunction' => 'Monitor %1$s Function',
'RunningRecentVer' => 'Koristite najnoviju verziju Zoneminder servera, v%s.',
'VersionMismatch' => 'Version mismatch, system is version %1$s, database is %2$s.',
);
// The next section allows you to describe a series of word ending and counts used to
// generate the correctly conjugated forms of words depending on a count that is associated
// with that word.
// This intended to allow phrases such a '0 potatoes', '1 potato', '2 potatoes' etc to
// conjugate correctly with the associated count.
// In some languages such as English this is fairly simple and can be expressed by assigning
// a count with a singular or plural form of a word and then finding the nearest (lower) value.
// So '0' of something generally ends in 's', 1 of something is singular and has no extra
// ending and 2 or more is a plural and ends in 's' also. So to find the ending for '187' of
// something you would find the nearest lower count (2) and use that ending.
//
// So examples of this would be
// $zmVlangPotato = array( 0=>'Potatoes', 1=>'Potato', 2=>'Potatoes' );
// $zmVlangSheep = array( 0=>'Sheep' );
//
// where you can have as few or as many entries in the array as necessary
// If your language is similar in form to this then use the same format and choose the
// appropriate zmVlang function below.
// If however you have a language with a different format of plural endings then another
// approach is required . For instance in Russian the word endings change continuously
// depending on the last digit (or digits) of the numerator. In this case then zmVlang
// arrays could be written so that the array index just represents an arbitrary 'type'
// and the zmVlang function does the calculation about which version is appropriate.
//
// So an example in Russian might be (using English words, and made up endings as I
// don't know any Russian!!)
// 'Potato' => array( 1=>'Potati', 2=>'Potaton', 3=>'Potaten' ),
//
// and the zmVlang function decides that the first form is used for counts ending in
// 0, 5-9 or 11-19 and the second form when ending in 1 etc.
//
// Variable arrays expressing plurality, see the zmVlang description above
$VLANG = array(
'Event' => array( 0=>'Events', 1=>'Event', 2=>'Events' ),
'Monitor' => array( 0=>'Monitors', 1=>'Monitor', 2=>'Monitors' ),
);
// You will need to choose or write a function that can correlate the plurality string arrays
// with variable counts. This is used to conjugate the Vlang arrays above with a number passed
// in to generate the correct noun form.
//
// In languages such as English this is fairly simple
// Note this still has to be used with printf etc to get the right formatting
function zmVlang( $langVarArray, $count )
{
krsort( $langVarArray );
foreach ( $langVarArray as $key=>$value )
{
if ( abs($count) >= $key )
{
return( $value );
}
}
die( 'Error, unable to correlate variable language string' );
}
// This is an version that could be used in the Russian example above
// The rules are that the first word form is used if the count ends in
// 0, 5-9 or 11-19. The second form is used then the count ends in 1
// (not including 11 as above) and the third form is used when the
// count ends in 2-4, again excluding any values ending in 12-14.
//
// function zmVlang( $langVarArray, $count )
// {
// $secondlastdigit = substr( $count, -2, 1 );
// $lastdigit = substr( $count, -1, 1 );
// // or
// // $secondlastdigit = ($count/10)%10;
// // $lastdigit = $count%10;
//
// // Get rid of the special cases first, the teens
// if ( $secondlastdigit == 1 && $lastdigit != 0 )
// {
// return( $langVarArray[1] );
// }
// switch ( $lastdigit )
// {
// case 0 :
// case 5 :
// case 6 :
// case 7 :
// case 8 :
// case 9 :
// {
// return( $langVarArray[1] );
// break;
// }
// case 1 :
// {
// return( $langVarArray[2] );
// break;
// }
// case 2 :
// case 3 :
// case 4 :
// {
// return( $langVarArray[3] );
// break;
// }
// }
// die( 'Error, unable to correlate variable language string' );
// }
// This is an example of how the function is used in the code which you can uncomment and
// use to test your custom function.
//$monitors = array();
//$monitors[] = 1; // Choose any number
//echo sprintf( $CLANG['MonitorCount'], count($monitors), zmVlang( $VLANG['VlangMonitor'], count($monitors) ) );
// In this section you can override the default prompt and help texts for the options area
// These overrides are in the form show below where the array key represents the option name minus the initial ZM_
// So for example, to override the help text for ZM_LANG_DEFAULT do
$OLANG = array(
'OPTIONS_FFMPEG' => array(
'Help' => "Parameters in this field are passed on to FFmpeg. Multiple parameters can be separated by ,~~ ".
"Examples (do not enter quotes)~~~~".
"\"allowed_media_types=video\" Set datatype to request fromcam (audio, video, data)~~~~".
"\"reorder_queue_size=nnn\" Set number of packets to buffer for handling of reordered packets~~~~".
"\"loglevel=debug\" Set verbosity of FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug)"
),
'OPTIONS_RTSPTrans' => array(
'Help' => "This sets the RTSP Transport Protocol for FFmpeg.~~ ".
"TCP - Use TCP (interleaving within the RTSP control channel) as transport protocol.~~".
"UDP - Use UDP as transport protocol. Higher resolution cameras have experienced some 'smearing' while using UDP, if so try TCP~~".
"UDP Multicast - Use UDP Multicast as transport protocol~~".
"HTTP - Use HTTP tunneling as transport protocol, which is useful for passing proxies.~~"
),
'OPTIONS_LIBVLC' => array(
'Help' => "Parameters in this field are passed on to libVLC. Multiple parameters can be separated by ,~~ ".
"Examples (do not enter quotes)~~~~".
"\"--rtp-client-port=nnn\" Set local port to use for rtp data~~~~".
"\"--verbose=2\" Set verbosity of libVLC"
),
'OPTIONS_EXIF' => array(
'Help' => "Enable this option to embed EXIF data into each jpeg frame."
),
'OPTIONS_RTSPDESCRIBE' => array(
'Help' => "Sometimes, during the initial RTSP handshake, the camera will send an updated media URL. ".
"Enable this option to tell ZoneMinder to use this URL. Disable this option to ignore the ".
"value from the camera and use the value as entered in the monitor configuration~~~~".
"Generally this should be enabled. However, there are cases where the camera can get its".
"own URL incorrect, such as when the camera is streaming through a firewall"),
'OPTIONS_MAXFPS' => array(
'Help' => "This field has certain limitations when used for non-local devices.~~ ".
"Failure to adhere to these limitations will cause a delay in live video, irregular frame skipping, ".
"and missed events~~".
"For streaming IP cameras, do not use this field to reduce the frame rate. Set the frame rate in the".
" camera, instead. You can, however, use a value that is slightly higher than the frame rate in the camera. ".
"In this case, this helps keep the cpu from being overtaxed in the event of a network problem.~~".
"Some, mostly older, IP cameras support snapshot mode. In this case ZoneMinder is actively polling the camera ".
"for new images. In this case, it is safe to use the field."
),
// 'LANG_DEFAULT' => array(
// 'Prompt' => "This is a new prompt for this option",
// 'Help' => "This is some new help for this option which will be displayed in the popup window when the ? is clicked"
// ),
);
?>

View File

@ -237,6 +237,7 @@ $SLANG = array(
'Cause' => 'Cause',
'CheckMethod' => 'Alarm Check Method',
'ChooseDetectedCamera' => 'Choose Detected Camera',
'ChooseDetectedProfile' => 'Choose Detected Profile',
'ChooseFilter' => 'Choose Filter',
'ChooseLogFormat' => 'Choose a log format',
'ChooseLogSelection' => 'Choose a log selection',

View File

@ -192,7 +192,7 @@ echo output_link_if_exists( array(
<script src="<?php echo cache_bust($skinJsFile) ?>"></script>
<script src="js/logger.js"></script>
<?php
if ($basename == 'watch') {
if ($basename == 'watch' or $basename == 'log' ) {
// This is used in the log popup for the export function. Not sure if it's used anywhere else
?>
<script type="text/javascript" src="js/overlay.js"></script>
@ -345,7 +345,7 @@ if ($reload == 'reload') ob_start();
<?php
$connections = dbFetchOne( "SHOW status WHERE variable_name='threads_connected'", 'Value' );
$max_connections = dbFetchOne( "SHOW variables WHERE variable_name='max_connections'", 'Value' );
$percent_used = 100 * $connections / $max_connections;
$percent_used = $max_connections ? 100 * $connections / $max_connections : 100;
echo '<li'. ( $percent_used > 90 ? ' class="warning"' : '' ).'>'.translate('DB').':'.$connections.'/'.$max_connections.'</li>';
?>
<li><?php echo translate('Storage') ?>:

View File

@ -40,7 +40,7 @@ var popupSizes = {
'export': { 'width': 400, 'height': 340 },
'filter': { 'width': 900, 'height': 700 },
'frame': { 'addWidth': 32, 'minWidth': 384, 'addHeight': 200 },
'frames': { 'width': 600, 'height': 600 },
'frames': { 'addWidth': 600, 'addHeight': 600 },
'function': { 'width': 350, 'height': 260 },
'group': { 'width': 760, 'height': 600 },
'groups': { 'width': 540, 'height': 420 },

View File

@ -55,7 +55,7 @@ if ( ! empty($refreshParent) ) {
?>;
var closePopup = <?php
if ( ( ! empty($closePopup) ) and ( $closePopup == true ) ) {
echo 'true';
echo 'true';
} else {
echo 'false';
}
@ -67,5 +67,5 @@ var imagePrefix = "<?php echo "?view=image&eid=" ?>";
var auth_hash;
<?php if ( ZM_OPT_USE_AUTH && ZM_AUTH_HASH_LOGINS ) { ?>
auth_hash = '<?php echo isset($_SESSION['AuthHash']) ? $_SESSION['AuthHash'] : ''; ?>';
auth_hash = '<?php echo generateAuthHash(ZM_AUTH_HASH_IPS) ?>';
<?php } ?>

View File

@ -321,7 +321,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
?>
<td class="colMark">
<input type="checkbox" name="markMids[]" value="<?php echo $monitor['Id'] ?>" onclick="setButtonStates( this )"<?php if ( !canEdit( 'Monitors' ) ) { ?> disabled="disabled"<?php } ?>/>
<span class="glyphicon glyphicon-sort"></span>
<span class="glyphicon glyphicon-sort" title="Click and drag to change order"></span>
</td>
<?php
}

View File

@ -52,10 +52,28 @@ if (isset($_REQUEST['scale'])) {
$scale = reScale( SCALE_BASE, $Monitor->DefaultScale(), ZM_WEB_DEFAULT_SCALE );
}
$codec = 'auto';
if (isset($_REQUEST['codec'])) {
$codec = $_REQUEST['codec'];
session_start();
$_SESSION['zmEventCodec'.$Event->MonitorId()] = $codec;
session_write_close();
} else if ( isset( $_SESSION['zmEventCodec'.$Event->MonitorId()] ) ) {
$codec = $_SESSION['zmEventCodec'.$Event->MonitorId()];
} else {
$codec = $Monitor->DefaultCodec();
}
$codecs = array(
'auto' => translate('Auto'),
'H264' => translate('H264'),
'H265' => translate('H265'),
'MJPEG' => translate('MJPEG'),
);
$replayModes = array(
'none' => translate('None'),
'single' => translate('ReplaySingle'),
'all' => translate('ReplayAll'),
'none' => translate('None'),
'single' => translate('ReplaySingle'),
'all' => translate('ReplayAll'),
'gapless' => translate('ReplayGapless'),
);
@ -75,7 +93,7 @@ if ( ( ! $replayMode ) or ( ! $replayModes[$replayMode] ) ) {
}
$video_tag = false;
if ( $Event->DefaultVideo() and ( 'mp4' == pathinfo($Event->DefaultVideo(), PATHINFO_EXTENSION) ) ) {
if ( $Event->DefaultVideo() and ( 'mp4' == pathinfo($Event->DefaultVideo(), PATHINFO_EXTENSION) ) && ( $codec == 'H264' || $codec == 'auto' ) ) {
$video_tag = true;
}
// videojs zoomrotate only when direct recording
@ -96,7 +114,7 @@ $connkey = generateConnKey();
$focusWindow = true;
$popup = ((isset($_REQUEST['popup'])) && ($_REQUEST['popup'] = 1));
$popup = (isset($_REQUEST['popup']) && ($_REQUEST['popup'] == 1));
xhtmlHeaders(__FILE__, translate('Event') );
?>
@ -123,22 +141,22 @@ if ( ! $Event->Id() ) {
<div id="menuBar1">
<div id="nameControl">
<input type="text" id="eventName" name="eventName" value="<?php echo validHtmlStr($Event->Name()) ?>" />
<button value="Rename" onclick="renameEvent()"<?php if ( !canEdit( 'Events' ) ) { ?> disabled="disabled"<?php } ?>>
<button value="Rename" type="button" onclick="renameEvent()"<?php if ( !canEdit('Events') ) { ?> disabled="disabled"<?php } ?>>
<?php echo translate('Rename') ?></button>
</div>
<?php
if ( canEdit('Events') ) {
?>
<div id="deleteEvent"><button onclick="deleteEvent()"><?php echo translate('Delete') ?></button></div>
<div id="editEvent"><button onclick="editEvent()"><?php echo translate('Edit') ?></button></div>
<div id="archiveEvent"<?php echo $Event->Archived == 1 ? ' class="hidden"' : '' ?>><button onclick="archiveEvent()"><?php echo translate('Archive') ?></button></div>
<div id="unarchiveEvent"<?php echo $Event->Archived == 0 ? ' class="hidden"' : '' ?>><button onclick="unarchiveEvent()"><?php echo translate('Unarchive') ?></button></div>
<div id="deleteEvent"><button type="button" onclick="deleteEvent()"><?php echo translate('Delete') ?></button></div>
<div id="editEvent"><button type="button" onclick="editEvent()"><?php echo translate('Edit') ?></button></div>
<div id="archiveEvent"<?php echo $Event->Archived == 1 ? ' class="hidden"' : '' ?>><button type="button" onclick="archiveEvent()"><?php echo translate('Archive') ?></button></div>
<div id="unarchiveEvent"<?php echo $Event->Archived == 0 ? ' class="hidden"' : '' ?>><button type="button" onclick="unarchiveEvent()"><?php echo translate('Unarchive') ?></button></div>
<?php
} // end if can edit Events
?>
<div id="framesEvent"><button onclick="showEventFrames()"><?php echo translate('Frames') ?></button></div>
<div id="framesEvent"><button type="button" onclick="showEventFrames()"><?php echo translate('Frames') ?></button></div>
<div id="streamEvent" class="hidden"><button onclick="showStream()"><?php echo translate('Stream') ?></button></div>
<div id="stillsEvent"><button onclick="showStills()"><?php echo translate('Stills') ?></button></div>
<div id="stillsEvent"><button type="button" onclick="showStills()"><?php echo translate('Stills') ?></button></div>
<?php
if ( $Event->DefaultVideo() ) {
?>
@ -146,13 +164,14 @@ if ( canEdit('Events') ) {
<?php
} else {
?>
<div id="videoEvent"><button onclick="videoEvent();"><?php echo translate('Video') ?></button></div>
<div id="videoEvent"><button type="button" onclick="videoEvent();"><?php echo translate('Video') ?></button></div>
<?php
} // end if Event->DefaultVideo
?>
<div id="exportEvent"><button onclick="exportEvent();"><?php echo translate('Export') ?></button></div>
<div id="exportEvent"><button type="button" onclick="exportEvent();"><?php echo translate('Export') ?></button></div>
<div id="replayControl"><label for="replayMode"><?php echo translate('Replay') ?></label><?php echo buildSelect( "replayMode", $replayModes, "changeReplayMode();" ); ?></div>
<div id="scaleControl"><label for="scale"><?php echo translate('Scale') ?></label><?php echo buildSelect( "scale", $scales, "changeScale();" ); ?></div>
<div id="codecControl"><label for="codec"><?php echo translate('Codec') ?></label><?php echo htmlSelect('codec', $codecs, $codec, array('onchange'=>'changeCodec(this);') ); ?></div>
</div>
</div>
<div id="content">
@ -168,7 +187,7 @@ if ( $video_tag ) {
</video>
</div><!--videoFeed-->
<?php
} // end if DefaultVideo
} else {
?>
<?php if ( !$video_tag ) { ?>
<div id="imageFeed">
@ -218,8 +237,8 @@ if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
<div id="eventImageFrame">
<img id="eventImage" src="graphics/transparent.png" alt=""/>
<div id="eventImageBar">
<div id="eventImageClose"><input type="button" value="<?php echo translate('Close') ?>" onclick="hideEventImage()"/></div>
<div id="eventImageStats" class="hidden"><input type="button" value="<?php echo translate('Stats') ?>" onclick="showFrameStats()"/></div>
<div id="eventImageClose"><button type="button" onclick="hideEventImage()"><?php echo translate('Close') ?></button></div>
<div id="eventImageStats" class="hidden"><button type="button" onclick="showFrameStats()"><?php echo translate('Stats') ?></button></div>
<div id="eventImageData"><?php echo translate('Frame') ?> <span id="eventImageNo"></span></div>
</div>
</div>

View File

@ -18,7 +18,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canView( 'Events' ) ) {
if ( !canView('Events') ) {
$view = 'error';
return;
}
@ -29,7 +29,7 @@ $eid = validInt($_REQUEST['eid']);
if ( !empty($_REQUEST['fid']) )
$fid = validInt($_REQUEST['fid']);
$Event = new Event( $eid );
$Event = new Event($eid);
$Monitor = $Event->Monitor();
if ( !empty($fid) ) {
@ -39,7 +39,7 @@ if ( !empty($fid) ) {
} else {
$frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId = ? AND Score = ?', NULL, array( $eid, $Event->MaxScore() ) );
}
$Frame = new Frame( $frame );
$Frame = new Frame($frame);
$maxFid = $Event->Frames();
@ -91,10 +91,10 @@ xhtmlHeaders(__FILE__, translate('Frame').' - '.$Event->Id()." - ".$Frame->Frame
<?php if ( canEdit( 'Events' ) ) { ?><a href="?view=none&amp;action=delete&amp;markEid=<?php echo $Event->Id() ?>"><?php echo translate('Delete') ?></a><?php } ?>
<a href="#" onclick="closeWindow(); return( false );"><?php echo translate('Close') ?></a>
</div>
<div id="scaleControl"><label for="scale"><?php echo translate('Scale') ?></label><?php echo buildSelect( "scale", $scales, "changeScale();" ); ?></div>
<h2><?php echo translate('Frame') ?> <?php echo $Event->Id()."-".$Frame->FrameId()." (".$Frame->Score().")" ?></h2>
<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(); ?>"/>
<div id="scaleControl"><label for="scale"><?php echo translate('Scale') ?></label><?php echo buildSelect('scale', $scales, 'changeScale();'); ?></div>
<h2><?php echo translate('Frame') ?> <?php echo $Event->Id().'-'.$Frame->FrameId().' ('.$Frame->Score().')' ?></h2>
<input type="hidden" name="base_width" id="base_width" value="<?php echo $Event->Width(); ?>"/>
<input type="hidden" name="base_height" id="base_height" value="<?php echo $Event->Height(); ?>"/>
</form>
</div>
<div id="content">
@ -109,11 +109,13 @@ xhtmlHeaders(__FILE__, translate('Frame').' - '.$Event->Id()." - ".$Frame->Frame
</p>
<p id="controls">
<?php if ( $Frame->FrameId() > 1 ) { ?>
<a id="firstLink" href="?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $firstFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>"><?php echo translate('First') ?></a>
<a id="prevLink" href="?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $prevFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>"><?php echo translate('Prev') ?></a>
<?php } if ( $Frame->FrameId() < $maxFid ) { ?>
<a id="nextLink" href="?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $nextFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>"><?php echo translate('Next') ?></a>
<a id="lastLink" href="?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $lastFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>"><?php echo translate('Last') ?></a>
<button type="button" id="firstLink" onclick="window.location='?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $firstFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>';"><?php echo translate('First') ?></button>
<button type="button" id="prevLink" onclick="window.location='?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $prevFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>';"><?php echo translate('Prev') ?></button>
<?php
}
if ( $Frame->FrameId() < $maxFid ) { ?>
<button type="button" id="nextLink" onclick="window.location='?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $nextFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>';"><?php echo translate('Next') ?></button>
<button type="button" id="lastLink" onclick="window.location='?view=frame&amp;eid=<?php echo $Event->Id() ?>&amp;fid=<?php echo $lastFid ?>&amp;scale=<?php echo $scale ?>&amp;show=<?php echo $show ?>';"><?php echo translate('Last') ?></button>
<?php } ?>
</p>
<?php if (file_exists ($dImagePath)) { ?>

View File

@ -18,11 +18,11 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canView( 'Events' ) ) {
if ( !canView('Events') ) {
$view = 'error';
return;
}
require_once( 'includes/Frame.php' );
require_once('includes/Frame.php');
$Event = new Event( $_REQUEST['eid'] );
$sql = 'SELECT *, unix_timestamp( TimeStamp ) AS UnixTimeStamp FROM Frames WHERE EventID = ? ORDER BY FrameId';
@ -30,7 +30,7 @@ $frames = dbFetchAll( $sql, NULL, array( $_REQUEST['eid'] ) );
$focusWindow = true;
xhtmlHeaders(__FILE__, translate('Frames')." - ".$Event->Id() );
xhtmlHeaders(__FILE__, translate('Frames').' - '.$Event->Id() );
?>
<body>
<div id="page">

View File

@ -142,6 +142,10 @@ function setButtonState( element, butClass ) {
}
}
function changeCodec(element) {
location.replace(thisUrl + '?view=event&eid=' + eventData.Id + filterQuery + sortQuery+'&codec='+element.value);
}
function changeScale() {
let scale = $j('#scale').val();
let newWidth;
@ -887,7 +891,7 @@ function unarchiveEvent() {
}
function showEventFrames() {
createPopup( '?view=frames&eid='+eventData.Id, 'zmFrames', 'frames' );
createPopup( '?view=frames&eid='+eventData.Id, 'zmFrames', 'frames', WEB_LIST_THUMB_WIDTH, WEB_LIST_THUMB_HEIGHT );
}
function showStream() {

View File

@ -55,3 +55,5 @@ var streamMode = '<?php echo $streamMode ?>';
//
var deleteString = "<?php echo translate('Delete') ?>";
var causeString = "<?php echo translate('AttrCause') ?>";
var WEB_LIST_THUMB_WIDTH = '<?php echo ZM_WEB_LIST_THUMB_WIDTH ?>';
var WEB_LIST_THUMB_HEIGHT = '<?php echo ZM_WEB_LIST_THUMB_HEIGHT ?>';

View File

@ -471,7 +471,15 @@ function setAlarmState( currentAlarmState ) {
}
var streamCmdParms = "view=request&request=stream&connkey="+connKey;
var streamCmdReq = new Request.JSON( { url: monitorUrl+thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getStreamCmdResponse } );
if ( auth_hash )
streamCmdParms += '&auth='+auth_hash;
var streamCmdReq = new Request.JSON( {
url: monitorUrl,
method: 'get',
timeout: AJAX_TIMEOUT,
link: 'cancel',
onSuccess: getStreamCmdResponse
} );
var streamCmdTimer = null;
var streamStatus;
@ -546,7 +554,16 @@ function streamCmdQuery() {
}
var statusCmdParms = "view=request&request=status&entity=monitor&id="+monitorId+"&element[]=Status&element[]=FrameRate";
var statusCmdReq = new Request.JSON( { url: monitorUrl+thisUrl, method: 'post', data: statusCmdParms, timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getStatusCmdResponse } );
if ( auth_hash )
statusCmdParms += '&auth='+auth_hash;
var statusCmdReq = new Request.JSON( {
url: monitorUrl,
method: 'get',
data: statusCmdParms,
timeout: AJAX_TIMEOUT,
link: 'cancel',
onSuccess: getStatusCmdResponse
} );
var statusCmdTimer = null;
function getStatusCmdResponse( respObj, respText ) {
@ -652,11 +669,13 @@ function initPage() {
// Imported from watch.js and modified for new zone edit view
//
var delay = (Math.random()+0.1)*statusRefreshTimeout;
//console.log("Delay for status updates is: " + delay );
if ( streamMode == "single" ) {
statusCmdTimer = statusCmdQuery.delay( (Math.random()+0.1)*statusRefreshTimeout );
statusCmdTimer = statusCmdQuery.delay( delay );
watchdogCheck.pass('status').periodical(statusRefreshTimeout*2);
} else {
streamCmdTimer = streamCmdQuery.delay( (Math.random()+0.1)*statusRefreshTimeout );
streamCmdTimer = streamCmdQuery.delay( delay );
watchdogCheck.pass('stream').periodical(statusRefreshTimeout*2);
}

View File

@ -101,7 +101,7 @@ var streamMode = "<?php echo $streamMode ?>";
var connKey = '<?php echo $connkey ?>';
var monitorId = <?php echo $monitor->Id() ?>;
var monitorUrl = '<?php echo ( $monitor->Server()->Url() ) ?>';
var monitorUrl = '<?php echo ( $monitor->Url() ) ?>';
var streamSrc = "<?php echo preg_replace( '/&amp;/', '&', $streamSrc ) ?>";

View File

@ -1,5 +1,5 @@
var streamCmdParms = "view=request&request=stream&connkey="+connKey;
var streamCmdReq = new Request.JSON( { url: monitorUrl+thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel' } );
var streamCmdReq = new Request.JSON( { url: monitorUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel' } );
function streamCmdQuit( action ) {
if ( action )

View File

@ -1,4 +1,4 @@
var connKey = '<?php echo $connkey ?>';
var monitorUrl = '<?php echo ( $monitor->Server()->Url() ) ?>';
var monitorUrl = '<?php echo ( $monitor->Url() ) ?>';
var CMD_QUIT = <?php echo CMD_QUIT ?>;

View File

@ -45,17 +45,16 @@ xhtmlHeaders(__FILE__, translate('SystemLog') );
<tr class="row">
<td class="col text-center">
<div class="btn-group">
<button type="button" class="btn btn-sm" onclick="expandLog()"> <?php echo translate('More') ?></button>
<button type="button" class="btn btn-sm" onclick="clearLog()"> <?php echo translate('Clear') ?></button>
<button type="button" class="btn btn-sm" onclick="refreshLog()"> <?php echo translate('Refresh') ?></button>
<button type="button" class="btn btn-sm" onclick="exportLog()"> <?php echo translate('Export') ?></button>
<button type="button" class="btn btn-sm" onclick="closeWindow()"> <?php echo translate('Close') ?></button>
<button type="button" onclick="expandLog()"><?php echo translate('More') ?></button>
<button type="button" onclick="clearLog()"><?php echo translate('Clear') ?></button>
<button type="button" onclick="refreshLog()"><?php echo translate('Refresh') ?></button>
<button type="button" onclick="exportLog()"><?php echo translate('Export') ?></button>
<button type="button" onclick="closeWindow()"><?php echo translate('Close') ?></button>
</div> <!--btn-->
</td>
</tr>
</table>
</div> <!--header-->
</tr>
</table>
</div> <!--header-->
<div id="content">
<div id="filters">
@ -89,7 +88,7 @@ xhtmlHeaders(__FILE__, translate('SystemLog') );
</td>
</tr>
</table>
<input type="reset" value="<?php echo translate('Reset') ?>" onclick="resetLog()"/>
<button type="reset" onclick="resetLog()"><?php echo translate('Reset') ?></button>
</div>
<form name="logForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
<input type="hidden" name="view" value="<?php echo $view ?>"/>
@ -137,8 +136,8 @@ xhtmlHeaders(__FILE__, translate('SystemLog') );
<div id="exportError">
<?php echo translate('ExportFailed') ?>: <span id="exportErrorText"></span>
</div>
<input type="button" id="exportButton" value="<?php echo translate('Export') ?>" onclick="exportRequest()"/>
<input type="button" value="<?php echo translate('Cancel') ?>" class="overlayCloser"/>
<button type="button" id="exportButton" value="Export" onclick="exportRequest()"><?php echo translate('Export') ?></button>
<button type="button" value="Cancel" class="overlayCloser"><?php echo translate('Cancel') ?></button>
</form>
</div>
</div>

View File

@ -116,6 +116,7 @@ if ( ! $monitor ) {
'DefaultView' => 'Events',
'DefaultRate' => '100',
'DefaultScale' => '100',
'DefaultCodec' => 'auto',
'SignalCheckPoints' => '10',
'SignalCheckColour' => '#0000c0',
'WebColour' => 'red',
@ -173,7 +174,7 @@ if ( !empty($_REQUEST['preset']) ) {
}
}
if ( !empty($_REQUEST['probe']) ) {
$probe = unserialize(base64_decode($_REQUEST['probe']));
$probe = json_decode(base64_decode($_REQUEST['probe']));
foreach ( $probe as $name=>$value ) {
if ( isset($value) ) {
# Does isset handle NULL's? I don't think this code is correct.
@ -452,6 +453,12 @@ $savejpegopts = array(
'Frames + Analysis images (if available)' => 3,
);
$codecs = array(
'auto' => translate('Auto'),
'H264' => translate('H264'),
'H265' => translate('H265'),
'MJPEG' => translate('MJPEG'),
);
xhtmlHeaders(__FILE__, translate('Monitor')." - ".validHtmlStr($monitor->Name()) );
?>
@ -663,6 +670,7 @@ if ( $tab != 'misc' ) {
<input type="hidden" name="newMonitor[DefaultView]" value="<?php echo validHtmlStr($monitor->DefaultView()) ?>"/>
<input type="hidden" name="newMonitor[DefaultRate]" value="<?php echo validHtmlStr($monitor->DefaultRate()) ?>"/>
<input type="hidden" name="newMonitor[DefaultScale]" value="<?php echo validHtmlStr($monitor->DefaultScale()) ?>"/>
<input type="hidden" name="newMonitor[DefaultCodec]" value="<?php echo validHtmlStr($monitor->DefaultCodec()) ?>"/>
<input type="hidden" name="newMonitor[WebColour]" value="<?php echo validHtmlStr($monitor->WebColour()) ?>"/>
<input type="hidden" name="newMonitor[Exif]" value="<?php echo validHtmlStr($monitor->Exif()) ?>"/>
<?php
@ -981,7 +989,15 @@ echo htmlSelect('newMonitor[OutputContainer]', $videowriter_containers, $monitor
</td>
</tr>
<tr><td><?php echo translate('OptionalEncoderParam') ?></td><td><textarea name="newMonitor[EncoderParameters]" rows="4" cols="36"><?php echo validHtmlStr($monitor->EncoderParameters()) ?></textarea></td></tr>
<tr><td><?php echo translate('RecordAudio') ?></td><td><input type="checkbox" name="newMonitor[RecordAudio]" value="1"<?php if ( $monitor->RecordAudio() ) { ?> checked="checked"<?php } ?>/></td></tr>
<tr><td><?php echo translate('RecordAudio') ?></td><td>
<?php if ( $monitor->Type() == 'Ffmpeg' ) { ?>
<input type="checkbox" name="newMonitor[RecordAudio]" value="1"<?php if ( $monitor->RecordAudio() ) { ?> checked="checked"<?php } ?>/>
<?php } else { ?>
Audio recording only available with FFMPEG using H264 Passthrough
<input type="hidden" name="newMonitor[RecordAudio]" value="<?php echo $monitor->RecordAudio() ? 1 : 0 ?>"/>
<?php } ?>
</td></tr>
<?php
break;
case 'timestamp' :
@ -1059,6 +1075,7 @@ echo htmlSelect('newMonitor[OutputContainer]', $videowriter_containers, $monitor
</select></td></tr>
<tr><td><?php echo translate('DefaultRate') ?></td><td><?php echo htmlSelect( "newMonitor[DefaultRate]", $rates, $monitor->DefaultRate() ); ?></td></tr>
<tr><td><?php echo translate('DefaultScale') ?></td><td><?php echo htmlSelect( "newMonitor[DefaultScale]", $scales, $monitor->DefaultScale() ); ?></td></tr>
<tr><td><?php echo translate('DefaultCodec') ?></td><td><?php echo htmlSelect( "newMonitor[DefaultCodec]", $codecs, $monitor->DefaultCodec() ); ?></td></tr>
<tr>
<td><?php echo translate('SignalCheckPoints') ?></td>
<td>

View File

@ -18,7 +18,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canEdit( 'Monitors' ) ) {
if ( !canEdit('Monitors') ) {
$view = 'error';
return;
}
@ -73,26 +73,26 @@ function probeCameras( $localIp ) {
} elseif ( $tokens[1] == 'location' ) {
// $camera['location'] = $tokens[2];
} else {
Logger::Debug('Unknown token ' . $tokens[1] );
Logger::Debug('Unknown token ' . $tokens[1]);
}
}
} // end foreach token
$cameras[] = $camera;
}
} // end foreach line
}
return( $cameras );
}
} // end if results from execOnvif
return $cameras;
} // end function probeCameras
function probeProfiles( $device_ep, $soapversion, $username, $password ) {
$profiles = array();
if ( $lines = @execONVIF( "profiles $device_ep $soapversion $username $password" ) ) {
foreach ( $lines as $line ) {
$line = rtrim( $line );
if ( preg_match( '|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches ) ) {
if ( preg_match('|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches) ) {
$stream_uri = $matches[7];
// add user@pass to URI
if ( preg_match( '|^(\S+://)(.+)$|', $stream_uri, $tokens ) ) {
if ( preg_match('|^(\S+://)(.+)$|', $stream_uri, $tokens) ) {
$stream_uri = $tokens[1].$username.':'.$password.'@'.$tokens[2];
}
@ -106,17 +106,15 @@ function probeProfiles( $device_ep, $soapversion, $username, $password ) {
'Profile' => $matches[1],
'Name' => $matches[2],
'Encoding' => $matches[3],
);
$profiles[] = $profile;
} else {
Logger::Debug("Line did not match preg: $line");
}
}
}
return( $profiles );
}
} // end foreach line
} // end if results from execONVIF
return $profiles;
} // end function probeProfiles
//==== STEP 1 ============================================================
@ -124,20 +122,20 @@ $focusWindow = true;
xhtmlHeaders(__FILE__, translate('MonitorProbe') );
if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {
if ( !isset($_REQUEST['step']) || ($_REQUEST['step'] == '1') ) {
$monitors = array();
foreach ( dbFetchAll( "select Id, Name, Host from Monitors where Type = 'Remote' order by Host" ) as $monitor ) {
if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) ) {
//echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."<br/>";
$monitors[gethostbyname($matches[2])] = $monitor;
} else {
//echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."<br/>";
$monitors[gethostbyname($monitor['Host'])] = $monitor;
}
foreach ( dbFetchAll("SELECT Id, Name, Host FROM Monitors WHERE Type = 'Remote' ORDER BY Host") as $monitor ) {
if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) ) {
//echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."<br/>";
$monitors[gethostbyname($matches[2])] = $monitor;
} else {
//echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."<br/>";
$monitors[gethostbyname($monitor['Host'])] = $monitor;
}
}
$detcameras = probeCameras( '' );
$detcameras = probeCameras('');
foreach ( $detcameras as $camera ) {
if ( preg_match( '|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|', $camera['monitor']['Host'], $matches ) ) {
$ip = $matches[1];
@ -157,13 +155,13 @@ if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {
}
*/
// $sourceDesc = htmlspecialchars(serialize($camera['monitor']));
$sourceDesc = base64_encode(serialize($camera['monitor']));
$sourceString = $camera['model'].' @ '.$host . ' using version ' . $camera['monitor']['SOAP'] ;
$cameras[$sourceDesc] = $sourceString;
$sourceDesc = base64_encode(json_encode($camera['monitor']));
$sourceString = $camera['model'].' @ '.$host . ' using version ' . $camera['monitor']['SOAP'] ;
$cameras[$sourceDesc] = $sourceString;
}
if ( count($cameras) <= 0 )
$cameras[0] = translate('NoDetectedCameras');
$cameras[0] = translate('NoDetectedCameras');
?>
<body>
@ -180,22 +178,23 @@ if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {
<?php echo translate('OnvifProbeIntro') ?>
</p>
<p>
<label for="probe"><?php echo translate('DetectedCameras') ?></label><?php echo buildSelect( "probe", $cameras, 'configureButtons( this )' ); ?>
<label for="probe"><?php echo translate('DetectedCameras') ?></label>
<?php echo htmlSelect('probe', $cameras, null, array('onchange'=>'configureButtons(this)')); ?>
</p>
<p>
<?php echo translate('OnvifCredentialsIntro') ?>
</p>
<p>
<label for="username"><?php echo translate('Username') ?></label>
<input type="text" name="username" value="" onChange="configureButtons( this )" />
<input type="text" name="username" value="" onChange="configureButtons(this)"/>
</p>
<p>
<label for="password"><?php echo translate('Password') ?></label>
<input type="password" name="password" value=""onChange="configureButtons( this )" />
<input type="password" name="password" value=""onChange="configureButtons(this)"/>
</p>
<div id="contentButtons">
<input type="button" value="<?php echo translate('Cancel') ?>" onclick="closeWindow()"/>
<input type="submit" name="nextBtn" value="<?php echo translate('Next') ?>" onclick="gotoStep2( this )" disabled="disabled"/>
<input type="submit" name="nextBtn" value="<?php echo translate('Next') ?>" onclick="gotoStep2(this)" disabled="disabled"/>
</div>
</form>
</div>
@ -205,44 +204,42 @@ if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {
<?php
//==== STEP 2 ============================================================
}
else if($_REQUEST['step'] == "2")
{
} else if($_REQUEST['step'] == '2') {
if ( empty($_REQUEST['probe']) )
Fatal("No probe passed in request. Please go back and try again.");
Fatal('No probe passed in request. Please go back and try again.');
#|| empty($_REQUEST['username']) ||
#empty($_REQUEST['password']) )
$probe = unserialize(base64_decode($_REQUEST['probe']));
$probe = json_decode(base64_decode($_REQUEST['probe']));
Logger::Debug(print_r($probe,true));
foreach ( $probe as $name=>$value ) {
if ( isset($value) ) {
$monitor[$name] = $value;
}
if ( isset($value) ) {
$monitor[$name] = $value;
}
}
$camera['monitor'] = $monitor;
//print $monitor['Host'].", ".$_REQUEST['username'].", ".$_REQUEST['password']."<br/>";
$detprofiles = probeProfiles( $monitor['Host'], $monitor['SOAP'], $_REQUEST['username'], $_REQUEST['password']);
$detprofiles = probeProfiles($monitor['Host'], $monitor['SOAP'], $_REQUEST['username'], $_REQUEST['password']);
foreach ( $detprofiles as $profile ) {
$monitor = $camera['monitor'];
$sourceString = "${profile['Name']} : ${profile['Encoding']}" .
" (${profile['Width']}x${profile['Height']} @ ${profile['MaxFPS']}fps)";
// copy technical details
$monitor['Width'] = $profile['Width'];
$monitor['Height'] = $profile['Height'];
// The maxfps fields do not work for ip streams. Can re-enable if that is fixed.
// $monitor['MaxFPS'] = $profile['MaxFPS'];
// $monitor['AlarmMaxFPS'] = $profile['AlarmMaxFPS'];
$monitor['Path'] = $profile['Path'];
// $sourceDesc = htmlspecialchars(serialize($monitor));
$sourceDesc = base64_encode(serialize($monitor));
$profiles[$sourceDesc] = $sourceString;
$monitor = $camera['monitor'];
$sourceString = "${profile['Name']} : ${profile['Encoding']}" .
" (${profile['Width']}x${profile['Height']} @ ${profile['MaxFPS']}fps)";
// copy technical details
$monitor['Width'] = $profile['Width'];
$monitor['Height'] = $profile['Height'];
// The maxfps fields do not work for ip streams. Can re-enable if that is fixed.
// $monitor['MaxFPS'] = $profile['MaxFPS'];
// $monitor['AlarmMaxFPS'] = $profile['AlarmMaxFPS'];
$monitor['Path'] = $profile['Path'];
$sourceDesc = base64_encode(json_encode($monitor));
$profiles[$sourceDesc] = $sourceString;
}
if ( count($profiles) <= 0 )
$profiles[0] = translate('NoDetectedProfiles');
$profiles[0] = translate('NoDetectedProfiles');
?>
<body>
@ -254,17 +251,18 @@ else if($_REQUEST['step'] == "2")
<form name="contentForm" id="contentForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
<input type="hidden" name="view" value="none"/>
<input type="hidden" name="mid" value="<?php echo validNum($_REQUEST['mid']) ?>"/>
<input type="hidden" name="step" value=""/>
<input type="hidden" name="step"/>
<p>
<?php echo translate('ProfileProbeIntro') ?>
</p>
<p>
<label for="probe"><?php echo translate('DetectedProfiles') ?></label><?php echo buildSelect( 'probe', $profiles, 'configureButtons( this )' ); ?>
<label for="probe"><?php echo translate('DetectedProfiles') ?></label>
<?php echo htmlSelect('probe', $profiles, null, array('onchange'=>'configureButtons(this)')); ?>
</p>
<div id="contentButtons">
<input type="button" name="prevBtn" value="<?php echo translate('Prev') ?>" onclick="gotoStep1( this )"/>
<input type="button" name="prevBtn" value="<?php echo translate('Prev') ?>" onclick="gotoStep1(this)"/>
<input type="button" value="<?php echo translate('Cancel') ?>" onclick="closeWindow()"/>
<input type="submit" name="saveBtn" value="<?php echo translate('Save') ?>" onclick="submitCamera( this )" disabled="disabled"/>
<input type="submit" name="saveBtn" value="<?php echo translate('Save') ?>" onclick="submitCamera(this)" disabled="disabled"/>
</div>
</form>
</div>
@ -272,5 +270,5 @@ else if($_REQUEST['step'] == "2")
</body>
</html>
<?php
}
} // end if step 1 or 2
?>

View File

@ -15,7 +15,7 @@
//
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canEdit( 'System' ) ) {

View File

@ -62,39 +62,39 @@ $minY = 0;
$maxY = $monitor->Height()-1;
if ( !isset($newZone) ) {
if ( $zid > 0 ) {
$zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId = ? AND Id=?', NULL, array( $monitor->Id(), $zid ) );
} else {
$zone = array(
'Id' => 0,
'Name' => translate('New'),
'Type' => 'Active',
'MonitorId' => $monitor->Id(),
'NumCoords' => 4,
'Coords' => sprintf( "%d,%d %d,%d, %d,%d %d,%d", $minX, $minY, $maxX, $minY, $maxX, $maxY, $minX, $maxY ),
'Area' => $monitor->Width() * $monitor->Height(),
'AlarmRGB' => 0xff0000,
'CheckMethod' => 'Blobs',
'MinPixelThreshold' => '',
'MaxPixelThreshold' => '',
'MinAlarmPixels' => '',
'MaxAlarmPixels' => '',
'FilterX' => '',
'FilterY' => '',
'MinFilterPixels' => '',
'MaxFilterPixels' => '',
'MinBlobPixels' => '',
'MaxBlobPixels' => '',
'MinBlobs' => '',
'MaxBlobs' => '',
'OverloadFrames' => '',
'ExtendAlarmFrames' => '',
);
}
$zone['Points'] = coordsToPoints( $zone['Coords'] );
$zone['AreaCoords'] = preg_replace( '/\s+/', ',', $zone['Coords'] );
if ( $zid > 0 ) {
$zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId = ? AND Id=?', NULL, array( $monitor->Id(), $zid ) );
} else {
$zone = array(
'Id' => 0,
'Name' => translate('New'),
'Type' => 'Active',
'MonitorId' => $monitor->Id(),
'NumCoords' => 4,
'Coords' => sprintf( "%d,%d %d,%d, %d,%d %d,%d", $minX, $minY, $maxX, $minY, $maxX, $maxY, $minX, $maxY ),
'Area' => $monitor->Width() * $monitor->Height(),
'AlarmRGB' => 0xff0000,
'CheckMethod' => 'Blobs',
'MinPixelThreshold' => '',
'MaxPixelThreshold' => '',
'MinAlarmPixels' => '',
'MaxAlarmPixels' => '',
'FilterX' => '',
'FilterY' => '',
'MinFilterPixels' => '',
'MaxFilterPixels' => '',
'MinBlobPixels' => '',
'MaxBlobPixels' => '',
'MinBlobs' => '',
'MaxBlobs' => '',
'OverloadFrames' => '',
'ExtendAlarmFrames' => '',
);
}
$zone['Points'] = coordsToPoints( $zone['Coords'] );
$zone['AreaCoords'] = preg_replace( '/\s+/', ',', $zone['Coords'] );
$newZone = $zone;
$newZone = $zone;
} # end if new Zone
# Ensure Zone fits within the limits of the Monitor

View File

@ -69,6 +69,7 @@ if ( empty($_REQUEST['path']) ) {
}
if ( !empty($_REQUEST['eid']) ) {
Logger::Debug("Loading by eid");
$Event = Event::find_one(array('Id'=>$_REQUEST['eid']));
if ( !$Event ) {
header('HTTP/1.0 404 Not Found');
@ -136,6 +137,7 @@ Logger::Debug("Got virtual frame from Bulk Frames previous delta: " . $previousB
}
// Frame can be non-existent. We have Bulk frames. So now we should try to load the bulk frame
$path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d',$Frame->FrameId()).'-'.$show.'.jpg';
Logger::Debug("Path: $path");
}
} else {
@ -225,12 +227,14 @@ if ( !empty($_REQUEST['scale']) ) {
$width = 0;
if ( !empty($_REQUEST['width']) ) {
Logger::Debug("Setting width: " . $_REQUEST['width']);
if ( is_numeric($_REQUEST['width']) ) {
$x = $_REQUEST['width'];
if ( $x >= 10 and $x <= 8000 )
$width = $x;
}
}
$height = 0;
if ( !empty($_REQUEST['height']) ) {
if ( is_numeric($_REQUEST['height']) ) {
@ -246,14 +250,12 @@ if ( $errorText ) {
# Clears the output buffer. Not sure what is there, but have had troubles.
ob_end_clean();
header('Content-type: image/jpeg');
if ( ( $scale==0 || $scale==100 ) && $width==0 && $height==0 ) {
if ( ( $scale==0 || $scale==100 ) && ($width==0) && ($height==0) ) {
# This is so that Save Image As give a useful filename
if ( $Event ) {
$filename = $Event->MonitorId().'_'.$Event->Id().'_'.$Frame->FrameId().'.jpg';
header('Content-Disposition: inline; filename="' . $filename . '"');
}
ob_clean();
flush();
if ( !readfile($path) ) {
Error('No bytes read from '. $path);
}
@ -271,6 +273,7 @@ if ( $errorText ) {
$width = ($height * $oldWidth) / $oldHeight;
} elseif ( $width != 0 && $height == 0 ) {
$height = ($width * $oldHeight) / $oldWidth;
Logger::Debug("Figuring out height using width: $height = ($width * $oldHeight) / $oldWidth");
}
if ( $width == $oldWidth && $height == $oldHeight ) {
Warning('No change to width despite scaling.');
@ -283,8 +286,6 @@ if ( $errorText ) {
$filename = $Event->MonitorId().'_'.$Event->Id().'_'.$Frame->FrameId()."-${width}x${height}.jpg";
header('Content-Disposition: inline; filename="' . $filename . '"');
}
//ob_clean();
//flush();
if ( !( file_exists($scaled_path) and readfile($scaled_path) ) ) {
Logger::Debug("Cached scaled image does not exist at $scaled_path or is no good.. Creating it");
ob_start();