Added Video

This commit is contained in:
Deon George 2016-07-04 16:00:33 +10:00
parent ec4c7ae3d1
commit 3012004901
25 changed files with 1356 additions and 143 deletions

View File

@ -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');

View File

@ -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));
}
}

View File

@ -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;

View File

@ -0,0 +1,198 @@
<?php
namespace App\Console\Commands;
use Log;
use Illuminate\Console\Command;
use App\Model\Video;
use App\Model\Tag;
use App\Model\Person;
class VideoImport extends Command
{
use \App\Traits\Files;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'video:import
{--dir= : Directory to Parse}
{--file= : File to Import}
{--ignoredupe : Ignore duplicate files}
{--deletedupe : Delete duplicate files}
{--dumpid3 : Dump ID3 data}
{--people= : People to reference in video}
{--tags= : Add tag to video}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import videos into the database';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$files = $this->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));
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands;
use DB;
use Log;
use Illuminate\Console\Command;
use App\Model\Video;
class VideoMove extends Command
{
use \App\Traits\Files;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'video:move
{--file= : Video File}
{--id= : Video ID}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Moves Videos to their new location';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if ($this->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();
});
}
}

View File

@ -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,
];
/**

View File

@ -0,0 +1,107 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Model\Video;
use App\Jobs\VideoDelete;
class VideoController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->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');
}
}

View File

@ -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]+');;

60
app/Jobs/VideoDelete.php Normal file
View File

@ -0,0 +1,60 @@
<?php
namespace App\Jobs;
use Log;
use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Model\Video;
class VideoDelete extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
private $video;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Video $video)
{
$this->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();
}
}

39
app/Jobs/VideoMove.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace App\Jobs;
use Log;
use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Model\Video;
use Artisan;
class VideoMove extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
private $video;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Video $video)
{
$this->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]);
}
}

View File

@ -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));
}
/**

353
app/Model/Video.php Normal file
View File

@ -0,0 +1,353 @@
<?php
namespace App\Model;
use DB;
use Illuminate\Database\Eloquent\Model;
class Video extends Model
{
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');
}
/**
* 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');
}
}

View File

@ -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'));
}
});
}
/**

View File

@ -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;
}
}

132
composer.lock generated
View File

@ -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",

View File

@ -1,9 +1,8 @@
<?php
return [
'dir'=>'/var/www/sites/co.dlcm.p/store',
'dir'=>'/var/www/sites/co.dlcm.p/store/photos',
'import'=>[
'accepted'=>['jpg'],
'log'=>'/tmp/import.log',
],
];

9
config/video.php Normal file
View File

@ -0,0 +1,9 @@
<?php
return [
'dir'=>'/var/www/sites/co.dlcm.p/store/video',
'import'=>[
'accepted'=>['m4v','mov','mp4','avi'],
'log'=>'/tmp/import.log',
],
];

View File

@ -0,0 +1,53 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateVideosTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('videos', function(Blueprint $table)
{
$table->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');
}
}

View File

@ -47,8 +47,10 @@
<div class="collapse navbar-collapse" id="app-navbar-collapse">
<!-- Left Side Of Navbar -->
<ul class="nav navbar-nav">
<li><a href="{{ url('/deletes') }}">Deletes</a></li>
<li><a href="{{ url('/duplicates') }}">Duplicates</a></li>
<li><a href="{{ url('/p/deletes') }}">Photo Deletes</a></li>
<li><a href="{{ url('/p/duplicates') }}">Photo Duplicates</a></li>
<li><a href="{{ url('/v/deletes') }}">Video Deletes</a></li>
<li><a href="{{ url('/v/duplicates') }}">Video Duplicates</a></li>
</ul>
<!-- Right Side Of Navbar -->

View File

@ -32,7 +32,7 @@
<div class="text-center">{{ $photos->links() }}</div>
<div class="panel-body">
@if ($photos->count())
<form action="{{ url('/deletes') }}" method="POST">
<form action="{{ url('/p/deletes') }}" method="POST">
<table class="table table-striped table-condensed table-hover">
@foreach ($data as $k=>$v)
<tr>
@ -40,8 +40,8 @@
@foreach ($photos as $o)
<?php
switch ($v) :
case 'id': $x=$o->id; $y=sprintf('<input type="hidden" name="photo[]" value="%s"><a href="%s">%s</a>',$o->id,url('/info/'.$o->id),$o->id); break;
case 'thumbnail': $x=md5($o->thumbnail); $y=sprintf('<a href="%s"><img src="%s" width="200px"></a>',url('/view/'.$o->id),url('/thumbnail/'.$o->id)); break;
case 'id': $x=$o->id; $y=sprintf('<input type="hidden" name="photo[]" value="%s"><a href="%s">%s</a>',$o->id,url('/p/info/'.$o->id),$o->id); break;
case 'thumbnail': $x=md5($o->thumbnail); $y=sprintf('<a href="%s"><img src="%s" width="200px"></a>',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;
@ -53,7 +53,7 @@ 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('<a href="%s">%s</a>',url('/info/'.$id),$id); break;
case 'duplicates': $x=$y='';foreach($o->list_duplicate() as $id) $y.=($y ? '|' : '').sprintf('<a href="%s">%s</a>',url('/p/info/'.$id),$id); break;
case 'flag':
case 'remove':
case 'duplicate': $y=sprintf('<input type="checkbox" name="%s[%s]" value="1"%s>',$v,$o->id,$o->{$v} ? ' checked="checked"' : ''); break;

View File

@ -46,7 +46,7 @@ function changed($k,$v,$l=0)
<div class="text-center">{{ $photos->links() }}</div>
<div class="panel-body">
<form action="{{ url('/duplicates') }}" method="POST">
<form action="{{ url('/p/duplicates') }}" method="POST">
<table class="table table-striped table-condensed table-hover">
@foreach ($data as $k=>$v)
<tr>
@ -55,8 +55,8 @@ function changed($k,$v,$l=0)
<?php
$o = (new \App\Model\Photo())->where('id',$id)->first();
switch ($v) :
case 'id': $x=$id; $y=sprintf('<a href="%s">%s</a>',url('/info/'.$o->id),$o->id); break;
case 'thumbnail': $x=md5($o->thumbnail); $y=sprintf('<a href="%s"><img src="%s" width="200px"></a>',url('/view/'.$o->id),url('/thumbnail/'.$o->id)); break;
case 'id': $x=$id; $y=sprintf('<a href="%s">%s</a>',url('/p/info/'.$o->id),$o->id); break;
case 'thumbnail': $x=md5($o->thumbnail); $y=sprintf('<a href="%s"><img src="%s" width="200px"></a>',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;

View File

@ -11,11 +11,11 @@
<div class="panel-body">
<div class="col-md-2">
<a href="{{ url('/view/'.$photo->id) }}"><img src="{{ url('/thumbnail/'.$photo->id) }}" width="200px"></a>
<a href="{{ url('/p/view/'.$photo->id) }}"><img src="{{ url('/p/thumbnail/'.$photo->id) }}" width="200px"></a>
<div class="text-center">
<ul class="pagination">
<li <?php if (! $x = $photo->previous()) : ?>class="disabled"<?php endif ?>><a href="{{ $x ? url('/info/'.$x->id) : '#' }}">&lt;&lt;</a></li>
<li <?php if (! $x = $photo->next()) : ?>class="disabled"<?php endif ?>><a href="{{ $x ? url('/info/'.$x->id) : '#' }}">&gt;&gt;</a></li>
<li <?php if (! $x = $photo->previous()) : ?>class="disabled"<?php endif ?>><a href="{{ $x ? url('/p/info/'.$x->id) : '#' }}">&lt;&lt;</a></li>
<li <?php if (! $x = $photo->next()) : ?>class="disabled"<?php endif ?>><a href="{{ $x ? url('/p/info/'.$x->id) : '#' }}">&gt;&gt;</a></li>
</ul>
</div>
@ -66,10 +66,10 @@
</div>
<?php if ($photo->remove) : ?>
<form action="{{ url('/undelete/'.$photo->id) }}" method="POST">
<form action="{{ url('/p/undelete/'.$photo->id) }}" method="POST">
<button class="btn btn-default">Undelete</button>
<?php else : ?>
<form action="{{ url('/delete/'.$photo->id) }}" method="POST">
<form action="{{ url('/p/delete/'.$photo->id) }}" method="POST">
<button class="btn btn-default">Delete</button>
<?php endif ?>
{{ csrf_field() }}

View File

@ -0,0 +1,75 @@
@extends('layouts.app')
@section('content')
<?php $data = [
'ID'=>'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',
];?>
<div class="container">
<div class="row">
<div class="col-md-11 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
Permanently Delete Photos
</div>
<div class="text-center">{{ $videos->links() }}</div>
<div class="panel-body">
@if ($videos->count())
<form action="{{ url('/v/deletes') }}" method="POST">
<table class="table table-striped table-condensed table-hover">
@foreach ($data as $k=>$v)
<tr>
<th>{{ $k }}</th>
@foreach ($videos as $o)
<?php
switch ($v) :
case 'id': $x=$o->id; $y=sprintf('<input type="hidden" name="video[]" value="%s"><a href="%s">%s</a>',$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('<a href="%s">%s</a>',url('/v/info/'.$id),$id); break;
case 'flag':
case 'remove':
case 'duplicate': $y=sprintf('<input type="checkbox" name="%s[%s]" value="1"%s>',$v,$o->id,$o->{$v} ? ' checked="checked"' : ''); break;
endswitch ?>
<td><?php echo $y; ?></td>
@endforeach {{-- video --}}
</tr>
@endforeach {{-- data --}}
</table>
<input type="hidden" name="pagenext" value="{{ $videos->hasMorePages() ? $videos->currentPage()+1 : NULL }}">
<button class="btn btn-default">Confirm Delete</button>
{{ csrf_field() }}
</form>
@else
NONE!
@endif
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,94 @@
@extends('layouts.app')
@section('content')
<?php $data = [
'ID'=>'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)
<?php $duplicates = $video->list_duplicate(TRUE); ?>
<div class="container">
<div class="row">
<div class="col-md-11 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
Duplicate Video {{ $video->id }}
</div>
<div class="text-center">{{ $videos->links() }}</div>
<div class="panel-body">
<form action="{{ url('/v/duplicates') }}" method="POST">
<table class="table table-striped table-condensed table-hover">
@foreach ($data as $k=>$v)
<tr>
<th>{{ $k }}</th>
@foreach ($duplicates as $id)
<?php
$o = (new \App\Model\Video())->where('id',$id)->first();
switch ($v) :
case 'id': $x=$id; $y=sprintf('<a href="%s">%s</a>',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 ?>
<td class="{{ changed($v,$x) ? '' : 'danger' }}"><?php echo $y; ?></td>
@endforeach {{-- video --}}
</tr>
@endforeach {{-- data --}}
@foreach ($form as $v)
<tr>
<th>{{ $v }}</th>
@foreach ($duplicates as $id)
<?php $o = (new \App\Model\Video())->where('id',$id)->first(); ?>
<td><input type="checkbox" name="{{ sprintf('%s[%s]',$v,$o->id) }}" value="1" {{ $o->$v==1 ? 'checked="checked"' : '' }}></td>
@endforeach {{-- video --}}
</tr>
@endforeach {{-- form --}}
</table>
@foreach ($duplicates as $id)
<input type="hidden" name="video[]" value="{{ $id }}">
@endforeach {{-- video --}}
<input type="hidden" name="page" value="{{ $videos->currentPage() }}">
<button class="btn btn-default">Update</button>
{{ csrf_field() }}
</form>
</div>
</div>
</div>
</div>
</div>
@endforeach
@endsection

View File

@ -0,0 +1,80 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
Video {{ $video->id }}<?php if ($video->remove) : ?> - <strong>PENDING DELETE</strong><?php endif?>
</div>
<div class="panel-body">
<div class="col-md-2">
<!--<a href="{{ url('/v/view/'.$video->id) }}"><img src="{{ url('/v/thumbnail/'.$video->id) }}" width="200px"></a>-->
<div class="text-center">
<ul class="pagination">
<li <?php if (! $x = $video->previous()) : ?>class="disabled"<?php endif ?>><a href="{{ $x ? url('/v/info/'.$x->id) : '#' }}">&lt;&lt;</a></li>
<li <?php if (! $x = $video->next()) : ?>class="disabled"<?php endif ?>><a href="{{ $x ? url('/v/info/'.$x->id) : '#' }}">&gt;&gt;</a></li>
</ul>
</div>
</div>
<div class="col-md-10">
<div class="dl-horizontal">
<dt>Signature</dt><dd>{{ $video->signature(TRUE) }}</dd>
<dt>Filename</dt><dd>{{ $video->file_path(TRUE) }}<dd>
<?php if ($video->shouldMove()) : ?>
<dt>NEW Filename</dt><dd>{{ $video->file_path(TRUE,TRUE) }}<dd>
<?php endif ?>
<dt>Size</dt><dd>{{ $video->file_size() }}<dd>
<dt>Dimensions</dt><dd>{{ $video->width }} x {{ $video->height }}<dd>
<dt>Length</dt><dd>{{ $video->length }}<dd>
<dt>Type</dt><dd>{{ $video->type }}<dd>
<dt>Codec</dt><dd>{{ $video->codec }}<dd>
<dt>Audio Channels</dt><dd>{{ $video->audiochannels }}<dd>
<dt>Channels Mode</dt><dd>{{ $video->channelmode }}<dd>
<dt>Sample Rate</dt><dd>{{ $video->samplerate }}<dd>
<br/>
<dt>Date Taken</dt><dd>{{ $video->date_taken() }}<dd>
<dt>Camera</dt><dd>{{ $video->make }}<dd>
<dt>Model</dt><dd>{{ $video->model }}<dd>
<br/>
<dt>Location</dt><dd>
<?php if ($video->gps() == 'UNKNOWN') : ?>
UNKNOWN
<?php else : ?>
<div id="map" style="width: 400px; height: 300px"></div>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var myLatLng = {lat: {{ $video->gps_lat }}, lng: {{ $video->gps_lon }}};
var map = new google.maps.Map(document.getElementById("map"), {
zoom: 16,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var marker = new google.maps.Marker({
map: map,
position: myLatLng,
});
</script>
<?php endif ?>
</dd>
</div>
</div>
<?php if ($video->remove) : ?>
<form action="{{ url('/v/undelete/'.$video->id) }}" method="POST">
<button class="btn btn-default">Undelete</button>
<?php else : ?>
<form action="{{ url('/v/delete/'.$video->id) }}" method="POST">
<button class="btn btn-default">Delete</button>
<?php endif ?>
{{ csrf_field() }}
</form>
</div>
</div>
</div>
</div>
</div>
@endsection