Enabled duplicate review

This commit is contained in:
Deon George 2016-06-30 09:32:57 +10:00
parent c5f0bfffd0
commit e1c5b1e413
6 changed files with 191 additions and 23 deletions

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Http\Request;
use App\Http\Requests; use App\Http\Requests;
use App\Model\Photo; use App\Model\Photo;
@ -32,6 +33,31 @@ class PhotoController extends Controller
return redirect()->action('PhotoController@info',[$id]); return redirect()->action('PhotoController@info',[$id]);
} }
public function duplicates($id=NULL)
{
return view('photo.duplicates', ['photos'=> Photo::notRemove()->where('duplicate',1)->paginate(1)]);
}
public function duplicatesUpdate(Request $request)
{
foreach ($request->input('photo') as $id)
{
$po = Photo::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('PhotoController@duplicates','?page='.$request->input('page'));
}
public function info($id) public function info($id)
{ {
return view('photo.view', ['photo'=> Photo::findOrFail($id)]); return view('photo.view', ['photo'=> Photo::findOrFail($id)]);

View File

@ -19,8 +19,10 @@ Route::get('/', function () {
Route::auth(); Route::auth();
Route::get('/duplicates/{id?}', 'PhotoController@duplicates')->where('id', '[0-9]+');;
Route::get('/info/{id}', 'PhotoController@info')->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('/thumbnail/{id}', 'PhotoController@thumbnail')->where('id', '[0-9]+');;
Route::get('/view/{id}', 'PhotoController@view')->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('/delete/{id}', 'PhotoController@delete')->where('id', '[0-9]+');;
Route::post('/duplicates', 'PhotoController@duplicatesUpdate')->where('id', '[0-9]+');;
Route::post('/undelete/{id}', 'PhotoController@undelete')->where('id', '[0-9]+');; Route::post('/undelete/{id}', 'PhotoController@undelete')->where('id', '[0-9]+');;

View File

@ -26,7 +26,8 @@ class Photo extends Model
*/ */
public function scopeNotRemove($query) public function scopeNotRemove($query)
{ {
return $query->where(function($query) { return $query->where(function($query)
{
$query->where('remove','!=',TRUE) $query->where('remove','!=',TRUE)
->orWhere('remove','=',NULL); ->orWhere('remove','=',NULL);
}); });
@ -39,20 +40,41 @@ class Photo extends Model
*/ */
public function scopeNotDuplicate($query) public function scopeNotDuplicate($query)
{ {
return $query->where(function($query) { return $query->where(function($query)
{
$query->where('duplicate','!=',TRUE) $query->where('duplicate','!=',TRUE)
->orWhere('duplicate','=',NULL); ->orWhere('duplicate','=',NULL);
}); });
} }
public function date_taken() { public function date_taken()
{
return $this->date_taken ? (date('Y-m-d H:i:s',$this->date_taken).($this->subsectime ? '.'.$this->subsectime : '')) : 'UNKNOWN'; return $this->date_taken ? (date('Y-m-d H:i:s',$this->date_taken).($this->subsectime ? '.'.$this->subsectime : '')) : 'UNKNOWN';
} }
/**
* Return the date of the file
*/
public function file_date($type,$format=FALSE)
{
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 * Determine the new name for the image
*/ */
public function file_path($short=FALSE,$new=FALSE) { public function file_path($short=FALSE,$new=FALSE)
{
$file = $this->filename; $file = $this->filename;
if ($new) if ($new)
@ -66,21 +88,24 @@ class Photo extends Model
/** /**
* Calculate a file path ID based on the id of the file * Calculate a file path ID based on the id of the file
*/ */
public function file_path_id($sep=3,$depth=9) { public function file_path_id($sep=3,$depth=9)
{
return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/'); return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/');
} }
/** /**
* Display the GPS coordinates * Display the GPS coordinates
*/ */
public function gps() { public function gps()
{
return ($this->gps_lat AND $this->gps_lon) ? sprintf('%s/%s',$this->gps_lat,$this->gps_lon) : 'UNKNOWN'; return ($this->gps_lat AND $this->gps_lon) ? sprintf('%s/%s',$this->gps_lat,$this->gps_lon) : 'UNKNOWN';
} }
/** /**
* Return the image, rotated, minus exif data * Return the image, rotated, minus exif data
*/ */
public function image() { public function image()
{
$imo = $this->io(); $imo = $this->io();
if (array_key_exists('exif',$imo->getImageProfiles())) if (array_key_exists('exif',$imo->getImageProfiles()))
@ -91,11 +116,17 @@ class Photo extends Model
return $imo->getImageBlob(); return $imo->getImageBlob();
} }
public function info()
{
return $this->io() ? $this->io()->getImageProperties() : [];
}
/** /**
* Return an Imagick object or attribute * Return an Imagick object or attribute
* *
*/ */
public function io($attr=NULL) { protected function io($attr=NULL)
{
if (is_null($this->_io)) if (is_null($this->_io))
$this->_io = new \Imagick($this->file_path()); $this->_io = new \Imagick($this->file_path());
@ -105,11 +136,13 @@ class Photo extends Model
/** /**
* Calculate the GPS coordinates * Calculate the GPS coordinates
*/ */
public static function latlon(array $coordinate,$hemisphere) { public static function latlon(array $coordinate,$hemisphere)
{
if (! $coordinate OR ! $hemisphere) if (! $coordinate OR ! $hemisphere)
return NULL; return NULL;
for ($i=0; $i<3; $i++) { for ($i=0; $i<3; $i++)
{
$part = explode('/', $coordinate[$i]); $part = explode('/', $coordinate[$i]);
if (count($part) == 1) if (count($part) == 1)
@ -134,7 +167,8 @@ class Photo extends Model
* *
* useID boolean Determine if the path is based on the the ID or date * useID boolean Determine if the path is based on the the ID or date
*/ */
public function moveable() { public function moveable()
{
// If the source and target are the same, we dont need to move it // If the source and target are the same, we dont need to move it
if ($this->file_path() == $this->file_path(FALSE,TRUE)) if ($this->file_path() == $this->file_path(FALSE,TRUE))
return FALSE; return FALSE;
@ -212,7 +246,7 @@ class Photo extends Model
*/ */
public function signature($short=FALSE) public function signature($short=FALSE)
{ {
return $short ? static::signaturetrim($this->io()->getImageSignature()) : $this->io()->getImageSignature(); return $short ? static::signaturetrim($this->signature) : $this->signature;
} }
public static function signaturetrim($signature,$chars=6) public static function signaturetrim($signature,$chars=6)
@ -251,32 +285,38 @@ class Photo extends Model
/** /**
* Return the extension of the image * Return the extension of the image
*/ */
public function type($mime=FALSE) { 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 ? 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 * Find duplicate images based on some attributes of the current image
*/ */
public function list_duplicate() { public function list_duplicate($includeme=FALSE)
{
$po = DB::table('photo'); $po = DB::table('photo');
if ($this->id) if ($this->id AND ! $includeme)
$po->where('id','!=',$this->id); $po->where('id','!=',$this->id);
// Ignore photo's pending removal. // Ignore photo's pending removal.
$po->where(function($query) { $po->where(function($query)
{
$query->where('remove','!=',TRUE) $query->where('remove','!=',TRUE)
->orWhere('remove','=',NULL); ->orWhere('remove','=',NULL);
}); });
// Where the signature is the same // Where the signature is the same
$po->where(function($query) { $po->where(function($query)
{
$query->where('signature','=',$this->signature); $query->where('signature','=',$this->signature);
// Or they have the same time taken with the same camera // Or they have the same time taken with the same camera
if ($this->date_taken AND ($this->model OR $this->make)) { if ($this->date_taken AND ($this->model OR $this->make))
$query->orWhere(function($query) { {
$query->orWhere(function($query)
{
$query->where('date_taken','=',$this->date_taken ? $this->date_taken : NULL); $query->where('date_taken','=',$this->date_taken ? $this->date_taken : NULL);
$query->where('subsectime','=',$this->subsectime ? $this->subsectime : NULL); $query->where('subsectime','=',$this->subsectime ? $this->subsectime : NULL);
@ -290,6 +330,6 @@ class Photo extends Model
} }
}); });
return $po; return $po->pluck('id');
} }
} }

View File

@ -47,7 +47,7 @@
<div class="collapse navbar-collapse" id="app-navbar-collapse"> <div class="collapse navbar-collapse" id="app-navbar-collapse">
<!-- Left Side Of Navbar --> <!-- Left Side Of Navbar -->
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a href="{{ url('/home') }}">Home</a></li> <li><a href="{{ url('/duplicates') }}">Duplicates</a></li>
</ul> </ul>
<!-- Right Side Of Navbar --> <!-- Right Side Of Navbar -->

View File

@ -0,0 +1,100 @@
@extends('layouts.app')
@section('content')
<?php $data = [
'ID'=>'id',
'Thumbnail'=>'thumbnail',
'Signature'=>'signature',
'Date Taken'=>'datetaken',
'File Created'=>'created',
'File Modified'=>'modified',
'Filename'=>'filepath',
'Filesize'=>'filesize',
'Width'=>'width',
'Height'=>'height',
'Orientation'=>'orientation',
'Make'=>'make',
'Model'=>'model',
'Exif Diff'=>'exif',
];
$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 ($photos as $photo)
<?php $duplicates = $photo->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 Photo {{ $photo->id }}
</div>
<div class="text-center">{{ $photos->links() }}</div>
<div class="panel-body">
<form action="{{ url('/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\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 'signature': $x=$y=$o->signature(TRUE); break;
case 'datetaken': $x=$y=$o->date_taken(); break;
case 'created': $x=$y=$o->file_date('c',TRUE); break;
case 'modified': $x=$y=$o->file_date('m',TRUE); break;
case 'filepath': $x=$y=$o->file_path(TRUE); break;
case 'filesize': $x=$y=$o->size(); break;
case 'width': $x=$y=$o->width; break;
case 'height': $x=$y=$o->height; break;
case 'orientation': $x=$y=$o->orientation; break;
case 'make': $x=$y=$o->make; break;
case 'model': $x=$y=$o->model; break;
case 'exif': $y='<table class="table table-striped table-condensed">'; foreach ($o->info() as $a => $b) $y.=sprintf('<tr class="%s"><th>%s<><td>%s<td></tr>',(changed($a,$b,1) ? '' : 'warning'),$a,$b); $y.='</table>';$x=md5($y); break;
endswitch ?>
<td class="{{ changed($v,$x) ? '' : 'danger' }}"><?php echo $y; ?></td>
@endforeach {{-- photo --}}
</tr>
@endforeach {{-- data --}}
@foreach ($form as $v)
<tr>
<th>{{ $v }}</th>
@foreach ($duplicates as $id)
<?php $o = (new \App\Model\Photo())->where('id',$id)->first(); ?>
<td><input type="checkbox" name="{{ sprintf('%s[%s]',$v,$o->id) }}" value="1" {{ $o->$v==1 ? 'checked="checked"' : '' }}></td>
@endforeach {{-- photo --}}
</tr>
@endforeach {{-- form --}}
</table>
@foreach ($duplicates as $id)
<input type="hidden" name="photo[]" value="{{ $id }}">
@endforeach {{-- photo --}}
<input type="hidden" name="page" value="{{ $photos->currentPage() }}">
<button class="btn btn-default">Update</button>
{{ csrf_field() }}
</form>
</div>
</div>
</div>
</div>
</div>
@endforeach
@endsection

View File

@ -28,6 +28,7 @@
<dt>NEW Filename</dt><dd>{{ $photo->file_path(TRUE,TRUE) }}<dd> <dt>NEW Filename</dt><dd>{{ $photo->file_path(TRUE,TRUE) }}<dd>
<?php endif ?> <?php endif ?>
<dt>Size</dt><dd>{{ $photo->size() }}<dd> <dt>Size</dt><dd>{{ $photo->size() }}<dd>
<dt>Dimensions</dt><dd>{{ $photo->width }} x {{ $photo->height }} @ {{ $photo->orientation }}<dd>
<br/> <br/>
<dt>Date Taken</dt><dd>{{ $photo->date_taken() }}<dd> <dt>Date Taken</dt><dd>{{ $photo->date_taken() }}<dd>
<dt>Camera</dt><dd>{{ $photo->make }}<dd> <dt>Camera</dt><dd>{{ $photo->make }}<dd>
@ -56,8 +57,7 @@
<br/> <br/>
<dt>Exif Data</dt><dd> <dt>Exif Data</dt><dd>
<table> <table>
<?php foreach ($photo->io()->getImageProperties() as $k => $v) : ?> <?php foreach ($photo->info() as $k => $v) : ?>
<?php if (in_array($k,['signature'])) continue ?>
<tr><th>{{ $k }}<><td>{{ $v }}<td></tr> <tr><th>{{ $k }}<><td>{{ $v }}<td></tr>
<?php endforeach ?> <?php endforeach ?>
</table> </table>