updated to laravel 11
This commit is contained in:
113
app/Jobs/Media/GenerateVariants.php
Normal file
113
app/Jobs/Media/GenerateVariants.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Media;
|
||||
|
||||
use App\Models\Media;
|
||||
use App\Helpers;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Intervention\Image\Drivers\Imagick\Driver;
|
||||
|
||||
class GenerateVariants implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Media ID
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
public $media_name;
|
||||
|
||||
/**
|
||||
* Overwrite existing
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $overwrite;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Media $media The media to process
|
||||
*/
|
||||
public function __construct(Media $media, bool $overwrite = true)
|
||||
{
|
||||
$this->media_name = $media->name;
|
||||
$this->overwrite = $overwrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [new WithoutOverlapping($this->media_name)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$media = Media::find($this->media_name);
|
||||
if ($media === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Storage::disk('media')->exists($media->hash) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$variantData = $media->getVariantTypes($matchingMimeType);
|
||||
if(count($variantData) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$temp = $media->getAsTempFile();
|
||||
if($temp === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempDir = pathinfo($temp, PATHINFO_DIRNAME);
|
||||
$media->deleteAllVariants();
|
||||
|
||||
/* Images */
|
||||
if($matchingMimeType === 'image/*') {
|
||||
$manager = new ImageManager(new Driver());
|
||||
$image = $manager->read($temp);
|
||||
|
||||
$isPortrait = $image->height() > $image->width();
|
||||
|
||||
foreach ($variantData as $variantName => $size) {
|
||||
$image = $manager->read($temp);
|
||||
|
||||
if($isPortrait === true) {
|
||||
$width = $size['height'];
|
||||
$height = $size['width'];
|
||||
} else {
|
||||
$width = $size['width'];
|
||||
$height = $size['height'];
|
||||
}
|
||||
|
||||
if($variantName !== 'scaled' && ($image->height() < $height || $image->width() < $width)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$image->scaleDown($width, $height);
|
||||
$variantFile = $tempDir . '/' . $media->hash . '-' . $variantName . '.webp';
|
||||
$image->save($variantFile, quality: 75);
|
||||
|
||||
$media->addVariant($variantName, 'image/webp', 'webp', $variantFile);
|
||||
}//end foreach
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Media;
|
||||
use App\Models\MediaJob;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use FFMpeg;
|
||||
use FFMpeg\Coordinate\Dimension;
|
||||
use FFMpeg\FFProbe;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
use Intervention\Image\Facades\Image;
|
||||
|
||||
/** @property on $format */
|
||||
class MediaWorkerJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* MediaJob item
|
||||
*
|
||||
* @var MediaJob
|
||||
*/
|
||||
protected $mediaJob;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param MediaJob $mediaJob The mediaJob model.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(MediaJob $mediaJob)
|
||||
{
|
||||
$this->mediaJob = $mediaJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$media = $this->mediaJob->media()->first();
|
||||
$newMedia = false;
|
||||
$data = json_decode($this->mediaJob->data, true);
|
||||
|
||||
try {
|
||||
// FILE
|
||||
if (array_key_exists('file', $data) === true) {
|
||||
if (file_exists($data['file']) === false) {
|
||||
$this->throwMediaJobFailure('temporary upload file no longer exists');
|
||||
}
|
||||
|
||||
// convert HEIC files to JPG
|
||||
$fileExtension = File::extension($data['file']);
|
||||
if ($fileExtension === 'heic') {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'converting image');
|
||||
|
||||
// Get the path without the file name
|
||||
$uploadedFileDirectory = dirname($data['file']);
|
||||
|
||||
// Convert the HEIC file to JPG
|
||||
$jpgFileName = pathinfo($data['file'], PATHINFO_FILENAME) . '.jpg';
|
||||
$jpgFilePath = $uploadedFileDirectory . '/' . $jpgFileName;
|
||||
if (file_exists($jpgFilePath) === true) {
|
||||
$this->throwMediaJobFailure('file already exists on server');
|
||||
}
|
||||
|
||||
Image::make($data['file'])->save($jpgFilePath);
|
||||
|
||||
// Update the uploaded file path and file name
|
||||
unlink($data['file']);
|
||||
$data['file'] = $jpgFileName;
|
||||
}//end if
|
||||
|
||||
// get security
|
||||
$security = [];
|
||||
if ($media === null) {
|
||||
if (array_key_exists('security', $data) === true) {
|
||||
$security = $data['security'];
|
||||
}
|
||||
} else {
|
||||
$security['type'] = $media->security_type;
|
||||
$security['data'] = $media->security_data;
|
||||
}
|
||||
|
||||
// get storage
|
||||
$storage = '';
|
||||
if ($media === null) {
|
||||
if (array_key_exists('storage', $data) === true) {
|
||||
$storage = $data['storage'];
|
||||
}
|
||||
} else {
|
||||
$storage = $media->storage;
|
||||
}
|
||||
|
||||
if ($storage === '') {
|
||||
if (count($security) === 0 || $security['type'] === '') {
|
||||
if (strpos($data['mime_type'], 'image/') === 0) {
|
||||
$storage = 'local';
|
||||
} else {
|
||||
$storage = 'cdn';
|
||||
}
|
||||
} else {
|
||||
$storage = 'private';
|
||||
}
|
||||
}
|
||||
|
||||
// Check if file already exists
|
||||
$exists = Storage::disk($storage)->exists($data['name']);
|
||||
if ($exists === true) {
|
||||
if (array_key_exists('noreplace', $data) === true && isTrue($data['noreplace']) === true) {
|
||||
$this->throwMediaJobFailure('file already exists on server');
|
||||
}
|
||||
}
|
||||
|
||||
if ($exists === true) {
|
||||
$pathInfo = pathinfo($data['name']);
|
||||
$basename = $pathInfo['filename'];
|
||||
$extension = $pathInfo['extension'];
|
||||
$index = 0;
|
||||
|
||||
do {
|
||||
$index++;
|
||||
$data['name'] = $basename . '-' . $index . '.' . $extension;
|
||||
} while (Storage::disk($storage)->exists($data['name']) === true);
|
||||
}
|
||||
|
||||
if ($media === null) {
|
||||
$newMedia = true;
|
||||
$media = new Media([
|
||||
'user_id' => $this->mediaJob->user_id,
|
||||
'title' => $data['title'],
|
||||
'name' => $data['name'],
|
||||
'mime_type' => $data['mime_type'],
|
||||
'size' => $data['size'],
|
||||
'security_type' => $data['security']['type'],
|
||||
'security_data' => $data['security']['data'],
|
||||
'storage' => $storage,
|
||||
]);
|
||||
}//end if
|
||||
|
||||
$media->setStagingFile($data['file']);
|
||||
} else {
|
||||
if ($media === null) {
|
||||
$this->throwMediaJobFailure('The media item no longer exists');
|
||||
}
|
||||
}//end if
|
||||
|
||||
if (array_key_exists('transform', $data) === true) {
|
||||
$media->createStagingFile();
|
||||
|
||||
// Modifications
|
||||
if (strpos($media->mime_type, 'image/') === 0) {
|
||||
$modified = false;
|
||||
$image = Image::make($media->getStagingFilePath());
|
||||
|
||||
// ROTATE
|
||||
if (array_key_exists("rotate", $data['transform']) === true) {
|
||||
$rotate = intval($data['transform']['rotate']);
|
||||
if ($rotate !== 0) {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'rotating image');
|
||||
$image = $image->rotate($rotate);
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// FLIP-H/V
|
||||
if (array_key_exists('flip', $data['transform']) === true) {
|
||||
if (stripos($data['transform']['flip'], 'h') !== false) {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'flipping image');
|
||||
$image = $image->flip('h');
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
if (stripos($data['transform']['flip'], 'v') !== false) {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'flipping image');
|
||||
$image = $image->flip('v');
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// CROP
|
||||
if (array_key_exists("crop", $data['transform']) === true) {
|
||||
$cropData = $data['transform']['crop'];
|
||||
$width = intval(arrayDefaultValue("width", $cropData, $image->getWidth()));
|
||||
$height = intval(arrayDefaultValue("height", $cropData, $image->getHeight()));
|
||||
$x = intval(arrayDefaultValue("x", $cropData, 0));
|
||||
$y = intval(arrayDefaultValue("y", $cropData, 0));
|
||||
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'cropping image');
|
||||
$image = $image->crop($width, $height, $x, $y);
|
||||
$modified = true;
|
||||
}//end if
|
||||
|
||||
if ($modified === true) {
|
||||
$image->save();
|
||||
}
|
||||
} elseif (strpos($data['mime_type'], 'video/') === 0) {
|
||||
$stagingFilePath = $media->getStagingFilePath();
|
||||
$ffmpeg = FFMpeg\FFMpeg::create();
|
||||
$video = $ffmpeg->open($stagingFilePath);
|
||||
$format = $this->detectVideoFormat($stagingFilePath);
|
||||
$modified = false;
|
||||
|
||||
if ($format === null) {
|
||||
$this->mediaJob->setStatusFailed('Unsupported video format');
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var FFMpeg\Media\Video::filters */
|
||||
$filters = $video->filters();
|
||||
|
||||
// ROTATE
|
||||
if (array_key_exists("rotate", $data['transform']) === true) {
|
||||
$rotate = intval($data['transform']['rotate']);
|
||||
$rotate = (($rotate % 360 + 360) % 360); // remove excess rotations
|
||||
$rotate = intval(round($rotate / 90) * 90); // round to nearest 90%
|
||||
|
||||
if ($rotate > 0) {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'rotating video');
|
||||
|
||||
if ($rotate === 90) {
|
||||
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_270);
|
||||
$modified = true;
|
||||
} elseif ($rotate === 180) {
|
||||
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_180);
|
||||
$modified = true;
|
||||
} elseif ($rotate === 270) {
|
||||
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_90);
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FLIP-H/V
|
||||
if (array_key_exists('flip', $data['transform']) === true) {
|
||||
if (stripos($data['transform']['flip'], 'h') !== false) {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'flipping video');
|
||||
$filters->hflip()->synchronize();
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
if (stripos($data['transform']['flip'], 'v') !== false) {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'flipping video');
|
||||
$filters->vflip()->synchronize();
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// CROP
|
||||
if (array_key_exists("crop", $data['transform']) === true) {
|
||||
$cropData = $data['transform']['crop'];
|
||||
$videoStream = $video->getStreams()->videos()->first();
|
||||
|
||||
$width = intval(arrayDefaultValue("width", $cropData, $videoStream->get('width')));
|
||||
$height = intval(arrayDefaultValue("height", $cropData, $videoStream->get('height')));
|
||||
$x = intval(arrayDefaultValue("x", $cropData, 0));
|
||||
$y = intval(arrayDefaultValue("y", $cropData, 0));
|
||||
|
||||
$cropDimension = new Dimension($width, $height);
|
||||
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'cropping video');
|
||||
$filters->crop($cropDimension, $x, $y)->synchronize();
|
||||
$modified = true;
|
||||
}//end if
|
||||
|
||||
$tempFilePath = generateTempFilePath(pathinfo($stagingFilePath, PATHINFO_EXTENSION));
|
||||
if (method_exists($format, 'on') === true) {
|
||||
$mediaJob = $this->mediaJob;
|
||||
$format->on('progress', function ($video, $format, $percentage) use ($mediaJob) {
|
||||
$mediaJob->setStatusProcessing($percentage, 100, 'transcoded');
|
||||
});
|
||||
}
|
||||
|
||||
if ($modified === true) {
|
||||
$video->save($format, $tempFilePath);
|
||||
$media->changeStagingFile($tempFilePath);
|
||||
}
|
||||
}//end if
|
||||
}//end if
|
||||
|
||||
// Update attributes
|
||||
if (array_key_exists('title', $data) === true) {
|
||||
$media->title = $data['title'];
|
||||
}
|
||||
|
||||
// Relocate file (if requested)
|
||||
if (array_key_exists('security', $data) === true && array_key_exists('type', $data['security']) === true) {
|
||||
$media->security_type = $data['security']['type'];
|
||||
$media->security_data = $data['security']['data'];
|
||||
}
|
||||
|
||||
if (array_key_exists('storage', $data) === true) {
|
||||
if ($media->storage !== $data['storage']) {
|
||||
$media->createStagingFile();
|
||||
Storage::disk($media->storage)->delete($media->name);
|
||||
$media->storage = $data['storage'];
|
||||
}
|
||||
}
|
||||
|
||||
// Finish media object
|
||||
if ($media->hasStagingFile() === true) {
|
||||
$this->mediaJob->setStatusProcessing(0, 0, 'transferring to cdn');
|
||||
$media->deleteFile();
|
||||
$media->saveStagingFile(true);
|
||||
}
|
||||
|
||||
$media->save();
|
||||
$this->mediaJob->media_id = $media->id;
|
||||
$this->mediaJob->setStatusComplete();
|
||||
} catch (\Exception $e) {
|
||||
if ($this->mediaJob->status !== 'failed') {
|
||||
$this->mediaJob->setStatusFailed('Unexpected server error occurred');
|
||||
}
|
||||
|
||||
if ($media !== null) {
|
||||
$media->deleteStagingFile();
|
||||
if ($newMedia === true) {
|
||||
$media->delete();
|
||||
}
|
||||
}
|
||||
|
||||
Log::error($e->getMessage() . "\n" . $e->getFile() . " - " . $e->getLine() . "\n" . $e->getTraceAsString());
|
||||
$this->fail($e);
|
||||
}//end try
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the format of a video using FFProbe
|
||||
*
|
||||
* @param string $videoPath The video file path.
|
||||
* @return VideoInterface | null
|
||||
*/
|
||||
public function detectVideoFormat(string $videoPath): VideoInterface | null
|
||||
{
|
||||
$ffprobe = FFProbe::create();
|
||||
|
||||
$videoStream = $ffprobe
|
||||
->streams($videoPath) // Provide the path to the video file
|
||||
->videos() // Filter video streams
|
||||
->first();
|
||||
|
||||
$codecName = $videoStream->get('codec_name');
|
||||
|
||||
$codecToFormatClass = [
|
||||
'h264' => 'FFMpeg\Format\Video\X264',
|
||||
'wmv2' => 'FFMpeg\Format\Video\WMV',
|
||||
'vp9' => 'FFMpeg\Format\Video\WebM',
|
||||
'theora' => 'FFMpeg\Format\Video\Ogg',
|
||||
'mpeg4' => 'FFMpeg\Format\Video\Mpeg4',
|
||||
// Add more mappings as needed
|
||||
];
|
||||
|
||||
if (isset($codecToFormatClass[$codecName]) === false) {
|
||||
Log::info("Unsupported codec: $codecName");
|
||||
return null;
|
||||
}
|
||||
|
||||
$formatClassName = $codecToFormatClass[$codecName];
|
||||
|
||||
if (class_exists($formatClassName) === false) {
|
||||
Log::info("Format class does not exist: $formatClassName");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $formatClassName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set failure status of MediaJob and throw exception.
|
||||
*
|
||||
* @param string $error The failure message.
|
||||
* @return void
|
||||
*/
|
||||
private function throwMediaJobFailure(string $error): void
|
||||
{
|
||||
$this->mediaJob->setStatusFailed($error);
|
||||
throw new \Exception($error);
|
||||
}
|
||||
}
|
||||
43
app/Jobs/ProcessMedia.php
Normal file
43
app/Jobs/ProcessMedia.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Media;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ProcessMedia implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* File path
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
public $file;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param String $file The file path
|
||||
*/
|
||||
public function __construct(string $file)
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if(Storage::disk('temp')->exists($this->media->name)) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,9 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendEmailJob implements ShouldQueue
|
||||
class SendEmail implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Mail to receipt
|
||||
@@ -31,7 +28,6 @@ class SendEmailJob implements ShouldQueue
|
||||
*/
|
||||
public $mailable;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
Reference in New Issue
Block a user