From 30120049011cb40c77aebbf379df45ef40a84ec9 Mon Sep 17 00:00:00 2001 From: Deon George Date: Mon, 4 Jul 2016 16:00:33 +1000 Subject: [PATCH] Added Video --- .../Commands/{Import.php => PhotoImport.php} | 10 +- .../Commands/{Move.php => PhotoMove.php} | 22 +- .../Commands/{Update.php => PhotoUpdate.php} | 2 +- app/Console/Commands/VideoImport.php | 198 ++++++++++ app/Console/Commands/VideoMove.php | 113 ++++++ app/Console/Kernel.php | 8 +- app/Http/Controllers/VideoController.php | 107 ++++++ app/Http/routes.php | 25 +- app/Jobs/VideoDelete.php | 60 +++ app/Jobs/VideoMove.php | 39 ++ app/Model/Photo.php | 13 +- app/Model/Video.php | 353 ++++++++++++++++++ app/Providers/AppServiceProvider.php | 11 + app/Traits/Files.php | 62 +-- composer.lock | 132 +++---- config/photo.php | 3 +- config/video.php | 9 + .../2016_07_01_205600_create_videos_table.php | 53 +++ resources/views/layouts/app.blade.php | 6 +- resources/views/photo/deletereview.blade.php | 8 +- resources/views/photo/duplicates.blade.php | 6 +- resources/views/photo/view.blade.php | 10 +- resources/views/video/deletereview.blade.php | 75 ++++ resources/views/video/duplicates.blade.php | 94 +++++ resources/views/video/view.blade.php | 80 ++++ 25 files changed, 1356 insertions(+), 143 deletions(-) rename app/Console/Commands/{Import.php => PhotoImport.php} (95%) rename app/Console/Commands/{Move.php => PhotoMove.php} (73%) rename app/Console/Commands/{Update.php => PhotoUpdate.php} (98%) create mode 100644 app/Console/Commands/VideoImport.php create mode 100644 app/Console/Commands/VideoMove.php create mode 100644 app/Http/Controllers/VideoController.php create mode 100644 app/Jobs/VideoDelete.php create mode 100644 app/Jobs/VideoMove.php create mode 100644 app/Model/Video.php create mode 100644 config/video.php create mode 100644 database/migrations/2016_07_01_205600_create_videos_table.php create mode 100644 resources/views/video/deletereview.blade.php create mode 100644 resources/views/video/duplicates.blade.php create mode 100644 resources/views/video/view.blade.php diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/PhotoImport.php similarity index 95% rename from app/Console/Commands/Import.php rename to app/Console/Commands/PhotoImport.php index e677fae..ceb2339 100644 --- a/app/Console/Commands/Import.php +++ b/app/Console/Commands/PhotoImport.php @@ -10,7 +10,7 @@ use App\Model\Photo; use App\Model\PhotoTag; use App\Model\Tag; -class Import extends Command +class PhotoImport extends Command { use \App\Traits\Files; @@ -51,7 +51,7 @@ class Import extends Command */ public function handle() { - $files = $this->getFiles(['dir'=>$this->option('dir'),'file'=>$this->option('file')]); + $files = $this->getFiles(['dir'=>$this->option('dir'),'file'=>$this->option('file')],'photo'); if (! count($files)) exit; @@ -106,6 +106,12 @@ class Import extends Command $po->filename = $file; } + if (! is_readable($po->file_path())) + { + $this->warn(sprintf('Ignoring [%s], it is not readable',$po->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'); diff --git a/app/Console/Commands/Move.php b/app/Console/Commands/PhotoMove.php similarity index 73% rename from app/Console/Commands/Move.php rename to app/Console/Commands/PhotoMove.php index 7ce8456..7d31dac 100644 --- a/app/Console/Commands/Move.php +++ b/app/Console/Commands/PhotoMove.php @@ -7,7 +7,7 @@ use Log; use Illuminate\Console\Command; use App\Model\Photo; -class Move extends Command +class PhotoMove extends Command { use \App\Traits\Files; @@ -66,35 +66,35 @@ class Move extends Command $bar->setRedrawFrequency(100); $po->chunk(1,function($photo) use ($bar) { - while ($po = $photo->shift()) + while ($o = $photo->shift()) { - if (($x = $po->moveable()) === TRUE) { - Log::info(sprintf('%s: Moving (%s)[%s]',__METHOD__,$po->id,$po->filename)); + if (($x = $o->moveable()) === TRUE) { + Log::info(sprintf('%s: Moving (%s)[%s]',__METHOD__,$o->id,$o->filename)); - if ($this->makeParentDir(dirname($po->file_path(FALSE,TRUE))) AND rename($po->file_path(),$po->file_path(FALSE,TRUE))) + if ($this->makeParentDir(dirname($o->file_path(FALSE,TRUE))) AND rename($o->file_path(),$o->file_path(FALSE,TRUE))) { // Convert the path to a relative one. - $po->filename = $po->file_path(TRUE,TRUE); + $o->filename = $o->file_path(TRUE,TRUE); // @todo If the DB update failed, move it back. - if (! $po->save()) # AND ! rename($path,$po->file_path())) + if (! $o->save()) # AND ! rename($path,$o->file_path())) { - $this->error(sprintf('Save after rename failed for (%s)',$po->id)); + $this->error(sprintf('Save after rename failed for (%s)',$o->id)); continue; } } else { - $this->error(sprintf('Rename failed for (%s)',$po->id)); + $this->error(sprintf('Rename failed for (%s)',$o->id)); continue; } - chmod($po->file_path(),0444); + chmod($o->file_path(),0444); } else { if ($x > 0) - $this->warn(sprintf('Unable to move (%s) [%s] to [%s], moveable returned (%s)',$po->id,$po->file_path(),$po->file_path(FALSE,TRUE),$x)); + $this->warn(sprintf('Unable to move (%s) [%s] to [%s], moveable returned (%s)',$o->id,$o->file_path(),$o->file_path(FALSE,TRUE),$x)); } } diff --git a/app/Console/Commands/Update.php b/app/Console/Commands/PhotoUpdate.php similarity index 98% rename from app/Console/Commands/Update.php rename to app/Console/Commands/PhotoUpdate.php index 036427c..dbbf7de 100644 --- a/app/Console/Commands/Update.php +++ b/app/Console/Commands/PhotoUpdate.php @@ -6,7 +6,7 @@ use Log; use Illuminate\Console\Command; use App\Model\Photo; -class Update extends Command +class PhotoUpdate extends Command { use \App\Traits\Files; diff --git a/app/Console/Commands/VideoImport.php b/app/Console/Commands/VideoImport.php new file mode 100644 index 0000000..f62267e --- /dev/null +++ b/app/Console/Commands/VideoImport.php @@ -0,0 +1,198 @@ +getFiles(['dir'=>$this->option('dir'),'file'=>$this->option('file')],'video'); + if (! count($files)) + exit; + + // Show a progress bar + $bar = $this->output->createProgressBar(count($files)); + $bar->setFormat("%current%/%max% [%bar%] %percent:3s%% (%memory%) (%remaining%) "); + + $tags = NULL; + $t = $p = array(); + + // Tags + if ($this->option('tags')) + { + $tags = explode(',',$this->option('tags')); + $t = Tag::whereIn('tag',$tags)->pluck('id')->toArray(); + if (! $t OR count($t) != count($tags)) + { + $this->error(sprintf('Tag [%s] dont exist',join('|',$tags))); + abort(501); + } + } + + // People + if ($this->option('people')) + { + $tags = explode(',',$this->option('people')); + $p = Person::whereIn('tag',$tags)->pluck('id')->toArray(); + if (! $p OR count($p) != count($tags)) + { + $this->error(sprintf('People [%s] dont exist',join('|',$tags))); + abort(501); + } + } + + $c = 0; + foreach ($files as $file) + { + $bar->advance(); + + if (preg_match('/@__thumb/',$file) OR preg_match('/\/._/',$file)) + { + $this->warn(sprintf('Ignoring file [%s]',$file)); + continue; + } + + if (! in_array(strtolower(pathinfo($file,PATHINFO_EXTENSION)),config('video.import.accepted'))) + { + $this->warn(sprintf('Ignoring [%s]',$file)); + continue; + } + + if ($this->option('verbose')) + $this->info(sprintf('Processing file [%s]',$file)); + + $c++; + + $vo = Video::where('filename',$file)->first(); + + if (is_null($vo)) + { + $vo = new Video; + $vo->filename = $file; + } + + if (! is_readable($vo->file_path())) + { + $this->warn(sprintf('Ignoring [%s], it is not readable',$vo->file_path())); + continue; + } + + $vo->date_created = strtotime($vo->property('creationdate')); + $vo->signature = $vo->property('signature'); + + $vo->make = $vo->property('make'); + $vo->model = $vo->property('model'); + + $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)); + + // Record our people and tags + if ($p) + $vo->People()->sync($p); + if ($t) + $vo->Tags()->sync($t); + + if ($this->option('dumpid3')) + dd($vo->dump()); + } + + $bar->finish(); + + return $this->info(sprintf('Video processed: %s',$c)); + } +} diff --git a/app/Console/Commands/VideoMove.php b/app/Console/Commands/VideoMove.php new file mode 100644 index 0000000..57b34a1 --- /dev/null +++ b/app/Console/Commands/VideoMove.php @@ -0,0 +1,113 @@ +option('file')) + { + $vo = Video::notRemove()->notDuplicate()->where('filename',Video::path($this->option('file'))); + } + elseif ($this->option('id')) + { + $vo = Video::notRemove()->notDuplicate()->where('id',$this->option('id')); + } + else + { + $vo = Video::notRemove()->notDuplicate(); + } + + if (! $vo) + exit; + + // Show a progress bar + $bar = $this->output->createProgressBar($vo->count()); + $bar->setFormat("%current%/%max% [%bar%] %percent:3s%% (%memory%) (%remaining%) "); + $bar->setRedrawFrequency(100); + + $vo->chunk(1,function($video) use ($bar) { + while ($o = $video->shift()) + { + if (($x = $o->moveable()) === TRUE) { + Log::info(sprintf('%s: Moving (%s)[%s]',__METHOD__,$o->id,$o->filename)); + + $fs = $o->file_size(); + $ft = $o->file_date('m'); + if ($this->makeParentDir(dirname($o->file_path(FALSE,TRUE))) AND copy($o->file_path(),$o->file_path(FALSE,TRUE))) + { + // If the copy worked, remove the original. + if (file_exists($o->file_path(FALSE,TRUE)) AND $fs === filesize($o->file_path(FALSE,TRUE))) + { + touch($o->file_path(FALSE,TRUE),$ft); + unlink($o->file_path()); + } + + // Convert the path to a relative one. + $o->filename = $o->file_path(TRUE,TRUE); + + // @todo If the DB update failed, move it back. + if (! $o->save()) # AND ! rename($path,$o->file_path())) + { + $this->error(sprintf('Save after rename failed for (%s)',$o->id)); + continue; + } + } + else + { + $this->error(sprintf('Rename failed for (%s)',$o->id)); + continue; + } + + chmod($o->file_path(),0444); + } + else + { + if ($x > 0) + $this->warn(sprintf('Unable to move (%s) [%s] to [%s], moveable returned (%s)',$o->id,$o->file_path(),$o->file_path(FALSE,TRUE),$x)); + } + } + + $bar->advance(); + }); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 8f79ace..9afbd2a 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,9 +13,11 @@ class Kernel extends ConsoleKernel * @var array */ protected $commands = [ - Commands\Import::class, - Commands\Move::class, - Commands\Update::class, + Commands\PhotoImport::class, + Commands\PhotoMove::class, + Commands\PhotoUpdate::class, + Commands\VideoImport::class, + Commands\VideoMove::class, ]; /** diff --git a/app/Http/Controllers/VideoController.php b/app/Http/Controllers/VideoController.php new file mode 100644 index 0000000..2e9999a --- /dev/null +++ b/app/Http/Controllers/VideoController.php @@ -0,0 +1,107 @@ +middleware('guest'); + } + + public function delete($id) + { + $po = Video::notRemove()->findOrFail($id); + + if ($po) + { + $po->remove = TRUE; + $po->save(); + } + + return redirect()->action('VideoController@info',[$id]); + } + + //@todo + public function deletes() + { + return view('video.deletereview',['videos'=>Video::where('remove',1)->paginate(2)]); + } + + //@todo + public function deletesUpdate(Request $request) + { + foreach ($request->input('video') as $id) + { + $video = Video::findOrFail($id); + + if ($video->remove AND $request->input('remove.'.$id)) + $this->dispatch((new VideoDelete($video))->onQueue('delete')); + } + + return redirect()->action('VideoController@deletes',$request->input('pagenext') ? '?page='.$request->input('pagenext') : NULL); + } + + //@todo + public function duplicates($id=NULL) + { + return view('video.duplicates',['videos'=>is_null($id) ? Video::notRemove()->where('duplicate',1)->paginate(1) : Video::where('id',$id)->paginate(1)]); + } + + //@todo + public function duplicatesUpdate(Request $request) + { + foreach ($request->input('video') as $id) + { + $po = Video::findOrFail($id); + + // Set if duplicate + $po->duplicate = $request->input('duplicate.'.$id) ? 1 : NULL; + + // Set if flag + $po->flag = $request->input('flag.'.$id) ? 1 : NULL; + + // Set if delete + $po->remove = $request->input('remove.'.$id) ? 1 : NULL; + + $po->isDirty() AND $po->save(); + } + + return redirect()->action('VideoController@duplicates','?page='.$request->input('page')); + } + public function info($id) + { + return view('video.view', ['video'=> Video::findOrFail($id)]); + } + + public function undelete($id) + { + $po = Video::findOrFail($id); + + if ($po) + { + $po->remove = NULL; + $po->save(); + } + + return redirect()->action('VideoController@info',[$id]); + } + + //@todo + public function view($id) + { + return response(Video::findOrFail($id)->image())->header('Content-Type','video/mov'); + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index ae1a634..efa39ef 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -19,12 +19,19 @@ Route::get('/', function () { Route::auth(); -Route::get('/deletes/{id?}', 'PhotoController@deletes')->where('id', '[0-9]+');; -Route::get('/duplicates/{id?}', 'PhotoController@duplicates')->where('id', '[0-9]+');; -Route::get('/info/{id}', 'PhotoController@info')->where('id', '[0-9]+');; -Route::get('/thumbnail/{id}', 'PhotoController@thumbnail')->where('id', '[0-9]+');; -Route::get('/view/{id}', 'PhotoController@view')->where('id', '[0-9]+');; -Route::post('/delete/{id}', 'PhotoController@delete')->where('id', '[0-9]+');; -Route::post('/duplicates', 'PhotoController@duplicatesUpdate'); -Route::post('/deletes', 'PhotoController@deletesUpdate'); -Route::post('/undelete/{id}', 'PhotoController@undelete')->where('id', '[0-9]+');; +Route::get('/p/deletes/{id?}', 'PhotoController@deletes')->where('id', '[0-9]+');; +Route::get('/v/deletes/{id?}', 'VideoController@deletes')->where('id', '[0-9]+');; +Route::get('/p/duplicates/{id?}', 'PhotoController@duplicates')->where('id', '[0-9]+');; +Route::get('/v/duplicates/{id?}', 'VideoController@duplicates')->where('id', '[0-9]+');; +Route::get('/p/info/{id}', 'PhotoController@info')->where('id', '[0-9]+');; +Route::get('/v/info/{id}', 'VideoController@info')->where('id', '[0-9]+');; +Route::get('/p/thumbnail/{id}', 'PhotoController@thumbnail')->where('id', '[0-9]+');; +Route::get('/p/view/{id}', 'PhotoController@view')->where('id', '[0-9]+');; +Route::post('/p/delete/{id}', 'PhotoController@delete')->where('id', '[0-9]+');; +Route::post('/v/delete/{id}', 'VideoController@delete')->where('id', '[0-9]+');; +Route::post('/p/duplicates', 'PhotoController@duplicatesUpdate'); +Route::post('/v/duplicates', 'VideoController@duplicatesUpdate'); +Route::post('/p/deletes', 'PhotoController@deletesUpdate'); +Route::post('/v/deletes', 'VideoController@deletesUpdate'); +Route::post('/p/undelete/{id}', 'PhotoController@undelete')->where('id', '[0-9]+');; +Route::post('/v/undelete/{id}', 'VideoController@undelete')->where('id', '[0-9]+');; diff --git a/app/Jobs/VideoDelete.php b/app/Jobs/VideoDelete.php new file mode 100644 index 0000000..2c29a16 --- /dev/null +++ b/app/Jobs/VideoDelete.php @@ -0,0 +1,60 @@ +video = $video; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + if (! $this->video->remove) + { + Log::warning(sprintf('%s: NOT Deleting [%s] not marked for deletion',__METHOD__,$this->video->file_path())); + exit; + } + + // Remove tags; + if ($this->video->Tags->count()) + $this->video->Tags()->detach(); + + // Remove People; + if ($this->video->People->count()) + $this->video->People()->detach(); + + // Make sure our parent is writable + if (! is_writable(dirname($this->video->file_path()))) + Log::warning(sprintf('%s: NOT Deleting [%s] parent directory not writable',__METHOD__,$this->video->file_path())); + + // Perform delete + if (file_exists($this->video->file_path())) + unlink($this->video->file_path()); + + Log::warning(sprintf('%s: Deleted [%s]',__METHOD__,$this->video->file_path())); + $this->video->delete(); + } +} diff --git a/app/Jobs/VideoMove.php b/app/Jobs/VideoMove.php new file mode 100644 index 0000000..a63475f --- /dev/null +++ b/app/Jobs/VideoMove.php @@ -0,0 +1,39 @@ +video = $video; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + Log::info(sprintf('%s: Moving [%s]',__METHOD__,$this->video->id)); + Artisan::call('video:move',['--id' => $this->video->id]); + } +} diff --git a/app/Model/Photo.php b/app/Model/Photo.php index de0570d..af0a2d4 100644 --- a/app/Model/Photo.php +++ b/app/Model/Photo.php @@ -127,7 +127,8 @@ class Photo extends Model */ public function image() { - $imo = $this->io(); + if (is_null($imo = $this->io())) + return NULL; if (array_key_exists('exif',$imo->getImageProfiles())) $imo->removeImageProfile('exif'); @@ -143,6 +144,9 @@ class Photo extends Model */ 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()); @@ -257,11 +261,6 @@ class Photo extends Model return $imo->getImageBlob(); } - public static function path($path) - { - return preg_replace('/^\//','',str_replace(config('photo.dir'),'',$path)); - } - /** * Get the id of the previous photo */ @@ -340,7 +339,7 @@ class Photo extends Model */ public function type($mime=FALSE) { - return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION)); + return strtolower($mime ? 'image/jpeg' : pathinfo($this->filename,PATHINFO_EXTENSION)); } /** diff --git a/app/Model/Video.php b/app/Model/Video.php new file mode 100644 index 0000000..4715752 --- /dev/null +++ b/app/Model/Video.php @@ -0,0 +1,353 @@ +belongsToMany('App\Model\Person'); + } + + public function Tags() + { + return $this->belongsToMany('App\Model\Tag'); + } + + /** + * 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 + * + */ + protected function o($attr=NULL) + { + if (! $this->filename OR ! file_exists($this->file_path()) OR ! is_readable($this->file_path())) + return FALSE; + + if (is_null($this->_o)) + { + $this->_o = new \getID3; + $this->_o->analyze($this->file_path()); + $this->_o->getHashdata('sha1'); + } + + 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()) + return NULL; + + switch ($property) + { + case 'creationdate': + // Try creation_Data + $x = array_get($this->_o->info,'quicktime.comments.creation_date.0'); + + // Try creation_Data + if (is_null($x)) + $x = array_get($this->_o->info,'quicktime.comments.creationdate.0'); + + return $x; + + 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 '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; + case 'height': return array_get($this->_o->info,'video.resolution_y'); + case 'width': return array_get($this->_o->info,'video.resolution_x'); + case 'length': return array_get($this->_o->info,'playtime_seconds'); + case 'type': return array_get($this->_o->info,'video.dataformat'); + case 'codec': return array_get($this->_o->info,'audio.codec'); + case 'audiochannels': return array_get($this->_o->info,'audio.channels'); + case 'samplerate': return array_get($this->_o->info,'audio.sample_rate'); + case 'channelmode': return array_get($this->_o->info,'audio.channelmode'); + 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'); + default: + return NULL; + } + } + + /** + 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))) + { + // Didnt find it. + return NULL; + } + + foreach ($paths as $path) + { + $found = FALSE; + foreach ($data as $array) + { + if ($array['name'] === $path) + { + $found = TRUE; + if ($path != last($paths)) + $data = $array['subatoms']; + else + $data = $array; + break; + } + } + + if (! $found) + break; + } + + return isset($data[$key]) ? $data[$key] : NULL; + } + + public function properties() + { + return $this->o() ? $this->_io->info : []; + } + + /** + * Display the photo signature + */ + public function signature($short=FALSE) + { + return $short ? static::signaturetrim($this->signature) : $this->signature; + } + + public static function signaturetrim($signature,$chars=6) + { + return sprintf('%s...%s',substr($signature,0,$chars),substr($signature,-1*$chars)); + } + + /** + * Determine if the image should be moved + */ + public function shouldMove() + { + return ($this->filename != $this->file_path(TRUE,TRUE)); + } + + /** + * Return the extension of the image + */ + public function type($mime=FALSE) + { + return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION)); + } + + /** + * Find duplicate images based on some attributes of the current image + */ + public function list_duplicate($includeme=FALSE) + { + $po = DB::table('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 64bc64e..e1fe5bd 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -6,6 +6,8 @@ use Log; use Illuminate\Support\ServiceProvider; use App\Model\Photo; use App\Jobs\PhotoMove; +use App\Model\Video; +use App\Jobs\VideoMove; use Illuminate\Foundation\Bus\DispatchesJobs; class AppServiceProvider extends ServiceProvider @@ -27,6 +29,15 @@ class AppServiceProvider extends ServiceProvider $this->dispatch((new PhotoMove($photo))->onQueue('move')); } }); + + // Any video saved, queue it to be moved. + Video::saved(function($video) { + if (! $video->duplicate 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/app/Traits/Files.php b/app/Traits/Files.php index 7a4b0fb..8b166e3 100644 --- a/app/Traits/Files.php +++ b/app/Traits/Files.php @@ -10,41 +10,42 @@ trait Files /** * Get a list of files */ - public function getFiles(array $option) + public function getFiles(array $option,$type) { - // Make sure we got a directory or a file to import - if (is_null($option['file']) AND is_null($option['dir'])) - abort(500,'Missing filename, please use --file= OR --dir='); + // Make sure we got a directory or a file to import + if (is_null($option['file']) AND is_null($option['dir'])) + abort(500,'Missing filename, please use --file= OR --dir='); - Log::info(sprintf('%s: Processing: %s',__METHOD__,($option['file'] ? $option['file'] : $option['dir']))); + Log::info(sprintf('%s: Processing: %s',__METHOD__,($option['file'] ? $option['file'] : $option['dir']))); - $files = []; + $files = []; + $dir = ''; - if ($option['dir']) - { - // Remove our trailing slash from the directory. - $dir = preg_replace('/\/$/','',$option['dir']); + if ($option['dir']) + { + // Remove our trailing slash from the directory. + $dir = preg_replace('/\/$/','',$option['dir']); - // Exclude . & .. from the path. - $files = array_diff(scandir($dir),array('.','..')); + // Exclude . & .. from the path. + $files = array_diff(scandir($dir),array('.','..')); - } - elseif ($option['file']) - { - $dir = dirname($option['file']); - $files = array(basename($option['file'])); - } + } + elseif ($option['file'] AND file_exists($option['file'])) + { + $dir = dirname($option['file']); + $files = array(basename($option['file'])); + } - // Determine if our dir is releative to where we store data - $dir = Photo::path($dir); + // Determine if our dir is releative to where we store data + $dir = static::path($dir,$type); - // Add our path - if ($dir) - array_walk($files,function(&$value,$key,$path='') { - if ($path) { - $value = sprintf('%s/%s',$path,$value); - } - },$dir); + // Add our path + if ($dir) + array_walk($files,function(&$value,$key,$path='') { + if ($path) { + $value = sprintf('%s/%s',$path,$value); + } + },$dir); return $files; } @@ -58,5 +59,10 @@ trait Files return FALSE; else return is_writable(dirname($dir)) AND mkdir($dir); - } + } + + public static function path($path,$type) + { + return (strpos($path,config($type.'.dir').'/') === 0) ? str_replace(config($type.'.dir').'/','',$path) : $path; + } } diff --git a/composer.lock b/composer.lock index 02ff547..0be769f 100644 --- a/composer.lock +++ b/composer.lock @@ -987,16 +987,16 @@ }, { "name": "symfony/console", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "382fc9ed852edabd6133e34f8549d7a7d99db115" + "reference": "a7abb7153f6d1da47f87ec50274844e246b09d9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/382fc9ed852edabd6133e34f8549d7a7d99db115", - "reference": "382fc9ed852edabd6133e34f8549d7a7d99db115", + "url": "https://api.github.com/repos/symfony/console/zipball/a7abb7153f6d1da47f87ec50274844e246b09d9f", + "reference": "a7abb7153f6d1da47f87ec50274844e246b09d9f", "shasum": "" }, "require": { @@ -1043,20 +1043,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-06-06 15:08:35" + "time": "2016-06-29 07:02:21" }, { "name": "symfony/debug", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "e67e1552dd7313df1cf6535cb606751899e0e727" + "reference": "c54bc3539c3b87e86799533801e8ae0e971d78c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/e67e1552dd7313df1cf6535cb606751899e0e727", - "reference": "e67e1552dd7313df1cf6535cb606751899e0e727", + "url": "https://api.github.com/repos/symfony/debug/zipball/c54bc3539c3b87e86799533801e8ae0e971d78c2", + "reference": "c54bc3539c3b87e86799533801e8ae0e971d78c2", "shasum": "" }, "require": { @@ -1100,20 +1100,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-06-06 15:08:35" + "time": "2016-06-29 05:40:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.1.1", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f5b7563f67779c6d3d5370e23448e707c858df3e" + "reference": "7f9839ede2070f53e7e2f0849b9bd14748c434c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f5b7563f67779c6d3d5370e23448e707c858df3e", - "reference": "f5b7563f67779c6d3d5370e23448e707c858df3e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7f9839ede2070f53e7e2f0849b9bd14748c434c5", + "reference": "7f9839ede2070f53e7e2f0849b9bd14748c434c5", "shasum": "" }, "require": { @@ -1160,20 +1160,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-06-06 11:42:41" + "time": "2016-06-29 05:41:56" }, { "name": "symfony/finder", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "39e5f3d533d07b5416b9d7aad53a27f939d4f811" + "reference": "3eb4e64c6145ef8b92adefb618a74ebdde9e3fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/39e5f3d533d07b5416b9d7aad53a27f939d4f811", - "reference": "39e5f3d533d07b5416b9d7aad53a27f939d4f811", + "url": "https://api.github.com/repos/symfony/finder/zipball/3eb4e64c6145ef8b92adefb618a74ebdde9e3fe9", + "reference": "3eb4e64c6145ef8b92adefb618a74ebdde9e3fe9", "shasum": "" }, "require": { @@ -1209,20 +1209,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-05-13 18:03:36" + "time": "2016-06-29 05:40:00" }, { "name": "symfony/http-foundation", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d268a643884f85e91d6ba11ca68de96833f3f6e5" + "reference": "1341139f906d295baa4f4abd55293d07e25a065a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d268a643884f85e91d6ba11ca68de96833f3f6e5", - "reference": "d268a643884f85e91d6ba11ca68de96833f3f6e5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1341139f906d295baa4f4abd55293d07e25a065a", + "reference": "1341139f906d295baa4f4abd55293d07e25a065a", "shasum": "" }, "require": { @@ -1262,20 +1262,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2016-06-06 11:33:26" + "time": "2016-06-29 07:02:21" }, { "name": "symfony/http-kernel", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "97cc1c15e3406e7a2adf14ad6b0e41a04d4a6fc4" + "reference": "177b63b2d50b63fa6d82ea41359ed9928cc7a1fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/97cc1c15e3406e7a2adf14ad6b0e41a04d4a6fc4", - "reference": "97cc1c15e3406e7a2adf14ad6b0e41a04d4a6fc4", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/177b63b2d50b63fa6d82ea41359ed9928cc7a1fb", + "reference": "177b63b2d50b63fa6d82ea41359ed9928cc7a1fb", "shasum": "" }, "require": { @@ -1283,7 +1283,7 @@ "psr/log": "~1.0", "symfony/debug": "~2.8|~3.0", "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/http-foundation": "~2.8|~3.0" + "symfony/http-foundation": "~2.8.8|~3.0.8|~3.1.2|~3.2" }, "conflict": { "symfony/config": "<2.8" @@ -1344,7 +1344,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2016-06-06 16:52:35" + "time": "2016-06-30 16:30:17" }, { "name": "symfony/polyfill-mbstring", @@ -1515,16 +1515,16 @@ }, { "name": "symfony/process", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "bf6e2d1fa8b93fdd7cca6b684c0ea213cf0255dd" + "reference": "d7cde1f9d94d87060204f863779389b61c382eeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/bf6e2d1fa8b93fdd7cca6b684c0ea213cf0255dd", - "reference": "bf6e2d1fa8b93fdd7cca6b684c0ea213cf0255dd", + "url": "https://api.github.com/repos/symfony/process/zipball/d7cde1f9d94d87060204f863779389b61c382eeb", + "reference": "d7cde1f9d94d87060204f863779389b61c382eeb", "shasum": "" }, "require": { @@ -1560,20 +1560,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-06-06 11:33:26" + "time": "2016-06-29 05:40:00" }, { "name": "symfony/routing", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "c780454838a1131adc756d737a4b4cc1d18f8c64" + "reference": "9038984bd9c05ab07280121e9e10f61a7231457b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/c780454838a1131adc756d737a4b4cc1d18f8c64", - "reference": "c780454838a1131adc756d737a4b4cc1d18f8c64", + "url": "https://api.github.com/repos/symfony/routing/zipball/9038984bd9c05ab07280121e9e10f61a7231457b", + "reference": "9038984bd9c05ab07280121e9e10f61a7231457b", "shasum": "" }, "require": { @@ -1635,20 +1635,20 @@ "uri", "url" ], - "time": "2016-05-30 06:58:27" + "time": "2016-06-29 05:40:00" }, { "name": "symfony/translation", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2b0aacaa613c0ec1ad8046f972d8abdcb19c1db7" + "reference": "6bf844e1ee3c820c012386c10427a5c67bbefec8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2b0aacaa613c0ec1ad8046f972d8abdcb19c1db7", - "reference": "2b0aacaa613c0ec1ad8046f972d8abdcb19c1db7", + "url": "https://api.github.com/repos/symfony/translation/zipball/6bf844e1ee3c820c012386c10427a5c67bbefec8", + "reference": "6bf844e1ee3c820c012386c10427a5c67bbefec8", "shasum": "" }, "require": { @@ -1699,20 +1699,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2016-06-06 11:33:26" + "time": "2016-06-29 05:40:00" }, { "name": "symfony/var-dumper", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "d8bb851da153d97abe7c2b71a65dee19f324bcf7" + "reference": "2f046e9a9d571f22cc8b26783564876713b06579" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d8bb851da153d97abe7c2b71a65dee19f324bcf7", - "reference": "d8bb851da153d97abe7c2b71a65dee19f324bcf7", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2f046e9a9d571f22cc8b26783564876713b06579", + "reference": "2f046e9a9d571f22cc8b26783564876713b06579", "shasum": "" }, "require": { @@ -1762,7 +1762,7 @@ "debug", "dump" ], - "time": "2016-05-24 10:03:10" + "time": "2016-06-29 05:40:00" }, { "name": "vlucas/phpdotenv", @@ -3383,16 +3383,16 @@ }, { "name": "symfony/css-selector", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "e8a66c51bf65f188c02f8120c0748b2291d3a2d0" + "reference": "b8999c1f33c224b2b66b38253f5e3a838d0d0115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/e8a66c51bf65f188c02f8120c0748b2291d3a2d0", - "reference": "e8a66c51bf65f188c02f8120c0748b2291d3a2d0", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b8999c1f33c224b2b66b38253f5e3a838d0d0115", + "reference": "b8999c1f33c224b2b66b38253f5e3a838d0d0115", "shasum": "" }, "require": { @@ -3432,20 +3432,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2016-06-06 11:33:26" + "time": "2016-06-29 05:40:00" }, { "name": "symfony/dom-crawler", - "version": "v3.0.7", + "version": "v3.0.8", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "49b588841225b205700e5122fa01911cabada857" + "reference": "62769e3409006b937bb333b29da8df9a8b262975" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/49b588841225b205700e5122fa01911cabada857", - "reference": "49b588841225b205700e5122fa01911cabada857", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/62769e3409006b937bb333b29da8df9a8b262975", + "reference": "62769e3409006b937bb333b29da8df9a8b262975", "shasum": "" }, "require": { @@ -3488,20 +3488,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2016-04-12 18:09:53" + "time": "2016-06-29 05:40:00" }, { "name": "symfony/yaml", - "version": "v3.1.1", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c5a7e7fc273c758b92b85dcb9c46149ccda89623" + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c5a7e7fc273c758b92b85dcb9c46149ccda89623", - "reference": "c5a7e7fc273c758b92b85dcb9c46149ccda89623", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2884c26ce4c1d61aebf423a8b912950fe7c764de", + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de", "shasum": "" }, "require": { @@ -3537,7 +3537,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-06-14 11:18:07" + "time": "2016-06-29 05:41:56" }, { "name": "way/generators", diff --git a/config/photo.php b/config/photo.php index 7506f2b..246f9f0 100644 --- a/config/photo.php +++ b/config/photo.php @@ -1,9 +1,8 @@ '/var/www/sites/co.dlcm.p/store', + 'dir'=>'/var/www/sites/co.dlcm.p/store/photos', 'import'=>[ 'accepted'=>['jpg'], - 'log'=>'/tmp/import.log', ], ]; diff --git a/config/video.php b/config/video.php new file mode 100644 index 0000000..6aa171c --- /dev/null +++ b/config/video.php @@ -0,0 +1,9 @@ +'/var/www/sites/co.dlcm.p/store/video', + 'import'=>[ + 'accepted'=>['m4v','mov','mp4','avi'], + 'log'=>'/tmp/import.log', + ], +]; diff --git a/database/migrations/2016_07_01_205600_create_videos_table.php b/database/migrations/2016_07_01_205600_create_videos_table.php new file mode 100644 index 0000000..019b752 --- /dev/null +++ b/database/migrations/2016_07_01_205600_create_videos_table.php @@ -0,0 +1,53 @@ +bigInteger('id', true); + $table->timestamps(); + $table->integer('date_created')->nullable(); + $table->string('filename', 128); + $table->string('signature', 64)->nullable(); + $table->string('make', 32)->nullable(); + $table->string('model', 32)->nullable(); + $table->string('type', 32)->nullable(); + $table->string('codec', 32)->nullable(); + $table->integer('audiochannels')->nullable(); + $table->string('channelmode', 16)->nullable(); + $table->float('samplerate', 10, 0)->nullable(); + $table->integer('height')->nullable(); + $table->integer('width')->nullable(); + $table->float('length')->nullable(); + $table->integer('orientation')->nullable(); + $table->float('gps_lat', 10, 0)->nullable(); + $table->float('gps_lon', 10, 0)->nullable(); + $table->float('gps_altitude', 10, 0)->nullable(); + $table->boolean('duplicate')->nullable(); + $table->boolean('remove')->nullable(); + $table->boolean('flag')->nullable(); + }); + } + + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('videos'); + } + +} diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 91b5987..0568a9a 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -47,8 +47,10 @@ + + + +@endsection diff --git a/resources/views/video/duplicates.blade.php b/resources/views/video/duplicates.blade.php new file mode 100644 index 0000000..53f756c --- /dev/null +++ b/resources/views/video/duplicates.blade.php @@ -0,0 +1,94 @@ +@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', +]; + +$form = [ +'duplicate', +'flag', +'remove', +]; + +function changed($k,$v,$l=0) +{ + static $changed = []; + + if (! isset($changed[$l][$k])) + $changed[$l][$k] = $v; + + return $changed[$l][$k] === $v; +} ?> + +@foreach ($videos as $video) +list_duplicate(TRUE); ?> +
+
+
+
+
+ Duplicate Video {{ $video->id }} +
+ +
{{ $videos->links() }}
+
+
+ + @foreach ($data as $k=>$v) + + + @foreach ($duplicates as $id) +where('id',$id)->first(); +switch ($v) : +case 'id': $x=$id; $y=sprintf('%s',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': $x=$y=$o->width; break; +case 'height': $x=$y=$o->height; break; +case 'make': $x=$y=$o->make; break; +case 'model': $x=$y=$o->model; break; +endswitch ?> + + @endforeach {{-- video --}} + + @endforeach {{-- data --}} + @foreach ($form as $v) + + + @foreach ($duplicates as $id) + where('id',$id)->first(); ?> + + @endforeach {{-- video --}} + + @endforeach {{-- form --}} +
{{ $k }}
{{ $v }}$v==1 ? 'checked="checked"' : '' }}>
+ @foreach ($duplicates as $id) + + @endforeach {{-- video --}} + + + {{ csrf_field() }} +
+
+
+
+
+
+@endforeach +@endsection diff --git a/resources/views/video/view.blade.php b/resources/views/video/view.blade.php new file mode 100644 index 0000000..2e7988f --- /dev/null +++ b/resources/views/video/view.blade.php @@ -0,0 +1,80 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
+ Video {{ $video->id }}remove) : ?> - PENDING DELETE +
+ +
+
+ +
+
    +
  • previous()) : ?>class="disabled"><<
  • +
  • next()) : ?>class="disabled">>>
  • +
+
+ +
+
+
+
Signature
{{ $video->signature(TRUE) }}
+
Filename
{{ $video->file_path(TRUE) }}
+ shouldMove()) : ?> +
NEW Filename
{{ $video->file_path(TRUE,TRUE) }}
+ +
Size
{{ $video->file_size() }}
+
Dimensions
{{ $video->width }} x {{ $video->height }}
+
Length
{{ $video->length }}
+
Type
{{ $video->type }}
+
Codec
{{ $video->codec }}
+
Audio Channels
{{ $video->audiochannels }}
+
Channels Mode
{{ $video->channelmode }}
+
Sample Rate
{{ $video->samplerate }}
+
+
Date Taken
{{ $video->date_taken() }}
+
Camera
{{ $video->make }}
+
Model
{{ $video->model }}
+
+
Location
+ gps() == 'UNKNOWN') : ?> + UNKNOWN + +
+ + + +
+
+
+ + remove) : ?> +
+ + + + + + {{ csrf_field() }} +
+
+
+
+
+
+@endsection