Compare commits

..

10 Commits

148 changed files with 11141 additions and 7143 deletions

View File

@ -13,3 +13,6 @@ trim_trailing_whitespace = false
[*.{yml,yaml}] [*.{yml,yaml}]
indent_size = 2 indent_size = 2
[docker-compose.yml]
indent_size = 4

View File

@ -1,22 +1,25 @@
APP_ADMIN=
APP_NAME=Laravel APP_NAME=Laravel
APP_NAME_HTML_LONG="<b>Laravel</b>Application" APP_NAME_HTML_LONG="<b>Laravel</b>Application"
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=false
APP_TIMEZONE=Australia/Melbourne
APP_URL=http://localhost APP_URL=http://localhost
LOG_CHANNEL=stack LOG_CHANNEL=daily
DB_CONNECTION=mysql DB_CONNECTION=pgsql
DB_HOST=database DB_HOST=postgres
DB_PORT=3306 DB_PORT=5432
DB_DATABASE=database DB_DATABASE=photo
DB_USERNAME=web DB_USERNAME=photo
DB_PASSWORD= DB_PASSWORD=
#DB_SCHEMA=photo
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
CACHE_DRIVER=file CACHE_STORE=file
QUEUE_CONNECTION=database QUEUE_DRIVER=database
SESSION_DRIVER=file SESSION_DRIVER=file
SESSION_LIFETIME=120 SESSION_LIFETIME=120
@ -24,9 +27,9 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_DRIVER=smtp MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io MAIL_HOST=
MAIL_PORT=2525 MAIL_PORT=
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null

View File

@ -0,0 +1,35 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class PostgresBytea implements CastsAttributes
{
/**
* Cast the given value.
*
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
// For stream resources, we need to fseek in case we've already read it.
if (is_resource($value)) {
rewind($value);
$value = stream_get_contents($value);
}
return hex2bin($value);
}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
return bin2hex($value);
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Jobs\{PhotoDelete,VideoDelete};
use App\Traits\Type;
class CatalogAutoDelete extends Command
{
use DispatchesJobs,Type;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'catalog:autodelete {type : Photo | Video }';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Auto Delete Catalog Items';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$class = $this->getModelType($this->argument('type'));
$class::where('remove',1)->each(function($o) {
foreach ($o->myduplicates()->get() as $oo) {
if (! $oo->signature OR ! $oo->file_signature)
continue;
if ($oo->signature == $o->signature AND $oo->file_signature == $o->file_signature) {
$this->info(sprintf('Removing: %s (%s)',$o->id,$o->filename));
// Dispatch Job to move file.
switch (strtolower($this->argument('type'))) {
case 'photo':
$this->dispatch((new PhotoDelete($o))->onQueue('delete'));
break;
case 'video':
$this->dispatch((new VideoDelete($o))->onQueue('delete'));
break;
default:
$this->error('Dont know how to handle: ',$this->argument('type'));
}
}
}
});
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Traits\Type;
class CatalogDump extends Command
{
use Type;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'catalog:dump {type : Photo | Video } {id : Photo ID}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Scan Photo for metadata';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$class = $this->getModelType($this->argument('type'));
$o = $class::findOrFail($this->argument('id'));
if (! $o->isReadable()) {
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
exit;
}
dump($o->properties());
}
}

View File

@ -3,27 +3,29 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Jobs\{PhotoMove,VideoMove}; use App\Jobs\CatalogMove as Job;
use App\Traits\Type;
class CatalogMove extends Command class CatalogMove extends Command
{ {
use DispatchesJobs; use Type;
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'catalog:move {type : Photo | Video }'; protected $signature = 'catalog:move'
.' {type : Photo | Video }'
.' {id : Photo ID}';
/** /**
* The console command description. * The console command description.
* *
* @var string * @var string
*/ */
protected $description = 'Trigger Moves'; protected $description = 'Move Photo/Video based on their meta data';
/** /**
* Execute the console command. * Execute the console command.
@ -32,35 +34,10 @@ class CatalogMove extends Command
*/ */
public function handle() public function handle()
{ {
$class = 'App\Models\\'.$this->argument('type'); $class = $this->getModelType($this->argument('type'));
if (! class_exists($class)) $o = $class::findOrFail($this->argument('id'));
abort(500,sprintf('No class [%s]',$this->argument('type')));
foreach ($class::where('filename','LIKE','%INCOMING%')->get() as $o) { return Job::dispatchSync($o);
// Catch any files that are already there.
if ($o->moveable() === 1) {
$o->duplicate = TRUE;
$o->save();
continue;
}
$x = NULL;
if (! $o->scanned OR $o->duplicate OR $o->remove OR ($x=$o->moveable()) !== TRUE) {
$this->warn(sprintf('Ignoring (%s)[%s]... [%s]',$o->id,$o->file_path(),$x));
continue;
}
switch (strtolower($this->argument('type'))) {
case 'photo':
$this->dispatch((new PhotoMove($o))->onQueue('move'));
break;
case 'video':
$this->dispatch((new VideoMove($o))->onQueue('move'));
break;
default:
$this->error('Dont know how to handle: ',$this->argument('type'));
}
}
} }
} }

View File

@ -4,6 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Jobs\CatalogScan as Job;
use App\Traits\Type; use App\Traits\Type;
class CatalogScan extends Command class CatalogScan extends Command
@ -15,17 +16,17 @@ class CatalogScan extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'catalog:scan'. protected $signature = 'catalog:scan'
' {type : Photo | Video }'. .' {type : Photo | Video }'
' {id : Photo ID}'. .' {id : Photo ID}'
' {--dirty : Show Dirty}'; .' {--dirty : Show Dirty}';
/** /**
* The console command description. * The console command description.
* *
* @var string * @var string
*/ */
protected $description = 'Scan Photo for metadata'; protected $description = 'Scan Photo/Video for metadata';
/** /**
* Execute the console command. * Execute the console command.
@ -38,54 +39,6 @@ class CatalogScan extends Command
$o = $class::findOrFail($this->argument('id')); $o = $class::findOrFail($this->argument('id'));
if (! $o->isReadable()) { return Job::dispatchSync($o,$this->option('dirty'));
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
return;
}
$o->setDateCreated();
$o->setSubSecTime();
$o->setSignature();
$o->setMakeModel();
$o->setLocation();
$o->setHeightWidth();
$o->setThumbnail();
// If this is a duplicate
$x = $o->myduplicates()->get();
if (count($x)) {
foreach ($x as $oo) {
// And that photo is not marked as a duplicate
if (! $oo->duplicate) {
$o->duplicate = '1';
$this->warn(sprintf('Image [%s] marked as a duplicate',$o->filename));
// If the file signature also matches, we'll mark it for deletion
if ($oo->file_signature AND $o->file_signature == $oo->file_signature) {
$this->warn(sprintf('Image [%s] marked for deletion',$o->filename));
$o->remove = '1';
}
break;
}
}
}
$o->scanned = '1';
if ($o->getDirty()) {
$this->warn(sprintf('Image [%s] metadata changed',$o->filename));
if ($this->option('dirty'))
dump(['id'=>$o->id,'data'=>$o->getDirty()]);
}
// If the file signature changed, abort the update.
if ($o->getOriginal('file_signature') AND $o->wasChanged('file_signature')) {
dump(['old'=>$o->getOriginal('file_signature'),'new'=>$o->file_signature]);
abort(500,'File Signature Changed?');
}
$o->save();
} }
} }

View File

@ -2,9 +2,12 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use App\Jobs\CatalogScan; use App\Jobs\CatalogScan;
use App\Traits\Type; use App\Traits\Type;
@ -13,14 +16,18 @@ class CatalogScanAll extends Command
{ {
use DispatchesJobs,Type; use DispatchesJobs,Type;
private int|bool $depth = true;
protected const chunk_size = 5;
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'catalog:scanall'. protected $signature = 'catalog:scanall'
' {type : Photo | Video }'. .' {type : Photo | Video }'
' {--scanned : Rescan Scanned Photos}'; .' {--i|ignore : Ignore missing files}'
.' {--s|scan : Force Rescan of all files }';
/** /**
* The console command description. * The console command description.
@ -30,40 +37,128 @@ class CatalogScanAll extends Command
protected $description = '(re)Scan Media'; protected $description = '(re)Scan Media';
/** /**
* Create a new command instance. * Execute the console command.
* *
* @return void * @return int
*/ */
public function __construct() public function handle(): int
{ {
parent::__construct(); $started = Carbon::now();
$class = $this->getModelType($this->argument('type'));
Log::info('Scanning disk: '.Storage::disk('nas')->path(''));
$c = 0;
// Scan files in dir, and make sure file lives in DB, (touch it if it does), otherwise create it
foreach (Storage::disk('nas')->directories($class::dir_prefix()) as $dir) {
Log::info(sprintf(' - DIR: %s',$dir));
// Take x files at a time and check the DB
foreach ($this->files($dir,$class::config,$class::dir_prefix())->chunk(self::chunk_size) as $chunk) {
$list = $class::whereIn('filename',$chunk)->get();
// If there is a new file found it wont be in the DB
if ($list->count() !== self::chunk_size)
foreach ($chunk->diff($list->pluck('filename')) as $file) {
Log::info(sprintf('Found new file [%s] - queueing scan',$file));
$o = new $class;
$o->filename = $file;
$o->file_signature = $o->getObjectOriginal('file_signature');
$o->save();
CatalogScan::dispatch($o)
->onQueue('scan');
$c++;
}
foreach ($list as $o) {
// Check the details are valid
if ($o->file_signature === $o->getObjectOriginal('file_signature')) {
// For sanity, we'll check a couple of other attrs
if (($o->width !== $o->getObjectOriginal('width')) || ($o->height !== $o->getObjectOriginal('height')))
Log::alert(sprintf('Dimensions [%s] (%s x %s) mismatch for [%s]',
$o->dimensions,
$o->getObjectOriginal('width'),
$o->getObjectOriginal('height'),
$o->file_name(FALSE)));
} else {
Log::alert(sprintf('File Signature [%s] doesnt match [%s] for [%s]',
$o->getObjectOriginal('file_signature'),
$o->file_signature,
$o->file_name(FALSE)));
}
if ($o->signature !== $o->getObjectOriginal('signature')) {
Log::notice(sprintf('Updating image signature for [%s] to [%s] was [%s]',$o->filename,$o->signature,$o->getObjectOriginal('signature')));
$o->signature = $o->getObjectOriginal('signature');
}
if ($o->isDirty())
$o->save();
else
$o->touch();
if ($this->option('scan')) {
Log::info(sprintf('Forcing re-scan of [%s] - queued',$o->filename));
CatalogScan::dispatch($o)
->onQueue('scan');
}
$c++;
}
}
break;
}
Log::info('Checking for missing files');
// Find DB records before $started, check they exist (they shouldnt), and delete if not
if (! $this->option('ignore'))
foreach ($class::select(['id','filename'])->where('updated_at','<',$started)->cursor() as $o)
Log::error(sprintf('It appears that file [%s] is missing (%d)',$o->filename,$o->id));
Log::info(sprintf('Processed [%s]',$c));
return self::SUCCESS;
} }
/** /**
* Execute the console command. * Recursively find files that we should catalog
* *
* @return mixed * @param string $dir Directory to get files from
* @param string $type Configuration key to refer to config())
* @param string $prefix Remove the prefix from the filename
* @return Collection
*/ */
public function handle() public function files(string $dir,string $type,string $prefix): Collection
{ {
$class = $this->getModelType($this->argument('type')); $files = collect(Storage::disk('nas')->files($dir))
->map(fn($item)=>preg_replace('#^'.$prefix.'#','',$item))
->filter(function($item) use ($type) {
return ((! ($x=strrpos($item,'.')))
|| (! in_array(strtolower(substr($item,$x+1)),config($type.'.import.accepted'))))
? NULL
: $item;
});
if ($this->option('scanned')) { if (! $this->depth)
$class::whereNotNull('scanned') return $files;
->update(['scanned'=>NULL]);
}
$c = 0; if (is_numeric($this->depth))
$class::NotScanned()->each(function ($item) use ($c) { $this->depth--;
if ($item->remove) {
Log::warning(sprintf('Not scanning [%s], marked for removal',$item->id));
return;
}
$this->dispatch((new CatalogScan($item))->onQueue('scan')); foreach (Storage::disk('nas')->directories($dir) as $dir)
$c++; $files = $files->merge($this->files($dir,$type,$prefix));
});
Log::info(sprintf('Processed [%s]',$c)); if (is_numeric($this->depth))
$this->depth++;
return $files;
} }
} }

View File

@ -1,50 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Jobs\CatalogVerify as Job;
class CatalogVerify extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'catalog:verify'.
' {type : Photo | Video }';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Verify media on disk and in the DB';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if ($this->argument('type')) {
Job::dispatch($this->argument('type'))->onQueue('scan');
} else {
}
}
}

View File

@ -1,145 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Models\{Person,Photo,Tag};
use App\Traits\Files;
class PhotoImport extends Command
{
use DispatchesJobs;
use Files;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'photo:import
{--dir= : Directory to Parse}
{--file= : File to Import}
{--ignoredupe : Ignore duplicate files}
{--deletedupe : Delete duplicate files}
{--people= : People to reference in photo}
{--tags= : Add tag to photo}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import photos 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')
],'photo');
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('photo.import.accepted'))) {
$this->warn(sprintf('Ignoring [%s]',$file));
continue;
}
if ($this->option('verbose'))
$this->info(sprintf('Processing file [%s]',$file));
$c++;
$o = Photo::where('filename',$file)->first();
// The photo doesnt exist
if (is_null($o)) {
$o = new Photo;
$o->filename = $file;
}
if ($o->exists)
$this->warn(sprintf('%s [%s] already in DB: %s',$o->objectType(),$file,$o->id));
// Make sure we can read the photo.
if (! is_readable($o->file_path())) {
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
continue;
}
$o->save();
if ($o->wasRecentlyCreated)
$this->info(sprintf('%s [%s] stored in DB: %s',$o->objectType(),$file,$o->id));
// Record our people and tags
if ($p)
$o->People()->sync($p);
if ($t)
$o->Tags()->sync($t);
$this->dispatch((new \App\Jobs\CatalogScan($o))->onQueue('scan'));
}
$bar->finish();
return $this->info(sprintf('Photos processed: %s',$c));
}
}

View File

