-
-
Save 4unkur/e066fd3058b62c1c38808448aa682da7 to your computer and use it in GitHub Desktop.
Laravel Job class which converts video to MP4 format + appends logo image on the left top corner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Jobs; | |
use App\Exceptions\ProcessVideoException; | |
use App\Interview; | |
use App\Plan; | |
use App\ProcessVideoPitchLog; | |
use App\RawVideoPitch; | |
use App\Resume; | |
use App\Video; | |
use App\Events\ResumeUpdated; | |
use Illuminate\Bus\Queueable; | |
use Illuminate\Support\Facades\File; | |
use mikehaertl\shellcommand\Command; | |
use Illuminate\Queue\SerializesModels; | |
use Illuminate\Queue\InteractsWithQueue; | |
use Illuminate\Contracts\Queue\ShouldQueue; | |
use Illuminate\Foundation\Bus\Dispatchable; | |
class ProcessVideo implements ShouldQueue | |
{ | |
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | |
public $tries = 3; | |
public $timeout = 1800; // 30 min | |
/** | |
* @var RawVideoPitch | |
*/ | |
private $rawVideoPitch; | |
private $videoFileName; | |
private $inputPath; | |
private $outputPath; | |
private $model; | |
/** | |
* @var ProcessVideoPitchLog | |
*/ | |
private $processVideoPitchLog; | |
private $duration; | |
/** | |
* @param ProcessVideoPitchLog $processVideoPitchLog | |
*/ | |
public function __construct(ProcessVideoPitchLog $processVideoPitchLog) | |
{ | |
$this->processVideoPitchLog = $processVideoPitchLog; | |
$this->rawVideoPitch = $this->processVideoPitchLog->rawVideoPitch; | |
$this->model = $this->rawVideoPitch->videoAttachable; | |
} | |
/** | |
* @throws ProcessVideoException | |
*/ | |
public function handle() | |
{ | |
$this->processVideoPitchLog->updateStatus(ProcessVideoPitchLog::STATUS_STARTED); | |
$this->createOutputFolderIfNotExists($this->model->getMorphClass()); | |
$this->videoFileName = Video::FOLDER[$this->model->getMorphClass()] . '/' . pathinfo($this->rawVideoPitch->path)['filename']; | |
$this->inputPath = storage_path('app/' . $this->rawVideoPitch->path); | |
$this->outputPath = storage_path('app/' . $this->videoFileName); | |
$this->convertToMp4(); | |
if ($this->isVideoDurationExceeded()) { | |
$this->removeOutputFile(); | |
$this->processVideoPitchLog->updateStatus(ProcessVideoPitchLog::STATUS_DURATION_EXCEEDED); | |
return false; | |
} | |
$video = Video::create([ | |
'mp4_file' => $this->videoFileName . '.mp4', | |
'original_file_name' => $this->rawVideoPitch->filename, | |
'user_id' => $this->rawVideoPitch->user_id, | |
'raw_video_pitch_id' => $this->rawVideoPitch->id, | |
'process_video_pitch_log_id' => $this->processVideoPitchLog->id, | |
'duration' => $this->duration | |
]); | |
switch ($this->model->getMorphClass()) { | |
case Resume::class : | |
$video->resumes()->save($this->model); | |
event(new ResumeUpdated($this->model)); | |
break; | |
case Interview::class : | |
$video->user()->associate($this->model->employer); | |
$video->interview()->associate($this->model); | |
$video->save(); | |
$this->model->video_id = $video->id; | |
$this->model->save(); | |
break; | |
} | |
} | |
private function getConvertToMp4Command() | |
{ | |
$logo = public_path('images/logo.png'); | |
return <<<COMMAND | |
ffmpeg -y -i {$this->inputPath} -i $logo -filter_complex "overlay=x=(main_w-overlay_w)/(main_w-overlay_w):y=(main_h-overlay_h)/(main_h-overlay_h)" -c:v libx264 -pix_fmt yuv420p -profile:v baseline -level 3 -c:a aac -strict -2 {$this->outputPath}.mp4 | |
COMMAND; | |
} | |
private function getVideoDurationCommand() | |
{ | |
return <<<COMMAND | |
ffprobe -i {$this->outputPath}.mp4 -show_entries format=duration -v quiet -of csv="p=0" | |
COMMAND; | |
} | |
private function isVideoDurationExceeded() | |
{ | |
$this->duration = $this->getVideoDuration(); | |
if ($this->model->getMorphClass() == Resume::class) { | |
$allowedDuration = $this->model->subscription | |
? $this->model->subscription->plan->video_pitch_limit_seconds | |
: Plan::byName(Plan::SILVER)->first()->video_pitch_limit_seconds; | |
if ($this->duration > $allowedDuration) { | |
return true; | |
} | |
return false; | |
} | |
} | |
private function removeOutputFile() | |
{ | |
File::delete($this->outputPath . '.mp4'); | |
} | |
private function createOutputFolderIfNotExists($model) | |
{ | |
$outputFolder = storage_path('app/' . Video::FOLDER[$model]); | |
if (!is_dir($outputFolder)) { | |
mkdir($outputFolder); | |
} | |
} | |
/** | |
* @return int | |
* @throws ProcessVideoException | |
*/ | |
private function getVideoDuration() | |
{ | |
$getVideoDurationCommand = new Command($this->getVideoDurationCommand()); | |
$getVideoDurationCommand->execute(); | |
$duration = $getVideoDurationCommand->getOutput(); | |
if (empty($duration)) { | |
$this->removeOutputFile(); | |
throw new ProcessVideoException('Video duration could not be fetched'); | |
} | |
return intval($duration); | |
} | |
/** | |
* @throws ProcessVideoException | |
*/ | |
private function convertToMp4() | |
{ | |
$rawCommand = $this->getConvertToMp4Command(); | |
$command = new Command($rawCommand); | |
$result = $command->execute(); | |
if (empty($result)) { | |
$this->processVideoPitchLog | |
->updateStatus(ProcessVideoPitchLog::STATUS_FAILED) | |
->log([ | |
'error' => $command->getError(), | |
'code' => $command->getExitCode(), | |
'command' => $rawCommand, | |
]); | |
throw new ProcessVideoException( | |
$command->getError(), | |
$command->getExitCode() | |
); | |
} | |
$this->processVideoPitchLog | |
->updateStatus(ProcessVideoPitchLog::STATUS_COMPLETED); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment