2016-06-22 05:49:20 +00:00
|
|
|
<?php
|
|
|
|
|
2019-11-08 12:51:47 +00:00
|
|
|
namespace App\Models;
|
2016-06-22 05:49:20 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
use Carbon\Carbon;
|
2019-11-10 01:09:03 +00:00
|
|
|
use Illuminate\Support\Facades\Log;
|
2024-08-31 12:23:07 +00:00
|
|
|
use Illuminate\Support\Traits\ForwardsCalls;
|
|
|
|
use Imagick;
|
2016-06-22 05:49:20 +00:00
|
|
|
|
2024-09-16 12:10:19 +00:00
|
|
|
use App\Casts\PostgresBytea;
|
2024-09-02 12:51:19 +00:00
|
|
|
use App\Jobs\CatalogMove;
|
|
|
|
|
2018-01-11 12:59:53 +00:00
|
|
|
class Photo extends Abstracted\Catalog
|
2016-06-22 05:49:20 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
use ForwardsCalls;
|
|
|
|
|
|
|
|
public const config = 'photo';
|
2016-06-22 05:49:20 +00:00
|
|
|
|
2024-09-16 12:10:19 +00:00
|
|
|
protected $casts = [
|
|
|
|
'created'=>'datetime:Y-m-d H:i:s',
|
|
|
|
'thumbnail'=>PostgresBytea::class,
|
|
|
|
];
|
|
|
|
|
2019-12-14 11:29:54 +00:00
|
|
|
protected static $includeSubSecTime = TRUE;
|
|
|
|
|
2024-09-16 12:10:19 +00:00
|
|
|
// Imagick Object
|
2024-08-31 12:23:07 +00:00
|
|
|
private ?Imagick $_o;
|
|
|
|
protected array $init = [
|
|
|
|
'creation_date',
|
|
|
|
'gps',
|
|
|
|
'heightwidth',
|
|
|
|
'signature',
|
|
|
|
'software',
|
|
|
|
'subsectime',
|
|
|
|
];
|
2016-06-22 05:49:20 +00:00
|
|
|
|
|
|
|
// How should the image be rotated, based on the value of orientation
|
2024-08-31 12:23:07 +00:00
|
|
|
private array $_rotate = [
|
2024-09-02 12:51:19 +00:00
|
|
|
3 => 180,
|
|
|
|
6 => 90,
|
|
|
|
8 => -90,
|
2016-06-22 05:49:20 +00:00
|
|
|
];
|
|
|
|
|
2024-09-02 12:51:19 +00:00
|
|
|
public static function boot()
|
|
|
|
{
|
|
|
|
parent::boot();
|
|
|
|
|
|
|
|
// Any photo saved, queue it to be moved.
|
|
|
|
self::saved(function($item) {
|
|
|
|
if ($item->scanned && (! $item->duplicate) && (! $item->remove) && ($x=$item->shouldMove()) === TRUE) {
|
|
|
|
Log::info(sprintf('%s: Need to Move [%s]','Photo',$item->id.'|'.serialize($x)));
|
|
|
|
|
|
|
|
CatalogMove::dispatch($item)
|
|
|
|
->onQueue('move');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-06-22 06:51:31 +00:00
|
|
|
/**
|
|
|
|
* Calculate the GPS coordinates
|
|
|
|
*/
|
2024-08-31 12:23:07 +00:00
|
|
|
public static function latlon(array $coordinate,string $hemisphere): ?float
|
2016-06-29 23:32:57 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
if ((! $coordinate) || (! $hemisphere))
|
2016-06-22 05:49:20 +00:00
|
|
|
return NULL;
|
|
|
|
|
2019-12-14 11:29:54 +00:00
|
|
|
for ($i=0; $i<3; $i++) {
|
2016-06-22 05:49:20 +00:00
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2019-12-14 11:29:54 +00:00
|
|
|
list($degrees,$minutes,$seconds) = $coordinate;
|
2016-06-22 05:49:20 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
$sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1;
|
2016-06-22 05:49:20 +00:00
|
|
|
|
2019-12-14 11:29:54 +00:00
|
|
|
return round($sign*($degrees+$minutes/60+$seconds/3600),($degrees>100 ? 3 : 4));
|
2016-06-22 05:49:20 +00:00
|
|
|
}
|
|
|
|
|
2016-06-29 04:04:02 +00:00
|
|
|
/**
|
2024-08-31 12:23:07 +00:00
|
|
|
* Forward calls to Imagick
|
|
|
|
*
|
|
|
|
* @param $method
|
|
|
|
* @param $parameters
|
|
|
|
* @return mixed|null
|
2016-06-29 04:04:02 +00:00
|
|
|
*/
|
2024-08-31 12:23:07 +00:00
|
|
|
public function __call($method,$parameters) {
|
|
|
|
if (str_starts_with($method,'Imagick_')) {
|
|
|
|
$method = preg_replace('/^Imagick_/','',$method);
|
2016-06-29 04:04:02 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
return $this->o ? $this->forwardCallTo($this->_o,$method,$parameters) : NULL;
|
2016-06-29 04:04:02 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
} else
|
|
|
|
return parent::__call($method,$parameters);
|
2016-06-29 10:49:02 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function __get($key): mixed
|
|
|
|
{
|
|
|
|
if ($key === 'o') {
|
|
|
|
if (isset($this->_o))
|
|
|
|
return $this->_o;
|
|
|
|
|
2024-09-16 12:10:19 +00:00
|
|
|
if ((! file_exists($this->file_name(FALSE))) || (! is_readable($this->file_name(FALSE))))
|
2024-08-31 12:23:07 +00:00
|
|
|
return $this->_o = NULL;
|
|
|
|
|
2024-09-16 12:10:19 +00:00
|
|
|
if (! isset($this->_o))
|
2024-08-31 12:23:07 +00:00
|
|
|
return $this->_o = new Imagick($this->file_name(FALSE));
|
2016-06-30 06:01:12 +00:00
|
|
|
}
|
2024-08-31 12:23:07 +00:00
|
|
|
|
|
|
|
return parent::__get($key);
|
2016-06-30 06:01:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
/* ATTRIBUTES */
|
2016-06-22 05:49:20 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function getFileSignatureAttribute(string $val=NULL): string
|
|
|
|
{
|
|
|
|
return $val ?: $this->getObjectOriginal('file_signature');
|
2016-06-22 05:49:20 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function getHeightAttribute(string $val=NULL): ?int
|
2016-06-30 06:01:12 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
return $val ?: $this->getObjectOriginal('height');
|
|
|
|
}
|
2019-11-15 12:39:20 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function getOrientationAttribute(int $val=NULL): ?int
|
|
|
|
{
|
|
|
|
return $val ?: $this->getObjectOriginal('orientation');
|
|
|
|
}
|
2019-11-15 12:39:20 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function getSignatureAttribute(string $val=NULL): ?string
|
|
|
|
{
|
|
|
|
return $val ?: $this->getObjectOriginal('signature');
|
2016-06-30 06:01:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function getWidthAttribute(string $val=NULL): ?int
|
2016-06-29 10:49:02 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
return $val ?: $this->getObjectOriginal('width');
|
2016-06-29 10:49:02 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
/* METHODS */
|
2016-06-29 10:49:02 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function custom_init(): void
|
2016-06-29 10:49:02 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
$this->orientation = $this->getObjectOriginal('orientation');
|
|
|
|
|
|
|
|
try {
|
|
|
|
if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
|
|
|
|
$this->o->setImageFormat('jpg');
|
|
|
|
|
|
|
|
$this->thumbnail = $this->o->getImageBlob();
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
Log::info(sprintf('Unable to create thumbnail for %s (%s)',$this->id,$e->getMessage()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->thumbnail === FALSE)
|
|
|
|
$this->thumbnail = NULL;
|
2016-06-29 10:49:02 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
public function getObjectOriginal(string $property): mixed
|
2018-01-11 12:59:53 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
switch ($property) {
|
|
|
|
case 'creation_date':
|
|
|
|
if ($this->Imagick_getImageProperty('exif:DateTimeOriginal') === '0000:00:00 00:00:00'
|
|
|
|
&& $this->Imagick_getImageProperty('exif:DateTime') === '0000:00:00 00:00:00')
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
$result = Carbon::create($x=
|
|
|
|
($this->Imagick_getImageProperty('exif:DateTimeOriginal') && ($this->Imagick_getImageProperty('exif:DateTimeOriginal') !== '0000:00:00 00:00:00'))
|
|
|
|
? $this->Imagick_getImageProperty('exif:DateTimeOriginal').$this->Imagick_getImageProperty('exif:OffsetTimeOriginal')
|
|
|
|
: $this->Imagick_getImageProperty('exif:DateTime').$this->Imagick_getImageProperty('exif:OffsetTime'));
|
|
|
|
|
|
|
|
return $result ?: NULL;
|
2019-12-27 02:32:42 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
case 'file_signature':
|
|
|
|
return md5_file($this->file_name(FALSE));
|
2019-12-26 04:56:31 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
case 'gps_lat':
|
|
|
|
return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLatitude')),$this->Imagick_getImageProperty('exif:GPSLatitudeRef'));
|
2019-12-26 04:56:31 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
case 'gps_lon':
|
|
|
|
return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLongitude')),$this->Imagick_getImageProperty('exif:GPSLongitudeRef'));
|
|
|
|
|
|
|
|
case 'height':
|
|
|
|
return $this->Imagick_getImageHeight();
|
|
|
|
|
|
|
|
case 'identifier':
|
|
|
|
return NULL;
|
2019-12-26 04:56:31 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
case 'make':
|
|
|
|
return $this->Imagick_getImageProperty('exif:Make');
|
|
|
|
|
|
|
|
case 'model':
|
|
|
|
return $this->Imagick_getImageProperty('exif:Model');
|
|
|
|
|
|
|
|
case 'orientation':
|
|
|
|
return $this->Imagick_getImageOrientation();
|
|
|
|
|
|
|
|
case 'signature':
|
|
|
|
return $this->Imagick_getImageSignature();
|
|
|
|
|
|
|
|
case 'software':
|
|
|
|
return $this->Imagick_getImageProperty('exif:Software');
|
|
|
|
|
|
|
|
case 'subsectime':
|
|
|
|
$this->subsectime = (int)$this->Imagick_getImageProperty('exif:SubSecTimeOriginal');
|
|
|
|
|
|
|
|
// In case of an error.
|
|
|
|
if ($this->subsectime > 32767)
|
|
|
|
$this->subsectime = 32767;
|
|
|
|
|
|
|
|
if ($this->subsectime === FALSE)
|
|
|
|
$this->subsectime = 0;
|
|
|
|
|
|
|
|
return $this->subsectime;
|
|
|
|
|
|
|
|
case 'width':
|
|
|
|
return $this->Imagick_getImageWidth();
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new \Exception('To implement: '.$property);
|
|
|
|
}
|
2018-01-11 12:59:53 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
/**
|
|
|
|
* Return the image, rotated
|
|
|
|
*/
|
|
|
|
public function image(): ?string
|
2018-01-11 12:59:53 +00:00
|
|
|
{
|
2024-09-17 13:07:00 +00:00
|
|
|
if (! $this->o)
|
|
|
|
return NULL;
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
$imo = clone($this->o);
|
2019-11-10 01:09:03 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
return $imo ? $this->rotate($imo) : NULL;
|
|
|
|
}
|
2020-01-02 21:21:55 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
/**
|
|
|
|
* Display the orientation of a photo
|
|
|
|
*/
|
|
|
|
public function orientation(): string
|
|
|
|
{
|
|
|
|
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?';
|
|
|
|
}
|
2018-01-11 12:59:53 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
/**
|
|
|
|
* Rotate the image
|
|
|
|
*/
|
|
|
|
private function rotate(\Imagick $imo,string $format='jpg'): string
|
2016-06-22 06:51:31 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
if (array_key_exists($this->orientation,$this->_rotate))
|
|
|
|
$imo->rotateImage(new \ImagickPixel('none'),$this->_rotate[$this->orientation]);
|
2019-12-14 11:29:54 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
$imo->setImageFormat($format);
|
2019-11-10 01:09:03 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
if (array_key_exists('exif',$imo->getImageProfiles()))
|
|
|
|
$imo->removeImageProfile('exif');
|
|
|
|
|
|
|
|
return $imo->getImageBlob();
|
2016-06-22 06:51:31 +00:00
|
|
|
}
|
|
|
|
|
2016-06-22 05:49:20 +00:00
|
|
|
/**
|
|
|
|
* Return the image's thumbnail
|
|
|
|
*/
|
2024-08-31 12:23:07 +00:00
|
|
|
public function thumbnail($rotate=TRUE): ?string
|
2016-06-22 05:49:20 +00:00
|
|
|
{
|
2019-12-14 11:29:54 +00:00
|
|
|
if (! $this->thumbnail) {
|
2024-08-31 12:23:07 +00:00
|
|
|
if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
|
|
|
|
$this->o->setImageFormat('jpg');
|
|
|
|
|
|
|
|
return $this->o->getImageBlob();
|
2020-01-01 22:42:59 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
2016-06-29 10:49:02 +00:00
|
|
|
}
|
2016-06-22 05:49:20 +00:00
|
|
|
|
2024-08-31 12:23:07 +00:00
|
|
|
if ((! $rotate) || (! array_key_exists($this->orientation,$this->_rotate)) || (! extension_loaded('imagick')))
|
2016-06-22 05:49:20 +00:00
|
|
|
return $this->thumbnail;
|
|
|
|
|
|
|
|
$imo = new \Imagick();
|
|
|
|
$imo->readImageBlob($this->thumbnail);
|
2020-01-01 22:42:59 +00:00
|
|
|
$imo->setImageFormat('jpg');
|
2016-06-22 05:49:20 +00:00
|
|
|
|
|
|
|
return $this->rotate($imo);
|
|
|
|
}
|
|
|
|
|
2016-06-22 06:51:31 +00:00
|
|
|
/**
|
|
|
|
* Return the extension of the image
|
|
|
|
*/
|
2024-08-31 12:23:07 +00:00
|
|
|
public function type(bool $mime=FALSE): string
|
2016-06-29 23:32:57 +00:00
|
|
|
{
|
2024-08-31 12:23:07 +00:00
|
|
|
return strtolower($mime ? mime_content_type($this->file_name(FALSE)) : pathinfo($this->filename,PATHINFO_EXTENSION));
|
2016-06-22 06:51:31 +00:00
|
|
|
}
|
2019-11-08 12:51:47 +00:00
|
|
|
}
|