@ -1,107 +0,0 @@
<?php
namespace App\Console\Commands;
use Log;
use Illuminate\Console\Command;
use App\Model\Photo;
class PhotoUpdate extends Command
{
use \App\Traits\Files;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'photo:update
{--dir= : Directory to Parse}
{--file= : File to import}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update Signatures';
/**
* 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')]);
if (! count($files))
exit;
// Show a progress bar
$bar = $this->output->createProgressBar(count($files));
$bar->setFormat("%current%/%max% [%bar%] %percent:3s%% (%memory%) (%remaining%) ");
$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('photo.import.accepted')))
{
$this->warn(sprintf('Ignoring [%s]',$file));
continue;
}
if ($this->option('verbose'))
$this->info(sprintf('Processing file [%s]',$file));
$c++;
$po = Photo::where('filename',$file)->first();
if (is_null($po))
{
$this->error(sprintf('File is not in the database [%s]?',$file));
Log::error(sprintf('%s: File is not in the database [%s]?',__METHOD__,$file));
continue;
}
$po->signature = $po->property('signature');
try {
$po->thumbnail = exif_thumbnail($po->file_path());
} catch (\Exception $e) {
// @todo Couldnt get the thumbnail, so we should create one.
}
if ($po->isDirty())
{
if (count($po->getDirty()) > 1 OR ! array_key_exists('signature',$po->getDirty()))
$this->error(sprintf('More than the signature changed for [%s] (%s)?',$po->id,join('|',array_keys($po->getDirty()))));
$this->info(sprintf('Signature update for [%s]',$po->id));
$po->save();
}
}
$bar->finish();
return $this->info(sprintf('Images processed: %s',$c));
}
}

View File

@ -1,149 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Models\{Person,Video,Tag};
use App\Traits\Files;
class VideoImport extends Command
{
use DispatchesJobs;
use 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++;
$o = Video::where('filename',$file)->first();
// The video doesnt exist
if (is_null($o)) {
$o = new Video;
$o->filename = $file;
}
if ($o->exists)
$this->warn(sprintf('%s [%s] already in DB: %s',$o->objectType(),$file,$o->id));
// Make sure we can read the video.
if (! is_readable($o->file_path())) {
$this->warn(sprintf('Ignoring [%s], it is not readable',$o->file_path()));
continue;
}
$o->save();
if ($o->wasRecentlyCreated)
$this->info(sprintf('%s [%s] stored in DB: %s',$o->objectType(),$file,$o->id));
// Record our people and tags
if ($p)
$o->People()->sync($p);
if ($t)
$o->Tags()->sync($t);
$this->dispatch((new \App\Jobs\CatalogScan($o))->onQueue('scan'));
if ($this->option('dumpid3'))
dd($o->properties());
}
$bar->finish();
return $this->info(sprintf('Videos processed: %s',$c));
}
}

View File

@ -1,102 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Support\Facades\Log;
use Illuminate\Console\Command;
use App\Traits\Files;
use App\Models\Video;
class VideoMove extends Command
{
use 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));
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.
$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], movable returned (%s)',$o->id,$o->file_path(),$o->file_path(FALSE,TRUE),$x));
if ($x == 1 AND $v = Video::where('filename',$o->file_path(TRUE,TRUE))->first())
$this->warn(sprintf('File is id (%s) [%s]',$v->file_path(),$v->id));
}
}
}
$bar->advance();
});
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')
// ->hourly();
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* @param \Exception $exception
* @return void
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
}

View File

@ -6,20 +6,22 @@ namespace App\Helpers;
* Description of VideoStream * Description of VideoStream
* *
* @author Rana * @author Rana
* modified by HazCod to use stream_get_contents and correct session shutoff
* https://github.com/HazCod
* @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial * @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
**/ **/
class VideoStream class VideoStream
{ {
private $path = ''; private string $path;
private $stream = ''; private mixed $stream;
private $buffer = 102400; private int $buffer = 102400;
private $start = -1; private int $start = -1;
private $end = -1; private int $end = -1;
private $size = 0; private int $size = 0;
function __construct($filePath) function __construct(string $filename)
{ {
$this->path = $filePath; $this->path = $filename;
} }
/** /**
@ -27,9 +29,8 @@ class VideoStream
**/ **/
private function open() private function open()
{ {
if (! ($this->stream = fopen($this->path,'rb')) ) { if (! ($this->stream=fopen($this->path,'rb',false, stream_context_create())))
die('Could not open stream for reading'); die('Could not open stream for reading');
}
} }
/** /**
@ -38,53 +39,55 @@ class VideoStream
private function setHeader() private function setHeader()
{ {
ob_get_clean(); ob_get_clean();
header('Content-Type: video/mp4'); header('Content-Type: '.mime_content_type($this->path));
header('Cache-Control: max-age=2592000, public'); header(sprintf('Cache-Control: max-age=%d, public',$x=60*60*24*30));
header('Expires: '.gmdate('D, d M Y H:i:s',time()+2592000) . ' GMT'); header(sprintf('Expires: %s GMT',gmdate('D, d M Y H:i:s',time()+$x)));
header('Last-Modified: '.gmdate('D, d M Y H:i:s',@filemtime($this->path)) . ' GMT' ); header(sprintf('Last-Modified: %s GMT',gmdate('D, d M Y H:i:s',filemtime($this->path))));
$this->start = 0; $this->start = 0;
$this->size = filesize($this->path); $this->size = filesize($this->path);
$this->end = $this->size - 1; $this->end = $this->size - 1;
header('Accept-Ranges: 0-'.$this->end); header('Accept-Ranges: 0-'.$this->end);
if (isset($_SERVER['HTTP_RANGE'])) { if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->end; $c_end = $this->end;
list(,$range) = explode('=',$_SERVER['HTTP_RANGE'],2); list(,$range) = explode('=',$_SERVER['HTTP_RANGE'],2);
if (strpos($range,',') !== false) { if (strpos($range,',') !== FALSE) {
header('HTTP/1.1 416 Requested Range Not Satisfiable'); header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size"); header(sprintf('Content-Range: bytes %d-%d/%d',$this->start,$this->end,$this->size));
exit; exit;
} }
if ($range == '-') { if ($range == '-') {
$c_start = $this->size - substr($range,1); $c_start = $this->size - substr($range,1);
}else{
} else {
$range = explode('-',$range); $range = explode('-',$range);
$c_start = $range[0]; $c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end; $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
} }
$c_end = ($c_end > $this->end) ? $this->end : $c_end; $c_end = ($c_end > $this->end) ? $this->end : $c_end;
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) { if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable'); header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size"); header(sprintf('Content-Range: bytes %d-%d/%d',$this->start,$this->end,$this->size));
exit; exit;
} }
$this->start = $c_start; $this->start = $c_start;
$this->end = $c_end; $this->end = $c_end;
$length = $this->end - $this->start + 1; $length = $this->end - $this->start + 1;
fseek($this->stream,$this->start); fseek($this->stream,$this->start);
header('HTTP/1.1 206 Partial Content'); header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length); header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size); header(sprintf('Content-Range: bytes %d-%d/%d',$this->start,$this->end,$this->size));
}
else } else {
{
header("Content-Length: ".$this->size); header("Content-Length: ".$this->size);
} }
} }
/** /**
@ -103,13 +106,14 @@ class VideoStream
{ {
$i = $this->start; $i = $this->start;
set_time_limit(0); set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end) { while ((! feof($this->stream)) && ($i <= $this->end) && (connection_aborted() == 0)) {
$bytesToRead = $this->buffer; $bytesToRead = $this->buffer;
if (($i+$bytesToRead) > $this->end) { if (($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1; $bytesToRead = $this->end - $i + 1;
} }
$data = fread($this->stream,$bytesToRead);
echo $data; echo stream_get_contents($this->stream,$bytesToRead);
flush(); flush();
$i += $bytesToRead; $i += $bytesToRead;
} }
@ -120,6 +124,7 @@ class VideoStream
**/ **/
function start() function start()
{ {
session_write_close();
$this->open(); $this->open();
$this->setHeader(); $this->setHeader();
$this->stream(); $this->stream();

View File

@ -1,39 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
class ConfirmPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Confirm Password Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
|
*/
use ConfirmsPasswords;
/**
* Where to redirect users when the intended url fails.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
}

View File

@ -1,22 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
}

View File

@ -1,52 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* Show our themed login page
*/
public function showLoginForm()
{
$login_note = '';
if (file_exists('login_note.txt'))
$login_note = file_get_contents('login_note.txt');
return view('adminlte::auth.login')->with('login_note',$login_note);
}
}

View File

@ -1,72 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = '/home';
}

View File

@ -1,41 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller
{
/*
|--------------------------------------------------------------------------
| Email Verification Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling email verification for any
| user that recently registered with the application. Emails may also
| be re-sent if the user didn't receive the original email message.
|
*/
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
}

View File

@ -3,11 +3,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController abstract class Controller extends \Illuminate\Routing\Controller
{ {
use AuthorizesRequests, DispatchesJobs, ValidatesRequests; use AuthorizesRequests, ValidatesRequests;
} }

View File

@ -1,15 +0,0 @@
<?php
namespace App\Http\Controllers;
class HomeController extends Controller
{
public function __construct()
{
//$this->middleware('guest');
}
public function home() {
return view('home');
}
}

View File

@ -12,16 +12,6 @@ class PhotoController extends Controller
protected $list_duplicates = 20; protected $list_duplicates = 20;
protected $list_deletes = 50; protected $list_deletes = 50;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
public function delete(Photo $o) public function delete(Photo $o)
{ {
$o->remove = TRUE; $o->remove = TRUE;
@ -48,14 +38,9 @@ class PhotoController extends Controller
]); ]);
} }
public function info(Photo $o)
{
return view('photo.view',['o'=>$o]);
}
public function thumbnail(Photo $o) public function thumbnail(Photo $o)
{ {
return response($o->thumbnail(TRUE)) return response($o->thumbnail())
->header('Content-Type','image/jpeg'); ->header('Content-Type','image/jpeg');
} }
@ -64,9 +49,16 @@ class PhotoController extends Controller
$o->remove = NULL; $o->remove = NULL;
$o->save(); $o->save();
return redirect()->action('PhotoController@info',[$o->id]); return redirect()
->action('PhotoController@info',[$o->id]);
} }
/**
* Render the photo to the browser
*
* @param Photo $o
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Foundation\Application|\Illuminate\Http\Response
*/
public function view(Photo $o) public function view(Photo $o)
{ {
return response($o->image()) return response($o->image())

View File

@ -13,16 +13,6 @@ class VideoController extends Controller
protected $list_duplicates = 20; protected $list_duplicates = 20;
protected $list_deletes = 50; protected $list_deletes = 50;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
public function delete(Video $o) public function delete(Video $o)
{ {
$o->remove = TRUE; $o->remove = TRUE;
@ -49,11 +39,6 @@ class VideoController extends Controller
]); ]);
} }
public function info(Video $o)
{
return view('video.view',['o'=>$o]);
}
public function undelete(Video $o) public function undelete(Video $o)
{ {
$o->remove = NULL; $o->remove = NULL;
@ -65,6 +50,7 @@ class VideoController extends Controller
public function view(Video $o) public function view(Video $o)
{ {
if ($o->isReadable()) if ($o->isReadable())
(new VideoStream($o->file_path()))->start(); (new VideoStream($o->file_name(FALSE)))
->start();
} }
} }

View File

@ -1,82 +0,0 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -1,17 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
];
}

View File

@ -1,23 +0,0 @@
<?php
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}

76
app/Jobs/CatalogMove.php Normal file
View File

@ -0,0 +1,76 @@
<?php
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Abstracted\Catalog;
use Illuminate\Support\Facades\Storage;
class CatalogMove implements ShouldQueue,ShouldBeUnique
{
use InteractsWithQueue,Queueable,SerializesModels;
// Our object
private Catalog $o;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Catalog $o) {
$this->o = $o->withoutRelations();
}
public function middleware(): array
{
return [new WithoutOverlapping($this->o->id)];
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
*/
public function handle()
{
$from = $this->o->file_name_rel(TRUE);
$to = $this->o->file_name_rel(FALSE);
Log::info(sprintf('%s: Moving [%s|%s] from [%s] to [%s]',
__METHOD__,
$this->o->objecttype(),
$this->o->id,
$from,
$to,
));
// If the move is successful, commit the transaction
if ($this->o->isMoveable()) {
// If our move fails, we'll abort the update
DB::beginTransaction();
$this->o->filename = $this->o->file_name();
$this->o->save();
if (Storage::disk($this->o::fs)->move($from,$to))
DB::commit();
else
Log::error(sprintf('%s: Move failed for file [%s]',__METHOD__,$from));
} else
Log::alert(sprintf('%s: Unable to move file [%s] with reason [%s]',
__METHOD__,
$from,
$this->o->isMoveableReason(),
));
}
}

View File

@ -2,39 +2,102 @@
namespace App\Jobs; namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Artisan;
use App\Models\Abstracted\Catalog; use App\Models\Abstracted\Catalog;
class CatalogScan extends Job implements ShouldQueue class CatalogScan implements ShouldQueue, ShouldBeUnique
{ {
use InteractsWithQueue, SerializesModels; use InteractsWithQueue,Queueable,SerializesModels;
// Our object // Our object
private $o = NULL; private Catalog $o;
private bool $show_dirty;
/** /**
* Create a new job instance. * Create a new job instance.
* *
* @return void * @return void
*/ */
public function __construct(Catalog $o) { public function __construct(Catalog $o,bool $show_dirty=FALSE) {
$this->o = $o; $this->o = $o->withoutRelations();
$this->show_dirty = $show_dirty;
}
public function middleware(): array
{
return [new WithoutOverlapping($this->o->id)];
}
/**
* Get the unique ID for the job.
*/
public function uniqueId(): string
{
return $this->o->id;
} }
/** /**
* Execute the job. * Execute the job.
* *
* @return void * @return void
* @throws \Exception
*/ */
public function handle() public function handle()
{ {
Log::info(sprintf('%s: Scanning [%s|%s]',__METHOD__,$this->o->objecttype(),$this->o->id)); Log::info(sprintf('%s: Scanning [%s|%s]',__METHOD__,$this->o->objecttype(),$this->o->id));
Artisan::call('catalog:scan',['type'=>$this->o->objecttype(),'id'=>$this->o->id]); if (! $this->o->isReadable()) {
Log::alert(sprintf('Ignoring [%s], it is not readable',$this->o->file_name(FALSE)));
return;
}
$this->o->init();
// If this is a duplicate
$x = $this->o->myduplicates()->get();
if (count($x)) {
foreach ($x as $this->oo) {
// And that catalog item is not marked as a duplicate
if (! $this->oo->duplicate) {
$this->o->duplicate = TRUE;
Log::alert(sprintf('Image [%s] marked as a duplicate',$this->o->filename));
// If the file signature also matches, we'll mark it for deletion
if ($this->oo->file_signature && ($this->o->file_signature == $this->oo->file_signature)) {
Log::alert(sprintf('Image [%s] marked for deletion',$this->o->filename));
$this->o->remove = TRUE;
}
break;
}
}
}
$this->o->scanned = TRUE;
if ($this->o->getDirty()) {
Log::alert(sprintf('Image [%s] metadata changed',$this->o->filename));
if ($this->show_dirty)
dump(['id'=>$this->o->id,'data'=>$this->o->getDirty()]);
}
// If the file signature changed, abort the update.
if ($this->o->getOriginal('file_signature') && $this->o->wasChanged('file_signature'))
throw new \Exception(sprintf('File Signature Changed for [%s] DB: [%s], File: [%s]?',
$this->o->file_name(),
$this->o->file_signature,
$this->o->getOriginal('file_signature'),
));
$this->o->save();
} }
} }

View File

