From cfc9bf5d9a3d34b46d1146975342de71e120d3d6 Mon Sep 17 00:00:00 2001 From: Deon George Date: Thu, 11 Jan 2018 23:59:53 +1100 Subject: [PATCH] Optimised scanning and importing --- .gitignore | 2 + app/Console/Commands/CatalogDump.php | 49 ++ app/Console/Commands/CatalogScan.php | 90 ++++ app/Console/Commands/PhotoImport.php | 87 +--- app/Console/Commands/PhotoScanAll.php | 58 +++ app/Console/Commands/VideoImport.php | 86 +-- app/Console/Commands/VideoScanAll.php | 58 +++ app/Console/Kernel.php | 4 + app/Http/Controllers/PhotoController.php | 12 +- app/Http/Controllers/VideoController.php | 12 +- app/Jobs/CatalogScan.php | 39 ++ app/Model/Abstracted/Catalog.php | 353 +++++++++++++ app/Model/Photo.php | 280 +++------- app/Model/Video.php | 287 ++-------- app/Providers/AppServiceProvider.php | 4 +- composer.json | 4 +- composer.lock | 489 +++++++++++++++++- ...2018_01_10_000000_photos_rename_fields.php | 34 ++ .../2018_01_11_090453_VideoAddScanned.php | 51 ++ .../2018_01_13_120330_PhotoAddIndex.php | 54 ++ .../views/catalog/deletereview.blade.php | 80 +++ resources/views/photo/deletereview.blade.php | 78 --- resources/views/photo/view.blade.php | 1 + resources/views/video/deletereview.blade.php | 75 --- resources/views/video/duplicates.blade.php | 2 + resources/views/video/view.blade.php | 12 +- 26 files changed, 1542 insertions(+), 759 deletions(-) create mode 100644 app/Console/Commands/CatalogDump.php create mode 100644 app/Console/Commands/CatalogScan.php create mode 100644 app/Console/Commands/PhotoScanAll.php create mode 100644 app/Console/Commands/VideoScanAll.php create mode 100644 app/Jobs/CatalogScan.php create mode 100644 app/Model/Abstracted/Catalog.php create mode 100644 database/migrations/2018_01_10_000000_photos_rename_fields.php create mode 100644 database/migrations/2018_01_11_090453_VideoAddScanned.php create mode 100644 database/migrations/2018_01_13_120330_PhotoAddIndex.php create mode 100644 resources/views/catalog/deletereview.blade.php delete mode 100644 resources/views/photo/deletereview.blade.php delete mode 100644 resources/views/video/deletereview.blade.php diff --git a/.gitignore b/.gitignore index d001d72..43474bf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ Homestead.yaml Homestead.json .env .*.swp +/storage/debugbar +/config/debugbar.php diff --git a/app/Console/Commands/CatalogDump.php b/app/Console/Commands/CatalogDump.php new file mode 100644 index 0000000..94fe0b9 --- /dev/null +++ b/app/Console/Commands/CatalogDump.php @@ -0,0 +1,49 @@ +argument('type'); + + if (! class_exists($class)) + abort(500,sprintf('No class [%s]',$this->argument('type'))); + + $o = $class::findOrFail($this->argument('id')); + + if (! is_readable($o->file_path())) + { + $this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path())); + exit; + } + + print_r($o->properties()); + } +} diff --git a/app/Console/Commands/CatalogScan.php b/app/Console/Commands/CatalogScan.php new file mode 100644 index 0000000..5fa9dc8 --- /dev/null +++ b/app/Console/Commands/CatalogScan.php @@ -0,0 +1,90 @@ +argument('type'); + + if (! class_exists($class)) + abort(500,sprintf('No class [%s]',$this->argument('type'))); + + $o = $class::findOrFail($this->argument('id')); + + if (! is_readable($o->file_path())) + { + $this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path())); + exit; + } + + $o->setDateCreated(); + $o->setSubSecTime(); + $o->setSignature(); + $o->setMakeModel(); + $o->setLocation(); + $o->setHeightWidth(); + $o->setThumbnail(); + + // If this is a duplicate + $x = $class::whereIN('id',$o->list_duplicate())->get(); + if (count($x)) { + $break = FALSE; + + foreach ($x as $oo) { + // If this photo siganture matches another. + if ($o->signature == $oo->signature) + { + // And that photo is not marked as a duplicate + if (! $oo->duplicate) + { + $o->duplicate = '1'; + $this->warn(sprintf('Image [%s] marked as a duplicate',$o->file_path())); + + // If the file signature also matches, we'll mark it for deletion + if ($oo->file_signature AND $o->file_signature == $oo->file_signature) + { + $this->warn(sprintf('Image [%s] marked for deletion',$o->file_path())); + $o->remove = '1'; + } + + break; + } + } + } + } + + $o->scanned = '1'; + + if ($o->getDirty()) + $this->warn(sprintf('Image [%s] metadata changed',$o->file_path())); + + $o->save(); + } +} diff --git a/app/Console/Commands/PhotoImport.php b/app/Console/Commands/PhotoImport.php index b7ca4b1..5cacdff 100644 --- a/app/Console/Commands/PhotoImport.php +++ b/app/Console/Commands/PhotoImport.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use Log; use Illuminate\Console\Command; +use Illuminate\Foundation\Bus\DispatchesJobs; use App\Model\Person; use App\Model\PhotoPerson; use App\Model\Photo; @@ -12,6 +13,7 @@ use App\Model\Tag; class PhotoImport extends Command { + use DispatchesJobs; use \App\Traits\Files; /** @@ -21,7 +23,7 @@ class PhotoImport extends Command */ protected $signature = 'photo:import {--dir= : Directory to Parse} - {--file= : File to import} + {--file= : File to Import} {--ignoredupe : Ignore duplicate files} {--deletedupe : Delete duplicate files} {--people= : People to reference in photo} @@ -108,90 +110,39 @@ class PhotoImport extends Command $c++; - $po = Photo::where('filename',$file)->first(); + $o = Photo::where('filename',$file)->first(); - if (is_null($po)) + if (is_null($o)) { - $po = new Photo; - $po->filename = $file; + $o = new Photo; + $o->filename = $file; } - if (! is_readable($po->file_path())) + if (! is_readable($o->file_path())) { - $this->warn(sprintf('Ignoring [%s], it is not readable',$po->file_path())); + $this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path())); continue; } - $po->date_taken = strtotime($po->property('exif:DateTime') ? $po->property('exif:DateTime') : $po->property('exif:DateTimeOriginal')); - $po->subsectime = $po->property('exif:SubSecTimeOriginal'); + if ($o->exists) + $this->warn(sprintf('%s [%s] already in DB: %s',$o->objectType(),$file,$o->id)); - $po->signature = $po->property('signature'); + $o->save(); - $po->make = $po->property('exif:Make'); - $po->model = $po->property('exif:Model'); - - $po->height = $po->property('height'); - $po->width = $po->property('width'); - $po->orientation = $po->property('orientation'); - - $po->gps_lat = Photo::latlon(preg_split('/,\s?/',$po->property('exif:GPSLatitude')),$po->property('exif:GPSLatitudeRef')); - $po->gps_lon = Photo::latlon(preg_split('/,\s?/',$po->property('exif:GPSLongitude')),$po->property('exif:GPSLongitudeRef')); - - try { - $po->thumbnail = exif_thumbnail($po->file_path()); - } catch (\Exception $e) { - // @todo Couldnt get the thumbnail, so we should create one. - } - - // If this is a duplicate - $x = Photo::whereIN('id',$po->list_duplicate())->get(); - if (count($x)) { - $skip = FALSE; - - foreach ($x as $o) { - // We'll only ignore based on the same signature. - if ($po->signature != $o->signature AND ! $po->exists) - continue; - - if ($this->option('ignoredupe')) - { - $skip = TRUE; - $this->warn(sprintf("Ignoring file [%s], it's the same as [%s] with id %s",$po->file_path(),$o->filename,$o->id)); - break; - - } - elseif ($this->option('deletedupe') AND ($po->filename != $o->filename)) - { - $skip = TRUE; - $this->error(sprintf("Deleting file [%s], it's the same as [%s] with id %s and signature [%s]\n",$po->file_path(),$o->filename,$o->id,$po->signature)); - unlink($po->file_path()); - } - } - - if ($skip) - continue; - - $po->duplicate = '1'; - $this->warn(sprintf('Image [%s] marked as a duplicate',$file)); - } - - if ($po->exists) - $this->warn(sprintf('Image [%s] already in DB: %s',$file,$po->id)); - - $po->save(); - - if ($po->wasRecentlyCreated) - $this->info(sprintf('Image [%s] stored in DB: %s',$file,$po->id)); + if ($o->wasRecentlyCreated) + $this->info(sprintf('%s [%s] stored in DB: %s',$o->objectType(),$file,$o->id)); // Record our people and tags if ($p) - $po->People()->sync($p); + $o->People()->sync($p); if ($t) - $po->Tags()->sync($t); + $o->Tags()->sync($t); + + $this->dispatch((new \App\Jobs\CatalogScan($o))->onQueue('scan')); } $bar->finish(); - return $this->info(sprintf('Images processed: %s',$c)); + return $this->info(sprintf('Photos processed: %s',$c)); } } diff --git a/app/Console/Commands/PhotoScanAll.php b/app/Console/Commands/PhotoScanAll.php new file mode 100644 index 0000000..12d911d --- /dev/null +++ b/app/Console/Commands/PhotoScanAll.php @@ -0,0 +1,58 @@ +chunk(200,function ($data) { + foreach ($data as $o) + { + if ($o->remove) { + Log::warning(sprintf('Not scanning [%s], marked for removal',$o->id)); + continue; + } + + $this->dispatch((new CatalogScan($o))->onQueue('scan')); + } + }); + } +} diff --git a/app/Console/Commands/VideoImport.php b/app/Console/Commands/VideoImport.php index f62267e..d5e0639 100644 --- a/app/Console/Commands/VideoImport.php +++ b/app/Console/Commands/VideoImport.php @@ -4,13 +4,14 @@ namespace App\Console\Commands; use Log; use Illuminate\Console\Command; +use Illuminate\Foundation\Bus\DispatchesJobs; use App\Model\Video; use App\Model\Tag; use App\Model\Person; class VideoImport extends Command { - + use DispatchesJobs; use \App\Traits\Files; /** @@ -108,91 +109,42 @@ class VideoImport extends Command $c++; - $vo = Video::where('filename',$file)->first(); + $o = Video::where('filename',$file)->first(); - if (is_null($vo)) + if (is_null($o)) { - $vo = new Video; - $vo->filename = $file; + $o = new Video; + $o->filename = $file; } - if (! is_readable($vo->file_path())) + if (! is_readable($o->file_path())) { - $this->warn(sprintf('Ignoring [%s], it is not readable',$vo->file_path())); + $this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path())); continue; } - $vo->date_created = strtotime($vo->property('creationdate')); - $vo->signature = $vo->property('signature'); + if ($o->exists) + $this->warn(sprintf('%s [%s] already in DB: %s',$o->objectType(),$file,$o->id)); - $vo->make = $vo->property('make'); - $vo->model = $vo->property('model'); + $o->save(); - $vo->height = $vo->property('height'); - $vo->width = $vo->property('width'); - $vo->type = $vo->property('type'); - $vo->length = $vo->property('length'); - $vo->codec = $vo->property('codec'); - $vo->audiochannels = $vo->property('audiochannels'); - $vo->channelmode = $vo->property('channelmode'); - $vo->samplerate = $vo->property('samplerate'); - - $vo->gps_lat = $vo->property('gps_lat'); - $vo->gps_lon = $vo->property('gps_lon'); - $vo->gps_altitude = $vo->property('gps_altitude'); - - // If this is a duplicate - $x = Video::whereIN('id',$vo->list_duplicate())->get(); - if (count($x)) { - $skip = FALSE; - - foreach ($x as $o) { - // We'll only ignore based on the same signature. - if ($vo->signature != $o->signature AND ! $vo->exists) - continue; - - if ($this->option('ignoredupe')) - { - $skip = TRUE; - $this->warn(sprintf("Ignoring file [%s], it's the same as [%s] with id %s",$vo->file_path(),$o->filename,$o->id)); - break; - - } - elseif ($this->option('deletedupe') AND ($vo->filename != $o->filename)) - { - $skip = TRUE; - $this->error(sprintf("Deleting file [%s], it's the same as [%s] with id %s and signature [%s]\n",$vo->file_path(),$o->filename,$o->id,$vo->signature)); - unlink($vo->file_path()); - } - } - - if ($skip) - continue; - - $vo->duplicate = '1'; - $this->warn(sprintf('Video [%s] marked as a duplicate',$file)); - } - - if ($vo->exists) - $this->warn(sprintf('Video [%s] already in DB: %s',$file,$vo->id)); - - $vo->save(); - - if ($vo->wasRecentlyCreated) - $this->info(sprintf('Video [%s] stored in DB: %s',$file,$vo->id)); + if ($o->wasRecentlyCreated) + $this->info(sprintf('%s [%s] stored in DB: %s',$o->objectType(),$file,$o->id)); // Record our people and tags if ($p) - $vo->People()->sync($p); + $o->People()->sync($p); if ($t) - $vo->Tags()->sync($t); + $o->Tags()->sync($t); + + $this->dispatch((new \App\Jobs\CatalogScan($o))->onQueue('scan')); if ($this->option('dumpid3')) - dd($vo->dump()); + dd($o->dump()); } $bar->finish(); - return $this->info(sprintf('Video processed: %s',$c)); + return $this->info(sprintf('Videos processed: %s',$c)); } } diff --git a/app/Console/Commands/VideoScanAll.php b/app/Console/Commands/VideoScanAll.php new file mode 100644 index 0000000..ad63063 --- /dev/null +++ b/app/Console/Commands/VideoScanAll.php @@ -0,0 +1,58 @@ +chunk(200,function ($data) { + foreach ($data as $o) + { + if ($o->remove) { + Log::warning(sprintf('Not scanning [%s], marked for removal',$o->id)); + continue; + } + + $this->dispatch((new CatalogScan($o))->onQueue('scan')); + } + }); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 9afbd2a..89a0dc8 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,11 +13,15 @@ class Kernel extends ConsoleKernel * @var array */ protected $commands = [ + Commands\CatalogScan::class, + Commands\CatalogDump::class, Commands\PhotoImport::class, Commands\PhotoMove::class, + Commands\PhotoScanAll::class, Commands\PhotoUpdate::class, Commands\VideoImport::class, Commands\VideoMove::class, + Commands\VideoScanAll::class, ]; /** diff --git a/app/Http/Controllers/PhotoController.php b/app/Http/Controllers/PhotoController.php index bd5379b..5ef2f3b 100644 --- a/app/Http/Controllers/PhotoController.php +++ b/app/Http/Controllers/PhotoController.php @@ -34,19 +34,19 @@ class PhotoController extends Controller return redirect()->action('PhotoController@info',[$id]); } - public function deletes() + public function deletes($id=NULL) { - return view('photo.deletereview',['photos'=>Photo::where('remove',1)->paginate(2)]); + return view('catalog.deletereview',['return'=>url('/p/deletes'),'catalog'=>is_null($id) ? Photo::where('remove',1)->paginate(50) : Photo::where('id',$id)]); } public function deletesUpdate(Request $request) { - foreach ($request->input('photo') as $id) + foreach ($request->input('remove') as $id=>$k) { - $photo = Photo::findOrFail($id); + $o = Photo::findOrFail($id); - if ($photo->remove AND $request->input('remove.'.$id)) - $this->dispatch((new PhotoDelete($photo))->onQueue('delete')); + 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); diff --git a/app/Http/Controllers/VideoController.php b/app/Http/Controllers/VideoController.php index dd2904d..ffdd4ed 100644 --- a/app/Http/Controllers/VideoController.php +++ b/app/Http/Controllers/VideoController.php @@ -35,19 +35,19 @@ class VideoController extends Controller return redirect()->action('VideoController@info',[$id]); } - public function deletes() + public function deletes($id=NULL) { - return view('video.deletereview',['videos'=>Video::where('remove',1)->paginate(2)]); + return view('catalog.deletereview',['return'=>url('v/deletes'),'catalog'=>is_null($id) ? Video::where('remove',1)->paginate(10) : Video::where('id',$id)->paginate(1)]); } public function deletesUpdate(Request $request) { - foreach ($request->input('video') as $id) + foreach ($request->input('remove') as $k=>$id) { - $video = Video::findOrFail($id); + $o = Video::findOrFail($k); - if ($video->remove AND $request->input('remove.'.$id)) - $this->dispatch((new VideoDelete($video))->onQueue('delete')); + if ($o->remove AND $request->input('remove.'.$k)) + $this->dispatch((new VideoDelete($o))->onQueue('delete')); } return redirect()->action('VideoController@deletes',$request->input('pagenext') ? '?page='.$request->input('pagenext') : NULL); diff --git a/app/Jobs/CatalogScan.php b/app/Jobs/CatalogScan.php new file mode 100644 index 0000000..cee62aa --- /dev/null +++ b/app/Jobs/CatalogScan.php @@ -0,0 +1,39 @@ +o = $o; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + Log::info(sprintf('%s: Scanning [%s|%s]',__METHOD__,$this->o->objecttype(),$this->o->id)); + Artisan::call('catalog:scan',['type'=>$this->o->objecttype(),'id'=>$this->o->id]); + } +} diff --git a/app/Model/Abstracted/Catalog.php b/app/Model/Abstracted/Catalog.php new file mode 100644 index 0000000..44ae42a --- /dev/null +++ b/app/Model/Abstracted/Catalog.php @@ -0,0 +1,353 @@ +belongsToMany('App\Model\Person'); + } + + public function Tags() + { + return $this->belongsToMany('App\Model\Tag'); + } + + /** + * Multimedia NOT duplicate. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeNotDuplicate($query) + { + return $query->where(function($query) + { + $query->where('duplicate','!=',TRUE) + ->orWhere('duplicate','=',NULL); + }); + } + + /** + * Multimedia NOT pending removal. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeNotRemove($query) + { + return $query->where(function($query) + { + $query->where('remove','!=',TRUE) + ->orWhere('remove','=',NULL); + }); + } + + /** + * Multimedia NOT scanned. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeNotScanned($query) + { + return $query->where(function($query) + { + $query->where('scanned','!=',TRUE) + ->orWhere('scanned','=',NULL); + }); + } + + abstract public function setDateCreated(); + abstract public function setLocation(); + abstract public function setMakeModel(); + abstract public function setSignature(); + abstract public function setSubSecTime(); + abstract public function setThumbnail(); + abstract public function view(); + + /** + * Date the multimedia was created + */ + public function date_taken() + { + return $this->date_created ? date('Y-m-d H:i:s',$this->date_created) : '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_created) OR ! $this->date_created) + ? sprintf('UNKNOWN/%07s',$this->file_path_id()) + : date('Y/m/d-His',$this->date_created)),$this->type()); + + return (($short OR preg_match('/^\//',$file)) ? '' : config('video.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,'/'),'/'); + } + + /** + * Display the file signature + */ + public function file_signature($short=FALSE) + { + return $short ? static::signaturetrim($this->file_signature) : $this->file_signature; + } + + /** + * Return the photo size + */ + public function file_size() + { + return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path()); + } + public function getFileSizeAttribute() + { + return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path()); + } + + public function getDateCreatedTextAttribute() + { + if ($this->date_created) + return date('d-m-Y H:i:s',$this->date_created); + } + + public function getDuplicateTextAttribute() + { + return $this->TextTrueFalse($this->duplicate); + } + + public function getFlagTextAttribute() + { + return $this->TextTrueFalse($this->flag); + } + + /** + * Return HTML Checkbox for flagged + */ + public function getFlagCheckboxAttribute() + { + return $this->HTMLCheckbox('flag',$this->id,$this->flag); + } + + /** + * Return HTML Checkbox for remove + */ + public function getRemoveCheckboxAttribute() + { + return $this->HTMLCheckbox('remove',$this->id,$this->remove); + } + + /** + * 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'; + } + + /** + * Get ID Info link + */ + protected function HTMLLinkAttribute($id,$url) + { + return sprintf('%s',url($url.$id),$id,$id); + } + + /** + * Return an HTML checkbox + */ + protected function HTMLCheckbox($name,$id,$value) + { + return sprintf('',$name,$id,$value ? ' checked="checked"' : ''); + } + + public function isParentWritable($dir) + { + if (file_exists($dir) AND is_writable($dir) AND is_dir($dir)) + return TRUE; + elseif ($dir == dirname($dir) OR file_exists($dir)) + return FALSE; + else + return ($this->isParentWritable(dirname($dir))); + } + + /** + * 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 (! $this->isParentWritable(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 (! $this->isParentWritable($this->file_path(FALSE,TRUE))) + return 4; + + return TRUE; + } + + /** + * Get the id of the previous record + */ + public function next() + { + return DB::table($this->getTable()) + ->where('id','>',$this->id) + ->orderby('id','ASC') + ->first(); + } + + /** + * Return my class shortname + */ + public function objectType() + { + return (new \ReflectionClass($this))->getShortName(); + } + + /** + * Get the id of the previous record + */ + public function previous() + { + return DB::table($this->getTable()) + ->where('id','<',$this->id) + ->orderby('id','DEC') + ->first(); + } + + public function setHeightWidth() + { + $this->height = $this->property('height'); + $this->width = $this->property('width'); + $this->orientation = $this->property('orientation'); + } + + /** + * Display the media 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)); + } + + protected function TextTrueFalse($value) + { + return $value ? 'TRUE' : 'FALSE'; + } + + public function list_duplicate($includeme=FALSE) + { + return $this->list_duplicates($includeme)->pluck('id'); + } + + /** + * Find duplicate images based on some attributes of the current image + */ + public function list_duplicates($includeme=FALSE) + { + $o = static::select(); + + if ($this->id AND ! $includeme) + $o->where('id','!=',$this->id); + + // Ignore photo's pending removal. + if (! $includeme) + $o->where(function($query) + { + $query->where('remove','!=',TRUE) + ->orWhere('remove','=',NULL); + }); + + // Where the signature is the same + $o->where(function($query) + { + $query->where('signature','=',$this->signature); + + // Or they have the same time taken with the same camera + if ($this->date_created AND ($this->model OR $this->make)) + { + $query->orWhere(function($query) + { + $query->where('date_created','=',$this->date_created ? $this->date_created : NULL); + if (Schema::hasColumn($this->getTable(),'subsectime')) + $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 $o->get(); + } +} diff --git a/app/Model/Photo.php b/app/Model/Photo.php index af0a2d4..abd26f5 100644 --- a/app/Model/Photo.php +++ b/app/Model/Photo.php @@ -3,14 +3,13 @@ namespace App\Model; use DB; -use Illuminate\Database\Eloquent\Model; -class Photo extends Model +class Photo extends Abstracted\Catalog { protected $table = 'photo'; // Imagick Object - private $_io; + private $_o; // How should the image be rotated, based on the value of orientation private $_rotate = [ @@ -19,68 +18,12 @@ class Photo extends Model 8=>-90, ]; - public function People() - { - return $this->belongsToMany('App\Model\Person'); - } - - public function Tags() - { - return $this->belongsToMany('App\Model\Tag'); - } - /** - * Photo's NOT pending removal. - * - * @return \Illuminate\Database\Eloquent\Builder + * Date the photo was taken */ - 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; + return $this->date_created ? (date('Y-m-d H:i:s',$this->date_created).($this->subsectime ? '.'.$this->subsectime : '')) : 'UNKNOWN'; } /** @@ -91,35 +34,17 @@ class Photo extends Model $file = $this->filename; if ($new) - $file = sprintf('%s.%s',((is_null($this->date_taken) OR ! $this->date_taken) + $file = sprintf('%s.%s',((is_null($this->date_created) OR ! $this->date_created) ? 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()); + : sprintf('%s_%03s',date('Y/m/d-His',$this->date_created),$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) + public function getIDLinkAttribute() { - 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 $this->HTMLLinkAttribute($this->id,url('p/info').'/'); } /** @@ -127,7 +52,7 @@ class Photo extends Model */ public function image() { - if (is_null($imo = $this->io())) + if (is_null($imo = $this->o())) return NULL; if (array_key_exists('exif',$imo->getImageProfiles())) @@ -138,31 +63,6 @@ class Photo extends Model return $imo->getImageBlob(); } - /** - * Return an Imagick object or attribute - * - */ - protected function io($attr=NULL) - { - if (! file_exists($this->file_path()) OR ! is_readable($this->file_path())) - return NULL; - - if (is_null($this->_io)) - $this->_io = new \Imagick($this->file_path()); - - return is_null($attr) ? $this->_io : $this->_io->getImageProperty($attr); - } - - public function isParentWritable($dir) - { - if (file_exists($dir) AND is_writable($dir) AND is_dir($dir)) - return TRUE; - elseif ($dir == dirname($dir) OR file_exists($dir)) - return FALSE; - else - return ($this->isParentWritable(dirname($dir))); - } - /** * Calculate the GPS coordinates */ @@ -193,46 +93,18 @@ class Photo extends Model } /** - * Determine if a file is moveable + * Return an Imagick object or attribute * - * useID boolean Determine if the path is based on the the ID or date */ - public function moveable() + protected function o($attr=NULL) { - // 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 (! file_exists($this->file_path()) OR ! is_readable($this->file_path())) + return NULL; - // 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; + if (is_null($this->_o)) + $this->_o = new \Imagick($this->file_path()); - // 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 (! $this->isParentWritable(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 (! $this->isParentWritable($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(); + return is_null($attr) ? $this->_o : $this->_o->getImageProperty($attr); } /** @@ -251,7 +123,6 @@ class Photo extends Model /** * Rotate the image - * */ private function rotate(\Imagick $imo) { @@ -261,68 +132,77 @@ class Photo extends Model return $imo->getImageBlob(); } - /** - * 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()) + if (! $this->o()) 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; + case 'creationdate': return strtotime($this->property('exif:DateTimeOriginal') ? $this->property('exif:DateTimeOriginal') : $this->property('exif:DateTime')); break; + case 'height': return $this->_o->getImageHeight(); break; + case 'orientation': return $this->_o->getImageOrientation(); break; + case 'signature': return $this->_o->getImageSignature(); break; + case 'width': return $this->_o->getImageWidth(); break; default: - return $this->_io->getImageProperty($property); + return $this->_o->getImageProperty($property); } } public function properties() { - return $this->io() ? $this->_io->getImageProperties() : []; + return $this->o() ? $this->_o->getImageProperties() : []; } - /** - * Display the photo signature - */ - public function signature($short=FALSE) + public function setDateCreated() { - return $short ? static::signaturetrim($this->signature) : $this->signature; + if ($this->property('creationdate')) + $this->date_created = $this->property('creationdate'); } - public static function signaturetrim($signature,$chars=6) + public function setLocation() { - return sprintf('%s...%s',substr($signature,0,$chars),substr($signature,-1*$chars)); + $this->gps_lat = static::latlon(preg_split('/,\s?/',$this->property('exif:GPSLatitude')),$this->property('exif:GPSLatitudeRef')); + $this->gps_lon = static::latlon(preg_split('/,\s?/',$this->property('exif:GPSLongitude')),$this->property('exif:GPSLongitudeRef')); + #$this->gps_altitude = $this->property('gps_altitude'); } - /** - * Determine if the image should be moved - */ - public function shouldMove() + public function setMakeModel() { - return ($this->filename != $this->file_path(TRUE,TRUE)); + $this->make = $this->property('exif:Make') ? $this->property('exif:Make') : NULL; + $this->model = $this->property('exif:Model') ? $this->property('exif:Model') : NULL; + $this->software = $this->property('exif:Software') ? $this->property('exif:Software') : NULL; + } + + public function setSignature() + { + $this->signature = $this->property('signature'); + $this->file_signature = md5_file($this->file_path()); + #$this->identifier = $this->property('identifier'); + } + + public function setSubSecTime() + { + $this->subsectime = $this->property('exif:SubSecTimeOriginal'); + } + + public function setThumbnail() + { + try { + $this->thumbnail = exif_thumbnail($this->file_path()); + } catch (\Exception $e) { + // @todo Couldnt get the thumbnail, so we should create one. + } } /** * 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; + return $this->o()->thumbnailimage(200,200,true,true) ? $this->_o->getImageBlob() : NULL; } if (! $rotate OR ! array_key_exists($this->orientation,$this->_rotate) OR ! extension_loaded('imagick')) @@ -336,53 +216,15 @@ class Photo extends Model /** * Return the extension of the image + * @todo mime-by-ext? */ public function type($mime=FALSE) { return strtolower($mime ? 'image/jpeg' : pathinfo($this->filename,PATHINFO_EXTENSION)); } - /** - * Find duplicate images based on some attributes of the current image - */ - public function list_duplicate($includeme=FALSE) + public function view() { - $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'); + return sprintf('',url('/p/thumbnail/'.$this->id)); } } diff --git a/app/Model/Video.php b/app/Model/Video.php index d43b0b9..b282148 100644 --- a/app/Model/Video.php +++ b/app/Model/Video.php @@ -3,178 +3,19 @@ namespace App\Model; use DB; -use Illuminate\Database\Eloquent\Model; -class Video extends Model +class Video extends Abstracted\Catalog { - protected $table = 'videos'; - // ID3 Object private $_o = NULL; - public function People() - { - return $this->belongsToMany('App\Model\Person'); - } - - public function Tags() - { - return $this->belongsToMany('App\Model\Tag'); - } + public function getIDLinkAttribute() + { + return $this->HTMLLinkAttribute($this->id,url('v/info').'/'); + } /** - * 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_created ? date('Y-m-d H:i:s',$this->date_created) : 'UNKNOWN'; - } - - public function dump() - { - return $this->_o->info; - } - - /** - * 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_created) OR ! $this->date_created) - ? sprintf('UNKNOWN/%07s',$this->file_path_id()) - : date('Y/m/d-His',$this->date_created)),$this->type()); - - return (($short OR preg_match('/^\//',$file)) ? '' : config('video.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'; - } - - public function isParentWritable($dir) - { - if (file_exists($dir) AND is_writable($dir) AND is_dir($dir)) - return TRUE; - elseif ($dir == dirname($dir) OR file_exists($dir)) - return FALSE; - else - return ($this->isParentWritable(dirname($dir))); - } - - /** - * 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 (! $this->isParentWritable(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 (! $this->isParentWritable($this->file_path(FALSE,TRUE))) - return 4; - - return TRUE; - } - - /** - * Get the id of the previous photo - */ - public function next() - { - $po = DB::table('videos'); - $po->where('id','>',$this->id); - $po->orderby('id','ASC'); - return $po->first(); - } - - /** - * Return an Imagick object or attribute + * Return an GetID3 object or attribute * */ protected function o($attr=NULL) @@ -192,17 +33,6 @@ class Video extends Model return is_null($attr) ? $this->_o : (array_key_exists($attr,$this->_o->info) ? $this->_o->info[$attr] : NULL); } - /** - * Get the id of the previous photo - */ - public function previous() - { - $po = DB::table('videos'); - $po->where('id','<',$this->id); - $po->orderby('id','DEC'); - return $po->first(); - } - public function property($property) { if (! $this->o()) @@ -222,6 +52,7 @@ class Video extends Model case 'make': return array_get($this->_o->info,'quicktime.comments.make.0'); case 'model': return array_get($this->_o->info,'quicktime.comments.model.0'); + case 'software': return array_get($this->_o->info,'quicktime.comments.software.0'); case 'signature': return array_get($this->_o->info,'sha1_data'); #case 'height': return $this->subatomsearch('quicktime.moov.subatoms',['trak','tkhd'],'height'); break; #case 'width': return $this->subatomsearch('quicktime.moov.subatoms',['trak','tkhd'],'width'); break; @@ -236,13 +67,22 @@ class Video extends Model case 'gps_lat': return array_get($this->_o->info,'quicktime.comments.gps_latitude.0'); case 'gps_lon': return array_get($this->_o->info,'quicktime.comments.gps_longitude.0'); case 'gps_altitude': return array_get($this->_o->info,'quicktime.comments.gps_altitude.0'); + case 'identifier': if ($x=array_get($this->_o->info,'quicktime.comments')) { + return array_get(array_flatten(array_only($x,'content.identifier')),0); + } + break; default: return NULL; } } + public function properties() + { + return $this->o() ? $this->_o->info : []; + } + /** - Navigate through ID3 data to find the value. + * Navigate through ID3 data to find the value. */ private function subatomsearch($atom,array $paths,$key,array $data=[]) { if (! $data AND is_null($data = array_get($this->_o->info,$atom))) @@ -274,30 +114,52 @@ class Video extends Model return isset($data[$key]) ? $data[$key] : NULL; } - public function properties() + public function setDateCreated() { - return $this->o() ? $this->_io->info : []; + if ($this->property('creationdate')) + $this->date_created = $this->property('creationdate'); } - /** - * Display the photo signature - */ - public function signature($short=FALSE) + public function setDateCreatedAttribute($value) { - return $short ? static::signaturetrim($this->signature) : $this->signature; + $this->attributes['date_created'] = strtotime($value); } - public static function signaturetrim($signature,$chars=6) + public function setLocation() { - return sprintf('%s...%s',substr($signature,0,$chars),substr($signature,-1*$chars)); + $this->gps_lat = $this->property('gps_lat'); + $this->gps_lon = $this->property('gps_lon'); + $this->gps_altitude = $this->property('gps_altitude'); } - /** - * Determine if the image should be moved - */ - public function shouldMove() + public function setMakeModel() { - return ($this->filename != $this->file_path(TRUE,TRUE)); + $this->make = $this->property('make'); + $this->model = $this->property('model'); + $this->software = $this->property('software'); + $this->type = $this->property('type'); + $this->length = $this->property('length'); + $this->codec = $this->property('codec'); + $this->audiochannels = $this->property('audiochannels'); + $this->channelmode = $this->property('channelmode'); + $this->samplerate = $this->property('samplerate'); + } + + public function setSignature() + { + $this->signature = $this->property('signature'); + $this->file_signature = md5_file($this->file_path()); + $this->identifier = $this->property('identifier'); + } + + public function setSubSecTime() + { + // NOOP + } + + public function setThumbnail() + { + // NOOP } /** @@ -312,47 +174,4 @@ class Video extends Model { return sprintf('',url('/v/view/'.$this->id)); } - - /** - * Find duplicate images based on some attributes of the current image - */ - public function list_duplicate($includeme=FALSE) - { - $po = DB::table('videos'); - - 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_created AND ($this->model OR $this->make)) - { - $query->orWhere(function($query) - { - $query->where('date_created','=',$this->date_created ? $this->date_created : 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'); - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e1fe5bd..e59bb3f 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -23,7 +23,7 @@ class AppServiceProvider extends ServiceProvider { // Any photo saved, queue it to be moved. Photo::saved(function($photo) { - if (! $photo->duplicate AND ($x=$photo->moveable()) === TRUE) + if ($photo->scanned AND ! $photo->duplicate AND ! $photo->remove AND ($x=$photo->moveable()) === TRUE) { Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$photo->id.'|'.serialize($x))); $this->dispatch((new PhotoMove($photo))->onQueue('move')); @@ -32,7 +32,7 @@ class AppServiceProvider extends ServiceProvider // Any video saved, queue it to be moved. Video::saved(function($video) { - if (! $video->duplicate AND ($x=$video->moveable()) === TRUE) + if ($video->scanned AND ! $video->duplicate AND ! $video->remove AND ($x=$video->moveable()) === TRUE) { Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$video->id.'|'.serialize($x))); $this->dispatch((new VideoMove($video))->onQueue('move')); diff --git a/composer.json b/composer.json index aee0569..076dc22 100644 --- a/composer.json +++ b/composer.json @@ -7,9 +7,11 @@ "require": { "php": ">=5.5.9", "laravel/framework": "5.5.*", - "james-heinrich/getid3": "^1.9" + "james-heinrich/getid3": "^1.9", + "doctrine/dbal": "^2.6" }, "require-dev": { + "barryvdh/laravel-debugbar": "^3.1" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index bccd5f7..2a2a38c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,363 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "9032a4f9673c0b58a684f3f9dbbf559c", + "content-hash": "a8aeeb055c8c6f1ee724994018b254a1", "packages": [ + { + "name": "doctrine/annotations", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-12-06T07:11:42+00:00" + }, + { + "name": "doctrine/cache", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "shasum": "" + }, + "require": { + "php": "~7.1" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2017-08-25T07:02:50+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-07-22T10:37:32+00:00" + }, + { + "name": "doctrine/common", + "version": "v2.8.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", + "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~7.1" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2017-08-31T08:43:38+00:00" + }, + { + "name": "doctrine/dbal", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "e3eed9b1facbb0ced3a0995244843a189e7d1b13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/e3eed9b1facbb0ced3a0995244843a189e7d1b13", + "reference": "e3eed9b1facbb0ced3a0995244843a189e7d1b13", + "shasum": "" + }, + "require": { + "doctrine/common": "^2.7.1", + "ext-pdo": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.6", + "phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5", + "symfony/console": "2.*||^3.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2017-11-19T13:38:54+00:00" + }, { "name": "doctrine/inflector", "version": "v1.2.0", @@ -1898,7 +2253,137 @@ "time": "2016-09-01T10:05:43+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "barryvdh/laravel-debugbar", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "01a859752094e00aa8548832312366753272f8af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/01a859752094e00aa8548832312366753272f8af", + "reference": "01a859752094e00aa8548832312366753272f8af", + "shasum": "" + }, + "require": { + "illuminate/routing": "5.5.x", + "illuminate/session": "5.5.x", + "illuminate/support": "5.5.x", + "maximebf/debugbar": "~1.14.0", + "php": ">=7.0", + "symfony/debug": "^3", + "symfony/finder": "^3" + }, + "require-dev": { + "illuminate/framework": "5.5.x" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], + "time": "2017-09-18T13:32:46+00:00" + }, + { + "name": "maximebf/debugbar", + "version": "v1.14.1", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "64251a392344e3d22f3d21c3b7c531ba96eb01d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/64251a392344e3d22f3d21c3b7c531ba96eb01d2", + "reference": "64251a392344e3d22f3d21c3b7c531ba96eb01d2", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.14-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug", + "debugbar" + ], + "time": "2017-09-13T12:19:36+00:00" + } + ], "aliases": [], "minimum-stability": "stable", "stability-flags": [], diff --git a/database/migrations/2018_01_10_000000_photos_rename_fields.php b/database/migrations/2018_01_10_000000_photos_rename_fields.php new file mode 100644 index 0000000..e67ce72 --- /dev/null +++ b/database/migrations/2018_01_10_000000_photos_rename_fields.php @@ -0,0 +1,34 @@ +renameColumn('date_taken', 'date_created'); + }); + } + + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('photo', function(Blueprint $table) + { + $table->renameColumn('date_created', 'date_taken'); + }); + } +} diff --git a/database/migrations/2018_01_11_090453_VideoAddScanned.php b/database/migrations/2018_01_11_090453_VideoAddScanned.php new file mode 100644 index 0000000..dea9f30 --- /dev/null +++ b/database/migrations/2018_01_11_090453_VideoAddScanned.php @@ -0,0 +1,51 @@ +boolean('scanned')->nullable(); + $table->string('file_signature')->nullable(); + $table->string('identifier')->nullable(); + $table->string('software')->nullable(); + }); + + Schema::table('videos', function (Blueprint $table) { + $table->boolean('scanned')->nullable(); + $table->string('file_signature')->nullable(); + $table->string('identifier')->nullable(); + $table->string('software')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('photo', function (Blueprint $table) { + $table->dropColumn('scanned'); + $table->dropColumn('file_signature'); + $table->dropColumn('identifier'); + $table->dropColumn('software'); + }); + Schema::table('videos', function (Blueprint $table) { + $table->dropColumn('scanned'); + $table->dropColumn('file_signature'); + $table->dropColumn('identifier'); + $table->dropColumn('software'); + }); + } +} diff --git a/database/migrations/2018_01_13_120330_PhotoAddIndex.php b/database/migrations/2018_01_13_120330_PhotoAddIndex.php new file mode 100644 index 0000000..99d2a45 --- /dev/null +++ b/database/migrations/2018_01_13_120330_PhotoAddIndex.php @@ -0,0 +1,54 @@ +index(['signature']); + $table->index(['filename']); + $table->index(['remove']); + $table->index(['duplicate']); + $table->index(['date_created','subsectime','model','make']); + }); + Schema::table('videos', function (Blueprint $table) { + $table->index(['signature']); + $table->index(['filename']); + $table->index(['remove']); + $table->index(['duplicate']); + $table->index(['date_created','model','make']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('photo', function (Blueprint $table) { + $table->dropIndex(['signature']); + $table->dropIndex(['filename']); + $table->dropIndex(['remove']); + $table->dropIndex(['duplicate']); + $table->dropIndex(['date_created','subsectime','model','make']); + }); + Schema::table('videos', function (Blueprint $table) { + $table->dropIndex(['signature']); + $table->dropIndex(['filename']); + $table->dropIndex(['remove']); + $table->dropIndex(['duplicate']); + $table->dropIndex(['date_created','model','make']); + }); + } +} diff --git a/resources/views/catalog/deletereview.blade.php b/resources/views/catalog/deletereview.blade.php new file mode 100644 index 0000000..c47de75 --- /dev/null +++ b/resources/views/catalog/deletereview.blade.php @@ -0,0 +1,80 @@ +@extends('layouts.app') + +['id','idlink'], + 'Signature'=>['signature','signature'], + 'File Signature'=>['file_signature','file_signature'], + 'Date Created'=>['date_created','datecreatedtext'], + 'Filename'=>['filename','filename'], + 'Filesize'=>['filesize','filesize'], + 'Is Duplicate'=>['duplicate','duplicatetext'], + 'Flagged'=>['flag','flagtext'], + 'Delete'=>['remove','removecheckbox'], +];?> + +@section('content') +
+
+
+
+
+ Permanently Delete Items +
+ +
{{ $catalog->links() }}
+ +
+ @if ($catalog->count()) +
+ + + + + + + @foreach ($catalog as $o) + + @php + $d=$o->list_duplicates(TRUE); $src=[]; + @endphp + + @foreach($d as $item) + @if($src AND $item->id == $src->id) @continue @endif + @php($diff=collect($src)->diffAssoc($item)) + + @endforeach + + @endforeach +
SourceRemove
+ @if($d->where('duplicate',null)->count() > 1) + More than 1 source? + @elseif($d->count()) + @php($src = $d->first()) + {!! $src->view() !!} + + @foreach($data as $k=>$v) + + @endforeach +
{{$k}}{!! $src->{$v[1]} !!}
+ @endif +
+ {!! $item->view() !!} + + @foreach($data as $k=>$v) + + @endforeach +
{{$k}}{!! $item->{$v[1]} !!}
+
+ + + {{ csrf_field() }} +
+ @else + NONE! + @endif +
+
+
+
+
+@endsection diff --git a/resources/views/photo/deletereview.blade.php b/resources/views/photo/deletereview.blade.php deleted file mode 100644 index 0c3783d..0000000 --- a/resources/views/photo/deletereview.blade.php +++ /dev/null @@ -1,78 +0,0 @@ -@extends('layouts.app') - -@section('content') -'id', - 'Thumbnail'=>'thumbnail', - 'Signature'=>'signature', - 'Date Taken'=>'datetaken', - 'File Created'=>'created', - 'File Modified'=>'modified', - 'Filename'=>'filepath', - 'Filesize'=>'filesize', - 'Width'=>'width', - 'Height'=>'height', - 'Orientation'=>'orientation', - 'Make'=>'make', - 'Model'=>'model', - 'Duplicates'=>'duplicates', - 'Is Duplicate'=>'duplicate', - 'Flagged'=>'flag', - 'Delete'=>'remove', -];?> - -
-
-
-
-
- Permanently Delete Photos -
- -
{{ $photos->links() }}
-
-@if ($photos->count()) -
- - @foreach ($data as $k=>$v) - - -@foreach ($photos as $o) -id; $y=sprintf('%s',$o->id,url('/p/info/'.$o->id),$o->id); break; -case 'thumbnail': $x=md5($o->thumbnail); $y=sprintf('',url('/p/view/'.$o->id),url('/p/thumbnail/'.$o->id)); break; -case 'signature': $x=$y=$o->signature(TRUE); break; -case 'datetaken': $x=$y=$o->date_taken(); break; -case 'created': $x=$y=$o->file_date('c',TRUE); break; -case 'modified': $x=$y=$o->file_date('m',TRUE); break; -case 'filepath': $x=$y=$o->file_path(TRUE); break; -case 'filesize': $x=$y=$o->file_size(); break; -case 'width': -case 'height': -case 'orientation': -case 'make': -case 'model': $y=$o->{$v}; break; -case 'duplicates': $x=$y='';foreach($o->list_duplicate() as $id) $y.=($y ? '|' : '').sprintf('%s',url('/p/info/'.$id),$id); break; -case 'flag': -case 'remove': -case 'duplicate': $y=sprintf('',$v,$o->id,$o->{$v} ? ' checked="checked"' : ''); break; -endswitch ?> - - @endforeach {{-- photo --}} - - @endforeach {{-- data --}} -
{{ $k }}
- - - {{ csrf_field() }} -
-@else - NONE! -@endif -
-
-
-
-
-@endsection diff --git a/resources/views/photo/view.blade.php b/resources/views/photo/view.blade.php index 7fbc20d..576eef9 100644 --- a/resources/views/photo/view.blade.php +++ b/resources/views/photo/view.blade.php @@ -33,6 +33,7 @@
Date Taken
{{ $photo->date_taken() }}
Camera
{{ $photo->make }}
Model
{{ $photo->model }}
+
Software
{{ $photo->software }}

Location
gps() == 'UNKNOWN') : ?> diff --git a/resources/views/video/deletereview.blade.php b/resources/views/video/deletereview.blade.php deleted file mode 100644 index 4b29431..0000000 --- a/resources/views/video/deletereview.blade.php +++ /dev/null @@ -1,75 +0,0 @@ -@extends('layouts.app') - -@section('content') -'id', - 'Signature'=>'signature', - 'Date Created'=>'datecreated', - 'File Created'=>'created', - 'File Modified'=>'modified', - 'Filename'=>'filepath', - 'Filesize'=>'filesize', - 'Width'=>'width', - 'Height'=>'height', - 'Make'=>'make', - 'Model'=>'model', - 'Duplicates'=>'duplicates', - 'Is Duplicate'=>'duplicate', - 'Flagged'=>'flag', - 'Delete'=>'remove', -];?> - -
-
-
-
-
- Permanently Delete Photos -
- -
{{ $videos->links() }}
-
-@if ($videos->count()) -
- - @foreach ($data as $k=>$v) - - -@foreach ($videos as $o) -id; $y=sprintf('%s',$o->id,url('/v/info/'.$o->id),$o->id); break; -case 'signature': $x=$y=$o->signature(TRUE); break; -case 'datecreated': $x=$y=$o->date_taken(); break; -case 'created': $x=$y=$o->file_date('c',TRUE); break; -case 'modified': $x=$y=$o->file_date('m',TRUE); break; -case 'filepath': $x=$y=$o->file_path(TRUE); break; -case 'filesize': $x=$y=$o->file_size(); break; -case 'width': -case 'height': -case 'orientation': -case 'make': -case 'model': $y=$o->{$v}; break; -case 'duplicates': $x=$y='';foreach($o->list_duplicate() as $id) $y.=($y ? '|' : '').sprintf('%s',url('/v/info/'.$id),$id); break; -case 'flag': -case 'remove': -case 'duplicate': $y=sprintf('',$v,$o->id,$o->{$v} ? ' checked="checked"' : ''); break; -endswitch ?> - - @endforeach {{-- video --}} - - @endforeach {{-- data --}} -
{{ $k }}
- - - {{ csrf_field() }} -
-@else - NONE! -@endif -
-
-
-
-
-@endsection diff --git a/resources/views/video/duplicates.blade.php b/resources/views/video/duplicates.blade.php index 2d8a9a0..030fb19 100644 --- a/resources/views/video/duplicates.blade.php +++ b/resources/views/video/duplicates.blade.php @@ -5,6 +5,7 @@ 'ID'=>'id', 'Video'=>'video', 'Signature'=>'signature', + 'File Signature'=>'file_signature', 'Length'=>'length', 'Date Created'=>'datecreated', 'File Created'=>'created', @@ -58,6 +59,7 @@ case 'id': $x=$id; $y=sprintf('%s',url('/v/info/'.$o->id),$o->i case 'video': $x=$o->signature(TRUE);$y=$o->view();; break; case 'length': $x=$y=$o->length; break; case 'signature': $x=$y=$o->signature(TRUE); break; +case 'file_signature': $x=$y=$o->file_signature(TRUE); break; case 'datecreated': $x=$y=$o->date_taken(); break; case 'created': $x=$y=$o->file_date('c',TRUE); break; case 'modified': $x=$y=$o->file_date('m',TRUE); break; diff --git a/resources/views/video/view.blade.php b/resources/views/video/view.blade.php index c43cd92..fb0bfb6 100644 --- a/resources/views/video/view.blade.php +++ b/resources/views/video/view.blade.php @@ -6,7 +6,7 @@
- Video {{ $video->id }}remove) : ?> - PENDING DELETE + Video {{ $video->id }}remove) : ?> - PENDING DELETEduplicate) : ?> - DUPLICATE
@@ -39,6 +39,8 @@
Date Taken
{{ $video->date_taken() }}
Camera
{{ $video->make }}
Model
{{ $video->model }}
+
Software
{{ $video->software }}
+
Identifier
{{ $video->identifier }}

Location
gps() == 'UNKNOWN') : ?> @@ -60,6 +62,14 @@
+ @if($x = $video->list_duplicate()) +
Duplicates
+ @php +$y=''; foreach($video->list_duplicate() as $id) $y.=($y ? '|' : '').sprintf('%s',url('/v/info/'.$id),$id); + echo($y); + @endphp +
+ @endif