Internal optimisations and additional flags for Photo/Video

This commit is contained in:
Deon George 2020-01-05 00:28:00 +11:00
parent 93364ab53a
commit 1ffc2d994e
19 changed files with 402 additions and 279 deletions

View File

@ -0,0 +1,77 @@
<?php
namespace App\Console\Commands;
use App\Jobs\PhotoMove;
use App\Jobs\VideoMove;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Jobs\PhotoDelete;
use App\Models\Photo;
use App\Traits\Type;
class CatalogAutoDelete extends Command
{
use DispatchesJobs,Type;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'catalog:autodelete {type : Photo | Video }';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Auto Delete Catalog Items';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$class = $this->getModelType($this->argument('type'));
$class::where('remove',1)->each(function($o) {
foreach ($o->myduplicates() as $oo) {
if (! $oo->signature OR ! $oo->file_signature)
continue;
if ($oo->signature == $o->signature AND $oo->file_signature == $o->file_signature) {
$this->info(sprintf('Removing: %s (%s)',$o->id,$o->filename));
continue;
// Dispatch Job to move file.
switch (strtolower($this->argument('type'))) {
case 'photo':
$this->dispatch((new PhotoDelete($o))->onQueue('delete'));
break;
case 'video':
$this->dispatch((new VideoDelete($o))->onQueue('delete'));
break;
default:
$this->error('Dont know how to handle: ',$this->argument('type'));
}
}
}
});
}
}

View File

@ -15,7 +15,10 @@ class CatalogScan extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'catalog:scan {type : Photo | Video } {id : Photo ID}'; protected $signature = 'catalog:scan'.
' {type : Photo | Video }'.
' {id : Photo ID}'.
' {--dirty : Show Dirty}';
/** /**
* The console command description. * The console command description.
@ -49,17 +52,17 @@ class CatalogScan extends Command
$o->setThumbnail(); $o->setThumbnail();
// If this is a duplicate // If this is a duplicate
$x = $o->duplicates()->get(); $x = $o->myduplicates()->get();
if (count($x)) { if (count($x)) {
foreach ($x as $oo) { foreach ($x as $oo) {
// And that photo is not marked as a duplicate // And that photo is not marked as a duplicate
if (! $oo->duplicate) { if (! $oo->duplicate) {
$o->duplicate = '1'; $o->duplicate = '1';
$this->warn(sprintf('Image [%s] marked as a duplicate',$o->file_path())); $this->warn(sprintf('Image [%s] marked as a duplicate',$o->filename));
// If the file signature also matches, we'll mark it for deletion // If the file signature also matches, we'll mark it for deletion
if ($oo->file_signature AND $o->file_signature == $oo->file_signature) { if ($oo->file_signature AND $o->file_signature == $oo->file_signature) {
$this->warn(sprintf('Image [%s] marked for deletion',$o->file_path())); $this->warn(sprintf('Image [%s] marked for deletion',$o->filename));
$o->remove = '1'; $o->remove = '1';
} }
@ -72,9 +75,15 @@ class CatalogScan extends Command
if ($o->getDirty()) { if ($o->getDirty()) {
$this->warn(sprintf('Image [%s] metadata changed',$o->filename)); $this->warn(sprintf('Image [%s] metadata changed',$o->filename));
dump(['id'=>$o->id,'data'=>$o->getDirty()]);
if ($this->option('dirty'))
dump(['id'=>$o->id,'data'=>$o->getDirty()]);
} }
// If the file signature changed, abort the update.
if ($o->getOriginal('file_signature') AND $o->getDirty('file_signature'))
abort(500,'File Signature Changed?');
$o->save(); $o->save();
} }
} }

View File

@ -61,6 +61,7 @@ class CatalogScanAll extends Command
} }
$this->dispatch((new CatalogScan($item))->onQueue('scan')); $this->dispatch((new CatalogScan($item))->onQueue('scan'));
$c++;
}); });
Log::info(sprintf('Processed [%s]',$c)); Log::info(sprintf('Processed [%s]',$c));

View File

@ -1,63 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Jobs\PhotoDelete;
use App\Models\Photo;
class PhotoAutoDelete extends Command
{
use DispatchesJobs;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'photo:autodelete';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Auto Delete Photos';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Photo::where('remove',1)->chunk(10,function($chunk) {
foreach ($chunk as $o) {
foreach ($o->list_duplicates(TRUE) as $oo) {
if (! $oo->signature OR ! $oo->file_signature)
continue;
if ($oo->signature == $o->signature AND $oo->file_signature == $o->file_signature) {
if ($oo->remove) {
$this->info(sprintf('Removing: %s (%s)',$oo->id,$oo->filename));
$this->dispatch((new PhotoDelete($o))->onQueue('delete'));
}
}
}
}
});
}
}

View File

@ -2,13 +2,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Photo; use App\Models\Photo;
use App\Jobs\PhotoDelete; use App\Traits\Multimedia;
class PhotoController extends Controller class PhotoController extends Controller
{ {
use Multimedia;
protected $list_duplicates = 20; protected $list_duplicates = 20;
protected $list_deletes = 50; protected $list_deletes = 50;
@ -22,77 +22,41 @@ class PhotoController extends Controller
$this->middleware('guest'); $this->middleware('guest');
} }
public function delete($id) public function delete(Photo $o)
{ {
$o = Photo::notRemove()->findOrFail($id); $o->remove = TRUE;
$o->save();
if ($o) return redirect()->action('PhotoController@info',[$o->id]);
{
$o->remove = TRUE;
$o->save();
}
return redirect()->action('PhotoController@info',[$id]);
} }
public function deletes($id=NULL) public function deletes($id=NULL)
{ {
return view('catalog.deletereview',[ return view('catalog.deletereview',[
'catalog'=>is_null($id) ? Photo::where('remove',1)->with(['software.model.make'])->paginate($this->list_deletes) : Photo::where('id',$id)->paginate(1),
'return'=>url('p/deletes'), 'return'=>url('p/deletes'),
'catalog'=>is_null($id) ? Photo::where('remove',1)->with(['software.model.make'])->paginate($this->list_deletes) : Photo::where('id',$id)->paginate(1) 'type'=>'photo',
]); ]);
} }
public function deletesUpdate(Request $request)
{
foreach ($request->input('remove') as $id=>$k)
{
$o = Photo::findOrFail($id);
if ($o->remove AND $request->input('remove.'.$id))
$this->dispatch((new PhotoDelete($o))->onQueue('delete'));
}
return redirect()->action('PhotoController@deletes',$request->input('pagenext') ? '?page='.$request->input('pagenext') : NULL);
}
public function duplicates($id=NULL) public function duplicates($id=NULL)
{ {
return view('catalog.duplicatereview',[ return view('catalog.duplicatereview',[
'catalog'=>is_null($id) ? Photo::duplicates()->with(['software.model.make'])->paginate($this->list_duplicates) : Photo::where('id',$id)->paginate(1),
'return'=>url('p/duplicates'), 'return'=>url('p/duplicates'),
'catalog'=>is_null($id) ? Photo::notRemove()->where('duplicate',1)->with(['software.model.make'])->paginate($this->list_duplicates) : Photo::where('id',$id)->paginate(1) 'type'=>'photo',
]); ]);
} }
public function duplicatesUpdate(Request $request)
{
foreach ($request->input('items') as $id)
{
$o = Photo::findOrFail($id);
// Set if duplicate
$o->duplicate = $request->input('duplicate.'.$id) ? 1 : NULL;
// Set if flag
$o->flag = $request->input('flag.'.$id) ? 1 : NULL;
// Set if delete
$o->remove = $request->input('remove.'.$id) ? 1 : NULL;
$o->save();
}
return redirect()->action('PhotoController@duplicates','?page='.$request->input('page'));
}
public function info(Photo $o) public function info(Photo $o)
{ {
return view('photo.view',['o'=>$o]); return view('photo.view',['o'=>$o]);
} }
public function thumbnail($id) public function thumbnail(Photo $o)
{ {
return response(Photo::findOrFail($id)->thumbnail(TRUE))->header('Content-Type','image/jpeg'); return response($o->thumbnail(TRUE))
->header('Content-Type','image/jpeg');
} }
public function undelete(Photo $o) public function undelete(Photo $o)
@ -103,8 +67,9 @@ class PhotoController extends Controller
return redirect()->action('PhotoController@info',[$o->id]); return redirect()->action('PhotoController@info',[$o->id]);
} }
public function view($id) public function view(Photo $o)
{ {
return response(Photo::findOrFail($id)->image())->header('Content-Type','image/jpeg'); return response($o->image())
->header('Content-Type','image/jpeg');
} }
} }

View File

@ -2,14 +2,17 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Video; use App\Models\Video;
use App\Jobs\VideoDelete;
use App\Helpers\VideoStream; use App\Helpers\VideoStream;
use App\Traits\Multimedia;
class VideoController extends Controller class VideoController extends Controller
{ {
use Multimedia;
protected $list_duplicates = 20;
protected $list_deletes = 50;
/** /**
* Create a new controller instance. * Create a new controller instance.
* *
@ -20,71 +23,30 @@ class VideoController extends Controller
$this->middleware('guest'); $this->middleware('guest');
} }
public function delete($id) public function delete(Video $o)
{ {
$o = Video::notRemove()->findOrFail($id); $o->remove = TRUE;
$o->save();
if ($o) return redirect()->action('VideoController@info',[$o->id]);
{
$o->remove = TRUE;
$o->save();
}
return redirect()->action('VideoController@info',[$id]);
} }
public function deletes($id=NULL) public function deletes($id=NULL)
{ {
return view('catalog.deletereview',[ return view('catalog.deletereview',[
'catalog'=>is_null($id) ? Video::where('remove',1)->with(['software.model.make'])->paginate($this->list_deletes) : Video::where('id',$id)->paginate(1),
'return'=>url('v/deletes'), 'return'=>url('v/deletes'),
'catalog'=>is_null($id) ? Video::where('remove',1)->with(['software.model.make'])->paginate(50) : Video::where('id',$id)->paginate(1) 'type'=>'photo',
]); ]);
} }
public function deletesUpdate(Request $request)
{
foreach ($request->input('remove') as $id=>$k)
{
$o = Video::findOrFail($id);
if ($o->remove AND $request->input('remove.'.$id))
$this->dispatch((new VideoDelete($o))->onQueue('delete'));
elseif (! $o->remove) {
$o->remove = TRUE;
$o->save();
}
}
return redirect()->action('VideoController@deletes',$request->input('pagenext') ? '?page='.$request->input('pagenext') : NULL);
}
public function duplicates($id=NULL) public function duplicates($id=NULL)
{ {
return view('catalog.duplicatereview',[ return view('catalog.duplicatereview',[
'catalog'=>is_null($id) ? Video::duplicates()->with(['software.model.make'])->paginate($this->list_duplicates) : Video::where('id',$id)->paginate(1),
'return'=>url('v/duplicates'), 'return'=>url('v/duplicates'),
'catalog'=>is_null($id) ? Video::notRemove()->where('duplicate',1)->with(['software.model.make'])->paginate(50) : Video::where('id',$id)->paginate(1)]); 'type'=>'photo',
} ]);
public function duplicatesUpdate(Request $request)
{
foreach ($request->input('items') as $id)
{
$o = Video::findOrFail($id);
// Set if duplicate
$o->duplicate = $request->input('duplicate.'.$id) ? 1 : NULL;
// Set if flag
$o->flag = $request->input('flag.'.$id) ? 1 : NULL;
// Set if delete
$o->remove = $request->input('remove.'.$id) ? 1 : NULL;
$o->save();
}
return redirect()->action('VideoController@duplicates','?page='.$request->input('page'));
} }
public function info(Video $o) public function info(Video $o)
@ -100,8 +62,9 @@ class VideoController extends Controller
return redirect()->action('VideoController@info',[$o->id]); return redirect()->action('VideoController@info',[$o->id]);
} }
public function view($id) public function view(Video $o)
{ {
(new VideoStream(Video::findOrFail($id)->file_path()))->start(); if ($o->isReadable())
(new VideoStream($o->filename))->start();
} }
} }

View File

@ -4,6 +4,7 @@ namespace App\Models\Abstracted;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -12,6 +13,7 @@ use App\Models\{Person,Software,Tag};
abstract class Catalog extends Model abstract class Catalog extends Model
{ {
protected static $includeSubSecTime = FALSE; protected static $includeSubSecTime = FALSE;
protected $dates = ['created'];
/** /**
* People in Multimedia Object * People in Multimedia Object
@ -44,18 +46,39 @@ abstract class Catalog extends Model
} }
/** /**
* Search Database for duplicates of this object * Find records marked as duplicate
* *
* @param $query * @param $query
* @return mixed * @return mixed
*/ */
public function scopeDuplicates($query) { public function scopeDuplicates($query) {
$query->notRemove()
->where('duplicate',TRUE)
->where(function($q) {
$q->Where('ignore_duplicate','<>',TRUE)
->orWhereNull('ignore_duplicate');
});
}
/**
* Search Database for duplicates of this object
*
* @param $query
* @return mixed
*/
public function scopeMyDuplicates($query) {
if (! $this->exists) if (! $this->exists)
return $query; return $query;
// Exclude this record // Exclude this record
$query->where('id','<>',$this->attributes['id']); $query->where('id','<>',$this->attributes['id']);
// Skip ignore dups
$query->where(function($q) {
$q->whereNull('ignore_duplicate')
->orWhere('ignore_duplicate','=',0);
});
// Exclude those marked as remove // Exclude those marked as remove
$query->where(function ($q) { $query->where(function ($q) {
$q->where('remove','<>',TRUE) $q->where('remove','<>',TRUE)
@ -69,11 +92,11 @@ abstract class Catalog extends Model
// Where the signature is the same // Where the signature is the same
->orWhere(function($q) { ->orWhere(function($q) {
// Or they have the same time taken with the same camera // Or they have the same time taken with the same camera
if ($this->attributes['date_created'] AND $this->software_id) { if ($this->attributes['created'] AND $this->software_id) {
$q->where('date_created','=',$this->attributes['date_created'] ?: NULL); $q->where('created','=',$this->attributes['created'] ?: NULL);
if (static::$includeSubSecTime) if (static::$includeSubSecTime)
$q->where('subsectime','=',$this->attributes['subsectime'] ?: NULL); $q->where('subsectime','=',Arr::get($this->attributes,'subsectime'));
$q->where('software_id','=',$this->attributes['software_id']); $q->where('software_id','=',$this->attributes['software_id']);
} }
@ -124,9 +147,7 @@ abstract class Catalog extends Model
} }
// Children objects must inherit this methods // Children objects must inherit this methods
abstract public function setDateCreated();
abstract public function setLocation(); abstract public function setLocation();
abstract public function setSignature();
abstract public function setSubSecTime(); abstract public function setSubSecTime();
abstract public function setThumbnail(); abstract public function setThumbnail();
abstract public function getHtmlImageURL(); abstract public function getHtmlImageURL();
@ -136,9 +157,16 @@ abstract class Catalog extends Model
*/ */
public function date_taken(): string public function date_taken(): string
{ {
return $this->date_created ? $this->date_created->format('Y-m-d H:i:s') : 'UNKNOWN'; return $this->created
? $this->created->format('Y-m-d H:i:s').(static::$includeSubSecTime ? sprintf('.%03d',$this->subsectime) : '')
: 'UNKNOWN';
} }
/**
* What device was the multimedia created on
*
* @return string
*/
public function device(): string public function device(): string
{ {
$result = ''; $result = '';
@ -169,11 +197,13 @@ abstract class Catalog extends Model
switch ($type) switch ($type)
{ {
case 'a': $t = fileatime($this->file_path()); case 'a': $t = fileatime($this->filename);
break; break;
case 'c': $t = filectime($this->file_path());
case 'c': $t = filectime($this->filename);
break; break;
case 'm': $t = filemtime($this->file_path());
case 'm': $t = filemtime($this->filename);
break; break;
} }
@ -188,12 +218,15 @@ abstract class Catalog extends Model
public function file_name(bool $short=TRUE): string public function file_name(bool $short=TRUE): string
{ {
// If the date created is not set, the file name will be based on the ID of the file. // If the date created is not set, the file name will be based on the ID of the file.
$file = sprintf('%s.%s',((is_null($this->date_created) OR ! $this->date_created) $file = sprintf('%s.%s',(is_null($this->created)
? sprintf('UNKNOWN/%07s',$this->file_path_id()) ? sprintf('UNKNOWN/%07s',$this->file_path_id())
: sprintf('%s_%03s',$this->date_created->format('Y/m/d-His'),$this->subsectime). : $this->created->format('Y/m/d-His').((! is_null($this->subsectime)) ? sprintf('.%03d',$this->subsectime) : '' ).
((! static::$includeSubSecTime OR $this->subsectime) ? '' : sprintf('-%05s',$this->id))),$this->type()); ((! static::$includeSubSecTime OR ! is_null($this->subsectime)) ? '' : sprintf('-%05s',$this->id)).
($this->ignore_duplicate ? sprintf('-%06d',$this->id) : '')
),$this->type()
);
return (($short OR preg_match('/^\//',$file)) ? '' : config('photo.dir').DIRECTORY_SEPARATOR).$file; return (($short OR preg_match('/^\//',$file)) ? '' : config($this->type.'.dir').DIRECTORY_SEPARATOR).$file;
} }
/** /**
@ -205,11 +238,9 @@ abstract class Catalog extends Model
$file = $this->filename; $file = $this->filename;
if ($new) if ($new)
$file = sprintf('%s.%s',((is_null($this->date_created) OR ! $this->date_created) $file = $this->file_name(FALSE);
? sprintf('UNKNOWN/%07s',$this->file_path_id())
: $this->date_created->format('Y/m/d-His')),$this->type());
return (($short OR preg_match('/^\//',$file)) ? '' : config('video.dir').DIRECTORY_SEPARATOR).$file; return (($short OR preg_match('/^\//',$file)) ? '' : config($this->type.'.dir').DIRECTORY_SEPARATOR).$file;
} }
/** /**
@ -271,6 +302,14 @@ abstract class Catalog extends Model
return $this->HTMLCheckbox('flag',$this->id,$this->flag); return $this->HTMLCheckbox('flag',$this->id,$this->flag);
} }
/**
* Return HTML Checkbox for ignore
*/
public function getIgnoreCheckboxAttribute()
{
return $this->HTMLCheckbox('ignore_duplicate',$this->id,$this->ignore_duplicate);
}
public function getDateCreatedAttribute() { public function getDateCreatedAttribute() {
return $this->attributes['date_created'] ? Carbon::createFromTimestamp($this->attributes['date_created']) : NULL; return $this->attributes['date_created'] ? Carbon::createFromTimestamp($this->attributes['date_created']) : NULL;
} }
@ -328,7 +367,7 @@ abstract class Catalog extends Model
*/ */
public function isReadable(): bool public function isReadable(): bool
{ {
return is_readable($this->file_path()); return is_readable($this->filename);
} }
/** /**
@ -344,7 +383,7 @@ abstract class Catalog extends Model
*/ */
protected function HTMLLinkAttribute($id,$url) protected function HTMLLinkAttribute($id,$url)
{ {
return sprintf('<a href="%s" target="%s">%s</a>',url($url.$id),$id,$id); return sprintf('<a href="%s" target="%s">%s</a>',url($url,$id),$id,$id);
} }
/** /**
@ -398,17 +437,6 @@ abstract class Catalog extends Model
return TRUE; return TRUE;
} }
/**
* Flag to indicate if a file should be moved.
*
* @return bool
*/
public function mustMove(): bool
{
dump(['f'=>$this->filename,'fn'=>$this->file_name(),'test'=>($this->filename == $this->file_name())]);
return $this->filename !== $this->file_name();
}
/** /**
* Get the id of the previous record * Get the id of the previous record
*/ */
@ -441,6 +469,11 @@ abstract class Catalog extends Model
->first(); ->first();
} }
public function setDateCreated()
{
$this->created = $this->property('creationdate');
}
public function setHeightWidth() public function setHeightWidth()
{ {
$this->height = $this->property('height'); $this->height = $this->property('height');
@ -448,6 +481,13 @@ abstract class Catalog extends Model
$this->orientation = $this->property('orientation'); $this->orientation = $this->property('orientation');
} }
public function setSignature()
{
$this->signature = $this->property('signature');
$this->file_signature = md5_file($this->filename);
}
/** /**
* Display the media signature * Display the media signature
*/ */
@ -484,7 +524,7 @@ abstract class Catalog extends Model
*/ */
public function shouldMove(): bool public function shouldMove(): bool
{ {
return ($this->filename != $this->file_path(TRUE,TRUE)); return $this->filename !== $this->file_name();
} }
protected function TextTrueFalse($value): string protected function TextTrueFalse($value): string
@ -498,7 +538,7 @@ abstract class Catalog extends Model
* @param bool $includeme * @param bool $includeme
* @return mixed * @return mixed
*/ */
public function list_duplicate($includeme=FALSE) private function list_duplicate($includeme=FALSE)
{ {
return $this->list_duplicates($includeme)->pluck('id'); return $this->list_duplicates($includeme)->pluck('id');
} }
@ -507,7 +547,7 @@ abstract class Catalog extends Model
* Find duplicate images based on some attributes of the current image * Find duplicate images based on some attributes of the current image
* @deprecate Use static::duplicates() * @deprecate Use static::duplicates()
*/ */
public function list_duplicates($includeme=FALSE) private function list_duplicates($includeme=FALSE)
{ {
$o = static::select(); $o = static::select();

View File

@ -22,33 +22,7 @@ class Photo extends Abstracted\Catalog
public function getIDLinkAttribute() public function getIDLinkAttribute()
{ {
return $this->HTMLLinkAttribute($this->id,url('p/info').'/'); return $this->HTMLLinkAttribute($this->id,'p/info');
}
/**
* Date the photo was taken
*/
public function date_taken(): string
{
return $this->date_created
? ($this->date_created->format('Y-m-d H:i:s').($this->subsectime ? '.'.$this->subsectime : ''))
: 'UNKNOWN';
}
/**
* 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_created) OR ! $this->date_created)
? sprintf('UNKNOWN/%07s',$this->file_path_id())
: sprintf('%s_%03s',$this->date_created->format('Y/m/d-His'),$this->subsectime).
($this->subsectime ? '' : sprintf('-%05s',$this->id))),$this->type());
return (($short OR preg_match('/^\//',$file)) ? '' : config('photo.dir').DIRECTORY_SEPARATOR).$file;
} }
public function getHtmlImageURL(): string public function getHtmlImageURL(): string
@ -169,10 +143,6 @@ class Photo extends Abstracted\Catalog
return $this->o() ? $this->_o->getImageProperties() : []; return $this->o() ? $this->_o->getImageProperties() : [];
} }
public function setDateCreated()
{
$this->date_created = $this->property('creationdate');
}
public function setLocation() public function setLocation()
{ {
@ -202,15 +172,9 @@ class Photo extends Abstracted\Catalog
$this->software_id = $so->id; $this->software_id = $so->id;
} }
public function setSignature() public function setSubSecTime(): int
{ {
$this->signature = $this->property('signature'); $this->subsectime = (int)$this->property('exif:SubSecTimeOriginal');
$this->file_signature = md5_file($this->file_path());
}
public function setSubSecTime()
{
$this->subsectime = $this->property('exif:SubSecTimeOriginal');
// In case of an error. // In case of an error.
if ($this->subsectime > 32767) if ($this->subsectime > 32767)
@ -240,7 +204,7 @@ class Photo extends Abstracted\Catalog
public function thumbnail($rotate=TRUE) public function thumbnail($rotate=TRUE)
{ {
if (! $this->thumbnail) { if (! $this->thumbnail) {
if ($this->o()->thumbnailimage(200,200,true,false)) { if ($this->isReadable() AND $this->o()->thumbnailimage(200,200,true,false)) {
$this->_o->setImageFormat('jpg'); $this->_o->setImageFormat('jpg');
return $this->_o->getImageBlob(); return $this->_o->getImageBlob();

View File

@ -11,7 +11,7 @@ class Video extends Abstracted\Catalog
public function getIDLinkAttribute() public function getIDLinkAttribute()
{ {
return $this->HTMLLinkAttribute($this->id,url('v/info').'/'); return $this->HTMLLinkAttribute($this->id,'v/info');
} }
public function getHtmlImageURL() public function getHtmlImageURL()
@ -124,16 +124,6 @@ class Video extends Abstracted\Catalog
return isset($data[$key]) ? $data[$key] : NULL; return isset($data[$key]) ? $data[$key] : NULL;
} }
public function setDateCreated()
{
$this->date_created = $this->property('creationdate');
}
public function setDateCreatedAttribute($value)
{
$this->attributes['date_created'] = strtotime($value);
}
public function setLocation() public function setLocation()
{ {
$this->gps_lat = $this->property('gps_lat'); $this->gps_lat = $this->property('gps_lat');
@ -172,8 +162,8 @@ class Video extends Abstracted\Catalog
public function setSignature() public function setSignature()
{ {
$this->signature = $this->property('signature'); parent::setSignature();
$this->file_signature = md5_file($this->file_path());
$this->identifier = $this->property('identifier'); $this->identifier = $this->property('identifier');
} }

75
app/Traits/Multimedia.php Normal file
View File

@ -0,0 +1,75 @@
<?php
namespace App\Traits;
use App\Jobs\PhotoDelete;
use App\Models\Photo;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
* Multimedia Controller Functions
*
* @package App\Traits
*/
trait Multimedia
{
use Type;
private function controller(string $type): string
{
switch (strtolower($type)) {
case 'photo': return 'PhotoController';
case 'video': return 'Videoontroller';
default: abort(500,'Type not handled?');
}
}
public function deletesUpdate(Request $request)
{
$class = $this->getModelType($request->input('type'));
$this->updatePostItems($request,$class,TRUE);
return redirect()->action(
sprintf('%s@deletes',$this->controller($request->input('type'))),
sprintf('?page=%s',$request->input('page'))
);
}
public function duplicatesUpdate(Request $request)
{
$class = $this->getModelType($request->input('type'));
$this->updatePostItems($request,$class);
return redirect()->action(
sprintf('%s@duplicates',$this->controller($request->input('type'))),
sprintf('?page=%s',$request->input('page'))
);
}
private function updatePostItems(Request $request,string $class,bool $delete=FALSE)
{
foreach ($request->input('items') as $id) {
$o = $class::findOrFail($id);
// Set if duplicate
$o->duplicate = $request->input('duplicate.'.$id) ? 1 : NULL;
// Set if ignore duplicate
$o->ignore_duplicate = $request->input('ignore_duplicate.'.$id) ? 1 : NULL;
// Set if flag
$o->flag = $request->input('flag.'.$id) ? 1 : NULL;
// Set if delete
if ($delete AND $o->remove AND ($request->input('remove.'.$id) ? 1 : NULL)) {
Log::info(sprintf('Dispatching delete for [%s]',$o->id));
} else {
$o->remove = $request->input('remove.'.$id) ? 1 : NULL;
}
$o->save();
}
}
}

View File

@ -0,0 +1,84 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddOverrideKeepAttributes extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('photo', function (Blueprint $table) {
$table->dateTime('created')->nullable();
$table->dateTime('created_manual')->nullable();
$table->boolean('ignore_duplicate')->nullable();
});
Schema::table('videos', function (Blueprint $table) {
$table->dateTime('created')->nullable();
$table->dateTime('created_manual')->nullable();
$table->boolean('ignore_duplicate')->nullable();
});
\App\Models\Photo::each(function($o) {
$o->created = $o->date_created;
$o->date_created = NULL;
$o->save();
});
\App\Models\Video::each(function($o) {
$o->created = $o->date_created;
$o->date_created = NULL;
$o->save();
});
Schema::table('photo', function (Blueprint $table) {
$table->dropColumn('date_created');
});
Schema::table('videos', function (Blueprint $table) {
$table->dropColumn('date_created');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('photos', function (Blueprint $table) {
$table->integer('date_created')->nullable();
});
Schema::table('videos', function (Blueprint $table) {
$table->integer('date_created')->nullable();
});
\App\Models\Photo::each(function($o) {
$o->date_created = $o->created;
$o->created = NULL;
$o->save();
});
\App\Models\Video::each(function($o) {
$o->date_created = $o->created;
$o->created = NULL;
$o->save();
});
Schema::table('videos', function (Blueprint $table) {
$table->dropColumn(['created','created_manual','ignore_duplicate']);
});
Schema::table('photo', function (Blueprint $table) {
$table->dropColumn(['created','created_manual','ignore_duplicate']);
});
}
}

View File

@ -21,12 +21,13 @@
<form action="{{ $return }}" method="POST"> <form action="{{ $return }}" method="POST">
{{ csrf_field() }} {{ csrf_field() }}
<input type="hidden" name="type" value="{{ $type }}">
@include('catalog.widgets.duplicates') @include('catalog.widgets.duplicates')
<div class="pb-2"><button class="btn btn-sm btn-danger">Confirm Delete</button></div> <div class="pb-2"><button class="btn btn-sm btn-danger">Confirm Delete</button></div>
<input type="hidden" name="pagenext" value="{{ $catalog->hasMorePages() ? $catalog->currentPage()+1 : NULL }}"> <input type="hidden" name="page" value="{{ $catalog->hasMorePages() ? $catalog->currentPage()+1 : NULL }}">
</form> </form>
@else @else
NONE! NONE!

View File

@ -21,10 +21,13 @@
<form action="{{ $return }}" method="POST"> <form action="{{ $return }}" method="POST">
{{ csrf_field() }} {{ csrf_field() }}
<input type="hidden" name="type" value="{{ $type }}">
@include('catalog.widgets.duplicates') @include('catalog.widgets.duplicates')
<div class="pb-2"><button class="btn btn-sm btn-primary">Update</button></div> <div class="pb-2"><button class="btn btn-sm btn-primary">Update</button></div>
<input type="hidden" name="page" value="{{ $catalog->currentPage() }}">
</form> </form>
@else @else
NONE! NONE!

View File

@ -6,7 +6,15 @@
</tr> </tr>
</thead> </thead>
@php
// Remember what we have rendered
$rendered = collect();
@endphp
@foreach ($catalog as $o) @foreach ($catalog as $o)
@if($rendered->search($o->id)) @continue @endif
@php($rendered->push($o->id))
<tbody> <tbody>
<tr> <tr>
<td> <td>
@ -14,7 +22,7 @@
@include($o->type.'.widgets.thumbnail',['o'=>$o]) @include($o->type.'.widgets.thumbnail',['o'=>$o])
</td> </td>
@if (! ($d=$o->duplicates()->get())->count()) @if (! ($d=$o->myduplicates()->get())->count())
<td> <td>
No other duplicates found? No other duplicates found?
</td> </td>
@ -22,6 +30,8 @@
@else @else
@foreach($d as $item) @foreach($d as $item)
@if($rendered->search($item->id)) @continue @endif
@php($rendered->push($item->id))
<td> <td>
<input type="hidden" name="items[]" value="{{ $item->id }}"> <input type="hidden" name="items[]" value="{{ $item->id }}">
@include($item->type.'.widgets.thumbnail',['o'=>$item]) @include($item->type.'.widgets.thumbnail',['o'=>$item])

View File

@ -10,6 +10,7 @@
@section('contentheader_description') @section('contentheader_description')
@if(! $o->scanned)<button class="btn btn-sm btn-info">TO SCAN</button>@endif @if(! $o->scanned)<button class="btn btn-sm btn-info">TO SCAN</button>@endif
@if($o->duplicate)<button class="btn btn-sm btn-warning">DUPLICATE</button>@endif @if($o->duplicate)<button class="btn btn-sm btn-warning">DUPLICATE</button>@endif
@if($o->ignore_duplicate)<button class="btn btn-sm btn-secondary">DUPLICATE IGNORE</button>@endif
@if($o->remove)<button class="btn btn-sm btn-danger">PENDING DELETE</button>@endif @if($o->remove)<button class="btn btn-sm btn-danger">PENDING DELETE</button>@endif
@endsection @endsection
@section('page_title') @section('page_title')
@ -41,10 +42,10 @@
<div class="col-9"> <div class="col-9">
<div class="dl-horizontal"> <div class="dl-horizontal">
<dt>Signature</dt><dd>{{ $o->signature(TRUE) }}</dd> <dt>Signature</dt><dd>{{ $o->signature(TRUE) }}</dd>
<dt>Filename</dt><dd>{{ $o->file_path(TRUE) }}<dd> <dt>Filename</dt><dd>{{ $o->filename }}<dd>
@if ($o->shouldMove()) @if ($o->shouldMove())
<dt>NEW Filename</dt><dd>{{ $o->file_path(TRUE,TRUE) }}<dd> <dt>NEW Filename</dt><dd>{{ $o->file_name() }}<dd>
@endif @endif
<dt>Size</dt><dd>{{ $o->file_size() }}<dd> <dt>Size</dt><dd>{{ $o->file_size() }}<dd>
@ -70,7 +71,7 @@
</table> </table>
</dd> </dd>
<hr> <hr>
@if($x = $o->duplicates()->get()) @if(($x=$o->myduplicates()->get())->count())
<dt>Duplicates</dt> <dt>Duplicates</dt>
<dd> <dd>
@foreach($x as $oo) @foreach($x as $oo)

View File

@ -2,20 +2,21 @@
'ID'=>['id','idlink'], 'ID'=>['id','idlink'],
'Signature'=>['signature','signature'], 'Signature'=>['signature','signature'],
'File Signature'=>['file_signature','file_signature'], 'File Signature'=>['file_signature','file_signature'],
'Date Created'=>['date_created','date_created'], 'Date Created'=>['created','created'],
'Filename'=>['filename','filename'], 'Filename'=>['filename','filename'],
'Filesize'=>['filesize','filesize'], 'Filesize'=>['filesize','filesize'],
'Dimensions'=>['height','dimensions'], 'Dimensions'=>['height','dimensions'],
'Duplicate'=>['duplicate','duplicatecheckbox'], 'Duplicate'=>['duplicate','duplicatecheckbox'],
'Flagged'=>['flag','flagcheckbox'], 'Flagged'=>['flag','flagcheckbox'],
'Ignore Duplicate'=>['ignore','ignorecheckbox'],
'Delete'=>['remove','removecheckbox'], 'Delete'=>['remove','removecheckbox'],
];?> ];?>
<div class="card card-widget"> <div class="card card-widget">
<div class="card-header"> <div class="card-header">
<div class="user-block"> <div class="user-block">
<i class="float-left fa fa-2x fa-camera"></i><span class="username"><a href="#">#{{ $o->id }} - {{ \Illuminate\Support\Str::limit($o->filename,12).\Illuminate\Support\Str::substr($o->filename,-16) }}</a></span> <i class="float-left fa fa-2x fa-camera"></i><span class="username"><a href="{{ url('p/info',$o->id) }}">#{{ $o->id }} - {{ \Illuminate\Support\Str::limit($o->filename,12).\Illuminate\Support\Str::substr($o->filename,-16) }}</a></span>
<span class="description">{{ $o->date_created ? $o->date_created->toDateTimeString() : '-' }} [{{ $o->device() }}]</span> <span class="description">{{ $o->created ? $o->created->toDateTimeString() : '-' }} [{{ $o->device() }}]</span>
</div> </div>
<!-- /.user-block --> <!-- /.user-block -->
<div class="card-tools"> <div class="card-tools">

View File

@ -15,7 +15,7 @@
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('p/duplicates') }}" class="nav-link @if(preg_match('#^p/duplicates$#',request()->path())) active @endif"> <a href="{{ url('p/duplicates') }}" class="nav-link @if(preg_match('#^p/duplicates$#',request()->path())) active @endif">
<i class="fa fa-link nav-icon"></i> <p>Duplicate</p> <i class="fa fa-link nav-icon"></i> <p>Duplicate</p>
<span class="badge badge-warning right">{{ \App\Models\Photo::where('duplicate',TRUE)->count() }}</span> <span class="badge badge-warning right">{{ \App\Models\Photo::duplicates()->count() }}</span>
</a> </a>
</li> </li>
@ -37,7 +37,7 @@
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('v/duplicates') }}" class="nav-link @if(preg_match('#^v/duplicates$#',request()->path())) active @endif"> <a href="{{ url('v/duplicates') }}" class="nav-link @if(preg_match('#^v/duplicates$#',request()->path())) active @endif">
<i class="fa fa-link nav-icon"></i> <p>Duplicate</p> <i class="fa fa-link nav-icon"></i> <p>Duplicate</p>
<span class="badge badge-warning right">{{ \App\Models\Video::where('duplicate',TRUE)->count() }}</span> <span class="badge badge-warning right">{{ \App\Models\Video::duplicates()->count() }}</span>
</a> </a>
</li> </li>

View File

@ -10,6 +10,7 @@
@section('contentheader_description') @section('contentheader_description')
@if(! $o->scanned)<button class="btn btn-sm btn-info">TO SCAN</button>@endif @if(! $o->scanned)<button class="btn btn-sm btn-info">TO SCAN</button>@endif
@if($o->duplicate)<button class="btn btn-sm btn-warning">DUPLICATE</button>@endif @if($o->duplicate)<button class="btn btn-sm btn-warning">DUPLICATE</button>@endif
@if($o->ignore_duplicate)<button class="btn btn-sm btn-secondary">DUPLICATE IGNORE</button>@endif
@if($o->remove)<button class="btn btn-sm btn-danger">PENDING DELETE</button>@endif @if($o->remove)<button class="btn btn-sm btn-danger">PENDING DELETE</button>@endif
@endsection @endsection
@section('page_title') @section('page_title')
@ -41,10 +42,10 @@
<div class="col-8"> <div class="col-8">
<div class="dl-horizontal"> <div class="dl-horizontal">
<dt>Signature</dt><dd>{{ $o->signature(TRUE) }}</dd> <dt>Signature</dt><dd>{{ $o->signature(TRUE) }}</dd>
<dt>Filename</dt><dd>{{ $o->file_path(TRUE) }}<dd> <dt>Filename</dt><dd>{{ $o->filename }}<dd>
@if ($o->shouldMove()) @if ($o->shouldMove())
<dt>NEW Filename</dt><dd>{{ $o->file_path(TRUE,TRUE) }}<dd> <dt>NEW Filename</dt><dd>{{ $o->file_name() }}<dd>
@endif @endif
<dt>Size</dt><dd>{{ $o->file_size() }}<dd> <dt>Size</dt><dd>{{ $o->file_size() }}<dd>
@ -69,7 +70,7 @@
@endif @endif
</dd> </dd>
@if($x = $o->duplicates()->get()) @if(($x=$o->myduplicates()->get())->count())
<dt>Duplicates</dt> <dt>Duplicates</dt>
<dd> <dd>
@foreach($x as $oo) @foreach($x as $oo)

View File

@ -2,21 +2,22 @@
'ID'=>['id','idlink'], 'ID'=>['id','idlink'],
'Signature'=>['signature','signature'], 'Signature'=>['signature','signature'],
'File Signature'=>['file_signature','file_signature'], 'File Signature'=>['file_signature','file_signature'],
'Date Created'=>['date_created','date_created'], 'Date Created'=>['created','created'],
'Filename'=>['filename','filename'], 'Filename'=>['filename','filename'],
'Filesize'=>['filesize','filesize'], 'Filesize'=>['filesize','filesize'],
'Dimensions'=>['height','dimensions'], 'Dimensions'=>['height','dimensions'],
'Length'=>['length','length'], 'Length'=>['length','length'],
'Duplicate'=>['duplicate','duplicatecheckbox'], 'Duplicate'=>['duplicate','duplicatecheckbox'],
'Flagged'=>['flag','flagcheckbox'], 'Flagged'=>['flag','flagcheckbox'],
'Ignore Duplicate'=>['ignore','ignorecheckbox'],
'Delete'=>['remove','removecheckbox'], 'Delete'=>['remove','removecheckbox'],
];?> ];?>
<div class="card card-widget"> <div class="card card-widget">
<div class="card-header"> <div class="card-header">
<div class="user-block"> <div class="user-block">
<i class="float-left fa fa-2x fa-camera"></i><span class="username"><a href="#">#{{ $o->id }} - {{ \Illuminate\Support\Str::limit($o->filename,12).\Illuminate\Support\Str::substr($o->filename,-16) }}</a></span> <i class="float-left fa fa-2x fa-camera"></i><span class="username"><a href="{{ url('v/info',$o->id) }}">#{{ $o->id }} - {{ \Illuminate\Support\Str::limit($o->filename,12).\Illuminate\Support\Str::substr($o->filename,-16) }}</a></span>
<span class="description">{{ $o->date_created ? $o->date_created->toDateTimeString() : '-' }} [{{ $o->device() }}]</span> <span class="description">{{ $o->created ? $o->created->toDateTimeString() : '-' }} [{{ $o->device() }}]</span>
</div> </div>
<!-- /.user-block --> <!-- /.user-block -->
<div class="card-tools"> <div class="card-tools">
@ -28,7 +29,7 @@
<!-- /.card-body --> <!-- /.card-body -->
<div class="card-body"> <div class="card-body">
<a href="{{ url('p/view',$o->id) }}" target="{{ $o->id }}">{!! $o->getHtmlImageURL() !!}</a> <a href="{{ url('v/view',$o->id) }}" target="{{ $o->id }}">{!! $o->getHtmlImageURL() !!}</a>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->