@ -1,100 +0,0 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use App\Traits\Files;
use App\Traits\Type;
class CatalogVerify extends Job implements ShouldQueue
{
use Dispatchable, Queueable, InteractsWithQueue, SerializesModels, Type, Files;
// What we should verify
private $type = NULL;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(string $type) {
$this->type = $type;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Log::info(sprintf('%s: Scanning [%s]',__METHOD__,$this->type));
// Go through DB and verify files exist
$class = $this->getModelType($this->type);
$good = $bad = $ugly = 0;
$class::select('*')->each(function($o) use ($good,$bad,$ugly) {
if (! file_exists($o->file_name_current(FALSE))) {
Log::error(sprintf('Media doesnt exist: [%s] (%d:%s)',$this->type,$o->id,$o->file_name_current(FALSE)));
$bad++;
return;
}
if (($x=md5_file($o->file_name_current(FALSE))) !== $o->file_signature) {
Log::error(sprintf('Media MD5 doesnt match DB: [%s] (%d:%s) [%s:%s]',$this->type,$o->id,$o->file_name_current(FALSE),$x,$o->file_signature));
$ugly++;
return;
}
$good++;
});
Log::info(sprintf('DB Media Verify Complete: [%s] Good: [%d], Missing: [%d], Changed: [%d]',$this->type,$good,$bad,$ugly));
// Go through filesystem and see that a record exists in the DB, if not add it.
$parentdir = config($this->type.'.dir');
$good = $bad = 0;
foreach ($this->dirlist($parentdir) as $dir) {
foreach ($this->getFiles(['dir'=>$dir,'file'=>NULL],$this->type) as $file) {
if (! $class::where('filename',$file)->count()) {
$bad++;
Log::error(sprintf('File not in DB: [%s] (%s)',$this->type,$file));
} else {
$good++;
}
}
}
Log::info(sprintf('File Media Verify Complete: [%s] Good: [%d], Not In DB: [%d]',$this->type,$good,$bad));
}
/**
* Recursively get a list of dirs
* @param $path
* @return \Illuminate\Support\Collection
*/
private function dirlist($path)
{
$list = collect();
$list->push($path);
foreach (glob($path.'/*',GLOB_ONLYDIR) as $dir) {
foreach ($this->dirlist($dir) as $subdir)
$list->push($subdir);
}
return $list;
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
abstract class Job
{
/*
|--------------------------------------------------------------------------
| Queueable Jobs
|--------------------------------------------------------------------------
|
| This job base class provides a central location to place any logic that
| is shared across all of your jobs. The trait included with the class
| provides access to the "onQueue" and "delay" queue helper methods.
|
*/
use Queueable;
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\Jobs;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Artisan;
use App\Models\Photo;
class PhotoMove extends Job implements ShouldQueue
{
use InteractsWithQueue,SerializesModels;
public $photo;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Photo $o)
{
$this->photo = $o;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Log::info(sprintf('%s: Moving [%s]',__METHOD__,$this->photo->id));
Artisan::call('photo:move',['--id' => $this->photo->id]);
}
}

View File

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

42
app/Media/Base.php Normal file
View File

@ -0,0 +1,42 @@
<?php
namespace App\Media;
use Illuminate\Support\Facades\Log;
abstract class Base
{
protected const BLOCK_SIZE = 4096;
/** Full path to the file */
protected string $filename;
protected int $filesize;
protected string $type;
public function __construct(string $filename,string $type)
{
Log::info(sprintf('Create a media type [%s] for [%s]',get_class($this),$filename));
$this->filename = $filename;
$this->filesize = filesize($filename);
$this->type = $type;
}
/**
* Enable getting values for keys in the response
*
* @param string $key
* @return mixed|object
* @throws \Exception
*/
public function __get(string $key): mixed
{
switch ($key) {
case 'type':
return $this->type;
default:
throw new \Exception('Unknown key: '.$key);
}
}
}

30
app/Media/Factory.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Media;
use Illuminate\Support\Arr;
class Factory {
private const LOGKEY = 'MF-';
/**
* @var array event type to event class mapping
*/
public const map = [
'video/quicktime' => QuickTime::class,
];
/**
* Returns new event instance
*
* @param string $file
* @return Base
*/
public static function create(string $file): Base
{
$type = mime_content_type($file);
$class = Arr::get(self::map,$type,Unknown::class);
return new $class($file,$type);
}
}

126
app/Media/QuickTime.php Normal file
View File

@ -0,0 +1,126 @@
<?php
namespace App\Media;
use Illuminate\Support\Collection;
use App\Media\QuickTime\Atoms\{mdat,moov,Unknown};
use App\Traits\FindQuicktimeAtoms;
// https://developer.apple.com/documentation/quicktime-file-format/quicktime_movie_files
class QuickTime extends Base {
use FindQuicktimeAtoms;
private const LOGKEY = 'MFQ';
private Collection $atoms;
private const atom_classes = 'App\\Media\\QuickTime\\Atoms\\';
public function __construct(string $filename,string $type)
{
parent::__construct($filename,$type);
// Parse the atoms
$this->atoms = $this->get_atoms(self::atom_classes,Unknown::class,0,$this->filesize);
}
public function __get(string $key): mixed
{
switch ($key) {
case 'audio_channels':
case 'audio_codec':
case 'audio_samplerate':
return $this->getAudioAtoms()
->map(fn($item)=>$item->find_atoms(moov\trak\mdia\minf\stbl\stsd::class,1))
->flatten()
->map(fn($item)=>$item->{$key})
->join(',');
// Signatures are calculated by the sha of the MDAT atom.
case 'signature':
$atom = $this->find_atoms(mdat::class,1);
return $atom->signature;
// Creation Time is in the MOOV/MVHD atom
case 'creation_date':
case 'duration':
case 'preferred_rate':
case 'preferred_volume':
// Height/Width is in the moov/trak/tkhd attom
case 'height':
case 'width':
$atom = $this->find_atoms(moov::class,1);
return $atom->{$key};
case 'gps_altitude':
case 'gps_lat':
case 'gps_lon':
$atom = $this->find_atoms(moov::class,1)
->find_atoms(moov\meta::class,1);
return $atom->{$key};
case 'make':
case 'model':
case 'software':
return $this->find_atoms(moov\meta::class,1)->{$key};
case 'time_scale':
$atom = $this->find_atoms(moov\mvhd::class,1);
return $atom->{$key};
case 'type':
return parent::__get($key);
case 'video_codec':
return $this->getVideoAtoms()
->map(fn($item)=>$item->find_atoms(moov\trak\mdia\minf\stbl\stsd::class,1))
->flatten()
->map(fn($item)=>$item->{$key})
->join(',');
case 'video_framerate':
$atom = $this->getVideoAtoms()
->map(fn($item)=>$item->find_atoms(moov\trak\mdia\minf\stbl\stts::class,1))
->pop();
return $atom->frame_rate($this->time_scale);
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Find all the audio track atoms
*
* Audio atoms are in moov/trak/mdia/minf
*
* @return Collection
* @throws \Exception
*/
public function getAudioAtoms(): Collection
{
return $this->find_atoms(moov\trak\mdia\minf::class)
->filter(fn($item)=>$item->type==='soun');
}
/**
* Find all the video track atoms
*
* Audio atoms are in moov/trak/mdia/minf
*
* @return Collection
* @throws \Exception
*/
public function getVideoAtoms(): Collection
{
return $this->find_atoms(moov\trak\mdia\minf::class)
->filter(fn($item)=>$item->type==='vide');
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace App\Media\QuickTime;
use Illuminate\Support\Collection;
use App\Media\QuickTime\Atoms\moov\{mvhd,trak};
use App\Traits\FindQuicktimeAtoms;
abstract class Atom
{
use FindQuicktimeAtoms;
protected const record_size = 16384;
protected const BLOCK_SIZE = 4096;
protected int $offset;
protected int $size;
protected string $filename;
private mixed $fh;
private int $fp;
protected Collection $cache;
protected Collection $atoms;
public function __construct(int $offset,int $size,string $filename)
{
$this->offset = $offset;
// Quick validation
if ($size < 0)
throw new \Exception(sprintf('Atom cannot be negative. (%d)',$size));
$this->size = $size;
$this->filename = $filename;
$this->cache = collect();
}
public function __get(string $key): mixed
{
switch ($key) {
// Create time is in the MOOV/MVHD atom
case 'creation_date':
case 'duration':
case 'preferred_rate':
case 'preferred_volume':
$subatom = $this->find_atoms(mvhd::class,1);
return $subatom->{$key};
// Height is in the moov/trak/tkhd attom
case 'height':
// Width is in the moov/trak/tkhd attom
case 'width':
$atom = $this->find_atoms(trak::class);
return $atom->map(fn($item)=>$item->{$key})->filter()->max();
// Signatures are calculated by the sha of the MDAT atom.
case 'signature':
return $this->signature();
default:
throw new \Exception('Unknown key: '.$key);
}
}
protected function data(): string
{
// Quick validation
if ($this->size > self::record_size)
throw new \Exception(sprintf('Refusing to read more than %d of data',self::record_size));
$data = '';
if ($this->fopen()) {
while (! is_null($read=$this->fread()))
$data .= $read;
$this->fclose();
}
return $data;
}
protected function fclose(): bool
{
fclose($this->fh);
unset($this->fh);
return TRUE;
}
/**
* Open the file and seek to the atom
*
* @return bool
*/
protected function fopen(): bool
{
$this->fh = fopen($this->filename,'r');
fseek($this->fh,$this->offset);
$this->fp = 0;
return TRUE;
}
/**
* Read the atom from the file
*
* @param int $size
* @return string|NULL
*/
protected function fread(int $size=4096): ?string
{
if ($this->fp === $this->size)
return NULL;
if ($this->fp+$size > $this->size)
$size = $this->size-$this->fp;
$read = fread($this->fh,$size);
$this->fp += $size;
return $read;
}
protected function fseek(int $offset): int
{
$this->fp = $offset;
return fseek($this->fh,$this->offset+$this->fp);
}
protected function unpack(array $unpack=[]): string
{
return collect($unpack ?: static::unpack)->map(fn($v,$k)=>$v[0].$k)->join('/');
}
protected function unpack_size(array $unpack=[]): int
{
return collect($unpack ?: static::unpack)->map(fn($v,$k)=>$v[1])->sum();
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Media\QuickTime\Atoms;
use Illuminate\Support\Collection;
use Leenooks\Traits\ObjectIssetFix;
use App\Media\QuickTime\Atom;
use App\Media\QuickTime\Atoms\moov\trak\tkhd;
abstract class SubAtom extends Atom
{
use ObjectIssetFix;
protected ?string $unused_data;
protected const atom_record = [
'version'=>['c',1],
'flags'=>['a3',3],
'count'=>['N',4],
];
public function __get(string $key): mixed
{
switch ($key) {
// Height is in the moov/trak/tkhd attom
case 'height':
// Width is in the moov/trak/tkhd attom
case 'width':
$atom = $this->find_atoms(tkhd::class,1);
return $atom->{$key};
default:
throw new \Exception('Unknown key: '.$key);
}
}
/**
* Unpack data into our cache
*
* @param string|null $data
* @return Collection
* @throws \Exception
*/
protected function cache(?string $data=NULL): Collection
{
$data = $data ?: $this->data();
if (! count($this->cache) && $this->size) {
$this->cache = collect(unpack($this->unpack(),$data));
if ($this->size > ($x=$this->unpack_size()))
$this->unused_data = substr($data,$x);
}
return $this->cache;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Media\QuickTime\Atoms;
// An atom we dont know how to handle
use App\Media\QuickTime\Atom;
class Unknown extends Atom
{
private string $atom;
public function __construct(int $offset,int $size,string $filename,string $atom,?string $data)
{
parent::__construct($offset,$size,$filename,$data);
$this->atom = $atom;
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Media\QuickTime\Atoms;
// Unused space available in file.
use App\Media\QuickTime\Atom;
class free extends Atom
{
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Media\QuickTime\Atoms;
// File type compatibility—identifies the file type and differentiates it from similar file types,
// such as MPEG-4 files and JPEG-2000 files.
// The file type atom has an atom type value of 'ftyp' and contains the following fields:
// Size, Type, Major brand, Minor version, Compatible brands.
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atom;
class ftyp extends Atom
{
protected const unpack = [
'major'=>['a4',1],
'minor'=>['a4',4],
'compat'=>['a4',4],
];
public function __construct(int $offset,int $size,string $filename,?string $data) {
if ($size > 12)
throw new \Exception('FTYP atom larger than 12 bytes, we wont be able to handled that');
parent::__construct($offset,$size,$filename);
$this->cache['data'] = unpack($this->unpack(),$data);
if (Arr::get($this->cache,'data.compat') !== 'qt ')
throw new \Exception('This is not a QT format file');
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Media\QuickTime\Atoms;
// Movie sample data—media samples such as video frames and groups of audio samples.
// Usually this data can be interpreted only by using the movie resource.
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atom;
class mdat extends Atom
{
/**
* Calculate the signature of the data
*
* @param string $alg
* @return string
*/
public function signature(string $alg='sha1'): string
{
if (! Arr::has($this->cache,'signature')) {
if ($this->size) {
$this->fopen();
$hash = hash_init($alg);
while (!is_null($read = $this->fread(16384)))
hash_update($hash, $read);
$this->fclose();
$this->cache['signature'] = hash_final($hash);
} else {
$this->cache['signature'] = NULL;
}
}
return $this->cache['signature'];
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Media\QuickTime\Atoms;
// Movie resource metadata about the movie (number and type of tracks, location of sample data, and so on).
// Describes where the movie data can be found and how to interpret it.
use App\Media\QuickTime\Atom;
class moov extends Atom
{
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\';
public function __construct(int $offset,int $size,string $filename,?string $data) {
parent::__construct($offset,$size,$filename);
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Media\QuickTime\Atoms\moov;
// Unused space available in file.
use App\Media\QuickTime\Atoms\SubAtom;
class free extends SubAtom
{
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Media\QuickTime\Atoms\moov;
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atoms\moov\meta\{ilst,keys};
use App\Media\QuickTime\Atoms\{SubAtom,Unknown};
class meta extends SubAtom
{
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\meta\\';
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data);
$keys = $this->find_atoms(keys::class,1);
$values = $this->find_atoms(ilst::class,1);
$this->cache = $keys->cache->combine($values->cache);
}
public function __get(string $key): mixed
{
switch ($key) {
case 'gps':
$m = [];
$gps = Arr::get($this->cache,'mdta.com.apple.quicktime.location.ISO6709');
preg_match('/^([+-][0-9]{2,6}(?:\.[0-9]+)?)([+-][0-9]{3,7}(?:\.[0-9]+)?)([+-][0-9]+(?:\.[0-9]+)?)?/',$gps,$m);
return ['lat'=>(float)$m[1],'lon'=>(float)$m[2],'alt'=>(float)$m[3]];
case 'gps_altitude':
return Arr::get($this->gps,'alt');
case 'gps_lat':
return Arr::get($this->gps,'lat');
case 'gps_lon':
return Arr::get($this->gps,'lon');
case 'make':
case 'model':
case 'software':
return Arr::get($this->cache,'mdta.com.apple.quicktime.'.$key);
default:
return parent::__get($key);
}
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\meta;
// Unused space available in file.
use App\Media\QuickTime\Atoms\SubAtom;
class free extends SubAtom
{
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\meta;
// Item LiST container atom
use App\Media\QuickTime\Atoms\SubAtom;
class ilst extends SubAtom
{
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
$ptr = 0;
while ($ptr < strlen($data)) {
$key_size = unpack('Nsize',substr($data,$ptr,4));
// Sometimes atoms are terminated with a 0
if ($key_size['size'] === 0) {
$ptr += 4;
continue;
}
$a = unpack(sprintf('a4name/a%ddata',$key_size['size']-8),substr($data,$ptr+4,4+$key_size['size']-8));
$ptr += $key_size['size'];
// If we didnt get the right amount of data, something is wrong.
if (strlen($a['data']) < $key_size['size']-8)
break;
$b = unpack(sprintf('Nsize/a4name/a%ddata',strlen($a['data'])-8),$a['data']);
if ($b['name'] !== 'data')
throw new \Exception('Parsing of ILST got data that wasnt expected');
$c = unpack(sprintf('a4language/a4unknown/a%ddata',strlen($b['data'])-8),$b['data']);
// heirachy, name, size, offset
$this->cache = $this->cache->push($c['data']);
}
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\meta;
// The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom.
// This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys".
use App\Media\QuickTime\Atoms\SubAtom;
class keys extends SubAtom
{
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
$this->keys = collect();
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
for ($i=0; $i<$read['count']; $i++) {
$key_size = unpack('Nsize',substr($data,$ptr,4));
$keys = unpack(sprintf('a4namespace/a%dname',$key_size['size']-8),substr($data,$ptr+4,4+$key_size['size']-8));
$ptr += $key_size['size'];
$this->cache = $this->cache->push(sprintf('%s.%s',$keys['namespace'],$keys['name']));
}
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Media\QuickTime\Atoms\moov;
// Specifies the characteristics of an entire QuickTime movie.
use Carbon\Carbon;
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atoms\SubAtom;
class mvhd extends SubAtom
{
protected const unpack = [
'version'=>['c',1],
'flags'=>['a3',3],
'create'=>['N',4],
'modified'=>['N',4],
'time_scale'=>['N',4],
'duration'=>['N',4],
'prate'=>['a4',4],
'pvolume'=>['a2',2],
'matrix'=>['a36',36],
'prevtime'=>['N',4],
'prevdur'=>['N',4],
'postertime'=>['N',4],
'selecttime'=>['N',4],
'selectdur'=>['N',4],
'curtime'=>['N',4],
'nexttrack'=>['N',4],
];
public function __get($key): mixed
{
switch ($key) {
case 'creation_date':
// We need to convert from MAC time 1904-01-01 to epoch
return Carbon::createFromTimestamp(Arr::get($this->cache(),'create')-2082844800);
case 'duration':
return ($x=$this->time_scale) ? round(Arr::get($this->cache(),'duration')/$x,3) : NULL;
case 'preferred_rate':
return fixed_point_int_to_float(Arr::get($this->cache(),'prate'));
case 'preferred_volume':
return fixed_point_int_to_float(Arr::get($this->cache(),'pvolume'));
case 'time_scale':
return round(Arr::get($this->cache(),$key));
default:
return parent::__get($key);
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Media\QuickTime\Atoms\moov;
// An atom that defines a single track of a movie
use App\Media\QuickTime\Atoms\SubAtom;
use App\Media\QuickTime\Atoms\Unknown;
class trak extends SubAtom
{
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\';
public function __construct(int $offset,int $size,string $filename,?string $data) {
parent::__construct($offset,$size,$filename);
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak;
use App\Media\QuickTime\Atoms\moov\trak\mdia\hdlr;
use App\Media\QuickTime\Atoms\SubAtom;
use App\Media\QuickTime\Atoms\Unknown;
class mdia extends SubAtom
{
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\';
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
// We'll find atoms
$this->atoms = $this->get_atoms(
self::subatom_classes,
Unknown::class,
$offset,
$size,
$data,
NULL,
fn($atom)=>($atom instanceof hdlr) ? $atom->cache['csubtype'] : NULL
);
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia;
use App\Media\QuickTime\Atoms\SubAtom;
class hdlr extends SubAtom
{
protected const unpack = [
'version'=>['c',1],
'flags'=>['a3',3],
'ctype'=>['a4',4],
'csubtype'=>['a4',4],
'cmanufact'=>['a4',4],
'cflags'=>['a4',4],
'cmask'=>['a4',4],
];
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
$this->cache = $this->cache();
$this->cache['name'] = pascal_string($this->unused_data);
$this->unused_data = (($x=strlen($this->cache['name'])+1) < strlen($this->unused_data)) ? substr($data,$x) : NULL;
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia;
use Leenooks\Traits\ObjectIssetFix;
use App\Media\QuickTime\Atoms\SubAtom;
use App\Media\QuickTime\Atoms\Unknown;
class minf extends SubAtom
{
use ObjectIssetFix;
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\minf\\';
protected ?string $type;
public function __construct(int $offset,int $size,string $filename,?string $data,string $arg=NULL)
{
parent::__construct($offset,$size,$filename);
$this->type = $arg;
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data,$arg);
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
public function __get(string $key): mixed
{
switch ($key) {
case 'type':
return $this->{$key};
default:
return parent::__get($key);
}
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf;
use App\Media\QuickTime\Atoms\moov\trak\mdia\hdlr as MdiaHdlr;
class hdlr extends MdiaHdlr
{
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf;
use App\Media\QuickTime\Atoms\SubAtom;
use App\Media\QuickTime\Atoms\Unknown;
class stbl extends SubAtom
{
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\minf\\stbl\\';
public function __construct(int $offset,int $size,string $filename,?string $data,?string $arg=NULL)
{
parent::__construct($offset,$size,$filename);
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data,$arg);
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf\stbl;
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atoms\SubAtom;
class stsd extends SubAtom
{
protected const unpack = [
'size'=>['N',4],
'format'=>['a4',4],
'reserved'=>['a6',6],
'index'=>['n',2],
'encoder_version'=>['n',2],
'encoder_revision'=>['n',2],
'encoder_vendor'=>['a4',4],
];
protected const audio_record = [
'audio_channels'=>['n',2],
'audio_bit_depth'=>['n',2],
'audio_compression_id'=>['n',2],
'audio_packet_size'=>['n',2],
'audio_sample_rate'=>['a4',4],
];
protected const video_record = [
'temporal_quality'=>['N',4],
'spatial_quality'=>['N',4],
'width'=>['n',2],
'height'=>['n',2],
'resolution_x'=>['a4',4],
'resolution_y'=>['a4',4],
'data_size'=>['N',4],
'frame_count'=>['n',2],
//'codec_name'=>['a4',4], // pascal string
];
public function __construct(int $offset,int $size,string $filename,?string $data,string $arg=NULL)
{
parent::__construct($offset,$size,$filename);
$this->type = $arg;
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
for ($i=0; $i<$read['count']; $i++) {
$this->cache = collect(unpack($this->unpack(),substr($data,$ptr,$x=$this->unpack_size())));
$ptr += $x;
switch ($this->type) {
case 'soun':
// Audio Track
$this->audio = unpack($this->unpack(self::audio_record),substr($data,$ptr,$x=$this->unpack_size(self::audio_record)));
$ptr += $x;
break;
case 'vide':
// Video Track
$this->video = unpack($this->unpack(self::video_record),substr($data,$ptr,$x=$this->unpack_size(self::video_record)));
$ptr += $x;
// codec - pascal string
$this->video['codec'] = pascal_string(substr($data,$ptr));
$ptr += strlen($this->video['codec'])+1;
}
$this->extra = substr($data,$ptr);
}
}
public function __get(string $key):mixed
{
switch ($key) {
case 'audio_channels':
return Arr::get($this->audio,$key);
case 'audio_codec':
switch ($this->cache->get('format')) {
case 'mp4a': return 'ISO/IEC 14496-3 AAC';
default:
return $this->cache->get('format');
}
case 'audio_samplerate':
return fixed_point_int_to_float(Arr::get($this->audio,'audio_sample_rate',0));
case 'video_codec':
return Arr::get($this->video,'codec');
case 'video_framerate':
dd($this);
// return Arr::get($this->video,'codec');
default:
return parent::__get($key);
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf\stbl;
use App\Media\QuickTime\Atoms\SubAtom;
class stts extends SubAtom
{
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
for ($i=0; $i<$read['count']; $i++) {
$this->cache->push(unpack('Ncount/Nduration',substr($data,$ptr,8)));
$ptr += 8;
}
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
public function frame_rate(float $time_scale): float
{
return $this->cache
->pluck('duration')
->map(fn($item)=>$time_scale/$item)
->max();
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak;
use App\Media\QuickTime\Atoms\moov\meta as MoovMeta;
class meta extends MoovMeta
{
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak;
// An atom that specifies the characteristics of a single track within a movie
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atoms\SubAtom;
class tkhd extends SubAtom
{
protected const unpack = [
'version'=>['c',1],
'flags'=>['a3',3],
'create'=>['N',4],
'modified'=>['N',4],
'trakid'=>['N',4], // The value 0 cannot be used
'reserved1'=>['a4',4],
'duration'=>['N',4],
'reserved2'=>['a8',8],
'layer'=>['n',2],
'altgroup'=>['n',2],
'volume'=>['a2',2], // 16 bit fixed point
'reserved3'=>['a2',2],
'matrix'=>['a36',36],
'twidth'=>['a4',4], // 32 bit fixed point
'theight'=>['a4',4], // 32 bit fixed point
];
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename,$data);
$this->cache = $this->cache();
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
public function __get($key): mixed
{
switch ($key) {
case 'height':
return fixed_point_int_to_float(Arr::get($this->cache(),'theight'));
case 'width':
return fixed_point_int_to_float(Arr::get($this->cache(),'twidth'));
default:
return parent::__get($key);
}
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Media\QuickTime\Atoms;
// Unused space available in file.
use App\Media\QuickTime\Atom;
class skip extends Atom
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Media\QuickTime\Atoms;
// Reserved space—can be overwritten by an extended size field if the following atom exceeds 2^32 bytes,
// without displacing the contents of the following atom.
use App\Media\QuickTime\Atom;
class wide extends Atom
{
}

13
app/Media/Unknown.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Media;
/**
* This represents a media type that we dont know how to parse
*/
class Unknown extends Base {
public function __get(string $key): mixed
{
return NULL;
}
}

View File

@ -5,20 +5,43 @@ namespace App\Models\Abstracted;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage;
use App\Models\{Person,Software,Tag}; use App\Casts\PostgresBytea;
use App\Models\{Make,Person,Software,Tag};
abstract class Catalog extends Model abstract class Catalog extends Model
{ {
protected static $includeSubSecTime = FALSE; protected static $includeSubSecTime = FALSE;
protected $dates = ['created','created_manual'];
protected $casts = [ protected $casts = [
'created_manual' => 'datetime',
'subsectime' => 'int', 'subsectime' => 'int',
'thumbnail' => PostgresBytea::class,
]; ];
public const fs = 'nas';
private ?string $move_reason;
protected array $init = [];
/* STATIC */
/**
* Return the prefix for the file path - dependant on the object
*
* @return string
*/
public static function dir_prefix(): string
{
return config(static::config.'.dir').'/';
}
/* RELATIONS */
/** /**
* People in Multimedia Object * People in Multimedia Object
* *
@ -49,19 +72,19 @@ abstract class Catalog extends Model
return $this->belongsToMany(Tag::class); return $this->belongsToMany(Tag::class);
} }
/* SCOPES */
/** /**
* Find records marked as duplicate * Find records marked as duplicate
* *
* @param $query * @param $query
* @return mixed * @return mixed
*/ */
public function scopeDuplicates($query) { public function scopeDuplicates($query)
$query->notRemove() {
return $query->notRemove()
->where('duplicate',TRUE) ->where('duplicate',TRUE)
->where(function($q) { ->where(fn($q)=>$q->where('ignore_duplicate','<>',TRUE)->orWhereNull('ignore_duplicate'));
$q->Where('ignore_duplicate','<>',TRUE)
->orWhereNull('ignore_duplicate');
});
} }
/** /**
@ -78,40 +101,31 @@ abstract class Catalog extends Model
$query->where('id','<>',$this->attributes['id']); $query->where('id','<>',$this->attributes['id']);
// Skip ignore dups // Skip ignore dups
$query->where(function($q) { $query->where(fn($q)=>$q->whereNull('ignore_duplicate')
$q->whereNull('ignore_duplicate') ->orWhere('ignore_duplicate',FALSE));
->orWhere('ignore_duplicate','=',0);
});
// Exclude those marked as remove // Exclude those marked as remove
$query->where(function ($q) { $query->where(fn($q)=>$q->where('remove','<>',TRUE));
$q->where('remove','<>',TRUE)
->orWhere('remove','=',NULL);
});
$query->where(function ($q) { $query->where(function($q) {
$q->where('signature','=',$this->attributes['signature']) $q->when($this->attributes['signature'],fn($q)=>$q->where('signature','=',$this->attributes['signature']))
->orWhere('file_signature','=',$this->attributes['file_signature']) ->orWhere('file_signature','=',$this->attributes['file_signature'])
// Where the signature is the same // Where the signature is the same
->orWhere(function($q) { ->orWhere(function($q) {
// Or they have the same time taken with the same camera // Or they have the same time taken with the same camera
if ($this->attributes['created'] AND $this->software_id) { if ($this->attributes['created'] && $this->software_id) {
$q->where(function ($q) { $q->where(fn($q)=>$q->where('created','=',$this->attributes['created'])
$q->where('created','=',$this->attributes['created']) ->orWhere('created_manual','=',$this->attributes['created']));
->orWhere('created_manual','=',$this->attributes['created']);
});
if (static::$includeSubSecTime) if (static::$includeSubSecTime)
$q->where('subsectime','=',Arr::get($this->attributes,'subsectime')); $q->where('subsectime','=',Arr::get($this->attributes,'subsectime'));
$q->where('software_id','=',$this->attributes['software_id']); $q->where('software_id','=',$this->attributes['software_id']);
} elseif ($this->attributes['created_manual'] AND $this->software_id) { } elseif ($this->attributes['created_manual'] && $this->software_id) {
$q->where(function ($q) { $q->where(fn($q)=>$q->where('created','=',$this->attributes['created_manual'])
$q->where('created','=',$this->attributes['created_manual']) ->orWhere('created_manual','=',$this->attributes['created_manual']));
->orWhere('created_manual','=',$this->attributes['created_manual']);
});
if (static::$includeSubSecTime) if (static::$includeSubSecTime)
$q->where('subsectime','=',Arr::get($this->attributes,'subsectime')); $q->where('subsectime','=',Arr::get($this->attributes,'subsectime'));
@ -120,6 +134,8 @@ abstract class Catalog extends Model
} }
}); });
}); });
return $query;
} }
/** /**
@ -129,15 +145,11 @@ abstract class Catalog extends Model
*/ */
public function scopeNotDuplicate($query) public function scopeNotDuplicate($query)
{ {
return $query->where(function($query) return $query->where(
{ fn($q)=>$q->where('duplicate','<>',TRUE)
$query->where('duplicate','<>',TRUE)
->orWhere('duplicate','=',NULL) ->orWhere('duplicate','=',NULL)
->orWhere(function($q) { ->orWhere(fn($q)=>$q->where('duplicate','=',TRUE)->where('ignore_duplicate','=',TRUE))
$q->where('duplicate','=',TRUE) );
->where('ignore_duplicate','=',TRUE);
});
});
} }
/** /**
@ -147,11 +159,7 @@ abstract class Catalog extends Model
*/ */
public function scopeNotRemove($query) public function scopeNotRemove($query)
{ {
return $query->where(function($query) return $query->where(fn($q)=>$q->where('remove','<>',TRUE)->orWhere('remove','=',NULL));
{
$query->where('remove','<>',TRUE)
->orWhere('remove','=',NULL);
});
} }
/** /**
@ -161,27 +169,33 @@ abstract class Catalog extends Model
*/ */
public function scopeNotScanned($query) public function scopeNotScanned($query)
{ {
return $query->where(function($query) return $query->where(fn($q)=>$q->where('scanned','<>',TRUE)->orWhere('scanned','=',NULL));
{
$query->where('scanned','<>',TRUE)
->orWhere('scanned','=',NULL);
});
} }
// Children objects must inherit this methods /* ABSTRACTS */
abstract public function setLocation();
abstract public function setSubSecTime(); abstract public function getObjectOriginal(string $property): mixed;
abstract public function setThumbnail();
abstract public function getHtmlImageURL(); /* ATTRIBUTES */
/** /**
* Date the multimedia was created * Return the time the media was created on the device
*
* This will be (in priority order)
* + the value of created_manual (Carbon)
* + the value of created
*
* @param string|null $date
* @return Carbon|null
*/ */
public function date_taken(): string public function getCreatedAttribute(string $date=NULL): ?Carbon
{ {
return $this->created $result = $this->created_manual ?: ($date ? Carbon::create($date) : NULL);
? $this->created->format('Y-m-d H:i:s').(static::$includeSubSecTime ? sprintf('.%03d',$this->subsectime) : '')
: 'UNKNOWN'; if ($result && static::$includeSubSecTime)
$result->microseconds($this->subsectime*1000);
return $result ?: $this->getObjectOriginal('creation_date');
} }
/** /**
@ -189,7 +203,7 @@ abstract class Catalog extends Model
* *
* @return string * @return string
*/ */
public function device(): string public function getDeviceAttribute(): string
{ {
$result = ''; $result = '';
@ -208,103 +222,6 @@ abstract class Catalog extends Model
return $result; return $result;
} }
/**
* Return the date of the file
* @todo return Carbon date or NULL
*/
public function file_date($type,$format=FALSE)
{
if (! is_readable($this->file_path()))
return NULL;
switch ($type)
{
case 'a': $t = fileatime($this->file_path());
break;
case 'c': $t = filectime($this->file_path());
break;
case 'm': $t = filemtime($this->file_path());
break;
}
return $format ? date('d-m-Y H:i:s',$t) : $t;
}
/**
* Return what the filename should be.
*
* @return string
*/
public function file_name(bool $short=TRUE): string
{
// If the date created is not set, the file name will be based on the ID of the file.
$file = sprintf('%s.%s',(is_null($this->created)
? sprintf('UNKNOWN/%07s',$this->file_path_id())
: $this->created->format('Y/m/d-His').
((! is_null($this->subsectime)) ? sprintf('_%03d',$this->subsectime) : '' ).
sprintf('-%05s',$this->id))
,$this->type()
);
return (($short OR preg_match('/^\//',$file)) ? '' : config($this->type.'.dir').DIRECTORY_SEPARATOR).$file;
}
/**
* Return the current filename
*
* @return string
*/
public function file_name_current(bool $short=TRUE): string
{
return (($short OR preg_match('/^\//',$this->filename)) ? '' : config($this->type.'.dir').DIRECTORY_SEPARATOR).$this->filename;
}
/**
* Determine the new name for the image
* @deprecated use $this->file_name()
*/
public function file_path($short=FALSE,$new=FALSE)
{
$file = $this->filename;
if ($new)
$file = $this->file_name(TRUE);
return (($short OR preg_match('/^\//',$file)) ? '' : config($this->type.'.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): string
{
return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/');
}
/**
* Display the file signature
*/
public function file_signature($short=FALSE): string
{
return $short ? static::stringtrim($this->file_signature) : $this->file_signature;
}
/**
* Return the file size
* @deprecated
*/
public function file_size()
{
return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path());
}
public function getCreatedAttribute()
{
return $this->created_manual ?: ($this->attributes['created'] ? $this->asDateTime($this->attributes['created']) : NULL);
}
/** /**
* Return item dimensions * Return item dimensions
*/ */
@ -313,26 +230,94 @@ abstract class Catalog extends Model
return $this->width.'x'.$this->height; return $this->width.'x'.$this->height;
} }
/**
* Return the file size
*
* @return int|null
*/
public function getFileSizeAttribute(): ?int
{
return (! $this->isReadable()) ? NULL : filesize($this->file_name(FALSE));
}
public function getGPSAttribute(): ?string
{
return ($this->gps_lat && $this->gps_lon)
? sprintf('%s/%s',$this->gps_lat,$this->gps_lon)
: NULL;
}
/* METHODS */
/**
* Return the filename.
* If short is TRUE, it is the filename that it should be called (and can be compared to $this->filename)
* If short is FALSE, it is the true path of the actual file
*
* @param bool $short
* @return string
*/
public function file_name(bool $short=TRUE): string
{
if ($short || preg_match('#^/#',$this->filename)) {
// If the date created is not set, the file name will be based on the ID of the file.
$file = sprintf('%s.%s',
(is_null($this->created)
? sprintf('UNKNOWN/%07s',$this->file_path_id())
: $this->created->format('Y/m/d-His').
($this->subsectime ? sprintf('_%03d',$this->subsectime) : '' ).
sprintf('-%05s',$this->id)),
$this->type()
);
return $file;
} else
return Storage::disk(self::fs)
->path($this->file_name_rel());
}
public function file_name_rel(bool $source=TRUE): string
{
return config(static::config.'.dir').DIRECTORY_SEPARATOR.($source ? $this->filename : $this->file_name());
}
/**
* Determine the new name for the image
* @deprecated use $this->file_name(FALSE) to determine the name, and file_name(TRUE) to determine the new name
*/
public function file_path($short=FALSE,$new=FALSE)
{
$file = $this->filename;
if ($new)
$file = $this->file_name(TRUE);
return (($short OR preg_match('/^\//',$file)) ? '' : config(static::config.'.dir').DIRECTORY_SEPARATOR).$file;
}
/**
* Calculate a file path ID based on the id of the file
*
* We use this when we cannot determine the create time of the image
*/
public function file_path_id($sep=3,$depth=9): string
{
return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/');
}
/** /**
* Return HTML Checkbox for duplicate * Return HTML Checkbox for duplicate
* @deprecated use a component
*/ */
public function getDuplicateCheckboxAttribute() public function getDuplicateCheckboxAttribute()
{ {
return $this->HTMLCheckbox('duplicate',$this->id,$this->duplicate); return $this->HTMLCheckbox('duplicate',$this->id,$this->duplicate);
} }
/**
* Return the file size
*
* @return false|int|null
*/
public function getFileSizeAttribute()
{
return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path());
}
/** /**
* Return HTML Checkbox for flagged * Return HTML Checkbox for flagged
* @deprecated use a component
*/ */
public function getFlagCheckboxAttribute() public function getFlagCheckboxAttribute()
{ {
@ -341,6 +326,7 @@ abstract class Catalog extends Model
/** /**
* Return HTML Checkbox for ignore * Return HTML Checkbox for ignore
* @deprecated use a component
*/ */
public function getIgnoreCheckboxAttribute() public function getIgnoreCheckboxAttribute()
{ {
@ -348,7 +334,7 @@ abstract class Catalog extends Model
} }
/** /**
* @deprecated * @deprecated use a component
*/ */
public function getDuplicateTextAttribute() public function getDuplicateTextAttribute()
{ {
@ -356,7 +342,7 @@ abstract class Catalog extends Model
} }
/** /**
* @deprecated * @deprecated use a component
*/ */
public function getFlagTextAttribute() public function getFlagTextAttribute()
{ {
@ -365,6 +351,7 @@ abstract class Catalog extends Model
/** /**
* Return HTML Checkbox for remove * Return HTML Checkbox for remove
* @deprecated use a component
*/ */
public function getRemoveCheckboxAttribute() public function getRemoveCheckboxAttribute()
{ {
@ -372,25 +359,143 @@ abstract class Catalog extends Model
} }
/** /**
* Return the object type * Return an HTML checkbox
* @return string * @deprecated use a component
* @deprecated Use objecttype()
*/ */
public function getTypeAttribute(): string protected function HTMLCheckbox($name,$id,$value)
{ {
switch(get_class($this)) { return sprintf('<input type="checkbox" name="%s[%s]" value="1"%s>',$name,$id,$value ? ' checked="checked"' : '');
case 'App\Models\Photo': return 'photo';
case 'App\Models\Video': return 'video';
default: return 'unknown';
}
} }
/** /**
* Display the GPS coordinates * Set values from the media object
*
* @return void
* @throws \Exception
*/ */
public function gps(): string public function init(): void
{ {
return ($this->gps_lat AND $this->gps_lon) ? sprintf('%s/%s',$this->gps_lat,$this->gps_lon) : 'UNKNOWN'; foreach ($this->init as $item) {
Log::debug(sprintf('Init item [%s]',$item));
switch ($item) {
case 'creation_date':
$this->created = $this->getObjectOriginal('creation_date');
break;
case 'gps':
$this->gps_lat = $this->getObjectOriginal('gps_lat');
$this->gps_lon = $this->getObjectOriginal('gps_lon');
break;
case 'heightwidth':
$this->height = $this->getObjectOriginal('height');
$this->width = $this->getObjectOriginal('width');
break;
case 'signature':
$this->signature = $this->getObjectOriginal('signature');
$this->file_signature = $this->getObjectOriginal('file_signature');
break;
case 'software':
$ma = NULL;
if ($x=$this->getObjectOriginal('make'))
$ma = Make::firstOrCreate([
'name'=>$x,
]);
$mo = \App\Models\Model::firstOrCreate([
'name'=>$this->getObjectOriginal('model') ?: NULL,
'make_id'=>$ma?->id,
]);
$so = Software::firstOrCreate([
'name'=>$this->getObjectOriginal('software') ?: NULL,
'model_id'=>$mo->id,
]);
$this->software_id = $so->id;
break;
case 'subsectime':
$this->subsectime = $this->getObjectOriginal($item);
break;
default:
throw new \Exception('Unknown init item: '.$item);
}
}
$this->custom_init();
Log::debug('Init result',['dirty'=>$this->getDirty()]);
}
/**
* Does the file require moving
*
* @return bool
*/
public function isMoveable(): bool
{
// No change to the name
$this->move_reason = 'Filenames match already';
if ($this->filename === $this->file_name())
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
$this->move_reason = 'Target file exists';
if (Storage::disk(self::fs)->exists($this->file_name()))
return FALSE;
// Test if the source is readable
$this->move_reason = 'Source is not readable';
if (! $this->isReadable())
return FALSE;
// Test if the dir is writable (so we can remove the file)
$this->move_reason = 'Source parent dir not writable';
if (! $this->isParentWritable(dirname($this->file_name(FALSE))))
return FALSE;
// 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.
$this->move_reason = 'Target parent dir doesnt is not writable';
if (! $this->isParentWritable(dirname($this->file_name(FALSE))))
return FALSE;
// Otherwise we can move it
$this->move_reason = NULL;
return TRUE;
}
public function isMoveableReason(): ?string
{
return $this->move_reason ?? NULL;
}
/**
* Determine if the parent dir is writable
*
* @param string $dir
* @return bool
*/
public function isParentWritable(string $dir): bool
{
$path = Storage::disk(self::fs)->path($dir);
if (Storage::disk(self::fs)->exists($dir) && is_dir($path) && is_writable($path))
return TRUE;
elseif ($path === dirname($path))
return FALSE;
else
return ($this->isParentWritable(dirname($dir)));
} }
/** /**
@ -400,89 +505,19 @@ abstract class Catalog extends Model
*/ */
public function isReadable(): bool public function isReadable(): bool
{ {
return is_readable($this->file_path()); return is_readable($this->file_name(FALSE));
} }
/** /**
* Return an HTML checkbox * Get the id of the next record
*/ */
protected function HTMLCheckbox($name,$id,$value) public function next(): ?self
{ {
return sprintf('<input type="checkbox" name="%s[%s]" value="1"%s>',$name,$id,$value ? ' checked="checked"' : ''); return static::where('id','>',$this->id)
}
/**
* Get ID Info link
*/
protected function HTMLLinkAttribute($id,$url)
{
return sprintf('<a href="%s" target="%s">%s</a>',url($url,$id),$id,$id);
}
/**
* Determine if the parent dir is writable
*
* @param $dir
* @return bool
*/
public function isParentWritable($dir): bool
{
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
* @todo Change to boolean and rename isMoveable() Log any FALSE reason.
*/
public function moveable()
{
// If the source and target are the same, we dont need to move it
if ($this->file_path() == $this->file_path(FALSE,TRUE))
return FALSE;
// If there is already a file in the target.
// @todo If the target file is to be deleted, we could move this file
if (file_exists($this->file_path(FALSE,TRUE)))
return 1;
// Test if the source is readable
if (! is_readable($this->file_path()))
return 2;
// Test if the dir is writable (so we can remove the file)
if (! $this->isParentWritable(dirname($this->file_path())))
return 3;
// Test if the target dir is writable
// @todo The target dir may not exist yet, so we should check that a parent exists and is writable.
if (! $this->isParentWritable($this->file_path(FALSE,TRUE)))
return 4;
return TRUE;
}
/**
* Get the id of the previous record
*/
public function next()
{
return DB::table($this->getTable())
->where('id','>',$this->id)
->orderby('id','ASC') ->orderby('id','ASC')
->first(); ->first();
} }
abstract public function property(string $property);
/** /**
* Return my class shortname * Return my class shortname
*/ */
@ -494,62 +529,21 @@ abstract class Catalog extends Model
/** /**
* Get the id of the previous record * Get the id of the previous record
*/ */
public function previous() public function previous(): ?self
{ {
return DB::table($this->getTable()) return static::where('id','<',$this->id)
->where('id','<',$this->id)
->orderby('id','DESC') ->orderby('id','DESC')
->first(); ->first();
} }
public function setDateCreated()
{
$this->created = $this->property('creationdate') ?: NULL;
}
public function setHeightWidth()
{
$this->height = $this->property('height');
$this->width = $this->property('width');
$this->orientation = $this->property('orientation');
}
public function setSignature()
{
$this->signature = $this->property('signature');
$this->file_signature = md5_file($this->file_path());
}
/** /**
* Display the media signature * Display the media signature
*/ */
public function signature($short=FALSE) public function signature($short=FALSE)
{ {
return ($short AND $this->signature) ? static::stringtrim($this->signature) : $this->signature; return ($short && $this->signature)
} ? stringtrim($this->signature)
: $this->signature;
/**
* Trim a string
*
* @param string $string
* @param int $chrs
* @return string
*/
public static function stringtrim(string $string,int $chrs=6)
{
return sprintf('%s...%s',substr($string,0,$chrs),substr($string,-1*$chrs));
}
/**
* @deprecated
* @param string $string
* @param int $chrs
* @return string
*/
public static function signaturetrim(string $string,int $chrs=6)
{
return static::stringtrim($string,$chrs);
} }
/** /**
@ -560,25 +554,14 @@ abstract class Catalog extends Model
return $this->filename !== $this->file_name(); return $this->filename !== $this->file_name();
} }
protected function TextTrueFalse($value): string /** @deprecated is this really needed? */
private function TextTrueFalse($value): string
{ {
return $value ? 'TRUE' : 'FALSE'; return $value ? 'TRUE' : 'FALSE';
} }
/**
* @todo Check if this is redundant
*
* @param bool $includeme
* @return mixed
*/
private function list_duplicate($includeme=FALSE)
{
return $this->list_duplicates($includeme)->pluck('id');
}
/** /**
* Find duplicate images based on some attributes of the current image * Find duplicate images based on some attributes of the current image
* @deprecate Use static::duplicates()
*/ */
private function list_duplicates($includeme=FALSE) private function list_duplicates($includeme=FALSE)
{ {
@ -595,7 +578,7 @@ abstract class Catalog extends Model
->orWhere('remove','=',NULL); ->orWhere('remove','=',NULL);
}); });
// Where the signature is the same // Where the signalist_duplicatesture is the same
$o->where(function($query) $o->where(function($query)
{ {
$query->where('signature','=',$this->signature); $query->where('signature','=',$this->signature);
@ -607,7 +590,7 @@ abstract class Catalog extends Model
{ {
$query->where('date_created','=',$this->date_created ? $this->date_created : NULL); $query->where('date_created','=',$this->date_created ? $this->date_created : NULL);
if (Schema::hasColumn($this->getTable(),'subsectime')) if (Schema::hasColumn($this->getTable(),'subsectime'))
$query->where('subsectime','=',$this->subsectime ? $this->subsectime : NULL); $query->where('subsectime','=',$this->subsectime ?: NULL);
if (! is_null($this->model)) if (! is_null($this->model))
$query->where('model','=',$this->model); $query->where('model','=',$this->model);

View File

@ -2,54 +2,66 @@
namespace App\Models; namespace App\Models;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Traits\ForwardsCalls;
use Imagick;
use App\Casts\PostgresBytea;
use App\Jobs\CatalogMove;
class Photo extends Abstracted\Catalog class Photo extends Abstracted\Catalog
{ {
protected $table = 'photo'; use ForwardsCalls;
public const config = 'photo';
protected $casts = [
'created'=>'datetime:Y-m-d H:i:s',
'thumbnail'=>PostgresBytea::class,
];
protected static $includeSubSecTime = TRUE; protected static $includeSubSecTime = TRUE;
// Imagick Object // Imagick Object
private $_o; private ?Imagick $_o;
protected array $init = [
// How should the image be rotated, based on the value of orientation 'creation_date',
private $_rotate = [ 'gps',
3=>180, 'heightwidth',
6=>90, 'signature',
8=>-90, 'software',
'subsectime',
]; ];
public function getIDLinkAttribute() // How should the image be rotated, based on the value of orientation
private array $_rotate = [
3 => 180,
6 => 90,
8 => -90,
];
public static function boot()
{ {
return $this->HTMLLinkAttribute($this->id,'p/info'); parent::boot();
}
public function getHtmlImageURL(): string // Any photo saved, queue it to be moved.
{ self::saved(function($item) {
return sprintf('<img class="p-3" src="%s">',url('p/thumbnail',$this->id)); if ($item->scanned && (! $item->duplicate) && (! $item->remove) && ($x=$item->shouldMove()) === TRUE) {
} Log::info(sprintf('%s: Need to Move [%s]','Photo',$item->id.'|'.serialize($x)));
/** CatalogMove::dispatch($item)
* Return the image, rotated, minus exif data ->onQueue('move');
*/ }
public function image() });
{
if (is_null($imo = $this->o()))
return NULL;
if (array_key_exists('exif',$imo->getImageProfiles()))
$imo->removeImageProfile('exif');
return $this->rotate($imo);
} }
/** /**
* Calculate the GPS coordinates * Calculate the GPS coordinates
*/ */
public static function latlon(array $coordinate,$hemisphere) public static function latlon(array $coordinate,string $hemisphere): ?float
{ {
if (! $coordinate OR ! $hemisphere) if ((! $coordinate) || (! $hemisphere))
return NULL; return NULL;
for ($i=0; $i<3; $i++) { for ($i=0; $i<3; $i++) {
@ -67,29 +79,172 @@ class Photo extends Abstracted\Catalog
list($degrees,$minutes,$seconds) = $coordinate; list($degrees,$minutes,$seconds) = $coordinate;
$sign = ($hemisphere == 'W' || $hemisphere == 'S') ? -1 : 1; $sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1;
return round($sign*($degrees+$minutes/60+$seconds/3600),($degrees>100 ? 3 : 4)); return round($sign*($degrees+$minutes/60+$seconds/3600),($degrees>100 ? 3 : 4));
} }
/** /**
* Return an Imagick object or attribute * Forward calls to Imagick
*
* @param $method
* @param $parameters
* @return mixed|null
*/ */
protected function o($attr=NULL) public function __call($method,$parameters) {
if (str_starts_with($method,'Imagick_')) {
$method = preg_replace('/^Imagick_/','',$method);
return $this->o ? $this->forwardCallTo($this->_o,$method,$parameters) : NULL;
} else
return parent::__call($method,$parameters);
}
public function __get($key): mixed
{ {
if (! file_exists($this->file_path()) OR ! is_readable($this->file_path())) if ($key === 'o') {
return NULL; if (isset($this->_o))
return $this->_o;
if (is_null($this->_o)) if ((! file_exists($this->file_name(FALSE))) || (! is_readable($this->file_name(FALSE))))
$this->_o = new \Imagick($this->file_path()); return $this->_o = NULL;
return is_null($attr) ? $this->_o : $this->_o->getImageProperty($attr); if (! isset($this->_o))
return $this->_o = new Imagick($this->file_name(FALSE));
}
return parent::__get($key);
}
/* ATTRIBUTES */
public function getFileSignatureAttribute(string $val=NULL): string
{
return $val ?: $this->getObjectOriginal('file_signature');
}
public function getHeightAttribute(string $val=NULL): ?int
{
return $val ?: $this->getObjectOriginal('height');
}
public function getOrientationAttribute(int $val=NULL): ?int
{
return $val ?: $this->getObjectOriginal('orientation');
}
public function getSignatureAttribute(string $val=NULL): ?string
{
return $val ?: $this->getObjectOriginal('signature');
}
public function getWidthAttribute(string $val=NULL): ?int
{
return $val ?: $this->getObjectOriginal('width');
}
/* METHODS */
public function custom_init(): void
{
$this->orientation = $this->getObjectOriginal('orientation');
try {
if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
$this->o->setImageFormat('jpg');
$this->thumbnail = $this->o->getImageBlob();
}
} catch (\Exception $e) {
Log::info(sprintf('Unable to create thumbnail for %s (%s)',$this->id,$e->getMessage()));
}
if ($this->thumbnail === FALSE)
$this->thumbnail = NULL;
}
public function getObjectOriginal(string $property): mixed
{
switch ($property) {
case 'creation_date':
if ($this->Imagick_getImageProperty('exif:DateTimeOriginal') === '0000:00:00 00:00:00'
&& $this->Imagick_getImageProperty('exif:DateTime') === '0000:00:00 00:00:00')
return NULL;
$result = Carbon::create($x=
($this->Imagick_getImageProperty('exif:DateTimeOriginal') && ($this->Imagick_getImageProperty('exif:DateTimeOriginal') !== '0000:00:00 00:00:00'))
? $this->Imagick_getImageProperty('exif:DateTimeOriginal').$this->Imagick_getImageProperty('exif:OffsetTimeOriginal')
: $this->Imagick_getImageProperty('exif:DateTime').$this->Imagick_getImageProperty('exif:OffsetTime'));
return $result ?: NULL;
case 'file_signature':
return md5_file($this->file_name(FALSE));
case 'gps_lat':
return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLatitude')),$this->Imagick_getImageProperty('exif:GPSLatitudeRef'));
case 'gps_lon':
return self::latlon(preg_split('/,\s?/',$this->Imagick_getImageProperty('exif:GPSLongitude')),$this->Imagick_getImageProperty('exif:GPSLongitudeRef'));
case 'height':
return $this->Imagick_getImageHeight();
case 'identifier':
return NULL;
case 'make':
return $this->Imagick_getImageProperty('exif:Make');
case 'model':
return $this->Imagick_getImageProperty('exif:Model');
case 'orientation':
return $this->Imagick_getImageOrientation();
case 'signature':
return $this->Imagick_getImageSignature();
case 'software':
return $this->Imagick_getImageProperty('exif:Software');
case 'subsectime':
$this->subsectime = (int)$this->Imagick_getImageProperty('exif:SubSecTimeOriginal');
// In case of an error.
if ($this->subsectime > 32767)
$this->subsectime = 32767;
if ($this->subsectime === FALSE)
$this->subsectime = 0;
return $this->subsectime;
case 'width':
return $this->Imagick_getImageWidth();
default:
throw new \Exception('To implement: '.$property);
}
}
/**
* Return the image, rotated
*/
public function image(): ?string
{
$imo = clone($this->o);
return $imo ? $this->rotate($imo) : NULL;
} }
/** /**
* Display the orientation of a photo * Display the orientation of a photo
*/ */
public function orientation() { public function orientation(): string
{
switch ($this->orientation) { switch ($this->orientation) {
case 1: return 'None!'; case 1: return 'None!';
case 3: return 'Upside Down'; case 3: return 'Upside Down';
@ -102,118 +257,36 @@ class Photo extends Abstracted\Catalog
/** /**
* Rotate the image * Rotate the image
*/ */
private function rotate(\Imagick $imo) private function rotate(\Imagick $imo,string $format='jpg'): string
{ {
if (array_key_exists($this->orientation,$this->_rotate)) if (array_key_exists($this->orientation,$this->_rotate))
$imo->rotateImage(new \ImagickPixel('none'),$this->_rotate[$this->orientation]); $imo->rotateImage(new \ImagickPixel('none'),$this->_rotate[$this->orientation]);
$imo->setImageFormat('jpg'); $imo->setImageFormat($format);
if (array_key_exists('exif',$imo->getImageProfiles()))
$imo->removeImageProfile('exif');
return $imo->getImageBlob(); return $imo->getImageBlob();
} }
public function property(string $property)
{
if (! $this->o())
return NULL;
switch ($property) {
case 'creationdate':
if ($this->property('exif:DateTimeOriginal') == '0000:00:00 00:00:00'
&& $this->property('exif:DateTimeOriginal') == '0000:00:00 00:00:00')
return NULL;
return strtotime(
$this->property('exif:DateTimeOriginal') && $this->property('exif:DateTimeOriginal') != '0000:00:00 00:00:00'
? $this->property('exif:DateTimeOriginal')
: $this->property('exif:DateTime'));
break;
case 'height': return $this->_o->getImageHeight();
case 'orientation': return $this->_o->getImageOrientation();
case 'signature': return $this->_o->getImageSignature();
case 'width': return $this->_o->getImageWidth();
default:
return $this->_o->getImageProperty($property);
}
}
public function properties()
{
return $this->o() ? $this->_o->getImageProperties() : [];
}
public function setLocation()
{
$this->gps_lat = static::latlon(preg_split('/,\s?/',$this->property('exif:GPSLatitude')),$this->property('exif:GPSLatitudeRef'));
$this->gps_lon = static::latlon(preg_split('/,\s?/',$this->property('exif:GPSLongitude')),$this->property('exif:GPSLongitudeRef'));
}
public function setMakeModel()
{
$ma = NULL;
if ($this->property('exif:Make'))
$ma = Make::firstOrCreate([
'name'=>$this->property('exif:Make'),
]);
$mo = Model::firstOrCreate([
'name'=>$this->property('exif:Model') ?: NULL,
'make_id'=>$ma ? $ma->id : NULL,
]);
$so = Software::firstOrCreate([
'name'=>$this->property('exif:Software') ?: NULL,
'model_id'=>$mo->id,
]);
$this->software_id = $so->id;
}
public function setSubSecTime()
{
$this->subsectime = (int)$this->property('exif:SubSecTimeOriginal');
// In case of an error.
if ($this->subsectime > 32767)
$this->subsectime = 32767;
if ($this->subsectime === FALSE)
$this->subsectime = 0;
}
public function setThumbnail()
{
try {
$this->thumbnail = exif_thumbnail($this->file_path());
} catch (\Exception $e) {
// @todo Couldnt get the thumbnail, so we should create one.
Log::info(sprintf('Unable to create thumbnail for %s (%s)',$this->id,$e->getMessage()));
}
if ($this->thumbnail === FALSE)
$this->thumbnail = NULL;
}
/** /**
* Return the image's thumbnail * Return the image's thumbnail
*/ */
public function thumbnail($rotate=TRUE) public function thumbnail($rotate=TRUE): ?string
{ {
if (! $this->thumbnail) { if (! $this->thumbnail) {
if ($this->isReadable() AND $this->o()->thumbnailimage(200,200,true,false)) { if ($this->isReadable() && $this->o->thumbnailimage(150,150,true)) {
$this->_o->setImageFormat('jpg'); $this->o->setImageFormat('jpg');
return $this->_o->getImageBlob();
return $this->o->getImageBlob();
} else { } else {
return NULL; return NULL;
} }
} }
if (! $rotate OR ! array_key_exists($this->orientation,$this->_rotate) OR ! extension_loaded('imagick')) if ((! $rotate) || (! array_key_exists($this->orientation,$this->_rotate)) || (! extension_loaded('imagick')))
return $this->thumbnail; return $this->thumbnail;
$imo = new \Imagick(); $imo = new \Imagick();
@ -225,10 +298,9 @@ class Photo extends Abstracted\Catalog
/** /**
* Return the extension of the image * Return the extension of the image
* @todo mime-by-ext?
*/ */
public function type($mime=FALSE) public function type(bool $mime=FALSE): string
{ {
return strtolower($mime ? 'image/jpeg' : pathinfo($this->filename,PATHINFO_EXTENSION)); return strtolower($mime ? mime_content_type($this->file_name(FALSE)) : pathinfo($this->filename,PATHINFO_EXTENSION));
} }
} }

View File

@ -2,190 +2,119 @@
namespace App\Models; namespace App\Models;
use Illuminate\Support\Arr; use Illuminate\Support\Facades\Log;
use App\Jobs\CatalogMove;
use App\Media\{Base,Factory};
class Video extends Abstracted\Catalog class Video extends Abstracted\Catalog
{ {
// ID3 Object public const config = 'video';
private $_o = NULL;
public function getIDLinkAttribute() protected $casts = [
'created'=>'datetime:Y-m-d H:i:s',
];
// Media Object
private ?Base $_o;
protected array $init = [
'creation_date',
'gps',
'heightwidth',
'signature',
'software',
];
public static function boot()
{ {
return $this->HTMLLinkAttribute($this->id,'v/info'); parent::boot();
// Any video saved, queue it to be moved.
self::saved(function($item) {
if ($item->scanned && (! $item->duplicate) && (! $item->remove) && ($x=$item->shouldMove()) === TRUE) {
Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$item->id.'|'.serialize($x)));
CatalogMove::dispatch($item)
->onQueue('move');
}
});
} }
public function getHtmlImageURL() public function __get($key): mixed
{ {
return sprintf('<video width="320" height="240" src="%s" controls></video>',url('/v/view/'.$this->id)); if ($key === 'o') {
} if (isset($this->_o))
return $this->_o;
/** if ((! file_exists($this->file_name(FALSE))) || (! is_readable($this->file_name(FALSE))))
* Return an GetID3 object or attribute return $this->_o = NULL;
*
*/
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)) if (! isset($this->_o)) {
{ $this->_o = Factory::create($this->file_name(FALSE));
/*
* @todo There is a bug with getID3, where the second iteration yields different results return $this->_o;
* this is problematic when video:scanall is run from a queue and there is more than 1 job. }
* It also appears that only 1.9.18 gets date/gps data, a later version doesnt get gps data.
*/
$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); return parent::__get($key);
} }
public function property(string $property) /* METHODS */
public function custom_init()
{ {
if (! $this->o()) $this->audio_channels = $this->getObjectOriginal('audio_channels');
return NULL; $this->audio_codec = $this->getObjectOriginal('audio_codec');
$this->audio_samplerate = $this->getObjectOriginal('audio_samplerate');
$this->gps_altitude = $this->getObjectOriginal('gps_altitude');
$this->length = round($this->getObjectOriginal('length'),2);
$this->type = $this->getObjectOriginal('type');
$this->video_codec = $this->getObjectOriginal('video_codec');
$this->video_framerate = $this->getObjectOriginal('video_framerate');
/*
$x = new \getID3;
$x->analyze($this->file_name(FALSE));
dump(['id3'=>$x]);
*/
}
public function getObjectOriginal(string $property): mixed
{
switch ($property) { switch ($property) {
case 'creationdate': case 'file_signature':
// Try creation_Data return md5_file($this->file_name(FALSE));
$x = Arr::get($this->_o->info,'quicktime.comments.creation_date.0');
// Try creation_Data case 'length':
if (is_null($x)) return $this->o?->duration;
$x = Arr::get($this->_o->info,'quicktime.comments.creationdate.0');
return $x; case 'audio_channels':
case 'audio_codec':
case 'make': return Arr::get($this->_o->info,'quicktime.comments.make.0'); case 'audio_samplerate':
case 'model': return Arr::get($this->_o->info,'quicktime.comments.model.0'); case 'creation_date':
case 'software': return Arr::get($this->_o->info,'quicktime.comments.software.0'); case 'gps_altitude':
case 'signature': return Arr::get($this->_o->info,'sha1_data'); case 'gps_lat':
#case 'height': return $this->subatomsearch('quicktime.moov.subatoms',['trak','tkhd'],'height'); break; case 'gps_lon':
#case 'width': return $this->subatomsearch('quicktime.moov.subatoms',['trak','tkhd'],'width'); break; case 'height':
case 'height': return Arr::get($this->_o->info,'video.resolution_y'); case 'make':
case 'width': return Arr::get($this->_o->info,'video.resolution_x'); case 'model':
case 'length': return Arr::get($this->_o->info,'playtime_seconds'); case 'signature':
case 'type': return Arr::get($this->_o->info,'video.dataformat'); case 'software':
case 'codec': return Arr::get($this->_o->info,'audio.codec'); case 'type':
case 'audiochannels': return Arr::get($this->_o->info,'audio.channels'); case 'width':
case 'samplerate': return Arr::get($this->_o->info,'audio.sample_rate'); case 'video_codec':
case 'channelmode': return Arr::get($this->_o->info,'audio.channelmode'); case 'video_framerate':
case 'gps_lat': return Arr::get($this->_o->info,'quicktime.comments.gps_latitude.0'); return $this->o?->{$property};
case 'gps_lon': return Arr::get($this->_o->info,'quicktime.comments.gps_longitude.0');
case 'gps_altitude': return Arr::get($this->_o->info,'quicktime.comments.gps_altitude.0');
case 'identifier':
if ($x=Arr::get($this->_o->info,'quicktime.comments')) {
return Arr::get(Arr::flatten(Arr::only($x,'content.identifier')),0);
}
break;
default: default:
return NULL; throw new \Exception('To implement: '.$property);
} }
} }
public function properties()
{
return $this->o() ? $this->_o->info : [];
}
/** /**
* Navigate through ID3 data to find the value. * Return the extension of the video
*/ */
private function subatomsearch($atom,array $paths,$key,array $data=[]) { public function type($mime=FALSE): string
if (! $data AND is_null($data = Arr::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 setDateCreated()
{
$this->created = $this->property('creationdate') ? strtotime($this->property('creationdate')) : NULL;
}
public function setLocation()
{
$this->gps_lat = $this->property('gps_lat');
$this->gps_lon = $this->property('gps_lon');
$this->gps_altitude = $this->property('gps_altitude');
}
public function setMakeModel()
{
$ma = NULL;
if ($this->property('make'))
$ma = Make::firstOrCreate([
'name'=>$this->property('make'),
]);
$mo = Model::firstOrCreate([
'name'=>$this->property('model') ?: NULL,
'make_id'=>$ma ? $ma->id : NULL,
]);
$so = Software::firstOrCreate([
'name'=>$this->property('software') ?: NULL,
'model_id'=>$mo->id,
]);
$this->software_id = $so->id;
$this->type = $this->property('type');
$this->length = round($this->property('length'),2);
$this->codec = $this->property('codec');
$this->audiochannels = $this->property('audiochannels');
$this->channelmode = $this->property('channelmode');
$this->samplerate = $this->property('samplerate');
}
public function setSignature()
{
parent::setSignature();
$this->identifier = $this->property('identifier');
}
public function setSubSecTime()
{
// NOOP
}
public function setThumbnail()
{
// NOOP
}
/**
* 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)); return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION));
} }

View File

@ -2,50 +2,34 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Models\{Photo,Video}; use App\Models\{Photo,Video};
use App\Jobs\{PhotoMove,VideoMove};
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
use DispatchesJobs; use DispatchesJobs;
/** /**
* Register any application services. * Register any application services.
* *
* @return void * @return void
*/ */
public function register() public function register(): void
{ {
// //
} }
/** /**
* Bootstrap any application services. * Bootstrap any application services.
* *
* @return void * @return void
*/ */
public function boot() public function boot(): void
{ {
// Any photo saved, queue it to be moved. Route::model('po',Photo::class);
Photo::saved(function($photo) { Route::model('vo',Video::class);
if ($photo->scanned AND ! $photo->duplicate AND ! $photo->remove AND ($x=$photo->moveable()) === TRUE) { }
Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$photo->id.'|'.serialize($x)));
$this->dispatch((new PhotoMove($photo))->onQueue('move'));
}
});
// Any video saved, queue it to be moved.
Video::saved(function($video) {
if ($video->scanned AND ! $video->duplicate AND ! $video->remove AND ($x=$video->moveable()) === TRUE) {
Log::info(sprintf('%s: Need to Move [%s]',__METHOD__,$video->id.'|'.serialize($x)));
$this->dispatch((new VideoMove($video))->onQueue('move'));
}
});
}
} }

View File

@ -1,30 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
//
parent::boot();
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Traits;
use Illuminate\Support\Collection;
use App\Media\QuickTime\Atom;
trait FindQuicktimeAtoms
{
protected function get_atoms(string $class_prefix,string $unknown,int $offset,int $size,string $atom=NULL,string $passthru=NULL,\Closure $callback=NULL): Collection
{
// List of atoms
// File Type atom should proceed move, movie data, preview. free space atoms, if it exists.
// Can assume it doesnt exist if those other atoms are discovered first.
// Atoms can be present in any order
// Must contain a movie atom.
// Movie data atoms can exceed 2^32.
$rp = 0;
if (! $atom) {
$fh = fopen($this->filename,'r');
fseek($fh,$offset);
}
$result = collect();
while ($rp < $size) {
$read = $atom ? substr($atom,$rp,8) : fread($fh,8);
$rp += strlen($read);
$header = unpack('Nsize/a4atom',$read);
// For mdat atoms, if size = 1, the true size is in the 64 bit extended header
if (($header['atom'] === 'mdat') && ($header['size'] === 1))
throw new \Exception(sprintf('%s:! We havent handed large QT files yet.',self::LOGKEY));
// Load our class for this supplier
$class = $class_prefix.$header['atom'];
$data = $atom
? substr($atom,$rp,$header['size']-8)
: ($header['size']-8 && ($header['size']-8 <= self::BLOCK_SIZE) ? fread($fh,$header['size']-8) : NULL);
if ($header['size'] >= 8) {
$o = class_exists($class)
? new $class($offset+$rp,$header['size']-8,$this->filename,$data,$passthru)
: new $unknown($offset+$rp,$header['size']-8,$this->filename,$header['atom'],$data);
$result->push($o);
$rp += $header['size']-8;
// Only need to seek if we didnt read all the data
if ((! $atom) && ($header['size'] > self::BLOCK_SIZE))
fseek($fh,$offset+$rp);
} else {
dd([get_class($this) => $data]);
}
// Work out if data from the last atom next to be passed onto the next one
if ($callback)
$passthru = $callback($o);
}
if (! $atom) {
fclose($fh);
unset($fh);
}
return $result;
}
/**
* Recursively look through our object hierarchy of atoms for a specific atom class
*
* @param string $subatom
* @param int|NULL $expect
* @param int $depth
* @return Collection|Atom|NULL
* @throws \Exception
*/
public function find_atoms(string $subatom,?int $expect=NULL,int $depth=100): Collection|Atom|NULL
{
if (! isset($this->atoms) || ($depth < 0))
return NULL;
$subatomo = $this->atoms->filter(fn($item)=>get_class($item)===$subatom);
$subatomo = $subatomo
->merge($this->atoms->map(fn($item)=>$item->find_atoms($subatom,NULL,$depth-1))
->filter(fn($item)=>$item ? $item->count() : NULL)
->flatten());
if (! $subatomo->count())
return $subatomo;
if ($expect && ($subatomo->count() !== $expect))
throw new \Exception(sprintf('! Expected %d subatoms of %s, but have %d',$expect,$subatom,$subatomo->count()));
return ($expect === 1) ? $subatomo->pop() : $subatomo;
}
}

View File

@ -12,7 +12,7 @@ trait Type
{ {
private function getModelType(string $type): string private function getModelType(string $type): string
{ {
$class = 'App\Models\\'.$type; $class = 'App\Models\\'.ucfirst(strtolower($type));
if (! class_exists($class)) if (! class_exists($class))
abort(500,sprintf('No class [%s]',$type)); abort(500,sprintf('No class [%s]',$type));

View File

@ -1,17 +0,0 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class supports People.
*
* @package Photo
* @category Models
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license.html
*/
class Model_People extends ORM {
public function age($x=NULL) {
return sprintf('%02s',date_diff(new DateTime(date('Y-m-d',is_null($x) ? time() : $x)),new DateTime(date('Y-m-d',$this->date_birth)))->y);
}
}
?>

View File

@ -1,32 +0,0 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* Find photos that dont match their database entries.
*
* @package Photo
* @category Tasks
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license.html
*/
class Task_Photo_Stat extends Minion_Task {
protected $_options = array(
);
protected function _execute(array $params) {
$p = ORM::factory('Photo')->find_all();
foreach ($p as $po) {
if (! file_exists($po->file_path())) {
printf("ID [%s] doesnt exist (%s)?\n",$po->id,$po->file_path());
continue;
}
if (($x=$po->io()->getImageSignature()) !== $po->signature) {
printf("ID [%s] signature doesnt match (%s) [%s != %s]?\n",$po->id,$po->file_path(),$po->signature,$x);
continue;
}
}
}
}
?>

50
artisan Normal file → Executable file
View File

@ -1,53 +1,15 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
/* // Register the Composer autoloader...
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__.'/vendor/autoload.php'; require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php'; // Bootstrap Laravel and handle the command...
$status = (require_once __DIR__.'/bootstrap/app.php')
/* ->handleCommand(new ArgvInput);
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status); exit($status);

View File

@ -1,55 +1,18 @@
<?php <?php
/* use Illuminate\Foundation\Application;
|-------------------------------------------------------------------------- use Illuminate\Foundation\Configuration\Exceptions;
| Create The Application use Illuminate\Foundation\Configuration\Middleware;
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
$app = new Illuminate\Foundation\Application( return Application::configure(basePath: dirname(__DIR__))
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ->withRouting(
); web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
/* health: '/up',
|-------------------------------------------------------------------------- )
| Bind Important Interfaces ->withMiddleware(function (Middleware $middleware) {
|-------------------------------------------------------------------------- //
| })
| Next, we need to bind some important interfaces into the container so ->withExceptions(function (Exceptions $exceptions) {
| we will be able to resolve them when needed. The kernels serve the //
| incoming requests to this application from both the web and CLI. })->create();
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

5
bootstrap/providers.php Normal file
View File

@ -0,0 +1,5 @@
<?php
return [
App\Providers\AppServiceProvider::class,
];

View File

@ -1,75 +1,76 @@
{ {
"name": "laravel/laravel", "name": "laravel/laravel",
"type": "project", "type": "project",
"description": "The Laravel Framework.", "description": "The Laravel Framework.",
"keywords": [ "keywords": ["laravel", "framework"],
"framework", "license": "MIT",
"laravel" "require": {
], "php": "^8.3",
"license": "MIT", "ext-fileinfo": "*",
"require": { "ext-pdo": "*",
"php": "^7.2", "ext-imagick": "*",
"barryvdh/laravel-debugbar": "^3.2", "ext-pgsql": "*",
"fideloper/proxy": "^4.0", "laravel/framework": "^11.0",
"james-heinrich/getid3": "^1.9", "laravel/ui": "^4.5",
"laravel/framework": "^6.4", "leenooks/laravel": "^11.1",
"laravel/socialite": "^4.2", "leenooks/passkey": "^0.2"
"laravel/tinker": "^1.0", },
"leenooks/laravel": "^6.0" "require-dev": {
}, "barryvdh/laravel-debugbar": "^3.6",
"require-dev": { "fakerphp/faker": "^1.23",
"facade/ignition": "1.11.1", "laravel/tinker": "^2.9",
"fzaninotto/faker": "^1.4", "mockery/mockery": "^1.6",
"mockery/mockery": "^1.0", "nunomaduro/collision": "^8.0",
"nunomaduro/collision": "^3.0", "phpunit/phpunit": "^11.0.1",
"phpunit/phpunit": "^8.0" "spatie/laravel-ignition": "^2.4"
}, },
"config": { "autoload": {
"optimize-autoloader": true, "psr-4": {
"preferred-install": "dist", "App\\": "app/",
"sort-packages": true "Database\\Factories\\": "database/factories/",
}, "Database\\Seeders\\": "database/seeders/"
"extra": { },
"laravel": { "files": [
"dont-discover": [] "helpers.php"
} ]
}, },
"autoload": { "autoload-dev": {
"psr-4": { "psr-4": {
"App\\": "app/" "Tests\\": "tests/"
}, }
"classmap": [ },
"database/seeds", "repositories": {
"database/factories" "leenooks": {
] "type": "vcs",
}, "url": "https://gitea.dege.au/laravel/leenooks.git"
"autoload-dev": { },
"psr-4": { "passkey": {
"Tests\\": "tests/" "type": "vcs",
} "url": "https://gitea.dege.au/laravel/passkey.git"
}, }
"repositories": [ },
{ "scripts": {
"type": "vcs", "post-autoload-dump": [
"url": "https://dev.leenooks.net/leenooks/laravel" "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
}, "@php artisan package:discover --ansi"
{ ],
"type": "vcs", "post-root-package-install": [
"url": "https://github.com/leenooks/laravel-theme" "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
} ],
], "post-create-project-cmd": [
"minimum-stability": "dev", "@php artisan key:generate --ansi"
"prefer-stable": true, ]
"scripts": { },
"post-autoload-dump": [ "extra": {
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "laravel": {
"@php artisan package:discover --ansi" "dont-discover": []
], }
"post-root-package-install": [ },
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" "config": {
], "optimize-autoloader": true,
"post-create-project-cmd": [ "preferred-install": "dist",
"@php artisan key:generate --ansi" "sort-packages": true
] },
} "minimum-stability": "dev",
"prefer-stable": true
} }

7564
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,13 +7,15 @@ return [
| Application Name | Application Name
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This value is the name of your application. This value is used when the | This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or | framework needs to place the application's name in a notification or
| any other location as required by the application or its packages. | other UI elements where an application name needs to be displayed.
| |
*/ */
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'Laravel'),
'name_html_long' => env('APP_NAME_HTML_LONG', '<b>Laravel</b>Application'),
'admins' => env('APP_ADMINS',[]) ? explode(';',env('APP_ADMINS')) : [],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -39,7 +41,7 @@ return [
| |
*/ */
'debug' => env('APP_DEBUG', false), 'debug' => (bool) env('APP_DEBUG', false),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -48,26 +50,24 @@ return [
| |
| This URL is used by the console to properly generate URLs when using | This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of | the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks. | the application so that it's available within Artisan commands.
| |
*/ */
'url' => env('APP_URL', 'http://localhost'), 'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL', null),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Timezone | Application Timezone
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may specify the default timezone for your application, which | Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone | will be used by the PHP date and date-time functions. The timezone
| ahead and set this to a sensible default for you out of the box. | is set to "UTC" by default as it is suitable for most use cases.
| |
*/ */
'timezone' => 'Australia/Melbourne', 'timezone' => env('APP_TIMEZONE', 'UTC'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -75,165 +75,54 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The application locale determines the default locale that will be used | The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value | by Laravel's translation / localization methods. This option can be
| to any of the locales which will be supported by the application. | set to any locale for which you plan to have translation strings.
| |
*/ */
'locale' => 'en', 'locale' => env('APP_LOCALE', 'en'),
/* 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en', 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Encryption Key | Encryption Key
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This key is used by the Illuminate encrypter service and should be set | This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string, otherwise these encrypted strings | to a random, 32 character string to ensure that all encrypted values
| will not be safe. Please do this before deploying an application! | are secure. You should do this prior to deploying the application.
| |
*/ */
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC', 'cipher' => 'AES-256-CBC',
/* 'key' => env('APP_KEY'),
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
/*
* Other Service Providers...
*/
Orchestra\Asset\AssetServiceProvider::class,
Collective\Html\HtmlServiceProvider::class,
//\SocialiteProviders\Manager\ServiceProvider::class,
'previous_keys' => [
...array_filter(
explode(',', env('APP_PREVIOUS_KEYS', ''))
),
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Class Aliases | Maintenance Mode Driver
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This array of class aliases will be registered when this application | These configuration options determine the driver used to determine and
| is started. However, feel free to register as many as you wish as | manage Laravel's "maintenance mode" status. The "cache" driver will
| the aliases are "lazy" loaded so they don't hinder performance. | allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
| |
*/ */
'aliases' => [ 'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'App' => Illuminate\Support\Facades\App::class, 'store' => env('APP_MAINTENANCE_STORE', 'database'),
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Asset' => Orchestra\Support\Facades\Asset::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
], ],
]; ];

View File

@ -2,115 +2,128 @@
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Defaults | Authentication Defaults
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This option controls the default authentication "guard" and password | This option defines the default authentication "guard" and password
| reset options for your application. You may change these defaults | reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications. | as required, but they're a perfect start for most applications.
| |
*/ */
'defaults' => [ 'defaults' => [
'guard' => 'web', 'guard' => env('AUTH_GUARD', 'web'),
'passwords' => 'users', 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Guards | Authentication Guards
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Next, you may define every authentication guard for your application. | Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you | Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider. | which utilizes session storage plus the Eloquent user provider.
| |
| All authentication drivers have a user provider. This defines how the | All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage | users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data. | system used by the application. Typically, Eloquent is utilized.
| |
| Supported: "session", "token" | Supported: "session"
| |
*/ */
'guards' => [ 'guards' => [
'web' => [ 'web' => [
'driver' => 'session', 'driver' => 'session',
'provider' => 'users', 'provider' => 'users',
], ],
'api' => [ 'api' => [
'driver' => 'token', 'driver' => 'passport',
'provider' => 'users', 'provider' => 'users',
'hash' => false, ],
],
],
/* ],
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [ /*
'users' => [ |--------------------------------------------------------------------------
'driver' => 'eloquent', | User Providers
'model' => App\User::class, |--------------------------------------------------------------------------
], |
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
// 'users' => [ 'providers' => [
// 'driver' => 'database', 'users' => [
// 'table' => 'users', 'driver' => 'eloquent',
// ], 'model' => env('AUTH_MODEL', App\Models\User::class),
], ],
/* // 'users' => [
|-------------------------------------------------------------------------- // 'driver' => 'database',
| Resetting Passwords // 'table' => 'users',
|-------------------------------------------------------------------------- // ],
| ],
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [ /*
'users' => [ |--------------------------------------------------------------------------
'provider' => 'users', | Resetting Passwords
'table' => 'password_resets', |--------------------------------------------------------------------------
'expire' => 60, |
], | These configuration options specify the behavior of Laravel's password
], | reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
/* 'passwords' => [
|-------------------------------------------------------------------------- 'users' => [
| Password Confirmation Timeout 'provider' => 'users',
|-------------------------------------------------------------------------- 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
| 'expire' => 60,
| Here you may define the amount of seconds before a password confirmation 'throttle' => 60,
| times out and the user is prompted to re-enter their password via the ],
| confirmation screen. By default, the timeout lasts for three hours. ],
|
*/
'password_timeout' => 10800, /*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
'social' => [
'google' => [
'name' => 'Google',
'id' => 'google',
'class' => 'btn-danger',
'icon' => 'fab fa-google',
],
],
]; ];

View File

@ -1,59 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
],
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

View File

@ -9,16 +9,13 @@ return [
| Default Cache Store | Default Cache Store
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This option controls the default cache connection that gets used while | This option controls the default cache store that will be used by the
| using this caching library. This connection is used when another is | framework. This connection is utilized if another isn't explicitly
| not explicitly specified when executing a given caching function. | specified when running a cache operation inside the application.
|
| Supported: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb"
| |
*/ */
'default' => env('CACHE_DRIVER', 'file'), 'default' => env('CACHE_STORE', 'database'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -29,27 +26,30 @@ return [
| well as their drivers. You may even define multiple stores for the | well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches. | same cache driver to group types of items stored in your caches.
| |
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane", "null"
|
*/ */
'stores' => [ 'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [ 'array' => [
'driver' => 'array', 'driver' => 'array',
'serialize' => false,
], ],
'database' => [ 'database' => [
'driver' => 'database', 'driver' => 'database',
'table' => 'cache', 'connection' => env('DB_CACHE_CONNECTION'),
'connection' => null, 'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
], ],
'file' => [ 'file' => [
'driver' => 'file', 'driver' => 'file',
'path' => storage_path('framework/cache/data'), 'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
], ],
'memcached' => [ 'memcached' => [
@ -73,7 +73,8 @@ return [
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'cache', 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
], ],
'dynamodb' => [ 'dynamodb' => [
@ -85,6 +86,10 @@ return [
'endpoint' => env('DYNAMODB_ENDPOINT'), 'endpoint' => env('DYNAMODB_ENDPOINT'),
], ],
'octane' => [
'driver' => 'octane',
],
], ],
/* /*
@ -92,12 +97,12 @@ return [
| Cache Key Prefix | Cache Key Prefix
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| When utilizing a RAM based store such as APC or Memcached, there might | When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| be other applications utilizing the same cache. So, we'll specify a | stores, there might be other applications using the same cache. For
| value to get prefixed to all our keys so we can avoid collisions. | that reason, you may prefix every cache key to avoid collisions.
| |
*/ */
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
]; ];

View File

@ -10,26 +10,22 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may specify which of the database connections below you wish | Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course | to use as your default connection for database operations. This is
| you may use many connections at once using the Database library. | the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
| |
*/ */
'default' => env('DB_CONNECTION', 'mysql'), 'default' => env('DB_CONNECTION', 'sqlite'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Database Connections | Database Connections
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here are each of the database connections setup for your application. | Below are all of the database connections defined for your application.
| Of course, examples of configuring each database platform that is | An example configuration is provided for each database system which
| supported by Laravel is shown below to make development simple. | is supported by Laravel. You're free to add / remove connections.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
| |
*/ */
@ -37,7 +33,7 @@ return [
'sqlite' => [ 'sqlite' => [
'driver' => 'sqlite', 'driver' => 'sqlite',
'url' => env('DATABASE_URL'), 'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')), 'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '', 'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
@ -45,15 +41,35 @@ return [
'mysql' => [ 'mysql' => [
'driver' => 'mysql', 'driver' => 'mysql',
'url' => env('DATABASE_URL'), 'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'), 'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'), 'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''), 'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4', 'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => 'utf8mb4_unicode_ci', 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => true, 'strict' => true,
@ -65,30 +81,32 @@ return [
'pgsql' => [ 'pgsql' => [
'driver' => 'pgsql', 'driver' => 'pgsql',
'url' => env('DATABASE_URL'), 'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'), 'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'), 'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8', 'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'schema' => 'public', 'search_path' => env('DB_SCHEMA', 'public'),
'sslmode' => 'prefer', 'sslmode' => 'prefer',
], ],
'sqlsrv' => [ 'sqlsrv' => [
'driver' => 'sqlsrv', 'driver' => 'sqlsrv',
'url' => env('DATABASE_URL'), 'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'), 'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'), 'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8', 'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
], ],
], ],
@ -100,11 +118,14 @@ return [
| |
| This table keeps track of all the migrations that have already run for | This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of | your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database. | the migrations on disk haven't actually been run on the database.
| |
*/ */
'migrations' => 'migrations', 'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -113,7 +134,7 @@ return [
| |
| Redis is an open source, fast, and advanced key-value store that also | Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system | provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in. | such as Memcached. You may define your connection settings here.
| |
*/ */
@ -129,17 +150,19 @@ return [
'default' => [ 'default' => [
'url' => env('REDIS_URL'), 'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'), 'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null), 'username' => env('REDIS_USERNAME'),
'port' => env('REDIS_PORT', 6379), 'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_DB', 0), 'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
], ],
'cache' => [ 'cache' => [
'url' => env('REDIS_URL'), 'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'), 'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null), 'username' => env('REDIS_USERNAME'),
'port' => env('REDIS_PORT', 6379), 'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_CACHE_DB', 1), 'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
], ],
], ],

View File

@ -2,68 +2,82 @@
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Filesystem Disk | Default Filesystem Disk
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may specify the default filesystem disk that should be used | Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud | by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away! | based disks are available to your application for file storage.
| |
*/ */
'default' => env('FILESYSTEM_DRIVER', 'local'), 'default' => env('FILESYSTEM_DISK', 'local'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Cloud Filesystem Disk | Filesystem Disks
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Many applications store files both locally and in the cloud. For this | Below you may configure as many filesystem disks as necessary, and you
| reason, you may specify a default "cloud" driver here. This driver | may even configure multiple disks for the same driver. Examples for
| will be bound as the Cloud disk implementation in the container. | most supported storage drivers are configured here for reference.
| |
*/ | Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'cloud' => env('FILESYSTEM_CLOUD', 's3'), 'disks' => [
/* 'local' => [
|-------------------------------------------------------------------------- 'driver' => 'local',
| Filesystem Disks 'root' => storage_path('app'),
|-------------------------------------------------------------------------- 'throw' => false,
| ],
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [ 'nas' => [
'driver' => 'local',
'root' => storage_path('nas'),
'throw' => false,
'visibility' => 'public',
],
'local' => [ 'public' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app'), 'root' => storage_path('app/public'),
], 'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
'public' => [ 's3' => [
'driver' => 'local', 'driver' => 's3',
'root' => storage_path('app/public'), 'key' => env('AWS_ACCESS_KEY_ID'),
'url' => env('APP_URL').'/storage', 'secret' => env('AWS_SECRET_ACCESS_KEY'),
'visibility' => 'public', 'region' => env('AWS_DEFAULT_REGION'),
], 'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
's3' => [ ],
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
],
], /*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
]; ];

View File

@ -3,98 +3,138 @@
use Monolog\Handler\NullHandler; use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler; use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Log Channel | Default Log Channel
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This option defines the default log channel that gets used when writing | This option defines the default log channel that is utilized to write
| messages to the logs. The name specified in this option should match | messages to your logs. The value provided here should match one of
| one of the channels defined in the "channels" configuration array. | the channels present in the list of "channels" configured below.
| |
*/ */
'default' => env('LOG_CHANNEL', 'stack'), 'default' => env('LOG_CHANNEL', 'stack'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Log Channels | Deprecations Log Channel
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may configure the log channels for your application. Out of | This option controls the log channel that should be used to log warnings
| the box, Laravel uses the Monolog PHP logging library. This gives | regarding deprecated PHP and library features. This allows you to get
| you a variety of powerful log handlers / formatters to utilize. | your application ready for upcoming major versions of dependencies.
| |
| Available Drivers: "single", "daily", "slack", "syslog", */
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [ 'deprecations' => [
'stack' => [ 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'driver' => 'stack', 'trace' => env('LOG_DEPRECATIONS_TRACE', false),
'channels' => ['daily'], ],
'ignore_exceptions' => false,
],
'single' => [ /*
'driver' => 'single', |--------------------------------------------------------------------------
'path' => storage_path('logs/laravel.log'), | Log Channels
'level' => 'debug', |--------------------------------------------------------------------------
], |
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'daily' => [ 'channels' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
'slack' => [ 'stack' => [
'driver' => 'slack', 'driver' => 'stack',
'url' => env('LOG_SLACK_WEBHOOK_URL'), 'channels' => explode(',', env('LOG_STACK', 'daily')),
'username' => 'Laravel Log', 'ignore_exceptions' => false,
'emoji' => ':boom:', ],
'level' => 'critical',
],
'papertrail' => [ 'single' => [
'driver' => 'monolog', 'driver' => 'single',
'level' => 'debug', 'path' => storage_path('logs/laravel.log'),
'handler' => SyslogUdpHandler::class, 'level' => env('LOG_LEVEL', 'debug'),
'handler_with' => [ 'replace_placeholders' => true,
'host' => env('PAPERTRAIL_URL'), ],
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [ 'daily' => [
'driver' => 'monolog', 'driver' => 'daily',
'handler' => StreamHandler::class, 'path' => storage_path('logs/laravel.log'),
'formatter' => env('LOG_STDERR_FORMATTER'), 'level' => env('LOG_LEVEL', 'debug'),
'with' => [ 'days' => env('LOG_DAILY_DAYS', 14),
'stream' => 'php://stderr', 'replace_placeholders' => true,
], ],
],
'syslog' => [ 'webhook' => [
'driver' => 'syslog', 'driver' => 'daily',
'level' => 'debug', 'path' => storage_path('logs/webhook.log'),
], 'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'errorlog' => [ 'slack' => [
'driver' => 'errorlog', 'driver' => 'slack',
'level' => 'debug', 'url' => env('LOG_SLACK_WEBHOOK_URL'),
], 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'null' => [ 'papertrail' => [
'driver' => 'monolog', 'driver' => 'monolog',
'handler' => NullHandler::class, 'level' => env('LOG_LEVEL', 'debug'),
], 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
], 'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
]; ];

View File

@ -4,54 +4,108 @@ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Mail Driver | Default Mailer
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Laravel supports both SMTP and PHP's "mail" function as drivers for the | This option controls the default mailer that is used to send all email
| sending of e-mail. You may specify which one you're using throughout | messages unless another mailer is explicitly specified when sending
| your application here. By default, Laravel is setup for SMTP mail. | the message. All additional mailers can be configured within the
| | "mailers" array. Examples of each type of mailer are provided.
| Supported: "smtp", "sendmail", "mailgun", "ses",
| "postmark", "log", "array"
| |
*/ */
'driver' => env('MAIL_DRIVER', 'smtp'), 'default' => env('MAIL_MAILER', 'log'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| SMTP Host Address | Mailer Configurations
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may provide the host address of the SMTP server used by your | Here you may configure all of the mailers used by your application plus
| applications. A default option is provided that is compatible with | their respective settings. Several examples have been configured for
| the Mailgun mail service which will provide reliable deliveries. | you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
| |
*/ */
'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 'mailers' => [
/* 'smtp' => [
|-------------------------------------------------------------------------- 'transport' => 'smtp',
| SMTP Host Port 'url' => env('MAIL_URL'),
|-------------------------------------------------------------------------- 'host' => env('MAIL_HOST', '127.0.0.1'),
| 'port' => env('MAIL_PORT', 2525),
| This is the SMTP port used by your application to deliver e-mails to 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
| users of the application. Like the host we have set this value to 'username' => env('MAIL_USERNAME'),
| stay compatible with the Mailgun e-mail application by default. 'password' => env('MAIL_PASSWORD'),
| 'timeout' => null,
*/ 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
'verify_peer' => false,
],
'port' => env('MAIL_PORT', 587), 'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
],
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Global "From" Address | Global "From" Address
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| You may wish for all e-mails sent by your application to be sent from | You may wish for all emails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is | the same address. Here you may specify a name and address that is
| used globally for all e-mails that are sent by your application. | used globally for all emails that are sent by your application.
| |
*/ */
@ -60,77 +114,4 @@ return [
'name' => env('MAIL_FROM_NAME', 'Example'), 'name' => env('MAIL_FROM_NAME', 'Example'),
], ],
/*
|--------------------------------------------------------------------------
| E-Mail Encryption Protocol
|--------------------------------------------------------------------------
|
| Here you may specify the encryption protocol that should be used when
| the application send e-mail messages. A sensible default using the
| transport layer security protocol should provide great security.
|
*/
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
/*
|--------------------------------------------------------------------------
| SMTP Server Username
|--------------------------------------------------------------------------
|
| If your SMTP server requires a username for authentication, you should
| set it here. This will get used to authenticate with your server on
| connection. You may also set the "password" value below this one.
|
*/
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
/*
|--------------------------------------------------------------------------
| Sendmail System Path
|--------------------------------------------------------------------------
|
| When using the "sendmail" driver to send e-mails, we will need to know
| the path to where Sendmail lives on this server. A default path has
| been provided here, which will work well on most of your systems.
|
*/
'sendmail' => '/usr/sbin/sendmail -bs',
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings
|--------------------------------------------------------------------------
|
| If you are using Markdown based email rendering, you may configure your
| theme and component paths here, allowing you to customize the design
| of the emails. Or, you may simply stick with the Laravel defaults!
|
*/
'markdown' => [
'theme' => 'default',
'paths' => [
resource_path('views/vendor/mail'),
],
],
/*
|--------------------------------------------------------------------------
| Log Channel
|--------------------------------------------------------------------------
|
| If you are using the "log" driver, you may specify the logging channel
| if you prefer to keep mail messages separate from other log entries
| for simpler reading. Otherwise, the default channel will be used.
|
*/
'log_channel' => env('MAIL_LOG_CHANNEL'),
]; ];

View File

@ -1,7 +1,7 @@
<?php <?php
return [ return [
'dir'=>'/photos', 'dir'=>'Photos',
'import'=>[ 'import'=>[
'accepted'=>['jpg','jpeg','heic'], 'accepted'=>['jpg','jpeg','heic'],
], ],

View File

@ -7,22 +7,22 @@ return [
| Default Queue Connection Name | Default Queue Connection Name
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Laravel's queue API supports an assortment of back-ends via a single | Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each back-end using the same | API, giving you convenient access to each backend using identical
| syntax for every one. Here you may define a default connection. | syntax for each. The default queue connection is defined below.
| |
*/ */
'default' => env('QUEUE_CONNECTION', 'sync'), 'default' => env('QUEUE_CONNECTION', 'database'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Queue Connections | Queue Connections
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may configure the connection information for each server that | Here you may configure the connection options for every queue backend
| is used by your application. A default configuration has been added | used by your application. An example configuration is provided for
| for each back-end shipped with Laravel. You are free to add more. | each backend supported by Laravel. You're also free to add more.
| |
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
| |
@ -36,17 +36,20 @@ return [
'database' => [ 'database' => [
'driver' => 'database', 'driver' => 'database',
'table' => 'jobs', 'connection' => env('DB_QUEUE_CONNECTION'),
'queue' => 'default', 'table' => env('DB_QUEUE_TABLE', 'jobs'),
'retry_after' => 90, 'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
], ],
'beanstalkd' => [ 'beanstalkd' => [
'driver' => 'beanstalkd', 'driver' => 'beanstalkd',
'host' => 'localhost', 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => 'default', 'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => 90, 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0, 'block_for' => 0,
'after_commit' => false,
], ],
'sqs' => [ 'sqs' => [
@ -54,34 +57,55 @@ return [
'key' => env('AWS_ACCESS_KEY_ID'), 'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'), 'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'your-queue-name'), 'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
], ],
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'default', 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'), 'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90, 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null, 'block_for' => null,
'after_commit' => false,
], ],
], ],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Failed Queue Jobs | Failed Queue Jobs
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| These options configure the behavior of failed queue job logging so you | These options configure the behavior of failed queue job logging so you
| can control which database and table are used to store the jobs that | can control how and where failed jobs are stored. Laravel ships with
| have failed. You may change them to any database / table you wish. | support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
| |
*/ */
'failed' => [ 'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database'), 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'mysql'), 'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs', 'table' => 'failed_jobs',
], ],

View File

@ -9,16 +9,16 @@ return [
| Default Session Driver | Default Session Driver
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This option controls the default session "driver" that will be used on | This option determines the default session driver that is utilized for
| requests. By default, we will use the lightweight native driver but | incoming requests. Laravel supports a variety of storage options to
| you may specify any of the other wonderful drivers provided here. | persist session data. Database storage is a great default choice.
| |
| Supported: "file", "cookie", "database", "apc", | Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array" | "memcached", "redis", "dynamodb", "array"
| |
*/ */
'driver' => env('SESSION_DRIVER', 'file'), 'driver' => env('SESSION_DRIVER', 'database'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -27,13 +27,14 @@ return [
| |
| Here you may specify the number of minutes that you wish the session | Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them | to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option. | to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
| |
*/ */
'lifetime' => env('SESSION_LIFETIME', 120), 'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false, 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -41,21 +42,21 @@ return [
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This option allows you to easily specify that all of your session data | This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run | should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you can use the Session like normal. | automatically by Laravel and you may use the session like normal.
| |
*/ */
'encrypt' => false, 'encrypt' => env('SESSION_ENCRYPT', false),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Session File Location | Session File Location
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| When using the native session driver, we need a location where session | When utilizing the "file" session driver, the session files are placed
| files may be stored. A default has been set for you but a different | on disk. The default storage location is defined here; however, you
| location may be specified. This is only needed for file sessions. | are free to provide another location where they should be stored.
| |
*/ */
@ -72,33 +73,35 @@ return [
| |
*/ */
'connection' => env('SESSION_CONNECTION', null), 'connection' => env('SESSION_CONNECTION'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Session Database Table | Session Database Table
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| When using the "database" session driver, you may specify the table we | When using the "database" session driver, you may specify the table to
| should use to manage the sessions. Of course, a sensible default is | be used to store sessions. Of course, a sensible default is defined
| provided for you; however, you are free to change this as needed. | for you; however, you're welcome to change this to another table.
| |
*/ */
'table' => 'sessions', 'table' => env('SESSION_TABLE', 'sessions'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Session Cache Store | Session Cache Store
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| When using the "apc", "memcached", or "dynamodb" session drivers you may | When using one of the framework's cache driven session backends, you may
| list a cache store that should be used for these sessions. This value | define the cache store which should be used to store the session data
| must match with one of the application's configured cache "stores". | between requests. This must match one of your defined cache stores.
|
| Affects: "apc", "dynamodb", "memcached", "redis"
| |
*/ */
'store' => env('SESSION_STORE', null), 'store' => env('SESSION_STORE'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -118,9 +121,9 @@ return [
| Session Cookie Name | Session Cookie Name
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may change the name of the cookie used to identify a session | Here you may change the name of the session cookie that is created by
| instance by ID. The name specified here will get used every time a | the framework. Typically, you should not need to change this value
| new session cookie is created by the framework for every driver. | since doing so does not grant a meaningful security improvement.
| |
*/ */
@ -136,24 +139,24 @@ return [
| |
| The session cookie path determines the path for which the cookie will | The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of | be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary. | your application, but you're free to change this when necessary.
| |
*/ */
'path' => '/', 'path' => env('SESSION_PATH', '/'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Session Cookie Domain | Session Cookie Domain
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may change the domain of the cookie used to identify a session | This value determines the domain and subdomains the session cookie is
| in your application. This will determine which domains the cookie is | available to. By default, the cookie will be available to the root
| available to in your application. A sensible default has been set. | domain and all subdomains. Typically, this shouldn't be changed.
| |
*/ */
'domain' => env('SESSION_DOMAIN', null), 'domain' => env('SESSION_DOMAIN'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -162,11 +165,11 @@ return [
| |
| By setting this option to true, session cookies will only be sent back | By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep | to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you if it can not be done securely. | the cookie from being sent to you when it can't be done securely.
| |
*/ */
'secure' => env('SESSION_SECURE_COOKIE', false), 'secure' => env('SESSION_SECURE_COOKIE'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -175,11 +178,11 @@ return [
| |
| Setting this value to true will prevent JavaScript from accessing the | Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through | value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed. | the HTTP protocol. It's unlikely you should disable this option.
| |
*/ */
'http_only' => true, 'http_only' => env('SESSION_HTTP_ONLY', true),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -188,12 +191,27 @@ return [
| |
| This option determines how your cookies behave when cross-site requests | This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we | take place, and can be used to mitigate CSRF attacks. By default, we
| do not enable this as other CSRF protection services are in place. | will set this value to "lax" to permit secure cross-site requests.
| |
| Supported: "lax", "strict" | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
| |
*/ */
'same_site' => null, 'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
]; ];

View File

@ -1,7 +1,7 @@
<?php <?php
return [ return [
'dir'=>'/videos', 'dir'=>'HomeMovies',
'import'=>[ 'import'=>[
'accepted'=>['m4v','mov','mp4','avi'], 'accepted'=>['m4v','mov','mp4','avi'],
], ],

View File

@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('makes', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('name')->unique();
});
Schema::create('models', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('name')->nullable();
$table->bigInteger('make_id')->unsigned()->nullable();
$table->foreign('make_id')->references('id')->on('makes');
$table->unique(['name','make_id']);
});
Schema::create('software', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('name')->nullable();
$table->bigInteger('model_id')->unsigned()->nullable();
$table->foreign('model_id')->references('id')->on('models');
$table->unique(['name','model_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('software');
Schema::dropIfExists('models');
Schema::dropIfExists('makes');
}
};

View File

@ -0,0 +1,59 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('photos', function(Blueprint $table)
{
$table->id();
$table->timestamps();
$table->softDeletes();
$table->smallInteger('subsectime')->nullable();
$table->string('filename',256)->index();
$table->string('file_signature')->index()->nullable();
$table->dateTimeTz('created')->nullable();
$table->dateTimeTz('created_manual')->nullable();
$table->string('signature',64)->index()->nullable();
$table->integer('height')->nullable();
$table->integer('width')->nullable();
$table->integer('orientation')->nullable();
$table->float('gps_lat',10)->nullable();
$table->float('gps_lon',10)->nullable();
$table->binary('thumbnail')->nullable();
$table->string('identifier')->nullable();
$table->boolean('duplicate')->default(FALSE)->nullable();
$table->boolean('flag')->default(FALSE)->nullable();
$table->boolean('scanned')->default(FALSE)->nullable();
$table->boolean('ignore_duplicate')->default(FALSE)->nullable();
$table->boolean('remove')->default(FALSE)->nullable();
$table->bigInteger('model_id')->unsigned()->nullable();
$table->foreign('model_id')->references('id')->on('models');
$table->bigInteger('software_id')->unsigned()->nullable();
$table->foreign('software_id')->references('id')->on('software');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('photos');
}
};

Some files were not shown because too many files have changed in this diff Show More