Skip to content

Instantly share code, notes, and snippets.

@Nks
Last active September 20, 2024 02:07
Show Gist options
  • Save Nks/b3b1cd7398a560eda8ddb7e37901869e to your computer and use it in GitHub Desktop.
Save Nks/b3b1cd7398a560eda8ddb7e37901869e to your computer and use it in GitHub Desktop.
Laravel Media Library converting video to .mp4 after saving

How it works?

This listener will convert your .mov, .avi and a lot of others video files to .mp4 after adding media with https://github.com/spatie/laravel-medialibrary Feel free ask anything what you want.

Requirements

Usage

In EventServiceProvider add your event listener for spatie/laravel-medialibrary

protected $listen = [
        'Spatie\MediaLibrary\Events\MediaHasBeenAdded' => [
            'App\Listeners\MediaVideoConverterListener'
        ],

Want use other audio codec? Rewrite it by yourself in the code or just add this in the medialibrary.php config file with your codec.

'audio_codec'                 => 'libvo_aacenc',

Configure supervisord with following instructions:

[program:laravel_queue]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /home/vagrant/Code/laravel/artisan queue:work --timeout=0 --memory=512 --tries=3
autostart=true
autorestart=true
user=vagrant
numprocs=3
redirect_stderr=true
stdout_logfile=/home/vagrant/Code/laravel/storage/logs/default_queue.log

numprocs <- How many queues will be work on your server. Depends on your memory, CPU, etc. This is how many files will processing in one time. By default 3 video files will be in processing. --memory=512 <- by default it's 128 Mb

<?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);
}
}
@nmriahi
Copy link

nmriahi commented Jan 31, 2021

Hi, thanks for this great listener.
I have a question: Does it work also with Amazon S3 upload feature of the Meadia library package?

As far as I know, FFMPEG does not support uploading files to s3 or google cloud storage. But you can simply implement functionality where after store the file locally you running the method which will upload converted files to AWS or GCS or anywhere where you want.

Thank you for your quick reply @Nks. Actually I'm using Amazon S3 as the file driver of spatie media library package. I think as you said, I should use your event listener, before triggering the upload to S3 process.

@Nks
Copy link
Author

Nks commented Jan 31, 2021

Hi, thanks for this great listener.
I have a question: Does it work also with Amazon S3 upload feature of the Meadia library package?

As far as I know, FFMPEG does not support uploading files to s3 or google cloud storage. But you can simply implement functionality where after store the file locally you running the method which will upload converted files to AWS or GCS or anywhere where you want.

Thank you for your quick reply @Nks. Actually I'm using Amazon S3 as the file driver of spatie media library package. I think as you said, I should use your event listener, before triggering the upload to S3 process.

Technically this is easy to implement with a separated service:

  • Listen for the upload and call the event listener.
  • Separate from your listener with the separated service. eg. you can wrong a new Job and dispatch it from the current event listener. You still can use event listener in any case. Make sure that you are using a long queue. Do not use a default laravel timeout for the queue and keep your eyes on the retry_after option for your queue. It should be more than you expect that your job will work. I suggest using a separated queue and launch your job on a separated long polling queue (I'm using a 43200 timeouts for my jobs).
  • After converting the video file upload file to the s3 or to the google cloud storage (I'm using Google Cloud Storage). If you converting big files to the HLS better to use parallel uploading. I suggest using gsutils with the Symfony process package. You can launch it like this:
    gsutil -q -m -o Credentials:gs_service_key_file=/var/www/storage/gcs/your-file.json -o GSUtil:parallel_process_count=8 cp /path/to/hls/files/ gs://storage/path/. For AWS you can use your command tools like described here. If s3 supports upload files with parallel processing it should give you the fastest solution instead of upload a lot of files one by one.
  • Update your media model that you uploaded file to s3/gcs. You can store the information in meta.

@nmriahi
Copy link

nmriahi commented Jan 31, 2021

@Nks Thank you very much for your great helo. I woudld appreciate if I can have your code blocks for interacting with GCS.

@nmriahi
Copy link

nmriahi commented Feb 15, 2021

@Nks One other question:
As I understand, you have a Media model in your code with App\Media namespace which has these constants in it: MEDIA_STATUS_TO_CONVERT, MEDIA_STATUS_READY, MEDIA_STATUS_PROCESSING, MEDIA_VIDEO_EXT.
Because I don't have access to that model, replaced it with 'Spatie\MediaLibrary\Models\Media' in my listener, which doesn't have those constants.
Can you please guide me how to resolve the issue?

@dnnp2011
Copy link

dnnp2011 commented Jun 16, 2021

To whom it may concern:

use Spatie\MediaLibrary\Helpers\File as MediaLibraryFileHelper;

should now be (^9.0)

use Spatie\MediaLibrary\Support\File as MediaLibraryFileHelper;

@me-devms
Copy link

MEDIA_STATUS_TO_CONVERT, MEDIA_STATUS_READY, MEDIA_STATUS_PROCESSING, MEDIA_VIDEO_EXT.

Any Solution for these constants ?

@Nks
Copy link
Author

Nks commented Feb 16, 2022

@ilikewebsite you can use your own constants that you can define in your app.

@horizonvert1027
Copy link

@Nks I have to play .mov file in Laravel. Uploading is running well. but I can't play .mov file.
How to play .mov file with this library?

@Nks
Copy link
Author

Nks commented Jan 25, 2023

@horizonvert1027, you need to convert the file to any playable file in the browser. I suggest using mp4 format for this and convert it to mp4 from mov using ffmpeg: ffmpeg -i input.mov -qscale 0 output.mp4

The provided solution should already be able to convert any supported files to mp4 though.

@horizonvert1027
Copy link

@Nks, Thank you for your reply.
I have made a Laravel framework. But I don't know what to do. Can you send me your contact information?
My e-mail is [email protected]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment