Blurhash with Laravel Spatie Media Library

If you don't know BlurHash, it's a way to generate blurred placeholders for images that are efficiently storable in a DB because of their size of 20—30 characters. It's really a great tool and I use it as a placeholder for images in my apps. It makes the transition smoother until the image is loaded.

Integration with Laravel

To work with BlurHash in Laravel, I use the bepsvpt/blurhash package which exposes a Facade to make it easy to work in Laravel.

You can then generate the BlurHash from a file like this:


_10
use Bepsvpt\Blurhash\Facades\BlurHash;
_10
_10
// From a local file path
_10
BlurHash::encode($path); // LEHV6nWB2yk8pyo0adR*.7kCMdnj
_10
_10
// From an uploaded path
_10
BlurHash::encode($request->file('avatar')); // LEHV6nWB2yk8pyo0adR*.7kCMdnj

Integration with Spatie Media Library

Spatie Media Library is certainly the best media library out there in the Laravel ecosystem. They have so many good & well-thought features it'd be a crime not to use it in your app if you links files to your models.

I wanted to generate a BlurHash for my media and my first idea was to use an Event Listener.

Media Library fires a few events that we can listen for. So my first implementation looked like this:

app/Listeners/MediaBlurhasher.php

_28
<?php
_28
_28
namespace App\Listeners;
_28
_28
use Bepsvpt\Blurhash\Facades\BlurHash;
_28
use Spatie\MediaLibrary\MediaCollections\Events\MediaHasBeenAddedEvent;
_28
_28
class MediaBlurhasher
_28
{
_28
/**
_28
* Create the event listener.
_28
*/
_28
public function __construct()
_28
{
_28
//
_28
}
_28
_28
/**
_28
* Handle the event.
_28
*/
_28
public function handle(MediaHasBeenAddedEvent $event): void
_28
{
_28
$media = $event->media;
_28
_28
$hash = BlurHash::encode($media->getPath());
_28
$media->setCustomProperty('blurhash', $hash);
_28
}
_28
}

This worked fine, until I changed my media driver to cloud driver.

The $media->getPath() would return a local path, while the file would only exists on the cloud driver. This will throw an error as the BlurHash library will try to read the file that doesn't exist.

My first idea was to download the file content, create a new temporary file and use that file path to generate the BlurHash but that felt wrong. It'd make unecessary network requests and add some unecessary delay to the request. Ideally I'd reuse the original file so that I don't have to download it again just after uploading it.

So I changed my implementation, and instead of using a Listener where the uploaded file is not available, I'll generate the BlurHash where I upload my file, in my Controller.

app/Http/Controllers/PageController.php

_19
use App\Models\Page;
_19
use Bepsvpt\Blurhash\Facades\BlurHash;
_19
_19
class PageController extends Controller
_19
{
_19
public function store(StoreRequest $request, Page $page)
_19
{
_19
if ($request->hasFile('avatar')) {
_19
$page
_19
->addMedia($request->file('avatar'))
_19
->withCustomProperties([
_19
'blurhash' => BlurHash::encode($request->file('avatar')),
_19
])
_19
->toMediaCollection('avatar');
_19
}
_19
_19
// ...
_19
}
_19
}

And that wraps it! We can now get the blurhash from the media.