Enabled duplicate review
This commit is contained in:
parent
c5f0bfffd0
commit
e1c5b1e413
@ -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)]);
|
||||||
|
@ -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]+');;
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 -->
|
||||||
|
100
resources/views/photo/duplicates.blade.php
Normal file
100
resources/views/photo/duplicates.blade.php
Normal 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
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user