Merge branch 'ZoneMinder:master' into master
commit
ebc9920442
|
@ -1094,6 +1094,7 @@ DROP TABLE IF EXISTS MontageLayouts;
|
|||
CREATE TABLE MontageLayouts (
|
||||
`Id` int(10) unsigned NOT NULL auto_increment,
|
||||
`Name` TEXT NOT NULL,
|
||||
`UserId` int(10) UNSIGNED NOT NULL default 0,
|
||||
`Positions` LONGTEXT,
|
||||
/*`Positions` JSON,*/
|
||||
PRIMARY KEY (`Id`)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
--
|
||||
-- Update MontageLayout table to have UserId
|
||||
--
|
||||
|
||||
SELECT 'Checking for UserId in MontageLayouts';
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'MontageLayouts'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'UserId'
|
||||
) > 0,
|
||||
"SELECT 'Column UserId already exists in MontageLayouts'",
|
||||
"ALTER TABLE `MontageLayouts` ADD COLUMN `UserId` int(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `Name`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -0,0 +1,18 @@
|
|||
--
|
||||
-- Update MontageLayout table to have UserId
|
||||
--
|
||||
|
||||
SELECT 'Checking for UserId in MontageLayouts';
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'MontageLayouts'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'UserId'
|
||||
) > 0,
|
||||
"SELECT 'Column UserId already exists in MontageLayouts'",
|
||||
"ALTER TABLE `MontageLayouts` ADD COLUMN `UserId` int(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `Name`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -37,7 +37,7 @@
|
|||
%global _hardened_build 1
|
||||
|
||||
Name: zoneminder
|
||||
Version: 1.37.17
|
||||
Version: 1.37.18
|
||||
Release: 1%{?dist}
|
||||
Summary: A camera monitoring and analysis tool
|
||||
Group: System Environment/Daemons
|
||||
|
|
|
@ -0,0 +1,533 @@
|
|||
# ==========================================================================
|
||||
#
|
||||
# ZoneMinder Uniview Control Protocol Module
|
||||
# Copyright (C) 2022 ZoneMinder Inc
|
||||
#
|
||||
# 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 an implementation of the Uniview camera control
|
||||
# protocol. It is incomplete.
|
||||
#
|
||||
package ZoneMinder::Control::Uniview;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
|
||||
use Time::HiRes qw( usleep );
|
||||
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Cookies;
|
||||
|
||||
my $ChannelID = 1; # Usually...
|
||||
my $DefaultFocusSpeed = 50; # Should be between 1 and 100
|
||||
my $DefaultIrisSpeed = 50; # Should be between 1 and 100
|
||||
my ($user,$pass,$host,$port);
|
||||
|
||||
sub open {
|
||||
my $self = shift;
|
||||
$self->loadMonitor();
|
||||
$port = 80;
|
||||
|
||||
# Create a UserAgent for the requests
|
||||
$self->{UA} = LWP::UserAgent->new();
|
||||
$self->{UA}->cookie_jar( {} );
|
||||
|
||||
# Extract the username/password host/port from ControlAddress
|
||||
if ($self->{Monitor}{ControlAddress}
|
||||
and
|
||||
$self->{Monitor}{ControlAddress} ne 'user:pass@ip'
|
||||
and
|
||||
$self->{Monitor}{ControlAddress} ne 'user:port@ip'
|
||||
) {
|
||||
Debug("Using ControlAddress for credentials: $self->{Monitor}{ControlAddress}");
|
||||
if ($self->{Monitor}{ControlAddress} =~ /^([^:]+):([^@]+)@(.+)/ ) { # user:pass@host...
|
||||
$user = $1;
|
||||
$pass = $2;
|
||||
$host = $3;
|
||||
} elsif ( $self->{Monitor}{ControlAddress} =~ /^([^@]+)@(.+)/ ) { # user@host...
|
||||
$user = $1;
|
||||
$host = $2;
|
||||
} else { # Just a host
|
||||
$host = $self->{Monitor}{ControlAddress};
|
||||
}
|
||||
# Check if it is a host and port or just a host
|
||||
if ( $host =~ /([^:]+):(.+)/ ) {
|
||||
$host = $1;
|
||||
$port = $2 ? $2 : $port;
|
||||
}
|
||||
} elsif ( $self->{Monitor}{Path}) {
|
||||
Debug("Using Path for credentials: $self->{Monitor}{Path}");
|
||||
if (($self->{Monitor}->{Path} =~ /^(?<PROTOCOL>(https?|rtsp):\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>[^:\/]+)/)) {
|
||||
$user = $+{USERNAME} if $+{USERNAME};
|
||||
$pass = $+{PASSWORD} if $+{PASSWORD};
|
||||
$host = $+{ADDRESS} if $+{ADDRESS};
|
||||
}
|
||||
} else {
|
||||
Debug("Not using credentials");
|
||||
}
|
||||
# Save the base url
|
||||
$self->{BaseURL} = "http://$host:$port";
|
||||
|
||||
# Save and test the credentials
|
||||
if (defined($user)) {
|
||||
Debug("Credentials: $host:$port, $self->{Monitor}{ControlDevice}, $user, $pass");
|
||||
$self->{UA}->credentials("$host:$port", $self->{Monitor}{ControlDevice}, $user, $pass);
|
||||
} # end if defined user
|
||||
|
||||
my $url = $self->{BaseURL};
|
||||
my $response = $self->get($url);
|
||||
if ($response->status_line() eq '401 Unauthorized' and defined $user) {
|
||||
my $headers = $response->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}");
|
||||
}
|
||||
|
||||
my $realm = $self->{Monitor}->{ControlDevice};
|
||||
|
||||
if ( $$headers{'www-authenticate'} ) {
|
||||
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
|
||||
my %tokens = map { /(\w+)="?([^"]+)"?/i } split(', ', $tokens );
|
||||
if ( $tokens{realm} ) {
|
||||
if ( $realm ne $tokens{realm} ) {
|
||||
$realm = $tokens{realm};
|
||||
Debug("Changing REALM to $realm");
|
||||
$self->{UA}->credentials("$host:$port", $realm, $user, $pass);
|
||||
$response = $self->{UA}->get($url);
|
||||
if ( !$response->is_success() ) {
|
||||
Error('Authentication still failed after updating REALM' . $response->status_line);
|
||||
}
|
||||
$headers = $response->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}\n");
|
||||
} # end foreach
|
||||
} else {
|
||||
Error('Authentication failed, not a REALM problem');
|
||||
}
|
||||
} else {
|
||||
Debug('Failed to match realm in tokens');
|
||||
} # end if
|
||||
} else {
|
||||
debug('No headers line');
|
||||
} # end if headers
|
||||
} # end if not authen
|
||||
if ($response->is_success()) {
|
||||
$self->{state} = 'open';
|
||||
}
|
||||
Debug('Response: '. $response->status_line . ' ' . $response->content);
|
||||
return $response->is_success;
|
||||
} # end sub open
|
||||
|
||||
sub get {
|
||||
my $self = shift;
|
||||
my $url = shift;
|
||||
Debug("Getting $url");
|
||||
my $response = $self->{UA}->get($url);
|
||||
#Debug('Response: '. $response->status_line . ' ' . $response->content);
|
||||
return $response;
|
||||
}
|
||||
|
||||
sub put {
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
my $content = shift;
|
||||
if (!$cmd) {
|
||||
Error("No cmd specified in PutCmd");
|
||||
return;
|
||||
}
|
||||
my $req = HTTP::Request->new(PUT => $self->{BaseURL}.'/'.$cmd);
|
||||
if ( defined($content) ) {
|
||||
$req->content_type('application/x-www-form-urlencoded; charset=UTF-8');
|
||||
$req->content($content);
|
||||
}
|
||||
my $res = $self->{UA}->request($req);
|
||||
unless( $res->is_success ) {
|
||||
#
|
||||
# The camera timeouts connections at short intervals. When this
|
||||
# happens the user agent connects again and uses the same auth tokens.
|
||||
# The camera rejects this and asks for another token but the UserAgent
|
||||
# just gives up. Because of this I try the request again and it should
|
||||
# succeed the second time if the credentials are correct.
|
||||
#
|
||||
if ( $res->code == 401 ) {
|
||||
$res = $self->{UA}->request($req);
|
||||
unless( $res->is_success ) {
|
||||
#
|
||||
# It has failed authentication. The odds are
|
||||
# that the user has set some parameter incorrectly
|
||||
# so check the realm against the ControlDevice
|
||||
# entry and send a message if different
|
||||
#
|
||||
my $auth = $res->headers->www_authenticate;
|
||||
foreach (split(/\s*,\s*/,$auth)) {
|
||||
if ( $_ =~ /^realm\s*=\s*"([^"]+)"/i ) {
|
||||
if ( $self->{Monitor}{ControlDevice} ne $1 ) {
|
||||
Warning("Control Device appears to be incorrect.
|
||||
Control Device should be set to \"$1\".
|
||||
Control Device currently set to \"$self->{Monitor}{ControlDevice}\".");
|
||||
$self->{Monitor}{ControlDevice} = $1;
|
||||
$self->{UA}->credentials("$host:$port", $self->{Monitor}{ControlDevice}, $user, $pass);
|
||||
return PutCmd($self,$cmd,$content);
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# Check for username/password
|
||||
#
|
||||
if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) {
|
||||
Info('Check username/password is correct');
|
||||
} elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) {
|
||||
Info('No password in Control Address. Should there be one?');
|
||||
} elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) {
|
||||
Info('Password but no username in Control Address.');
|
||||
} else {
|
||||
Info('Missing username and password in Control Address.');
|
||||
}
|
||||
Error($res->status_line);
|
||||
}
|
||||
} else {
|
||||
Error($res->status_line);
|
||||
}
|
||||
} # end unless res->is_success
|
||||
} # end sub put
|
||||
#
|
||||
# The move continuous functions all call moveVector
|
||||
# with the direction to move in. This includes zoom
|
||||
#
|
||||
sub moveVector {
|
||||
my $self = shift;
|
||||
my $pandirection = shift;
|
||||
my $tiltdirection = shift;
|
||||
my $zoomdirection = shift;
|
||||
my $params = shift;
|
||||
my $command; # The ISAPI/PTZ command
|
||||
|
||||
# Calculate autostop time
|
||||
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
|
||||
# Change from microseconds to milliseconds
|
||||
$duration = int($duration/1000);
|
||||
my $momentxml;
|
||||
if( $duration ) {
|
||||
$momentxml = "<Momentary><duration>$duration</duration></Momentary>";
|
||||
$command = "ISAPI/PTZCtrl/channels/$ChannelID/momentary";
|
||||
}
|
||||
else {
|
||||
$momentxml = "";
|
||||
$command = "ISAPI/PTZCtrl/channels/$ChannelID/continuous";
|
||||
}
|
||||
# Calculate movement speeds
|
||||
my $x = $pandirection * $self->getParam( $params, 'panspeed', 0 );
|
||||
my $y = $tiltdirection * $self->getParam( $params, 'tiltspeed', 0 );
|
||||
my $z = $zoomdirection * $self->getParam( $params, 'speed', 0 );
|
||||
# Create the XML
|
||||
my $xml = "<PTZData><pan>$x</pan><tilt>$y</tilt><zoom>$z</zoom>$momentxml</PTZData>";
|
||||
# Send it to the camera
|
||||
$self->PutCmd($command,$xml);
|
||||
}
|
||||
sub zoomStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); }
|
||||
sub moveStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); }
|
||||
sub moveConUp { $_[0]->moveVector( 0, 1, 0, splice(@_,1)); }
|
||||
sub moveConUpRight { $_[0]->moveVector( 1, 1, 0, splice(@_,1)); }
|
||||
sub moveConRight { $_[0]->moveVector( 1, 0, 0, splice(@_,1)); }
|
||||
sub moveConDownRight { $_[0]->moveVector( 1, -1, 0, splice(@_,1)); }
|
||||
sub moveConDown { $_[0]->moveVector( 0, -1, 0, splice(@_,1)); }
|
||||
sub moveConDownLeft { $_[0]->moveVector( -1, -1, 0, splice(@_,1)); }
|
||||
sub moveConLeft { $_[0]->moveVector( -1, 0, 0, splice(@_,1)); }
|
||||
sub moveConUpLeft { $_[0]->moveVector( -1, 1, 0, splice(@_,1)); }
|
||||
sub zoomConTele { $_[0]->moveVector( 0, 0, 1, splice(@_,1)); }
|
||||
sub zoomConWide { $_[0]->moveVector( 0, 0,-1, splice(@_,1)); }
|
||||
#
|
||||
# Presets including Home set and clear
|
||||
#
|
||||
sub presetGoto {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam($params,'preset');
|
||||
$self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset/goto");
|
||||
}
|
||||
sub presetSet {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam($params,'preset');
|
||||
my $xml = "<PTZPreset><id>$preset</id></PTZPreset>";
|
||||
$self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset",$xml);
|
||||
}
|
||||
sub presetHome {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/homeposition/goto");
|
||||
}
|
||||
#
|
||||
# Focus controls all call Focus with a +/- speed
|
||||
#
|
||||
sub Focus {
|
||||
my $self = shift;
|
||||
my $speed = shift;
|
||||
my $xml = "<FocusData><focus>$speed</focus></FocusData>";
|
||||
$self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/focus",$xml);
|
||||
}
|
||||
sub focusConNear {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Calculate autostop time
|
||||
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
|
||||
# Get the focus speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
|
||||
$self->Focus(-$speed);
|
||||
if($duration) {
|
||||
usleep($duration);
|
||||
$self->moveStop($params);
|
||||
}
|
||||
}
|
||||
sub Near {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->Focus(-$DefaultFocusSpeed);
|
||||
}
|
||||
sub focusAbsNear {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Get the focus speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
|
||||
$self->Focus(-$speed);
|
||||
}
|
||||
sub focusRelNear {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
# Get the focus speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
|
||||
$self->Focus(-$speed);
|
||||
}
|
||||
sub focusConFar {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Calculate autostop time
|
||||
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
|
||||
# Get the focus speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
|
||||
$self->Focus($speed);
|
||||
if($duration) {
|
||||
usleep($duration);
|
||||
$self->moveStop($params);
|
||||
}
|
||||
}
|
||||
sub Far {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->Focus($DefaultFocusSpeed);
|
||||
}
|
||||
sub focusAbsFar {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Get the focus speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
|
||||
$self->Focus($speed);
|
||||
}
|
||||
sub focusRelFar {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Get the focus speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
|
||||
$self->Focus($speed);
|
||||
}
|
||||
#
|
||||
# Iris controls all call Iris with a +/- speed
|
||||
#
|
||||
sub Iris {
|
||||
my $self = shift;
|
||||
my $speed = shift;
|
||||
|
||||
my $xml = "<IrisData><iris>$speed</iris></IrisData>";
|
||||
$self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/iris",$xml);
|
||||
}
|
||||
sub irisConClose {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Calculate autostop time
|
||||
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
|
||||
# Get the iris speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
|
||||
$self->Iris(-$speed);
|
||||
if($duration) {
|
||||
usleep($duration);
|
||||
$self->moveStop($params);
|
||||
}
|
||||
}
|
||||
sub Close {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
$self->Iris(-$DefaultIrisSpeed);
|
||||
}
|
||||
sub irisAbsClose {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Get the iris speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
|
||||
$self->Iris(-$speed);
|
||||
}
|
||||
sub irisRelClose {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Get the iris speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
|
||||
$self->Iris(-$speed);
|
||||
}
|
||||
sub irisConOpen {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Calculate autostop time
|
||||
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
|
||||
# Get the iris speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
|
||||
$self->Iris($speed);
|
||||
if($duration) {
|
||||
usleep($duration);
|
||||
$self->moveStop($params);
|
||||
}
|
||||
}
|
||||
sub Open {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
$self->Iris($DefaultIrisSpeed);
|
||||
}
|
||||
sub irisAbsOpen {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Get the iris speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
|
||||
$self->Iris($speed);
|
||||
}
|
||||
sub irisRelOpen {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
# Get the iris speed
|
||||
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
|
||||
$self->Iris($speed);
|
||||
}
|
||||
|
||||
#
|
||||
# reset (reboot) the device
|
||||
#
|
||||
|
||||
sub reboot {
|
||||
my $self = shift;
|
||||
|
||||
$self->put('LAPI/V1.0/System/Reboot');
|
||||
}
|
||||
|
||||
sub get_config {
|
||||
my $self = shift;
|
||||
return {};
|
||||
}
|
||||
|
||||
sub set_config {
|
||||
my $self = shift;
|
||||
my $diff = shift;
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ping {
|
||||
return -1 if ! $host;
|
||||
|
||||
require Net::Ping;
|
||||
|
||||
my $p = Net::Ping->new();
|
||||
my $rv = $p->ping($host);
|
||||
$p->close();
|
||||
return $rv;
|
||||
}
|
||||
|
||||
sub probe {
|
||||
my ($ip, $user, $pass) = @_;
|
||||
|
||||
my $self = new ZoneMinder::Control::Uniview();
|
||||
# Create a UserAgent for the requests
|
||||
$self->{UA} = LWP::UserAgent->new();
|
||||
$self->{UA}->cookie_jar( {} );
|
||||
my $realm;
|
||||
|
||||
foreach my $port ( '80','443' ) {
|
||||
my $url = 'http://'.$user.':'.$pass.'@'.$ip.':'.$port.'/ISAPI/Streaming/channels/101';
|
||||
my $response = $self->get($url);
|
||||
if ($response->status_line() eq '401 Unauthorized' and defined $user) {
|
||||
my $headers = $response->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}");
|
||||
}
|
||||
|
||||
if ( $$headers{'www-authenticate'} ) {
|
||||
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
|
||||
my %tokens = map { /(\w+)="?([^"]+)"?/i } split(', ', $tokens );
|
||||
if ($tokens{realm}) {
|
||||
$realm = $tokens{realm};
|
||||
Debug('Changing REALM to '.$tokens{realm});
|
||||
$self->{UA}->credentials("$ip:$port", $tokens{realm}, $user, $pass);
|
||||
$response = $self->{UA}->get($url);
|
||||
if (!$response->is_success()) {
|
||||
Error('Authentication still failed after updating REALM' . $response->status_line);
|
||||
}
|
||||
$headers = $response->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}\n");
|
||||
} # end foreach
|
||||
} else {
|
||||
Debug('Failed to match realm in tokens');
|
||||
} # end if
|
||||
} else {
|
||||
Debug('No headers line');
|
||||
} # end if headers
|
||||
} # end if not authen
|
||||
Debug('Response: '. $response->status_line . ' ' . $response->content);
|
||||
if ($response->is_success) {
|
||||
return {
|
||||
url => 'http://'.$user.':'.$pass.'@'.$ip.':'.$port.'/h264',
|
||||
realm => $realm,
|
||||
};
|
||||
}
|
||||
} # end foreach port
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub profiles {
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -44,7 +44,7 @@ foreach ( $states as $state ) {
|
|||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Run State</h5>
|
||||
<h5 class="modal-title"><?php echo translate('Run State')?> </h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
|
@ -59,7 +59,7 @@ foreach ( $states as $state ) {
|
|||
<input type="hidden" name="action" value="state"/>
|
||||
<input type="hidden" name="apply" value="1"/>
|
||||
<div class="form-group">
|
||||
<label for="runState" class="col-md-3 col-form-label float-left">Change State</label>
|
||||
<label for="runState" class="col-md-3 col-form-label float-left"><?php echo translate('Change State')?></label>
|
||||
<div class="col-md-9">
|
||||
<select id="runState" name="runState" class="form-control">
|
||||
<?php echo $content ?>
|
||||
|
|
|
@ -7,6 +7,7 @@ class MontageLayout extends ZM_Object {
|
|||
protected $defaults = array(
|
||||
'Id' => null,
|
||||
'Name' => '',
|
||||
'UserId' => 0,
|
||||
'Positions' => 0,
|
||||
);
|
||||
|
||||
|
|
|
@ -17,30 +17,33 @@
|
|||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
require_once('includes/MontageLayout.php');
|
||||
|
||||
if ( isset($_REQUEST['object']) ) {
|
||||
if ( $_REQUEST['object'] == 'MontageLayout' ) {
|
||||
// System edit actions
|
||||
if ( ! canEdit('System') ) {
|
||||
ZM\Warning('Need System permissions to edit layouts');
|
||||
return;
|
||||
}
|
||||
require_once('includes/MontageLayout.php');
|
||||
if ( $action == 'Save' ) {
|
||||
if ($action == 'Save') {
|
||||
$Layout = null;
|
||||
|
||||
# Name is only populated when creating a new layout
|
||||
if ( $_REQUEST['Name'] != '' ) {
|
||||
$Layout = new ZM\MontageLayout();
|
||||
$Layout->Name($_REQUEST['Name']);
|
||||
} else {
|
||||
$Layout = new ZM\MontageLayout($_REQUEST['zmMontageLayout']);
|
||||
}
|
||||
$Layout->Positions($_REQUEST['Positions']);
|
||||
$Layout->save();
|
||||
zm_session_start();
|
||||
$_SESSION['zmMontageLayout'] = $Layout->Id();
|
||||
session_write_close();
|
||||
zm_setcookie('zmMontageLayout', $Layout->Id());
|
||||
$redirect = '?view=montage';
|
||||
if (canEdit('System') or !$Layout->Id() or ($user['Id'] == $Layout->UserId())) {
|
||||
$Layout->UserId($user['Id']);
|
||||
$Layout->Positions($_REQUEST['Positions']);
|
||||
$Layout->save();
|
||||
zm_session_start();
|
||||
$_SESSION['zmMontageLayout'] = $Layout->Id();
|
||||
session_write_close();
|
||||
zm_setcookie('zmMontageLayout', $Layout->Id());
|
||||
$redirect = '?view=montage';
|
||||
} else {
|
||||
ZM\Warning('Need System permissions to edit layouts');
|
||||
return;
|
||||
}
|
||||
} // end if save
|
||||
} # end if isset($_REQUEST['object'] )
|
||||
} # end if isset($_REQUEST['object'] )
|
||||
|
|
|
@ -166,6 +166,7 @@ if (ZM_TIME_FORMAT_PATTERN) {
|
|||
ZM\Logger::fetch()->initialise();
|
||||
|
||||
$GLOBALS['defaultUser'] = array(
|
||||
'Id' => 0,
|
||||
'Username' => 'admin',
|
||||
'Password' => '',
|
||||
'Language' => '',
|
||||
|
|
|
@ -145,23 +145,35 @@ function edit_layout(button) {
|
|||
});
|
||||
$j('#SaveLayout').show();
|
||||
$j('#EditLayout').hide();
|
||||
|
||||
const layout = layouts[document.getElementById('zmMontageLayout').value];
|
||||
if (user.Id && (layout.UserId == 0 || layout.UserId != user.Id)) {
|
||||
alert('You may not edit this layout, but you can create a new one from it. Please give it a name.');
|
||||
}
|
||||
} // end function edit_layout
|
||||
|
||||
function save_layout(button) {
|
||||
mode = VIEWING;
|
||||
|
||||
var form = button.form;
|
||||
var name = form.elements['Name'].value;
|
||||
const form = button.form;
|
||||
let name = form.elements['Name'].value;
|
||||
const layout = layouts[form.zmMontageLayout.value];
|
||||
|
||||
if ( !name ) {
|
||||
if (!name) {
|
||||
name = form.elements['zmMontageLayout'].options[form.elements['zmMontageLayout'].selectedIndex].text;
|
||||
}
|
||||
|
||||
if ( name=='Freeform' || name=='2 Wide' || name=='3 Wide' || name=='4 Wide' || name=='5 Wide' ) {
|
||||
alert('You cannot edit the built in layouts. Please give the layout a new name.');
|
||||
if ( name=='Freeform' || name=='2 Wide' || name=='3 Wide' || name=='4 Wide' || name=='5 Wide' ) {
|
||||
alert('You cannot edit the built in layouts. Please give the layout a new name.');
|
||||
return;
|
||||
} else if (user.Id && (layout.UserId != user.Id) && !canEdit('System') && (name != layout.Name)) {
|
||||
alert('You cannot edit someone else\'s layouts. Please give the layout a new name.');
|
||||
return;
|
||||
}
|
||||
} else if ( name=='Freeform' || name=='2 Wide' || name=='3 Wide' || name=='4 Wide' || name=='5 Wide' ) {
|
||||
alert('You cannot use that name. It conflicts with the built in layouts. Please give the layout a new name.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// In fixed positioning, order doesn't matter. In floating positioning, it does.
|
||||
var Positions = {};
|
||||
for ( var i = 0, length = monitors.length; i < length; i++ ) {
|
||||
|
|
|
@ -36,7 +36,10 @@ layouts[0] = {}; // reserved, should hold which fields to clear when transitioni
|
|||
global $layouts;
|
||||
foreach ( $layouts as $layout ) {
|
||||
?>
|
||||
layouts[<?php echo $layout->Id() ?>] = {"Name":"<?php echo $layout->Name()?>","Positions":<?php echo json_decode($layout->Positions())?$layout->Positions():'{}' ?>};
|
||||
layouts[<?php echo $layout->Id() ?>] = {
|
||||
"Name":"<?php echo $layout->Name()?>",
|
||||
"UserId":"<?php echo $layout->UserId()?>",
|
||||
"Positions":<?php echo json_decode($layout->Positions())?$layout->Positions():'{}' ?>};
|
||||
<?php
|
||||
} // end foreach layout
|
||||
global $FreeFormLayoutId;
|
||||
|
|
|
@ -290,14 +290,14 @@ $orientations = array(
|
|||
);
|
||||
|
||||
$deinterlaceopts = array(
|
||||
0x00000000 => 'Disabled',
|
||||
0x00001E04 => 'Four field motion adaptive - Soft', /* 30 change */
|
||||
0x00001404 => 'Four field motion adaptive - Medium', /* 20 change */
|
||||
0x00000A04 => 'Four field motion adaptive - Hard', /* 10 change */
|
||||
0x00000001 => 'Discard',
|
||||
0x00000002 => 'Linear',
|
||||
0x00000003 => 'Blend',
|
||||
0x00000205 => 'Blend (25%)',
|
||||
0x00000000 => translate('Disabled'),
|
||||
0x00001E04 => translate('Four field motion adaptive - Soft'), /* 30 change */
|
||||
0x00001404 => translate('Four field motion adaptive - Medium'), /* 20 change */
|
||||
0x00000A04 => translate('Four field motion adaptive - Hard'), /* 10 change */
|
||||
0x00000001 => translate('Discard'),
|
||||
0x00000002 => translate('Linear'),
|
||||
0x00000003 => translate('Blend'),
|
||||
0x00000205 => translate('Blend (25%)'),
|
||||
);
|
||||
|
||||
$deinterlaceopts_v4l2 = array(
|
||||
|
@ -317,23 +317,23 @@ $deinterlaceopts_v4l2 = array(
|
|||
);
|
||||
|
||||
$fastblendopts = array(
|
||||
0 => 'No blending',
|
||||
0 => translate ('No blending'),
|
||||
1 => '1.5625%',
|
||||
3 => '3.125%',
|
||||
6 => '6.25% (Indoor)',
|
||||
12 => '12.5% (Outdoor)',
|
||||
6 => translate('6.25% (Indoor)'),
|
||||
12 => translate('12.5% (Outdoor)'),
|
||||
25 => '25%',
|
||||
50 => '50%',
|
||||
);
|
||||
|
||||
$fastblendopts_alarm = array(
|
||||
0 => 'No blending (Alarm lasts forever)',
|
||||
0 => translate('No blending (Alarm lasts forever)'),
|
||||
1 => '1.5625%',
|
||||
3 => '3.125%',
|
||||
6 => '6.25%',
|
||||
12 => '12.5%',
|
||||
25 => '25%',
|
||||
50 => '50% (Alarm lasts a moment)',
|
||||
50 => translate('50% (Alarm lasts a moment)'),
|
||||
);
|
||||
|
||||
$label_size = array(
|
||||
|
@ -580,11 +580,11 @@ switch ($name) {
|
|||
</tr>
|
||||
<tr>
|
||||
<td class="text-right pr-3"><?php echo translate('ONVIF_Event_Listener') ?></td>
|
||||
<td><?php echo html_radio('newMonitor[ONVIF_Event_Listener]', array('1'=>translate('Enabled'), '0'=>'Disabled'), $monitor->ONVIF_Event_Listener()); ?></td>
|
||||
<td><?php echo html_radio('newMonitor[ONVIF_Event_Listener]', array('1'=>translate('Enabled'), '0'=>translate('Disabled')), $monitor->ONVIF_Event_Listener()); ?></td>
|
||||
</tr>
|
||||
<tr id="function_use_Amcrest_API">
|
||||
<td class="text-right pr-3"><?php echo translate('use_Amcrest_API') ?></td>
|
||||
<td><?php echo html_radio('newMonitor[use_Amcrest_API]', array('1'=>translate('Enabled'), '0'=>'Disabled'), $monitor->use_Amcrest_API()); ?></td>
|
||||
<td><?php echo html_radio('newMonitor[use_Amcrest_API]', array('1'=>translate('Enabled'), '0'=>translate('Disabled')), $monitor->use_Amcrest_API()); ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
break;
|
||||
|
@ -1052,10 +1052,10 @@ include('_monitor_source_nvsocket.php');
|
|||
<td>
|
||||
<?php
|
||||
$savejpegopts = array(
|
||||
0 => 'Disabled',
|
||||
1 => 'Frames only',
|
||||
2 => 'Analysis images only (if available)',
|
||||
3 => 'Frames + Analysis images (if available)',
|
||||
0 => translate('Disabled'),
|
||||
1 => translate('Frames only'),
|
||||
2 => translate('Analysis images only (if available)'),
|
||||
3 => translate('Frames + Analysis images (if available)'),
|
||||
);
|
||||
echo htmlSelect('newMonitor[SaveJPEGs]', $savejpegopts, $monitor->SaveJPEGs());
|
||||
?>
|
||||
|
@ -1064,15 +1064,15 @@ include('_monitor_source_nvsocket.php');
|
|||
<tr><td class="text-right pr-3"><?php echo translate('VideoWriter') ?></td><td>
|
||||
<?php
|
||||
$videowriteropts = array(
|
||||
0 => 'Disabled',
|
||||
0 => translate('Disabled'),
|
||||
);
|
||||
|
||||
$videowriteropts[1] = 'Encode';
|
||||
$videowriteropts[1] = translate('Encode');
|
||||
|
||||
if ( $monitor->Type() == 'Ffmpeg' )
|
||||
$videowriteropts[2] = 'Camera Passthrough';
|
||||
$videowriteropts[2] = translate('Camera Passthrough');
|
||||
else
|
||||
$videowriteropts[2] = array('text'=>'Camera Passthrough - only for FFMPEG','disabled'=>1);
|
||||
$videowriteropts[2] = array('text'=>translate('Camera Passthrough - only for FFMPEG'),'disabled'=>1);
|
||||
echo htmlSelect('newMonitor[VideoWriter]', $videowriteropts, $monitor->VideoWriter());
|
||||
?>
|
||||
</td>
|
||||
|
@ -1135,7 +1135,7 @@ echo htmlSelect('newMonitor[OutputContainer]', $videowriter_containers, $monitor
|
|||
<?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
|
||||
<?php echo translate('Audio recording only available with FFMPEG')?>
|
||||
<input type="hidden" name="newMonitor[RecordAudio]" value="<?php echo $monitor->RecordAudio() ? 1 : 0 ?>"/>
|
||||
<?php } ?>
|
||||
</td>
|
||||
|
|
|
@ -114,8 +114,8 @@ while ( $event = $result->fetch(PDO::FETCH_ASSOC) ) {
|
|||
<div class="filterBar">
|
||||
<?php echo $filterbar ?>
|
||||
<div id="DateTimeDiv">
|
||||
<label>Event Start Time</label>
|
||||
<input type="text" name="minTime" id="minTime" value="<?php echo preg_replace('/T/', ' ', $minTime) ?>"/> to
|
||||
<label><?php echo translate('Event Start Time') ?></label>
|
||||
<input type="text" name="minTime" id="minTime" value="<?php echo preg_replace('/T/', ' ', $minTime) ?>"/> <?php echo translate('to') ?>
|
||||
<input type="text" name="maxTime" id="maxTime" value="<?php echo preg_replace('/T/', ' ', $maxTime) ?>"/>
|
||||
</div>
|
||||
</div><!--FilterBar-->
|
||||
|
|
Loading…
Reference in New Issue