|
<?php |
|
|
|
namespace App\Listeners; |
|
|
|
use App\Media; |
|
use FFMpeg\FFMpeg; |
|
use FFMpeg\Format\Video\X264; |
|
use Illuminate\Queue\InteractsWithQueue; |
|
use Illuminate\Contracts\Queue\ShouldQueue; |
|
use Illuminate\Queue\SerializesModels; |
|
use Spatie\MediaLibrary\Events\MediaHasBeenAdded; |
|
use Spatie\MediaLibrary\Helpers\File as MediaLibraryFileHelper; |
|
|
|
class MediaVideoConverterListener implements ShouldQueue |
|
{ |
|
use InteractsWithQueue; |
|
use SerializesModels; |
|
|
|
protected $media; |
|
|
|
/** |
|
* Create the event listener. |
|
* |
|
*/ |
|
public function __construct() |
|
{ |
|
|
|
} |
|
|
|
/** |
|
* Handle the event. |
|
* |
|
* @return void |
|
*/ |
|
public function handle(MediaHasBeenAdded $event) |
|
{ |
|
$this->media = $event->media; |
|
//prevent any events from media model |
|
$this->media->flushEventListeners(); |
|
|
|
if ((!$this->isVideo()) |
|
|| $this->media->getCustomProperty('status') !== Media::MEDIA_STATUS_TO_CONVERT |
|
|| strtolower($this->media->extension) == 'mp4' || strtolower($this->media->mime_type) == 'video/mp4' |
|
) { |
|
$this->media->setCustomProperty('status', Media::MEDIA_STATUS_READY); |
|
$this->media->setCustomProperty('progress', 100); |
|
$this->media->save(); |
|
return; |
|
} |
|
|
|
$this->media->setCustomProperty('status', Media::MEDIA_STATUS_PROCESSING); |
|
$this->media->save(); |
|
|
|
try { |
|
$fullPath = $this->media->getPath(); |
|
$newFileFullPath = pathinfo($fullPath, PATHINFO_DIRNAME) |
|
. DIRECTORY_SEPARATOR . pathinfo($fullPath, PATHINFO_FILENAME) |
|
. Media::MEDIA_VIDEO_EXT; |
|
|
|
if (file_exists($newFileFullPath)) { |
|
unlink($newFileFullPath); |
|
} |
|
|
|
$ffmpeg = FFMpeg::create([ |
|
'ffmpeg.binaries' => config('medialibrary.ffmpeg_binaries'), |
|
'ffprobe.binaries' => config('medialibrary.ffprobe_binaries'), |
|
'timeout' => 3600, |
|
'ffmpeg.threads' => 12, |
|
]); |
|
|
|
$video = $ffmpeg->open($fullPath); |
|
|
|
$format = new X264(); |
|
|
|
$format->on('progress', function ($video, $format, $percentage) use ($fullPath, $newFileFullPath) { |
|
if ($percentage >= 100) { |
|
$this->mediaConvertingCompleted($fullPath, $newFileFullPath); |
|
} elseif (!($percentage % 10)) { |
|
$this->media->setCustomProperty('progress', $percentage); |
|
$this->media->save(); |
|
} |
|
}); |
|
|
|
$format->setAudioCodec(config('medialibrary.audio_codec', 'libvo_aacenc')) |
|
->setKiloBitrate(1000) |
|
->setAudioChannels(2) |
|
->setAudioKiloBitrate(256); |
|
|
|
$video->save($format, $newFileFullPath); |
|
} catch (\Exception $e) { |
|
$this->media->setCustomProperty('status', Media::MEDIA_STATUS_FAILED); |
|
$this->media->setCustomProperty('error', $e->getMessage()); |
|
$this->media->save(); |
|
} |
|
|
|
|
|
} |
|
|
|
/** |
|
* @param $originalFilePath |
|
* @param $convertedFilePath |
|
*/ |
|
protected function mediaConvertingCompleted($originalFilePath, $convertedFilePath) |
|
{ |
|
if (file_exists($originalFilePath)) { |
|
unlink($originalFilePath); |
|
} |
|
$this->media->file_name = pathinfo($convertedFilePath, PATHINFO_BASENAME); |
|
$this->media->mime_type = MediaLibraryFileHelper::getMimetype($convertedFilePath); |
|
$this->media->size = filesize($convertedFilePath); |
|
$this->media->setCustomProperty('status', Media::MEDIA_STATUS_READY); |
|
$this->media->setCustomProperty('progress', 100); |
|
$this->media->save(); |
|
} |
|
|
|
|
|
/** |
|
* Is media a video? |
|
* |
|
* @return bool |
|
*/ |
|
protected function isVideo() |
|
{ |
|
return (strpos($this->media->mime_type, 'video') !== false); |
|
} |
|
|
|
|
|
} |
This is just an example of how to proceed with the media conversion on upload. For better performance and handling more options, I suggest using a separate service layer in your application and have parameters set in the listener to your service. If you are using your solution to store the media data you can create your own events and listen to it. eg. in my current solutions I have separated servers with the same Redis queue. When a new video added I triggering the job "ConvertMedia" and listen to it on transcoding microservices and then converting it. I can pass parameters to the job if it is necessary.