The Laravel Medialibrary is a powerful package to manage uploads in your Laravel application. For a project I'm working on, I needed a way to add the image and video dimensions (width and height) as custom properties.
What are we building?
Let's build an Observer
for the Media
model to trigger a background job which sets the image and video dimensions using the spatie/image package (bundled with the media library) for the images and FFProbe
for videos.
The bulk of the work will be done in the UpdateMediaInfoAction
which we will be using in both a UpdateMediaInfoJob
triggered after uploading a file and an artisan UpdateMediaInfoCommand
to get the dimensions on existing uploads.
Observing media events
First, we want to start by observing the Media
model updated
event. Let's create the class by running:
php artisan make:observer MediaObserver -m=Media
In the newly created class, add the following code. Don't worry yet the job yet, we will be creating it soon.
To make sure the observer is actually called, add the following to your AppServiceProvider
in the boot
method:
Media::observe(MediaObserver::class);
Creating an action to update the media
There is currently no artisan command to make an Action
, but you can use the following instead:
php artisan make:class Actions/Media/UpdateMediaInfoAction
In order for FFProbe
to work, you need to install the PHP-FFMpeg using composer:
composer require php-ffmpeg/php-ffmpeg
which ffprobe
to see if it's already installed and get the proper path for it.In the media library config, you can set the paths to both ffmpeg
and ffprobe
. Add the following to your .env
file, but make sure to use the correct paths. For me using homebrew locally, these are the paths:
FFPROBE_PATH=/opt/homebrew/bin/ffprobe
FFMPEG_PATH=/opt/homebrew/bin/ffmpeg
Now let's fill the UpdateMediaInfoAction
class with the following. We will go through it below:
First, we check if the Media
item is actually a video or an image by checking the mime_type
property.
When dealing with images, we can use the Image
class already bundled with the media library package:
protected function getImageDimensions(string $path): array
{
$image = Image::load($path);
return [
'width' => $image->getWidth(),
'height' => $image->getHeight(),
];
}
For the videos, we will be using FFProbe
to figure out the dimensions and the duration of the video:
protected function getVideoInfo(string $path): array
{
$ffprobe = FFProbe::create([
'ffprobe.binaries' => config('media-library.ffprobe_path'),
]);
$dimensions = $ffprobe
->streams($path)
->videos()
->first()
->getDimensions();
$duration = $ffprobe
->format($path)
->get('duration');
return [
'dimensions' => [
'width' => $dimensions->getWidth(),
'height' => $dimensions->getHeight(),
],
'duration' => floatval($duration),
];
}
As you can see, we are using the ffprobe_path
from the media library config that we previously set in our .env
file.
Let's say this all worked, and we now have an array of custom properties to be set on the Media
item. Here is how we set them:
protected function updateMediaProperties(Media $media, array $properties): void
{
foreach ($properties as $key => $value) {
$media->setCustomProperty($key, $value);
}
$media->saveQuietly();
}
saveQuietly()
to not trigger the updated
event again.Background job to update the media
I opted for a queued job because, it can take a moment to get the dimensions of large images or videos. Create the job using:
php artisan make:job UpdateMediaInfoJob
In the newly created job class, add the following code:
As you can see, we check again if there are dimensions on the Media
item, just to be sure. I'm using the #[DeleteWhenMissingModels]
attribute to drop the job if the model has been deleted meanwhile.
Going forward, all newly uploaded images and videos get custom properties. Here is an example for an image:
{"dimensions": {"width": 1080, "height": 1080}}
This is how it looks in a video. The duration is in seconds:
{"duration": 1.52, "dimensions": {"width": 1080, "height": 1920}}
Artisan command to update existing media
Let's assume you already have some media items, I know I did. To process existing media items, let's create an artisan command.
php artisan make:command UpdateMediaInfoCommand
We will have a nice progress bar and after the command finishes, there is a table with a summary. Here is the code for the command:
That's it! You can now easily set the dimensions on both images and videos. If you don't use the wonderful spatie/laravel-medialibrary package, you should be able to extract the code from the action class to get the dimensions.