2016-05-06 03:33:28 +08:00
|
|
|
<?php
|
2019-02-22 22:19:07 +08:00
|
|
|
namespace ZM;
|
2019-02-27 00:28:56 +08:00
|
|
|
require_once('Storage.php');
|
|
|
|
require_once('functions.php');
|
2016-05-06 03:33:28 +08:00
|
|
|
|
2018-04-21 02:23:04 +08:00
|
|
|
$event_cache = array();
|
|
|
|
|
2016-05-06 03:33:28 +08:00
|
|
|
class Event {
|
2017-10-24 01:53:58 +08:00
|
|
|
|
|
|
|
private $fields = array(
|
|
|
|
'Id',
|
|
|
|
'Name',
|
|
|
|
'MonitorId',
|
|
|
|
'StorageId',
|
|
|
|
'Name',
|
2018-05-11 22:36:14 +08:00
|
|
|
'Cause',
|
|
|
|
'StartTime',
|
|
|
|
'EndTime',
|
|
|
|
'Width',
|
|
|
|
'Height',
|
|
|
|
'Length',
|
|
|
|
'Frames',
|
|
|
|
'AlarmFrames',
|
|
|
|
'DefaultVideo',
|
2017-11-22 12:57:44 +08:00
|
|
|
'SaveJPEGs',
|
2018-05-11 22:36:14 +08:00
|
|
|
'TotScore',
|
|
|
|
'AvgScore',
|
|
|
|
'MaxScore',
|
|
|
|
'Archived',
|
|
|
|
'Videoed',
|
|
|
|
'Uploaded',
|
|
|
|
'Emailed',
|
2018-05-16 03:56:17 +08:00
|
|
|
'Messaged',
|
2018-05-11 22:36:14 +08:00
|
|
|
'Executed',
|
|
|
|
'Notes',
|
|
|
|
'StateId',
|
|
|
|
'Orientation',
|
|
|
|
'DiskSpace',
|
2017-12-19 02:04:57 +08:00
|
|
|
'Scheme',
|
2018-05-11 22:36:14 +08:00
|
|
|
'Locked',
|
2017-10-24 01:53:58 +08:00
|
|
|
);
|
2017-10-11 01:39:17 +08:00
|
|
|
public function __construct( $IdOrRow = null ) {
|
2016-05-06 03:33:28 +08:00
|
|
|
$row = NULL;
|
|
|
|
if ( $IdOrRow ) {
|
2018-04-15 22:26:38 +08:00
|
|
|
if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) {
|
|
|
|
$row = dbFetchOne('SELECT *,unix_timestamp(StartTime) as Time FROM Events WHERE Id=?', NULL, array($IdOrRow));
|
2016-05-06 03:33:28 +08:00
|
|
|
if ( ! $row ) {
|
2017-01-02 23:34:15 +08:00
|
|
|
Error('Unable to load Event record for Id=' . $IdOrRow );
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
2018-04-15 22:26:38 +08:00
|
|
|
} elseif ( is_array($IdOrRow) ) {
|
2016-05-06 03:33:28 +08:00
|
|
|
$row = $IdOrRow;
|
|
|
|
} else {
|
2017-01-02 23:34:15 +08:00
|
|
|
$backTrace = debug_backtrace();
|
|
|
|
$file = $backTrace[1]['file'];
|
|
|
|
$line = $backTrace[1]['line'];
|
|
|
|
Error("Unknown argument passed to Event Constructor from $file:$line)");
|
2016-05-06 03:33:28 +08:00
|
|
|
Error("Unknown argument passed to Event Constructor ($IdOrRow)");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-15 22:26:38 +08:00
|
|
|
if ( $row ) {
|
|
|
|
foreach ($row as $k => $v) {
|
|
|
|
$this->{$k} = $v;
|
|
|
|
}
|
2018-04-21 02:23:04 +08:00
|
|
|
global $event_cache;
|
|
|
|
$event_cache[$row['Id']] = $this;
|
2018-04-15 22:26:38 +08:00
|
|
|
} else {
|
2017-10-11 01:39:17 +08:00
|
|
|
$backTrace = debug_backtrace();
|
|
|
|
$file = $backTrace[1]['file'];
|
|
|
|
$line = $backTrace[1]['line'];
|
2018-04-15 22:26:38 +08:00
|
|
|
Error('No row for Event ' . $IdOrRow . " from $file:$line");
|
|
|
|
}
|
2017-10-11 01:39:17 +08:00
|
|
|
} # end if isset($IdOrRow)
|
2016-05-06 03:33:28 +08:00
|
|
|
} // end function __construct
|
2017-06-01 08:54:34 +08:00
|
|
|
|
2017-10-11 03:39:14 +08:00
|
|
|
public function Storage( $new = null ) {
|
|
|
|
if ( $new ) {
|
|
|
|
$this->{'Storage'} = $new;
|
|
|
|
}
|
2018-04-15 09:50:27 +08:00
|
|
|
if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) {
|
|
|
|
if ( isset($this->{'StorageId'}) and $this->{'StorageId'} )
|
|
|
|
$this->{'Storage'} = Storage::find_one(array('Id'=>$this->{'StorageId'}));
|
2018-04-15 22:26:38 +08:00
|
|
|
if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) )
|
2018-04-15 09:50:27 +08:00
|
|
|
$this->{'Storage'} = new Storage(NULL);
|
2017-10-11 03:39:14 +08:00
|
|
|
}
|
|
|
|
return $this->{'Storage'};
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
2017-06-01 08:54:34 +08:00
|
|
|
|
2017-01-02 23:34:15 +08:00
|
|
|
public function Monitor() {
|
2018-09-15 21:42:59 +08:00
|
|
|
if ( isset($this->{'MonitorId'}) ) {
|
2018-09-20 21:02:20 +08:00
|
|
|
$Monitor = Monitor::find_one(array('Id'=>$this->{'MonitorId'}));
|
|
|
|
if ( $Monitor )
|
|
|
|
return $Monitor;
|
2018-09-15 21:42:59 +08:00
|
|
|
}
|
|
|
|
return new Monitor();
|
2017-01-02 23:34:15 +08:00
|
|
|
}
|
2017-06-01 08:54:34 +08:00
|
|
|
|
2016-05-06 03:33:28 +08:00
|
|
|
public function __call( $fn, array $args){
|
2017-10-18 01:09:14 +08:00
|
|
|
if ( count( $args ) ) {
|
|
|
|
$this->{$fn} = $args[0];
|
|
|
|
}
|
2017-01-02 23:34:15 +08:00
|
|
|
if ( array_key_exists( $fn, $this ) ) {
|
2016-05-06 03:33:28 +08:00
|
|
|
return $this->{$fn};
|
2017-10-18 01:09:14 +08:00
|
|
|
|
2018-09-15 21:42:59 +08:00
|
|
|
$backTrace = debug_backtrace();
|
|
|
|
$file = $backTrace[0]['file'];
|
|
|
|
$line = $backTrace[0]['line'];
|
|
|
|
Warning("Unknown function call Event->$fn from $file:$line");
|
|
|
|
$file = $backTrace[1]['file'];
|
|
|
|
$line = $backTrace[1]['line'];
|
|
|
|
Warning("Unknown function call Event->$fn from $file:$line");
|
|
|
|
Warning(print_r( $this, true ));
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function Time() {
|
2018-04-15 22:26:38 +08:00
|
|
|
if ( ! isset($this->{'Time'}) ) {
|
2016-05-06 03:33:28 +08:00
|
|
|
$this->{'Time'} = strtotime($this->{'StartTime'});
|
|
|
|
}
|
|
|
|
return $this->{'Time'};
|
|
|
|
}
|
|
|
|
|
|
|
|
public function Path() {
|
|
|
|
$Storage = $this->Storage();
|
2019-03-19 21:36:58 +08:00
|
|
|
if ( $Storage->Path() and $this->Relative_Path() ) {
|
|
|
|
return $Storage->Path().'/'.$this->Relative_Path();
|
|
|
|
} else {
|
|
|
|
Error("Event Path not complete. Storage: " . $Storage->Path() . " relative: " . $this->Relative_Path());
|
|
|
|
return '';
|
|
|
|
}
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
2017-06-01 08:54:34 +08:00
|
|
|
|
2016-05-06 03:33:28 +08:00
|
|
|
public function Relative_Path() {
|
2017-01-02 23:34:15 +08:00
|
|
|
$event_path = '';
|
|
|
|
|
2017-12-19 01:52:26 +08:00
|
|
|
if ( $this->{'Scheme'} == 'Deep' ) {
|
2017-01-02 23:34:15 +08:00
|
|
|
$event_path = $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/%H/%M/%S', $this->Time()) ;
|
2017-12-19 02:16:54 +08:00
|
|
|
} else if ( $this->{'Scheme'} == 'Medium' ) {
|
2017-12-20 00:01:03 +08:00
|
|
|
$event_path = $this->{'MonitorId'} .'/'.strftime( '%Y-%m-%d', $this->Time() ) . '/'.$this->{'Id'};
|
2017-01-02 23:34:15 +08:00
|
|
|
} else {
|
|
|
|
$event_path = $this->{'MonitorId'} .'/'.$this->{'Id'};
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
|
|
|
|
2018-04-15 22:26:38 +08:00
|
|
|
return $event_path;
|
2017-01-02 23:34:15 +08:00
|
|
|
} // end function Relative_Path()
|
2016-05-06 03:33:28 +08:00
|
|
|
|
2017-01-02 23:34:15 +08:00
|
|
|
public function Link_Path() {
|
2017-12-19 01:52:26 +08:00
|
|
|
if ( $this->{'Scheme'} == 'Deep' ) {
|
2017-01-02 23:34:15 +08:00
|
|
|
return $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/.', $this->Time()).$this->{'Id'};
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
2017-01-02 23:34:15 +08:00
|
|
|
Error('Calling Link_Path when not using deep storage');
|
2016-05-06 03:33:28 +08:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function delete() {
|
2019-03-19 21:36:58 +08:00
|
|
|
if ( ! $this->{'Id'} ) {
|
|
|
|
Error('Event delete on event with empty Id');
|
|
|
|
return;
|
|
|
|
}
|
2016-05-06 03:33:28 +08:00
|
|
|
if ( !ZM_OPT_FAST_DELETE ) {
|
2019-03-19 21:36:58 +08:00
|
|
|
dbQuery('DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}));
|
|
|
|
dbQuery('DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}));
|
2017-12-19 01:52:26 +08:00
|
|
|
if ( $this->{'Scheme'} == 'Deep' ) {
|
2016-05-06 03:33:28 +08:00
|
|
|
|
2017-01-02 23:34:15 +08:00
|
|
|
# Assumption: All events have a start time
|
2019-03-19 21:36:58 +08:00
|
|
|
$start_date = date_parse($this->{'StartTime'});
|
2017-01-02 23:34:15 +08:00
|
|
|
if ( ! $start_date ) {
|
2019-03-19 21:36:58 +08:00
|
|
|
Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.');
|
2017-01-02 23:34:15 +08:00
|
|
|
return;
|
|
|
|
}
|
2016-05-06 03:33:28 +08:00
|
|
|
$start_date['year'] = $start_date['year'] % 100;
|
|
|
|
|
2017-01-02 23:34:15 +08:00
|
|
|
# So this is because ZM creates a link under the day pointing to the time that the event happened.
|
|
|
|
$link_path = $this->Link_Path();
|
|
|
|
if ( ! $link_path ) {
|
2019-03-19 21:36:58 +08:00
|
|
|
Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.');
|
2017-01-02 23:34:15 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-06 03:33:28 +08:00
|
|
|
$Storage = $this->Storage();
|
2017-01-02 23:34:15 +08:00
|
|
|
$eventlink_path = $Storage->Path().'/'.$link_path;
|
2016-05-06 03:33:28 +08:00
|
|
|
|
2019-03-19 21:36:58 +08:00
|
|
|
if ( $id_files = glob($eventlink_path) ) {
|
2017-01-02 23:34:15 +08:00
|
|
|
if ( ! $eventPath = readlink($id_files[0]) ) {
|
|
|
|
Error("Unable to read link at $id_files[0]");
|
|
|
|
return;
|
|
|
|
}
|
2016-05-06 03:33:28 +08:00
|
|
|
# I know we are using arrays here, but really there can only ever be 1 in the array
|
2017-01-02 23:34:15 +08:00
|
|
|
$eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] );
|
2019-03-19 21:36:58 +08:00
|
|
|
deletePath($eventPath);
|
|
|
|
deletePath($id_files[0]);
|
|
|
|
$pathParts = explode('/', $eventPath);
|
2016-05-06 03:33:28 +08:00
|
|
|
for ( $i = count($pathParts)-1; $i >= 2; $i-- ) {
|
2019-03-19 21:36:58 +08:00
|
|
|
$deletePath = join('/', array_slice($pathParts, 0, $i));
|
|
|
|
if ( !glob($deletePath.'/*') ) {
|
|
|
|
deletePath($deletePath);
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-03-19 21:36:58 +08:00
|
|
|
Warning("Found no event files under $eventlink_path");
|
2016-05-06 03:33:28 +08:00
|
|
|
} # end if found files
|
|
|
|
} else {
|
|
|
|
$eventPath = $this->Path();
|
2019-03-19 21:36:58 +08:00
|
|
|
if ( ! $eventPath ) {
|
|
|
|
Error("No event Path in Event delete. Not deleting");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
deletePath($eventPath);
|
2016-05-06 03:33:28 +08:00
|
|
|
} # USE_DEEP_STORAGE OR NOT
|
|
|
|
} # ! ZM_OPT_FAST_DELETE
|
2019-03-19 21:36:58 +08:00
|
|
|
dbQuery('DELETE FROM Events WHERE Id = ?', array($this->{'Id'}));
|
2016-05-06 03:33:28 +08:00
|
|
|
} # end Event->delete
|
|
|
|
|
2018-04-06 09:42:15 +08:00
|
|
|
public function getStreamSrc( $args=array(), $querySep='&' ) {
|
2018-04-18 00:35:59 +08:00
|
|
|
|
2018-07-10 00:08:50 +08:00
|
|
|
$streamSrc = '';
|
2018-11-22 23:04:33 +08:00
|
|
|
$Server = null;
|
2018-05-04 00:36:50 +08:00
|
|
|
if ( $this->Storage()->ServerId() ) {
|
2018-11-22 23:04:33 +08:00
|
|
|
# The Event may have been moved to Storage on another server,
|
|
|
|
# So prefer viewing the Event from the Server that is actually
|
|
|
|
# storing the video
|
2018-05-04 00:36:50 +08:00
|
|
|
$Server = $this->Storage()->Server();
|
|
|
|
} else if ( $this->Monitor()->ServerId() ) {
|
|
|
|
# Assume that the server that recorded it has it
|
|
|
|
$Server = $this->Monitor()->Server();
|
|
|
|
} else {
|
2018-11-22 23:04:33 +08:00
|
|
|
# A default Server will result in the use of ZM_DIR_EVENTS
|
|
|
|
$Server = new Server();
|
2018-05-04 00:36:50 +08:00
|
|
|
}
|
2018-04-18 00:35:59 +08:00
|
|
|
|
2018-11-22 23:04:33 +08:00
|
|
|
# If we are in a multi-port setup, then use the multiport, else by
|
|
|
|
# passing null Server->Url will use the Port set in the Server setting
|
2018-07-10 00:08:50 +08:00
|
|
|
$streamSrc .= $Server->Url(
|
|
|
|
ZM_MIN_STREAMING_PORT ?
|
|
|
|
ZM_MIN_STREAMING_PORT+$this->{'MonitorId'} :
|
|
|
|
null);
|
2018-04-18 00:35:59 +08:00
|
|
|
|
2017-08-24 03:05:44 +08:00
|
|
|
if ( $this->{'DefaultVideo'} and $args['mode'] != 'jpeg' ) {
|
2018-11-30 03:26:30 +08:00
|
|
|
$streamSrc .= $Server->PathToIndex();
|
2018-02-23 02:05:53 +08:00
|
|
|
$args['eid'] = $this->{'Id'};
|
|
|
|
$args['view'] = 'view_video';
|
|
|
|
} else {
|
2018-11-30 03:26:30 +08:00
|
|
|
$streamSrc .= $Server->PathToZMS();
|
2016-05-06 03:33:28 +08:00
|
|
|
|
2018-02-23 02:05:53 +08:00
|
|
|
$args['source'] = 'event';
|
|
|
|
$args['event'] = $this->{'Id'};
|
|
|
|
if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) {
|
|
|
|
$args['connkey'] = $GLOBALS['connkey'];
|
|
|
|
}
|
|
|
|
if ( ZM_RAND_STREAM ) {
|
|
|
|
$args['rand'] = time();
|
|
|
|
}
|
|
|
|
}
|
2016-05-06 03:33:28 +08:00
|
|
|
|
|
|
|
if ( ZM_OPT_USE_AUTH ) {
|
2017-01-02 23:34:15 +08:00
|
|
|
if ( ZM_AUTH_RELAY == 'hashed' ) {
|
2018-05-04 00:36:50 +08:00
|
|
|
$args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
|
2018-11-22 23:04:33 +08:00
|
|
|
} else if ( ZM_AUTH_RELAY == 'plain' ) {
|
2017-06-09 02:01:22 +08:00
|
|
|
$args['user'] = $_SESSION['username'];
|
|
|
|
$args['pass'] = $_SESSION['password'];
|
2018-11-22 23:04:33 +08:00
|
|
|
} else if ( ZM_AUTH_RELAY == 'none' ) {
|
2017-06-09 02:01:22 +08:00
|
|
|
$args['user'] = $_SESSION['username'];
|
2016-05-06 03:33:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-04 00:36:50 +08:00
|
|
|
$streamSrc .= '?'.http_build_query($args,'', $querySep);
|
2016-05-06 03:33:28 +08:00
|
|
|
|
2018-02-23 02:05:53 +08:00
|
|
|
return $streamSrc;
|
2016-05-06 03:33:28 +08:00
|
|
|
} // end function getStreamSrc
|
2017-01-02 23:34:15 +08:00
|
|
|
|
2017-10-24 08:02:04 +08:00
|
|
|
function DiskSpace( $new='' ) {
|
2018-04-04 00:57:14 +08:00
|
|
|
if ( is_null($new) or ( $new != '' ) ) {
|
2017-10-24 08:02:04 +08:00
|
|
|
$this->{'DiskSpace'} = $new;
|
|
|
|
}
|
2018-09-15 21:42:59 +08:00
|
|
|
if ( (!array_key_exists('DiskSpace',$this)) or (null === $this->{'DiskSpace'}) ) {
|
2018-04-15 22:26:38 +08:00
|
|
|
$this->{'DiskSpace'} = folder_size($this->Path());
|
|
|
|
dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'}));
|
2017-10-10 21:36:56 +08:00
|
|
|
}
|
2017-10-11 01:39:17 +08:00
|
|
|
return $this->{'DiskSpace'};
|
2016-07-20 05:34:01 +08:00
|
|
|
}
|
2017-01-02 23:34:15 +08:00
|
|
|
|
|
|
|
function createListThumbnail( $overwrite=false ) {
|
2018-02-20 06:25:49 +08:00
|
|
|
# The idea here is that we don't really want to use the analysis jpeg as the thumbnail.
|
|
|
|
# The snapshot image will be generated during capturing
|
|
|
|
if ( file_exists($this->Path().'/snapshot.jpg') ) {
|
|
|
|
Logger::Debug("snapshot exists");
|
2018-01-22 10:27:01 +08:00
|
|
|
$frame = null;
|
|
|
|
} else {
|
2018-02-20 06:25:49 +08:00
|
|
|
# Load the frame with the highest score to use as a thumbnail
|
|
|
|
if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) {
|
|
|
|
Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} );
|
|
|
|
// FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame.
|
|
|
|
return false;
|
|
|
|
}
|
2018-01-22 10:27:01 +08:00
|
|
|
}
|
2017-01-02 23:34:15 +08:00
|
|
|
|
2018-02-20 06:25:49 +08:00
|
|
|
$imageData = $this->getImageSrc($frame, $scale, false, $overwrite);
|
2017-01-02 23:34:15 +08:00
|
|
|
if ( ! $imageData ) {
|
2018-02-20 06:25:49 +08:00
|
|
|
return false;
|
2017-01-02 23:34:15 +08:00
|
|
|
}
|
|
|
|
$thumbData = $frame;
|
|
|
|
$thumbData['Path'] = $imageData['thumbPath'];
|
2018-05-04 00:36:50 +08:00
|
|
|
$thumbData['Width'] = $this->ThumbnailWidth();
|
|
|
|
$thumbData['Height'] = $this->ThumbnailHeight();
|
|
|
|
$thumbData['url'] = '?view=image&eid='.$this->Id().'&fid='.$imageData['FrameId'].'&width='.$thumbData['Width'].'&height='.$thumbData['Height'];
|
2017-01-02 23:34:15 +08:00
|
|
|
|
2018-02-20 06:25:49 +08:00
|
|
|
return $thumbData;
|
2017-01-02 23:34:15 +08:00
|
|
|
} // end function createListThumbnail
|
|
|
|
|
2018-05-04 00:36:50 +08:00
|
|
|
function ThumbnailWidth( ) {
|
|
|
|
if ( ! ( array_key_exists('ThumbnailWidth', $this) ) ) {
|
|
|
|
if ( ZM_WEB_LIST_THUMB_WIDTH ) {
|
|
|
|
$this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH;
|
|
|
|
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'};
|
|
|
|
$this->{'ThumbnailHeight'} = reScale( $this->{'Height'}, $scale );
|
|
|
|
} elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) {
|
|
|
|
$this->{'ThumbnailHeight'} = ZM_WEB_LIST_THUMB_HEIGHT;
|
|
|
|
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$this->{'Height'};
|
|
|
|
$this->{'ThumbnailWidth'} = reScale( $this->{'Width'}, $scale );
|
|
|
|
} else {
|
|
|
|
Fatal( "No thumbnail width or height specified, please check in Options->Web" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $this->{'ThumbnailWidth'};
|
|
|
|
} // end function ThumbnailWidth
|
|
|
|
|
|
|
|
function ThumbnailHeight( ) {
|
|
|
|
if ( ! ( array_key_exists('ThumbnailHeight', $this) ) ) {
|
|
|
|
if ( ZM_WEB_LIST_THUMB_WIDTH ) {
|
|
|
|
$this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH;
|
|
|
|
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'};
|
|
|
|
$this->{'ThumbnailHeight'} = reScale( $this->{'Height'}, $scale );
|
|
|
|
} elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) {
|
|
|
|
$this->{'ThumbnailHeight'} = ZM_WEB_LIST_THUMB_HEIGHT;
|
|
|
|
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$this->{'Height'};
|
|
|
|
$this->{'ThumbnailWidth'} = reScale( $this->{'Width'}, $scale );
|
|
|
|
} else {
|
|
|
|
Fatal( "No thumbnail width or height specified, please check in Options->Web" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $this->{'ThumbnailHeight'};
|
|
|
|
} // end function ThumbnailHeight
|
|
|
|
|
|
|
|
function getThumbnailSrc( $args=array(), $querySep='&' ) {
|
|
|
|
# The thumbnail is theoretically the image with the most motion.
|
|
|
|
# We always store at least 1 image when capturing
|
|
|
|
|
2018-07-10 00:08:50 +08:00
|
|
|
$streamSrc = '';
|
2018-11-22 23:04:33 +08:00
|
|
|
$Server = null;
|
2018-05-04 00:36:50 +08:00
|
|
|
if ( $this->Storage()->ServerId() ) {
|
|
|
|
$Server = $this->Storage()->Server();
|
|
|
|
} else if ( $this->Monitor()->ServerId() ) {
|
2018-07-10 00:08:50 +08:00
|
|
|
# Assume that the server that recorded it has it
|
2018-05-04 00:36:50 +08:00
|
|
|
$Server = $this->Monitor()->Server();
|
|
|
|
} else {
|
2018-11-22 23:04:33 +08:00
|
|
|
$Server = new Server();
|
2018-07-10 00:08:50 +08:00
|
|
|
}
|
2018-11-30 03:26:30 +08:00
|
|
|
$streamSrc .= $Server->UrlToIndex(
|
2018-07-10 00:08:50 +08:00
|
|
|
ZM_MIN_STREAMING_PORT ?
|
|
|
|
ZM_MIN_STREAMING_PORT+$this->{'MonitorId'} :
|
|
|
|
null);
|
2018-05-04 00:36:50 +08:00
|
|
|
|
|
|
|
$args['eid'] = $this->{'Id'};
|
|
|
|
$args['fid'] = 'snapshot';
|
|
|
|
$args['view'] = 'image';
|
|
|
|
$args['width'] = $this->ThumbnailWidth();
|
|
|
|
$args['height'] = $this->ThumbnailHeight();
|
|
|
|
|
|
|
|
if ( ZM_OPT_USE_AUTH ) {
|
|
|
|
if ( ZM_AUTH_RELAY == 'hashed' ) {
|
|
|
|
$args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
|
2018-11-22 23:04:33 +08:00
|
|
|
} else if ( ZM_AUTH_RELAY == 'plain' ) {
|
2018-05-04 00:36:50 +08:00
|
|
|
$args['user'] = $_SESSION['username'];
|
|
|
|
$args['pass'] = $_SESSION['password'];
|
2018-11-22 23:04:33 +08:00
|
|
|
} else if ( ZM_AUTH_RELAY == 'none' ) {
|
2018-05-04 00:36:50 +08:00
|
|
|
$args['user'] = $_SESSION['username'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $streamSrc.'?'.http_build_query($args,'', $querySep);
|
|
|
|
} // end function getThumbnailSrc
|
|
|
|
|
2017-06-01 08:54:34 +08:00
|
|
|
// frame is an array representing the db row for a frame.
|
2018-02-20 06:25:49 +08:00
|
|
|
function getImageSrc($frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false) {
|
2018-04-18 00:35:59 +08:00
|
|
|
$Storage = $this->Storage();
|
2017-01-02 23:34:15 +08:00
|
|
|
$Event = $this;
|
|
|
|
$eventPath = $Event->Path();
|
|
|
|
|
2017-06-01 08:54:34 +08:00
|
|
|
if ( $frame and ! is_array($frame) ) {
|
|
|
|
# Must be an Id
|
2018-05-04 00:50:54 +08:00
|
|
|
Logger::Debug("Assuming that $frame is an Id");
|
2017-01-02 23:34:15 +08:00
|
|
|
$frame = array( 'FrameId'=>$frame, 'Type'=>'' );
|
2017-06-01 08:54:34 +08:00
|
|
|
}
|
2017-01-02 23:34:15 +08:00
|
|
|
|
2018-02-20 06:25:49 +08:00
|
|
|
if ( ( ! $frame ) and file_exists($eventPath.'/snapshot.jpg') ) {
|
2017-06-01 08:54:34 +08:00
|
|
|
# No frame specified, so look for a snapshot to use
|
|
|
|
$captImage = 'snapshot.jpg';
|
2018-01-22 10:27:01 +08:00
|
|
|
Logger::Debug("Frame not specified, using snapshot");
|
2018-05-04 00:50:54 +08:00
|
|
|
$frame = array('FrameId'=>'snapshot', 'Type'=>'');
|
2017-01-02 23:34:15 +08:00
|
|
|
} else {
|
2017-07-28 21:54:33 +08:00
|
|
|
$captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyze.jpg', $frame['FrameId'] );
|
2017-01-02 23:34:15 +08:00
|
|
|
if ( ! file_exists( $eventPath.'/'.$captImage ) ) {
|
2017-07-28 21:54:33 +08:00
|
|
|
$captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-capture.jpg', $frame['FrameId'] );
|
|
|
|
if ( ! file_exists( $eventPath.'/'.$captImage ) ) {
|
|
|
|
# Generate the frame JPG
|
|
|
|
if ( $Event->DefaultVideo() ) {
|
|
|
|
$videoPath = $eventPath.'/'.$Event->DefaultVideo();
|
|
|
|
|
|
|
|
if ( ! file_exists( $videoPath ) ) {
|
|
|
|
Error("Event claims to have a video file, but it does not seem to exist at $videoPath" );
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
#$command ='ffmpeg -v 0 -i '.$videoPath.' -vf "select=gte(n\\,'.$frame['FrameId'].'),setpts=PTS-STARTPTS" '.$eventPath.'/'.$captImage;
|
|
|
|
$command ='ffmpeg -ss '. $frame['Delta'] .' -i '.$videoPath.' -frames:v 1 '.$eventPath.'/'.$captImage;
|
|
|
|
Logger::Debug( "Running $command" );
|
|
|
|
$output = array();
|
|
|
|
$retval = 0;
|
|
|
|
exec( $command, $output, $retval );
|
|
|
|
Logger::Debug("Retval: $retval, output: " . implode("\n", $output));
|
|
|
|
} else {
|
2018-01-22 10:27:01 +08:00
|
|
|
Error("Can't create frame images from video because there is no video file for event ".$Event->Id().' at ' .$Event->Path() );
|
2017-07-28 21:54:33 +08:00
|
|
|
}
|
|
|
|
} // end if capture file exists
|
|
|
|
} // end if analyze file exists
|
2017-01-02 23:34:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
$captPath = $eventPath.'/'.$captImage;
|
2018-05-04 00:50:54 +08:00
|
|
|
if ( ! file_exists($captPath) ) {
|
2019-03-26 00:18:01 +08:00
|
|
|
Error("Capture file does not exist at $captPath");
|
2017-01-02 23:34:15 +08:00
|
|
|
}
|
|
|
|
|
2018-12-28 23:46:13 +08:00
|
|
|
//echo "CI:$captImage, CP:$captPath, TCP:$captPath<br>";
|
2017-01-02 23:34:15 +08:00
|
|
|
|
2019-03-26 00:18:01 +08:00
|
|
|
$analImage = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId']);
|
2017-01-02 23:34:15 +08:00
|
|
|
$analPath = $eventPath.'/'.$analImage;
|
|
|
|
|
2018-12-28 23:46:13 +08:00
|
|
|
//echo "AI:$analImage, AP:$analPath, TAP:$analPath<br>";
|
2017-01-02 23:34:15 +08:00
|
|
|
|
|
|
|
$alarmFrame = $frame['Type']=='Alarm';
|
|
|
|
|
2019-03-26 00:18:01 +08:00
|
|
|
$hasAnalImage = $alarmFrame && file_exists($analPath) && filesize($analPath);
|
2017-01-02 23:34:15 +08:00
|
|
|
$isAnalImage = $hasAnalImage && !$captureOnly;
|
|
|
|
|
2019-03-26 00:18:01 +08:00
|
|
|
if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists('imagecreatefromjpeg') ) {
|
|
|
|
$imagePath = $thumbPath = $isAnalImage ? $analPath : $captPath;
|
2017-01-02 23:34:15 +08:00
|
|
|
$imageFile = $imagePath;
|
|
|
|
$thumbFile = $thumbPath;
|
|
|
|
} else {
|
|
|
|
if ( version_compare( phpversion(), '4.3.10', '>=') )
|
2019-03-26 00:18:01 +08:00
|
|
|
$fraction = sprintf('%.3F', $scale/SCALE_BASE);
|
2017-01-02 23:34:15 +08:00
|
|
|
else
|
2019-03-26 00:18:01 +08:00
|
|
|
$fraction = sprintf('%.3f', $scale/SCALE_BASE);
|
|
|
|
$scale = (int)round($scale);
|
2017-01-02 23:34:15 +08:00
|
|
|
|
2018-12-28 23:46:13 +08:00
|
|
|
$thumbCaptPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $captPath );
|
|
|
|
$thumbAnalPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $analPath );
|
2017-01-02 23:34:15 +08:00
|
|
|
|
|
|
|
if ( $isAnalImage ) {
|
|
|
|
$imagePath = $analPath;
|
|
|
|
$thumbPath = $thumbAnalPath;
|
|
|
|
} else {
|
|
|
|
$imagePath = $captPath;
|
|
|
|
$thumbPath = $thumbCaptPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
$thumbFile = $thumbPath;
|
2017-06-01 08:54:34 +08:00
|
|
|
if ( $overwrite || ! file_exists( $thumbFile ) || ! filesize( $thumbFile ) ) {
|
2017-01-02 23:34:15 +08:00
|
|
|
// Get new dimensions
|
|
|
|
list( $imageWidth, $imageHeight ) = getimagesize( $imagePath );
|
|
|
|
$thumbWidth = $imageWidth * $fraction;
|
|
|
|
$thumbHeight = $imageHeight * $fraction;
|
|
|
|
|
|
|
|
// Resample
|
|
|
|
$thumbImage = imagecreatetruecolor( $thumbWidth, $thumbHeight );
|
|
|
|
$image = imagecreatefromjpeg( $imagePath );
|
|
|
|
imagecopyresampled( $thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight );
|
|
|
|
|
|
|
|
if ( !imagejpeg( $thumbImage, $thumbPath ) )
|
|
|
|
Error( "Can't create thumbnail '$thumbPath'" );
|
|
|
|
}
|
2017-06-01 08:54:34 +08:00
|
|
|
} # Create thumbnails
|
2017-01-02 23:34:15 +08:00
|
|
|
|
|
|
|
$imageData = array(
|
|
|
|
'eventPath' => $eventPath,
|
|
|
|
'imagePath' => $imagePath,
|
|
|
|
'thumbPath' => $thumbPath,
|
|
|
|
'imageFile' => $imagePath,
|
|
|
|
'thumbFile' => $thumbFile,
|
2017-06-01 08:54:34 +08:00
|
|
|
'imageClass' => $alarmFrame?'alarm':'normal',
|
2017-01-02 23:34:15 +08:00
|
|
|
'isAnalImage' => $isAnalImage,
|
|
|
|
'hasAnalImage' => $hasAnalImage,
|
2018-05-04 00:50:54 +08:00
|
|
|
'FrameId' => $frame['FrameId'],
|
2017-01-02 23:34:15 +08:00
|
|
|
);
|
|
|
|
|
2018-02-20 06:25:49 +08:00
|
|
|
return $imageData;
|
2017-01-02 23:34:15 +08:00
|
|
|
}
|
|
|
|
|
2018-04-21 02:23:04 +08:00
|
|
|
public static function find_one( $parameters = null, $options = null ) {
|
|
|
|
global $event_cache;
|
|
|
|
if (
|
|
|
|
( count($parameters) == 1 ) and
|
|
|
|
isset($parameters['Id']) and
|
|
|
|
isset($event_cache[$parameters['Id']]) ) {
|
|
|
|
return $event_cache[$parameters['Id']];
|
|
|
|
}
|
2018-09-08 04:31:11 +08:00
|
|
|
$results = Event::find( $parameters, $options );
|
2018-04-21 02:23:04 +08:00
|
|
|
if ( count($results) > 1 ) {
|
|
|
|
Error("Event Returned more than 1");
|
|
|
|
return $results[0];
|
|
|
|
} else if ( count($results) ) {
|
|
|
|
return $results[0];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-08 04:31:11 +08:00
|
|
|
public static function find( $parameters = null, $options = null ) {
|
2017-10-10 22:38:13 +08:00
|
|
|
$sql = 'SELECT * FROM Events ';
|
|
|
|
$values = array();
|
|
|
|
|
|
|
|
if ( $parameters ) {
|
|
|
|
$fields = array();
|
|
|
|
$sql .= 'WHERE ';
|
|
|
|
foreach ( $parameters as $field => $value ) {
|
|
|
|
if ( $value == null ) {
|
|
|
|
$fields[] = $field.' IS NULL';
|
|
|
|
} else if ( is_array( $value ) ) {
|
|
|
|
$func = function(){return '?';};
|
|
|
|
$fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')';
|
|
|
|
$values += $value;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
$fields[] = $field.'=?';
|
|
|
|
$values[] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$sql .= implode(' AND ', $fields );
|
|
|
|
}
|
2018-09-08 04:31:11 +08:00
|
|
|
if ( $options ) {
|
|
|
|
if ( isset($options['order']) ) {
|
|
|
|
$sql .= ' ORDER BY ' . $options['order'];
|
|
|
|
}
|
|
|
|
if ( isset($options['limit']) ) {
|
|
|
|
if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) {
|
2018-09-15 21:42:59 +08:00
|
|
|
$sql .= ' LIMIT ' . $options['limit'];
|
2018-09-08 04:31:11 +08:00
|
|
|
} else {
|
|
|
|
$backTrace = debug_backtrace();
|
|
|
|
$file = $backTrace[1]['file'];
|
|
|
|
$line = $backTrace[1]['line'];
|
2018-09-15 21:42:59 +08:00
|
|
|
Error("Invalid value for limit(".$options['limit'].") passed to Event::find from $file:$line");
|
2018-09-10 01:59:10 +08:00
|
|
|
return array();
|
2018-09-08 04:31:11 +08:00
|
|
|
}
|
|
|
|
}
|
2017-10-10 22:38:13 +08:00
|
|
|
}
|
2018-09-15 21:42:59 +08:00
|
|
|
$filters = array();
|
2017-10-10 22:38:13 +08:00
|
|
|
$result = dbQuery($sql, $values);
|
2018-10-29 21:59:26 +08:00
|
|
|
if ( $result ) {
|
|
|
|
$results = $result->fetchALL();
|
|
|
|
foreach ( $results as $row ) {
|
|
|
|
$filters[] = new Event($row);
|
|
|
|
}
|
2017-10-10 22:38:13 +08:00
|
|
|
}
|
|
|
|
return $filters;
|
|
|
|
}
|
|
|
|
|
2017-10-24 01:53:58 +08:00
|
|
|
public function save( ) {
|
2017-10-24 08:02:04 +08:00
|
|
|
|
|
|
|
$sql = 'UPDATE Events SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $this->fields ) ) . ' WHERE Id=?';
|
|
|
|
$values = array_map( function($field){return $this->{$field};}, $this->fields );
|
2017-10-24 01:53:58 +08:00
|
|
|
$values[] = $this->{'Id'};
|
2017-10-24 08:02:04 +08:00
|
|
|
dbQuery( $sql, $values );
|
2017-10-24 01:53:58 +08:00
|
|
|
}
|
2018-04-28 04:20:38 +08:00
|
|
|
public function link_to($text=null) {
|
|
|
|
if ( !$text )
|
|
|
|
$text = $this->{'Id'};
|
|
|
|
return '<a href="?view=event&eid='. $this->{'Id'}.'">'.$text.'</a>';
|
|
|
|
}
|
2017-10-10 22:38:13 +08:00
|
|
|
|
2018-05-08 05:07:30 +08:00
|
|
|
public function file_exists() {
|
|
|
|
if ( file_exists( $this->Path().'/'.$this->DefaultVideo() ) ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
$Storage= $this->Storage();
|
|
|
|
$Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server();
|
|
|
|
if ( $Server->Id() != ZM_SERVER_ID ) {
|
|
|
|
|
2018-11-30 03:26:30 +08:00
|
|
|
$url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json';
|
2018-05-08 05:07:30 +08:00
|
|
|
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");
|
|
|
|
// use key 'http' even if you send the request to https://...
|
|
|
|
$options = array(
|
|
|
|
'http' => array(
|
|
|
|
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
|
|
|
|
'method' => 'GET',
|
|
|
|
'content' => ''
|
|
|
|
)
|
|
|
|
);
|
|
|
|
$context = stream_context_create($options);
|
|
|
|
try {
|
|
|
|
$result = file_get_contents($url, false, $context);
|
|
|
|
if ($result === FALSE) { /* Handle error */
|
|
|
|
Error("Error restarting zmc using $url");
|
|
|
|
}
|
|
|
|
$event_data = json_decode($result,true);
|
|
|
|
Logger::Debug(print_r($event_data['event']['Event'],1));
|
|
|
|
return $event_data['event']['Event']['fileExists'];
|
|
|
|
} catch ( Exception $e ) {
|
|
|
|
Error("Except $e thrown trying to get event data");
|
|
|
|
}
|
|
|
|
} # end if not local
|
|
|
|
return false;
|
|
|
|
} # end public function file_exists()
|
|
|
|
|
2018-05-09 00:22:20 +08:00
|
|
|
public function file_size() {
|
|
|
|
if ( file_exists($this->Path().'/'.$this->DefaultVideo()) ) {
|
|
|
|
return filesize($this->Path().'/'.$this->DefaultVideo());
|
|
|
|
}
|
|
|
|
$Storage= $this->Storage();
|
|
|
|
$Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server();
|
|
|
|
if ( $Server->Id() != ZM_SERVER_ID ) {
|
|
|
|
|
2018-11-30 03:26:30 +08:00
|
|
|
$url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json';
|
2018-05-09 00:22:20 +08:00
|
|
|
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");
|
|
|
|
// use key 'http' even if you send the request to https://...
|
|
|
|
$options = array(
|
|
|
|
'http' => array(
|
|
|
|
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
|
|
|
|
'method' => 'GET',
|
|
|
|
'content' => ''
|
|
|
|
)
|
|
|
|
);
|
|
|
|
$context = stream_context_create($options);
|
|
|
|
try {
|
|
|
|
$result = file_get_contents($url, false, $context);
|
|
|
|
if ($result === FALSE) { /* Handle error */
|
|
|
|
Error("Error restarting zmc using $url");
|
|
|
|
}
|
|
|
|
$event_data = json_decode($result,true);
|
|
|
|
Logger::Debug(print_r($event_data['event']['Event'],1));
|
|
|
|
return $event_data['event']['Event']['fileSize'];
|
|
|
|
} catch ( Exception $e ) {
|
|
|
|
Error("Except $e thrown trying to get event data");
|
|
|
|
}
|
|
|
|
} # end if not local
|
|
|
|
return 0;
|
|
|
|
} # end public function file_size()
|
|
|
|
|
2016-05-06 03:33:28 +08:00
|
|
|
} # end class
|
2017-01-02 23:34:15 +08:00
|
|
|
|
2016-05-06 03:33:28 +08:00
|
|
|
?>
|