null, 'Name' => '', 'ServerId' => 0, 'StorageId' => 0, 'Type' => 'Ffmpeg', 'Function' => 'None', 'Enabled' => array('type'=>'boolean','default'=>1), 'LinkedMonitors' => array('type'=>'set', 'default'=>null), 'Triggers' => array('type'=>'set','default'=>''), '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, 'DecoderHWAccelName' => null, 'DecoderHWAccelDevice' => null, 'SaveJPEGs' => 3, 'VideoWriter' => '0', 'OutputCodec' => null, 'OutputContainer' => null, 'EncoderParameters' => null, 'RecordAudio' => array('type'=>'boolean', 'default'=>0), 'RTSPDescribe' => array('type'=>'boolean','default'=>0), '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, 'MinSectionLength' => 10, 'FrameSkip' => 0, 'MotionFrameSkip' => 0, 'AnalysisFPSLimit' => null, 'AnalysisUpdateDelay' => 0, 'MaxFPS' => null, 'AlarmMaxFPS' => null, 'FPSReportInterval' => 100, 'RefBlendPerc' => 6, 'AlarmRefBlendPerc' => 6, 'Controllable' => array('type'=>'boolean','default'=>0), 'ControlId' => null, 'ControlDevice' => null, 'ControlAddress' => null, 'AutoStopTimeout' => null, 'TrackMotion' => array('type'=>'boolean','default'=>0), 'TrackDelay' => null, 'ReturnLocation' => -1, 'ReturnDelay' => null, 'DefaultRate' => 100, 'DefaultScale' => 100, 'SignalCheckPoints' => 0, 'SignalCheckColour' => '#0000BE', 'WebColour' => 'red', 'Exif' => array('type'=>'boolean','default'=>0), 'Sequence' => null, 'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'TotalEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'HourEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'HourEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'DayEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'DayEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'WeekEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'WeekEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'MonthEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'MonthEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'ArchivedEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'ZoneCount' => 0, 'Refresh' => null, 'DefaultCodec' => 'auto', 'GroupIds' => array('default'=>array(), 'do_not_update'=>1), ); private $status_fields = array( 'Status' => null, 'AnalysisFPS' => null, 'CaptureFPS' => null, 'CaptureBandwidth' => null, ); public function Control() { if ( !array_key_exists('Control', $this) ) { if ( $this->ControlId() ) $this->{'Control'} = Control::find_one(array('Id'=>$this->{'ControlId'})); else Error("No ControlId"); if ( !(array_key_exists('Control', $this) and $this->{'Control'} ) ) $this->{'Control'} = new Control(); } return $this->{'Control'}; } public function Server() { return new Server($this->{'ServerId'}); } public function __call($fn, array $args){ if ( count($args) ) { if ( is_array($this->defaults[$fn]) and $this->defaults[$fn]['type'] == 'set' ) { $this->{$fn} = is_array($args[0]) ? implode(',',$args[0]) : $args[0]; } else { $this->{$fn} = $args[0]; } } if ( array_key_exists($fn, $this) ) { return $this->{$fn}; } else if ( array_key_exists($fn, $this->defaults) ) { if ( is_array($this->defaults[$fn]) ) { return $this->defaults[$fn]['default']; } return $this->defaults[$fn]; } else if ( array_key_exists($fn, $this->status_fields) ) { $sql = 'SELECT Status,CaptureFPS,AnalysisFPS,CaptureBandwidth FROM Monitor_Status WHERE MonitorId=?'; $row = dbFetchOne($sql, NULL, array($this->{'Id'})); if ( !$row ) { Error('Unable to load Monitor record for Id='.$this->{'Id'}); } else { foreach ($row as $k => $v) { $this->{$k} = $v; } } return $this->{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning("Unknown function call Monitor->$fn from $file:$line"); } } public function getStreamSrc($args, $querySep='&') { $streamSrc = $this->Server()->UrlToZMS( ZM_MIN_STREAMING_PORT ? ZM_MIN_STREAMING_PORT+$this->{'Id'} : null); $args['monitor'] = $this->{'Id'}; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $args['user'] = $_SESSION['username']; $args['pass'] = $_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $args['user'] = $_SESSION['username']; } } if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) { $args['connkey'] = $GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { $args['rand'] = time(); } $streamSrc .= '?'.http_build_query($args, '', $querySep); return $streamSrc; } // end function getStreamSrc public function Width($new = null) { if ( $new ) $this->{'Width'} = $new; $field = ( $this->Orientation() == '90' or $this->Orientation() == '270' ) ? 'Height' : 'Width'; if ( array_key_exists($field, $this) ) return $this->{$field}; return $this->defaults{$field}; } // end function Width public function Height($new=null) { if ( $new ) $this->{'Height'} = $new; $field = ( $this->Orientation() == '90' or $this->Orientation() == '270' ) ? 'Width' : 'Height'; if ( array_key_exists($field, $this) ) return $this->{$field}; return $this->defaults{$field}; } // end function Height public function SignalCheckColour($new=null) { $field = 'SignalCheckColour'; if ($new) { $this->{$field} = $new; } // Validate that it's a valid colour (we seem to allow color names, not just hex). // This also helps prevent XSS. if (array_key_exists($field, $this) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) { return $this->{$field}; } return $this->defaults{$field}; } // end function SignalCheckColour public static function find( $parameters = array(), $options = array() ) { return ZM_Object::_find(get_class(), $parameters, $options); } public static function find_one( $parameters = array(), $options = array() ) { return ZM_Object::_find_one(get_class(), $parameters, $options); } function zmcControl( $mode=false ) { if ( ! $this->{'Id'} ) { Warning("Attempt to control a monitor with no Id"); return; } if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ( $this->Type() == 'Local' ) { $zmcArgs = '-d '.$this->{'Device'}; } else { $zmcArgs = '-m '.$this->{'Id'}; } if ( $mode == 'stop' ) { daemonControl('stop', 'zmc', $zmcArgs); } else { if ( $mode == 'restart' ) { daemonControl('stop', 'zmc', $zmcArgs); } if ( $this->{'Function'} != 'None' ) { daemonControl('start', 'zmc', $zmcArgs); } } } else if ( $this->ServerId() ) { $Server = $this->Server(); $url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $url = '?user='.$_SESSION['username']; $url = '?pass='.$_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $url = '?user='.$_SESSION['username']; } } Logger::Debug("sending command to $url"); $context = stream_context_create(); try { $result = file_get_contents($url, false, $context); if ($result === FALSE) { /* Handle error */ Error("Error restarting zmc using $url"); } } catch ( Exception $e ) { Error("Except $e thrown trying to restart zmc"); } } else { Error("Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor."); } } // end function zmcControl function zmaControl($mode=false) { if ( ! $this->{'Id'} ) { Warning("Attempt to control a monitor with no Id"); return; } if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ( $this->{'Function'} == 'None' || $this->{'Function'} == 'Monitor' || $mode == 'stop' ) { if ( ZM_OPT_CONTROL ) { daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'}); } daemonControl('stop', 'zma', '-m '.$this->{'Id'}); } else { if ( $mode == 'restart' ) { if ( ZM_OPT_CONTROL ) { daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'}); } daemonControl('stop', 'zma', '-m '.$this->{'Id'}); } daemonControl('start', 'zma', '-m '.$this->{'Id'}); if ( ZM_OPT_CONTROL && $this->Controllable() && $this->TrackMotion() && ( $this->{'Function'} == 'Modect' || $this->{'Function'} == 'Mocord' ) ) { daemonControl('start', 'zmtrack.pl', '-m '.$this->{'Id'}); } if ( $mode == 'reload' ) { daemonControl('reload', 'zma', '-m '.$this->{'Id'}); } } } else if ( $this->ServerId() ) { $Server = $this->Server(); $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zma.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $url = '?user='.$_SESSION['username']; $url = '?pass='.$_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $url = '?user='.$_SESSION['username']; } } Logger::Debug("sending command to $url"); $context = stream_context_create(); try { $result = file_get_contents($url, false, $context); if ($result === FALSE) { /* Handle error */ Error("Error restarting zma using $url"); } } catch ( Exception $e ) { Error("Except $e thrown trying to restart zma"); } } else { Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.'); } // end if we are on the recording server } // end public function zmaControl public function GroupIds( $new='' ) { if ( $new != '' ) { if ( !is_array($new) ) { $this->{'GroupIds'} = array($new); } else { $this->{'GroupIds'} = $new; } } if ( !array_key_exists('GroupIds', $this) ) { if ( array_key_exists('Id', $this) and $this->{'Id'} ) { $this->{'GroupIds'} = dbFetchAll('SELECT GroupId FROM Groups_Monitors WHERE MonitorId=?', 'GroupId', array($this->{'Id'}) ); if ( ! $this->{'GroupIds'} ) $this->{'GroupIds'} = array(); } else { $this->{'GroupIds'} = array(); } } return $this->{'GroupIds'}; } public function delete() { $this->zmaControl('stop'); $this->zmcControl('stop'); // If fast deletes are on, then zmaudit will clean everything else up later // If fast deletes are off and there are lots of events then this step may // well time out before completing, in which case zmaudit will still tidy up if ( !ZM_OPT_FAST_DELETE ) { $markEids = dbFetchAll('SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($this->{'Id'})); foreach ($markEids as $markEid) deleteEvent($markEid); deletePath(ZM_DIR_EVENTS.'/'.basename($this->{'Name'})); deletePath(ZM_DIR_EVENTS.'/'.$this->{'Id'}); $Storage = $this->Storage(); if ( $Storage->Path() != ZM_DIR_EVENTS ) { deletePath($Storage->Path().'/'.basename($this->{'Name'})); deletePath($Storage->Path().'/'.$this->{'Id'}); } } // end if !ZM_OPT_FAST_DELETE // This is the important stuff dbQuery('DELETE FROM Zones WHERE MonitorId = ?', array($this->{'Id'})); if ( ZM_OPT_X10 ) dbQuery('DELETE FROM TriggersX10 WHERE MonitorId=?', array($this->{'Id'})); dbQuery('DELETE FROM Monitors WHERE Id = ?', array($this->{'Id'})); } // end function delete public function Storage($new = null) { if ( $new ) { $this->{'Storage'} = $new; } if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) { $this->{'Storage'} = isset($this->{'StorageId'}) ? Storage::find_one(array('Id'=>$this->{'StorageId'})) : new Storage(NULL); if ( ! $this->{'Storage'} ) $this->{'Storage'} = new Storage(NULL); } return $this->{'Storage'}; } public function Source( ) { $source = ''; if ( $this->{'Type'} == 'Local' ) { $source = $this->{'Device'}.' ('.$this->{'Channel'}.')'; } elseif ( $this->{'Type'} == 'Remote' ) { $source = preg_replace( '/^.*@/', '', $this->{'Host'} ); if ( $this->{'Port'} != '80' and $this->{'Port'} != '554' ) { $source .= ':'.$this->{'Port'}; } } elseif ( $this->{'Type'} == 'File' || $this->{'Type'} == 'cURL' ) { $source = preg_replace( '/^.*\//', '', $this->{'Path'} ); } elseif ( $this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite' ) { $url_parts = parse_url( $this->{'Path'} ); if ( ZM_WEB_FILTER_SOURCE == 'Hostname' ) { # Filter out everything but the hostname if ( isset($url_parts['host']) ) { $source = $url_parts['host']; } else { $source = $this->{'Path'}; } } elseif ( ZM_WEB_FILTER_SOURCE == "NoCredentials" ) { # Filter out sensitive and common items unset($url_parts['user']); unset($url_parts['pass']); #unset($url_parts['scheme']); unset($url_parts['query']); #unset($url_parts['path']); if ( isset($url_parts['port']) and ( $url_parts['port'] == '80' or $url_parts['port'] == '554' ) ) unset($url_parts['port']); $source = unparse_url($url_parts); } else { # Don't filter anything $source = $this->{'Path'}; } } if ( $source == '' ) { $source = 'Monitor ' . $this->{'Id'}; } return $source; } // end function Source public function UrlToIndex() { return $this->Server()->UrlToIndex(); //ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null); } } // end class Monitor ?>