180, 6=>90, 8=>-90, ]; /** * Photo's NOT pending removal. * * @return \Illuminate\Database\Eloquent\Builder */ public function scopeNotRemove($query) { return $query->where(function($query) { $query->where('remove','!=',TRUE) ->orWhere('remove','=',NULL); }); } /** * Photo's NOT duplicate. * * @return \Illuminate\Database\Eloquent\Builder */ public function scopeNotDuplicate($query) { return $query->where(function($query) { $query->where('duplicate','!=',TRUE) ->orWhere('duplicate','=',NULL); }); } public function date_taken() { return $this->date_taken ? (date('Y-m-d H:i:s',$this->date_taken).($this->subsectime ? '.'.$this->subsectime : '')) : 'UNKNOWN'; } /** * Return the date of the file */ public function file_date($type,$format=FALSE) { if (! is_readable($this->file_path())) return NULL; switch ($type) { case 'a': $t = fileatime($this->file_path()); break; case 'c': $t = filectime($this->file_path()); break; case 'm': $t = filemtime($this->file_path()); break; } return $format ? date('d-m-Y H:i:s',$t) : $t; } /** * Determine the new name for the image */ public function file_path($short=FALSE,$new=FALSE) { $file = $this->filename; if ($new) $file = sprintf('%s.%s',((is_null($this->date_taken) OR ! $this->date_taken) ? sprintf('UNKNOWN/%07s',$this->file_path_id()) : sprintf('%s_%03s',date('Y/m/d-His',$this->date_taken),$this->subsectime).($this->subsectime ? '' : sprintf('-%05s',$this->id))),$this->type()); return (($short OR preg_match('/^\//',$file)) ? '' : config('photo.dir').DIRECTORY_SEPARATOR).$file; } /** * Calculate a file path ID based on the id of the file */ public function file_path_id($sep=3,$depth=9) { return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/'); } /** * Return the photo size */ public function file_size() { return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path()); } /** * Display the GPS coordinates */ public function gps() { return ($this->gps_lat AND $this->gps_lon) ? sprintf('%s/%s',$this->gps_lat,$this->gps_lon) : 'UNKNOWN'; } /** * Return the image, rotated, minus exif data */ public function image() { $imo = $this->io(); if (array_key_exists('exif',$imo->getImageProfiles())) $imo->removeImageProfile('exif'); $this->rotate($imo); return $imo->getImageBlob(); } /** * Return an Imagick object or attribute * */ protected function io($attr=NULL) { if (is_null($this->_io)) $this->_io = new \Imagick($this->file_path()); return is_null($attr) ? $this->_io : $this->_io->getImageProperty($attr); } /** * Calculate the GPS coordinates */ public static function latlon(array $coordinate,$hemisphere) { if (! $coordinate OR ! $hemisphere) return NULL; for ($i=0; $i<3; $i++) { $part = explode('/', $coordinate[$i]); if (count($part) == 1) $coordinate[$i] = $part[0]; elseif (count($part) == 2) $coordinate[$i] = floatval($part[0])/floatval($part[1]); else $coordinate[$i] = 0; } list($degrees, $minutes, $seconds) = $coordinate; $sign = ($hemisphere == 'W' || $hemisphere == 'S') ? -1 : 1; return round($sign*($degrees+$minutes/60+$seconds/3600),$degrees > 100 ? 3 : 4); } /** * Determine if a file is moveable * * useID boolean Determine if the path is based on the the ID or date */ public function moveable() { // If the source and target are the same, we dont need to move it if ($this->file_path() == $this->file_path(FALSE,TRUE)) return FALSE; // If there is already a file in the target. // @todo If the target file is to be deleted, we could move this file if (file_exists($this->file_path(FALSE,TRUE))) return 1; // Test if the source is readable if (! is_readable($this->file_path())) return 2; // Test if the dir is writable (so we can remove the file) if (! is_writable(dirname($this->file_path()))) return 3; // Test if the target dir is writable // @todo The target dir may not exist yet, so we should check that a parent exists and is writable. if (! is_writable(dirname($this->file_path(FALSE,TRUE)))) return 4; return TRUE; } /** * Get the id of the previous photo */ public function next() { $po = DB::table('photo'); $po->where('id','>',$this->id); $po->orderby('id','ASC'); return $po->first(); } /** * Display the orientation of a photo */ public function orientation() { switch ($this->orientation) { case 1: return 'None!'; case 3: return 'Upside Down'; case 6: return 'Rotate Right'; case 8: return 'Rotate Left'; default: return 'unknown?'; } } /** * Rotate the image * */ private function rotate(\Imagick $imo) { if (array_key_exists($this->orientation,$this->_rotate)) $imo->rotateImage(new \ImagickPixel('none'),$this->_rotate[$this->orientation]); return $imo->getImageBlob(); } public static function path($path) { return preg_replace('/^\//','',str_replace(config('photo.dir'),'',$path)); } /** * Get the id of the previous photo */ public function previous() { $po = DB::table('photo'); $po->where('id','<',$this->id); $po->orderby('id','DEC'); return $po->first(); } public function property($property) { if (! $this->io()) return NULL; switch ($property) { case 'height': return $this->_io->getImageHeight(); break; case 'orientation': return $this->_io->getImageOrientation(); break; case 'signature': return $this->_io->getImageSignature(); break; case 'width': return $this->_io->getImageWidth(); break; default: return $this->_io->getImageProperty($property); } } public function properties() { return $this->io() ? $this->_io->getImageProperties() : []; } /** * Display the photo signature */ public function signature($short=FALSE) { return $short ? static::signaturetrim($this->signature) : $this->signature; } public static function signaturetrim($signature,$chars=6) { return sprintf('%s...%s',substr($signature,0,$chars),substr($signature,-1*$chars)); } /** * Determine if the image should be moved */ public function shouldMove() { return ($this->filename != $this->file_path(TRUE,TRUE)); } /** * Return the image's thumbnail * */ public function thumbnail($rotate=TRUE) { if (! $this->thumbnail) { return $this->io()->thumbnailimage(200,200,true,true) ? $this->_io->getImageBlob() : NULL; } if (! $rotate OR ! array_key_exists($this->orientation,$this->_rotate) OR ! extension_loaded('imagick')) return $this->thumbnail; $imo = new \Imagick(); $imo->readImageBlob($this->thumbnail); return $this->rotate($imo); } /** * Return the extension of the image */ public function type($mime=FALSE) { return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION)); } /** * Find duplicate images based on some attributes of the current image */ public function list_duplicate($includeme=FALSE) { $po = DB::table('photo'); if ($this->id AND ! $includeme) $po->where('id','!=',$this->id); // Ignore photo's pending removal. if (! $includeme) $po->where(function($query) { $query->where('remove','!=',TRUE) ->orWhere('remove','=',NULL); }); // Where the signature is the same $po->where(function($query) { $query->where('signature','=',$this->signature); // Or they have the same time taken with the same camera if ($this->date_taken AND ($this->model OR $this->make)) { $query->orWhere(function($query) { $query->where('date_taken','=',$this->date_taken ? $this->date_taken : NULL); $query->where('subsectime','=',$this->subsectime ? $this->subsectime : NULL); if (! is_null($this->model)) $query->where('model','=',$this->model); if (! is_null($this->make)) $query->where('make','=',$this->make); }); } }); return $po->pluck('id'); } }