Skip to content

Add Dimensions to Image and Video Uploads in Laravel

Blog  ✺  Laravel
Stylized mid journey image for Add Image and Video dimensions in Laravel

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
💡
This does not install ffmpeg on your system. Use 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();
}
💡
We use 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.


Before you go, please let me know on X (formerly Twitter) what other tutorials you would like to read? Sign up to my newsletter to get more tutorials.

Comments

You might like

Calm: Peaceful Pictures

Opinion  ✺  Photography

Background File Upload in Filament Forms

Filament  ✺  Video