From 98cde11e8622a5ca941661f45f16fac47e48190a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 26 Oct 2016 13:34:28 -0400 Subject: [PATCH] add a scale element to the frame view. Include some bits from StorageAreas to make it work --- web/includes/Event.php | 240 +++++++++++++++++++----- web/includes/Storage.php | 114 ++++++----- web/skins/classic/views/js/frame.js | 15 ++ web/skins/classic/views/js/frame.js.php | 2 + 4 files changed, 287 insertions(+), 84 deletions(-) create mode 100644 web/skins/classic/views/js/frame.js create mode 100644 web/skins/classic/views/js/frame.js.php diff --git a/web/includes/Event.php b/web/includes/Event.php index 36125165a..0aed91ce5 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -1,6 +1,4 @@ {$k} = $v; } } else { - Error("No row for Event " . $IdOrRow ); + Error('No row for Event ' . $IdOrRow ); } } // end function __construct public function Storage() { return new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); } + public function Monitor() { + return new Monitor( isset($this->{'MonitorId'}) ? $this->{'MonitorId'} : NULL ); + } public function __call( $fn, array $args){ - if(isset($this->{$fn})){ + if ( array_key_exists( $fn, $this ) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -50,38 +55,27 @@ class Event { return $Storage->Path().'/'.$this->Relative_Path(); } public function Relative_Path() { - $event_path = ""; + $event_path = ''; - if ( ZM_USE_DEEP_STORAGE ) - { - $event_path = - $this->{'MonitorId'} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - $this->Time() - ) - ; - } - else - { - $event_path = - $this->{'MonitorId'} - .'/'.$this->{'Id'} - ; + if ( ZM_USE_DEEP_STORAGE ) { + $event_path = $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/%H/%M/%S', $this->Time()) ; + } else { + $event_path = $this->{'MonitorId'} .'/'.$this->{'Id'}; } return( $event_path ); + } // end function Relative_Path() - } - - public function LinkPath() { + public function Link_Path() { if ( ZM_USE_DEEP_STORAGE ) { - return $this->{'MonitorId'} .'/'.strftime( "%y/%m/%d/.", $this->Time()).$this->{'Id'}; + return $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/.', $this->Time()).$this->{'Id'}; } - Error("Calling Link_Path when not using deep storage"); + Error('Calling Link_Path when not using deep storage'); return ''; } public function delete() { + # This wouldn't work with foreign keys dbQuery( 'DELETE FROM Events WHERE Id = ?', array($this->{'Id'}) ); if ( !ZM_OPT_FAST_DELETE ) { dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}) ); @@ -90,15 +84,29 @@ class Event { # Assumption: All events haev a start time $start_date = date_parse( $this->{'StartTime'} ); + if ( ! $start_date ) { + Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.' ); + return; + } $start_date['year'] = $start_date['year'] % 100; - $Storage = $this->Storage(); # So this is because ZM creates a link under teh day pointing to the time that the event happened. - $eventlink_path = $Storage->Path().'/'.$this->Link_Path(); + $link_path = $this->Link_Path(); + if ( ! $link_path ) { + Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.' ); + return; + } + + $Storage = $this->Storage(); + $eventlink_path = $Storage->Path().'/'.$link_path; if ( $id_files = glob( $eventlink_path ) ) { + if ( ! $eventPath = readlink($id_files[0]) ) { + Error("Unable to read link at $id_files[0]"); + return; + } # I know we are using arrays here, but really there can only ever be 1 in the array - $eventPath = preg_replace( '/\.'.$event['Id'].'$/', readlink($id_files[0]), $id_files[0] ); + $eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] ); deletePath( $eventPath ); deletePath( $id_files[0] ); $pathParts = explode( '/', $eventPath ); @@ -118,35 +126,183 @@ class Event { } # ! ZM_OPT_FAST_DELETE } # end Event->delete -public function getStreamSrc( $args, $querySep='&' ) { - return ZM_BASE_URL.'/index.php?view=view_video&eid='.$this->{'Id'}; + public function getStreamSrc( $args, $querySep='&' ) { + return ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php?view=view_video&eid='.$this->{'Id'}; $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; - $args[] = "source=event&event=".$this->{'Id'}; + $args[] = 'source=event&event='.$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']; + 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']; + $args[] = 'user='.$_SESSION['username']; } } - if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) { - $args[] = "connkey=".$GLOBALS['connkey']; + if ( !in_array( 'mode=single', $args ) && !empty($GLOBALS['connkey']) ) { + $args[] = 'connkey='.$GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { - $args[] = "rand=".time(); + $args[] = 'rand='.time(); } if ( count($args) ) { - $streamSrc .= "?".join( $querySep, $args ); + $streamSrc .= '?'.join( $querySep, $args ); } return( $streamSrc ); } // end function getStreamSrc + + function DiskSpace() { + return folder_size( $this->Path() ); + } + + function createListThumbnail( $overwrite=false ) { + # 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 ); + } + + $frameId = $frame['FrameId']; + + if ( ZM_WEB_LIST_THUMB_WIDTH ) { + $thumbWidth = ZM_WEB_LIST_THUMB_WIDTH; + $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'}; + $thumbHeight = reScale( $this->{'Height'}, $scale ); + } elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) { + $thumbHeight = ZM_WEB_LIST_THUMB_HEIGHT; + $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$this->{'Height'}; + $thumbWidth = reScale( $this->{'Width'}, $scale ); + } else { + Fatal( "No thumbnail width or height specified, please check in Options->Web" ); + } + + $imageData = $this->getImageSrc( $frame, $scale, false, $overwrite ); + if ( ! $imageData ) { + return ( false ); + } + $thumbData = $frame; + $thumbData['Path'] = $imageData['thumbPath']; + $thumbData['Width'] = (int)$thumbWidth; + $thumbData['Height'] = (int)$thumbHeight; + + return( $thumbData ); + } // end function createListThumbnail + + function getImageSrc( $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) { + $Storage = new Storage( $this->{'StorageId'} ); + $Event = $this; + $eventPath = $Event->Path(); + + if ( !is_array($frame) ) + $frame = array( 'FrameId'=>$frame, 'Type'=>'' ); + + if ( file_exists( $eventPath.'/snapshot.jpg' ) ) { + $captImage = "snapshot.jpg"; + } else { + $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; + Debug( "Running $command" ); + $output = array(); + $retval = 0; + exec( $command, $output, $retval ); + Debug("Retval: $retval, output: " . implode("\n", $output)); + } else { + Error("Can't create frame images from video becuase there is no video file for this event (".$Event->DefaultVideo() ); + } + } + } + + $captPath = $eventPath.'/'.$captImage; + if ( ! file_exists( $captPath ) ) { + Error( "Capture file does not exist at $captPath" ); + return ''; + } + $thumbCaptPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$captImage; + + //echo "CI:$captImage, CP:$captPath, TCP:$thumbCaptPath
"; + + $analImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId'] ); + $analPath = $eventPath.'/'.$analImage; + + $thumbAnalPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$analImage; + //echo "AI:$analImage, AP:$analPath, TAP:$thumbAnalPath
"; + + $alarmFrame = $frame['Type']=='Alarm'; + + $hasAnalImage = $alarmFrame && file_exists( $analPath ) && filesize( $analPath ); + $isAnalImage = $hasAnalImage && !$captureOnly; + + if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists( 'imagecreatefromjpeg' ) ) { + $imagePath = $thumbPath = $isAnalImage?$analPath:$captPath; + $imageFile = $imagePath; + $thumbFile = $thumbPath; + } else { + if ( version_compare( phpversion(), '4.3.10', '>=') ) + $fraction = sprintf( '%.3F', $scale/SCALE_BASE ); + else + $fraction = sprintf( '%.3f', $scale/SCALE_BASE ); + $scale = (int)round( $scale ); + + $thumbCaptPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbCaptPath ); + $thumbAnalPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbAnalPath ); + + if ( $isAnalImage ) { + $imagePath = $analPath; + $thumbPath = $thumbAnalPath; + } else { + $imagePath = $captPath; + $thumbPath = $thumbCaptPath; + } + + $thumbFile = $thumbPath; + if ( $overwrite || !file_exists( $thumbFile ) || !filesize( $thumbFile ) ) + { + // 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'" ); + } + } + + $imageData = array( + 'eventPath' => $eventPath, + 'imagePath' => $imagePath, + 'thumbPath' => $thumbPath, + 'imageFile' => $imagePath, + 'thumbFile' => $thumbFile, + 'imageClass' => $alarmFrame?"alarm":"normal", + 'isAnalImage' => $isAnalImage, + 'hasAnalImage' => $hasAnalImage, + ); + + return( $imageData ); + } + } # end class + ?> diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 546cb9cbe..81b9ad48c 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -1,51 +1,81 @@ $v) { - $this->{$k} = $v; - } - } else { - $this->{'Name'} = ''; - $this->{'Path'} = ''; - } + public function __construct( $IdOrRow = NULL ) { + $row = NULL; + if ( $IdOrRow ) { + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Storage record for Id=" . $IdOrRow ); + } + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } } + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + } else { + $this->{'Name'} = ''; + $this->{'Path'} = ''; + } + } - public function Path() { - if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { - return $this->{'Path'}; - } else if ( ! isset($this->{'Id'}) ) { - return ZM_DIR_EVENTS; - } - return $this->{'Name'}; - } - public function __call( $fn, array $args= NULL){ - if(isset($this->{$fn})){ - return $this->{$fn}; - #array_unshift($args, $this); - #call_user_func_array( $this->{$fn}, $args); - } + public function Path() { + if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { + return $this->{'Path'}; + } else if ( ! isset($this->{'Id'}) ) { + $path = ZM_DIR_EVENTS; + if ( $path[0] != '/' ) { + $this->{'Path'} = ZM_PATH_WEB.'/'.ZM_DIR_EVENTS; + } else { + $this->{'Path'} = ZM_DIR_EVENTS; + } + return $this->{'Path'}; + } - public static function find_all() { - $storage_areas = array(); - $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); - foreach ( $results as $row => $obj ) { - $storage_areas[] = $obj; - } - return $storage_areas; + return $this->{'Name'}; + } + public function Name() { + if ( isset( $this->{'Name'} ) and ( $this->{'Name'} != '' ) ) { + return $this->{'Name'}; + } else if ( ! isset($this->{'Id'}) ) { + return 'Default'; } + return $this->{'Name'}; + } + + public function __call( $fn, array $args= NULL){ + if(isset($this->{$fn})){ + return $this->{$fn}; +#array_unshift($args, $this); +#call_user_func_array( $this->{$fn}, $args); + } + } + public static function find_all() { + $storage_areas = array(); + $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); + $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); + foreach ( $results as $row => $obj ) { + $storage_areas[] = $obj; + } + return $storage_areas; + } + public function disk_usage_percent() { + $path = $this->Path(); + $total = disk_total_space( $path ); + if ( ! $total ) { + Error("disk_total_space returned false for " . $path ); + return 0; + } + $free = disk_free_space( $path ); + if ( ! $free ) { + Error("disk_free_space returned false for " . $path ); + } + $usage = round(($total - $free) / $total * 100); + return $usage; + } } ?> diff --git a/web/skins/classic/views/js/frame.js b/web/skins/classic/views/js/frame.js new file mode 100644 index 000000000..0705032bc --- /dev/null +++ b/web/skins/classic/views/js/frame.js @@ -0,0 +1,15 @@ +function changeScale() { + var scale = $('scale').get('value'); + var img = $('frameImg'); + if ( img ) { + var baseWidth = $('base_width').value; + var baseHeight = $('base_height').value; + var newWidth = ( baseWidth * scale ) / SCALE_BASE; + var newHeight = ( baseHeight * scale ) / SCALE_BASE; + + img.style.width = newWidth + "px"; + img.style.height = newHeight + "px"; + } + Cookie.write( 'zmWatchScale', scale, { duration: 10*365 } ); +} + diff --git a/web/skins/classic/views/js/frame.js.php b/web/skins/classic/views/js/frame.js.php new file mode 100644 index 000000000..dff39cdab --- /dev/null +++ b/web/skins/classic/views/js/frame.js.php @@ -0,0 +1,2 @@ + +var SCALE_BASE = ;