An open API service indexing awesome lists of open source software.

https://github.com/elegantengineeringtech/laravel-media

Extremely powerful media library for Laravel 🖼️
https://github.com/elegantengineeringtech/laravel-media

images laravel media php upload videos

Last synced: 7 months ago
JSON representation

Extremely powerful media library for Laravel 🖼️

Awesome Lists containing this project

README

          

# Extremely powerful media library for Laravel 🖼️

[![Latest Version on Packagist](https://img.shields.io/packagist/v/elegantly/laravel-media.svg?style=flat-square)](https://packagist.org/packages/elegantly/laravel-media)
[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/ElegantEngineeringTech/laravel-media/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3Arun-tests+branch%3Amain)
[![GitHub Coverage Action Status](https://img.shields.io/github/actions/workflow/status/ElegantEngineeringTech/laravel-media/coverage.yml?branch=main&label=coverage&style=flat-square)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3Acoverage+branch%3Amain)
[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/ElegantEngineeringTech/laravel-media/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
[![Total Downloads](https://img.shields.io/packagist/dt/elegantly/laravel-media.svg?style=flat-square)](https://packagist.org/packages/elegantly/laravel-media)

This package offers an extremely flexible media library, enabling you to store any type of file along with their conversions.

It provides advanced features such as:

- 🌐 Supports any filesystem solutions (local or cloud), such as S3, R2, Bunny.net, DO...
- ⚡ Supports any file conversion solutions (local or cloud), such as ffmpeg, Transloadit, Cloudflare, Coconut, and others.
- 🔄 Advanced nested media conversions
- 🚀 Rich metadata automatically extracted
- 🛠️ Highly flexible and customizable

I developed this package with the highest degree of flexibility possible and I have been using it in production for nearly two years, handling terabytes of files monthly.

## Table of Contents

1. [Requirements](#requirements)

1. [Installation](#installation)

1. [Basic Usage](#basic-usage)

- [Define Media Collection](#defining-media-collections)
- [Define Media Conversions](#defining-media-conversions)
- [Adding Media](#adding-media)
- [Retreiving Media](#adding-media)
- [Media properties](#media-properties)
- [Accessing Media Conversions](#accessing-media-conversions)
- [Blade components](#blade-components)

1. [Advanced Usage](#advanced-usage)

- [Transforming a file before storing it](#transforming-a-file-before-storing-it)
- [Async vs Sync conversions](#async-vs-sync-conversions)
- [Delayed conversions](#delayed-conversions)
- [`onAdded` MediaCollection Callback](#onadded-mediacollection-callback)
- [`onCompleted` MediaConversionDefinition Callback](#oncompleted-mediaconversiondefinition-callback)
- [Regenerating Child Conversions When a Parent Runs](#regenerating-child-conversions-when-a-parent-runs)
- [Custom conversions](#custom-conversions)
- [Manually generate conversions](#manually-generate-conversions)
- [Format Media Url](#format-media-url)

1. [Conversions Presets](#conversions-presets)

- [Image Placeholder](#image-placeholder)

1. [Customization](#customization)

- [Custom Media Model](#custom-media-model)

1. [Troubleshooting](#troubleshooting)
- [Ghostscript and Imagick Issues](#ghostscript-and-imagick-issues)

## Requirements

- PHP 8.1+
- Laravel 11.0+
- `spatie/image` for image conversions
- `spatie/pdf-to-image` for PDF to image conversions
- `ffmpeg` for video/audio processing

## Installation

You can install the package via composer:

```bash
composer require elegantly/laravel-media
```

You have to publish and run the migrations with:

```bash
php artisan vendor:publish --tag="media-migrations"
php artisan migrate
```

You can publish the config file with:

```bash
php artisan vendor:publish --tag="media-config"
```

This is the contents of the published config file:

```php
use Elegantly\Media\Jobs\DeleteModelMediaJob;
use Elegantly\Media\Models\Media;
use Elegantly\Media\Models\MediaConversion;
use Elegantly\Media\PathGenerators\UuidPathGenerator;
use Elegantly\Media\UrlFormatters\DefaultUrlFormatter;

return [
/**
* The media model.
* Define your own model here by extending \Elegantly\Media\Models\Media::class.
*/
'model' => Media::class,

/**
* The MediaConversion model.
* Define your own model here by extending \Elegantly\Media\Models\MediaConversion::class.
*/
'media_conversion_model' => MediaConversion::class,

/**
* The path used to store temporary file copies for conversions.
* This will be used with the storage_path() function.
*/
'temporary_storage_path' => 'app/tmp/media',

/**
* The default disk used for storing files.
*/
'disk' => env('MEDIA_DISK', env('FILESYSTEM_DISK', 'local')),

/**
* Determine if media should be deleted with the model
* when using the HasMedia Trait.
*/
'delete_media_with_model' => true,

/**
* Determine if media should be deleted with the model
* when it is soft deleted.
*/
'delete_media_with_trashed_model' => false,

/**
* Job class responsible for deleting media when the model is deleted.
* This helps with performance and monitoring by queuing media deletions.
*/
'delete_media_with_model_job' => DeleteModelMediaJob::class,

/**
* The default collection name assigned media.
*/
'default_collection_name' => 'default',

/**
* The default URL formatter class.
* Used when calling `$media->getUrl()`.
*/
'default_url_formatter' => DefaultUrlFormatter::class,

/**
* The default path generator class.
* Used when storing new files.
*/
'default_path_generator' => UuidPathGenerator::class,

/**
* Prefix for the generated file path.
* Set to null to disable the prefix.
* Override the generateBasePath method in the Media model for full customization.
*/
'generated_path_prefix' => null,

/**
* Queue connection name to use when dispatching media conversion jobs.
*/
'queue_connection' => env('QUEUE_CONNECTION', 'sync'),

/**
* Queue name to use for media conversion jobs.
* Set to null to use the default Laravel queue.
*/
'queue' => null,

/**
* Configuration for FFmpeg processing.
*/
'ffmpeg' => [
/**
* The binary path to the FFmpeg executable.
*/
'ffmpeg_binaries' => env('FFMPEG_BINARIES', 'ffmpeg'),

/**
* The binary path to the FFprobe executable.
*/
'ffprobe_binaries' => env('FFPROBE_BINARIES', 'ffprobe'),

/**
* Optional log channel for FFmpeg operations.
* Set to null to disable logging.
*/
'log_channel' => null,
],
];
```

Optionally, you can publish the views using

```bash
php artisan vendor:publish --tag="media-views"
```

## Basic Usage

### Defining Media Collections

Media Collections define how media are stored, transformed, and processed for a specific model. They provide granular control over file handling, accepted types, and transformations.

To associate a media collection with a Model, start by adding the `InteractWithMedia` interface and the `HasMedia` trait.

Next, define your collections in the `registerMediaCollections` method, as shown below:

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;
use Elegantly\Media\MediaCollection;

class Channel extends Model implements InteractWithMedia
{
use HasMedia;

public function registerMediaCollections(): array;
{
return [
new MediaCollection(
name: 'avatar',
single: true, // If true, only the latest file will be kept
disk: 's3', // (optional) Specify where the file will be stored
acceptedMimeTypes: [ // (optional) Specify accepted file types
'image/jpeg',
'image/png',
'image/webp'
]
)
];
}
}
```

### Defining Media Conversions

Media conversions create different variants of your media files. For example, a 720p version of a 1440p video or a WebP or PNG version of an image are common types of media conversions. Interestingly, a media conversion can also have its own additional conversions.

This package provides common converter to simplify your work:

- `MediaImageConverter`: This converter optimizes, resizes, or converts any image using `spatie/image`.
- `MediaMp4Converter`: This conversion optimizes, resizes, or converts any video or gif to mp4 video using `ffmpeg`.
- `MediaWebmConverter`: This conversion optimizes, resizes, or converts any video or gif to webm video using `ffmpeg`.
- `MediaWavConverter`: This conversion optimizes, resizes, converts or extract any audio in wav format using `ffmpeg`.
- `MediaMp3Converter`: This conversion optimizes, resizes, converts or extract any audio in mp3 format using `ffmpeg`.
- `MediaFrameConverter`: This conversion extracts a frame from a video using `ffmpeg`.
- `MediaPdfToImageConverter`: This conversion extracts an image from the PDF using `spatie/pdf-to-image`.

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;
use Elegantly\Media\MediaCollection;
use Elegantly\Media\MediaConversionDefinition;
use Elegantly\Media\Converters\MediaMp4Converter;
use Elegantly\Media\Converters\MediaFrameConverter;
use Elegantly\Media\Converters\MediaImageConverter;

class Channel extends Model implements InteractWithMedia
{
use HasMedia;

public function registerMediaCollections(): array;
{
return [
new MediaCollection(
name: 'videos',
conversions: [
new MediaConversionDefinition(
name: 'poster',
converter: fn ($media) => new MediaFrameConverter(
media: $media,
filename: "{$media->name}-thumbnail.jpg",
timecode: 0,
),
conversions: [
new MediaConversionDefinition(
name: '360p',
converter: fn ($media) => new MediaImageConverter(
media: $media,
filename: "{$media->name}.jpg"
width: 360
)
),
],
),
new MediaConversionDefinition(
name: '720p',
converter: fn ($media) => new MediaMp4Converter(
media: $media,
filename: "{$media->name}.mp4"
width: 720
)
),
]
)
];
}
}
```

### Adding Media

Add media to your model, using the `addMedia` method, from various sources:

- an url
- a resource or stream
- a `\Illuminate\Http\UploadedFile` instance
- a `\Illuminate\Http\File` instance

#### From a Controller

```php
use Elegantly\Media\Exceptions\InvalidMimeTypeException;

public function store(Request $request, Channel $channel)
{
try {
$channel->addMedia(
file: $request->file('avatar'),
collectionName: 'avatar',
name: "{$channel->name}-avatar"
);
} catch (InvalidMimeTypeException $exception){
// Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.
}
}
```

#### From a Livewire Component

```php
use Livewire\WithFileUploads;
use Elegantly\Media\Exceptions\InvalidMimeTypeException;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;

class ImageUploader extends Component
{
use WithFileUploads;

/** @var ?TemporaryUploadedFile */
public $avatar = null;

public function save()
{
try {
$this->channel->addMedia(
file: $this->avatar->getRealPath(),
collectionName: 'avatar',
name: "{$this->channel->name}-avatar"
);
} catch (InvalidMimeTypeException $exception){
// Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.
}
}
}
```

#### From an Url

```php
use Elegantly\Media\Exceptions\InvalidMimeTypeException;

try {
$channel->addMedia(
file: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
collectionName: 'videos',
name: "BigBuckBunny"
);
} catch (InvalidMimeTypeException $exception){
// Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.
}
```

### Retreiving Media

Retrieve media from your model:

```php
// Get all media from a specific collection
$avatars = $channel->getMedia('avatar');

// Get the first media from a collection
$avatar = $channel->getFirstMedia('avatar');

// Check if media exists
$hasAvatar = $channel->hasMedia('avatar');
```

### Media properties

Each media item provides rich metadata automatically:

```php
$media = $channel->getFirstMedia('avatar');

// File properties
$media->name; // file_name without the extension
$media->file_name;
$media->extension;
$media->mime_type;
$media->size; // in bytes
$media->humanReadableSize();

// Image/Video specific properties
$media->width; // in pixels
$media->height; // in pixels
$media->aspect_ratio;
$media->duration; // for video/audio
```

You can use dot notation to access either the root properties or a specific conversion:

```php
// Get the original media URL
$originalUrl = $media->getUrl();

// Get a specific conversion URL
$thumbnailUrl = $media->getUrl(
conversion: '360p',
fallback: true // Falls back to original if conversion doesn't exist
);

$posterUrl = $media->getUrl(
conversion: 'poster.360p',
fallback: 'poster' // Falls back to an other conversion if conversion doesn't exist
);

// Use the same logic with other properties such as
$media->getPath();
$media->getWidth();
// ...
```

### Access Media Conversions

To directly access conversions, use:

```php
// Check if a conversion exists
$hasThumbnail = $media->hasConversion('100p');

// Get a specific conversion
$thumbnailConversion = $media->getConversion('100p');

// Get the 'poster' conversion
$media->getParentConversion('poster.360p');

// Only get children conversions of poster
$media->getChildrenConversions('poster');
```

### Blade components

The package also provides blade components.

```html

```

```html

```

## Advanced Usage

### Transforming a file before storing it

You can define a transformation step that runs before a file is stored by using the `transform` method on a media collection.

This is useful if you want to **resize, optimize, or process an uploaded file** before it gets saved to disk.

For example, the snippet below shows how you can resize and optimize an uploaded image before storing it:

```php
use Illuminate\Http\File;
use Spatie\TemporaryDirectory\TemporaryDirectory;
use Spatie\Image\Image;
use Spatie\Image\Enums\Fit;

new MediaCollection(
name: 'avatar',
acceptedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
transform: function (File $file, TemporaryDirectory $temporaryDirectory): File {
$input = $file->getRealPath();
$output = $temporaryDirectory->path('avatar.jpg');

Image::load($input)
->fit(Fit::Contain, 500)
->optimize()
->save($output);

return new File($output);
}
);
```

### Async vs. Sync Conversions

When adding new media, its conversions can be either dispatched asynchronously or generated synchronously.

You can configure the strategy in the conversion definition using the `queued` and `queue` parameters:

```php
new MediaCollection(
name: 'avatar',
conversions: [
new MediaConversionDefinition(
name: '360p',
queued: true, // (default) Dispatch as a background job
queue: 'slow' // (optional) Specify a custom queue
converter: fn ($media) => new MediaImageConverter(
media: $media,
filename: "{$media->name}.jpg"
width: 360
)
),
new MediaConversionDefinition(
name: '180p',
queued: false, // Generate the conversion synchronously
converter: fn ($media) => new MediaImageConverter(
media: $media,
filename: "{$media->name}.jpg"
width: 180
)
),
]
)
```

Synchronous conversions can be particularly useful in specific use cases, such as generating a poster immediately upon upload.

### Delayed Conversions

There are scenarios where you might want to define conversions that should not be generated immediately. For instance, if a conversion is resource-intensive or not always required, you can defer its generation to a later time.

To achieve this, configure the conversion with the `immediate` parameter set to `false`. This allows you to generate the conversion manually when needed:

```php
new MediaCollection(
name: 'avatar',
conversions: [
new MediaConversionDefinition(
name: '360p',
immediate: false, // Conversion will not be generated at upload time
converter: fn ($media) => new MediaImageConverter(
media: $media,
filename: "{$media->name}.jpg"
width: 360
)
),
]
)
```

To generate the conversion later, you can use the following methods:

```php
// Generate the conversion synchronously
$media->executeConversion(
conversion: '360',
force: false // Skips execution if the conversion already exists
);

// Dispatch the conversion as a background job
$media->dispatchConversion(
conversion: '360',
force: false // Skips execution if the conversion already exists
);
```

### `onAdded` MediaCollection Callback

The `onAdded` callback allows you to define custom logic that will be executed whenever new media is added to your collection.

To use it, simply set the `onAdded` parameter when defining a `MediaCollection`. For example:

```php
new MediaCollection(
name: 'avatar',
onAdded: function ($media) {
// Example: Notify the model when new media is added
// $media->model->notify(new MediaAddedNotification($media));
}
);
```

With this, you can easily hook into the media addition process and trigger actions like sending notifications, logging, or other custom behavior.

> [!TIP]
> The same behavior can be achieved by listening to `Elegantly\Media\Events\MediaAddedEvent`.

### `onCompleted` MediaConversionDefinition Callback

The `onCompleted` callback allows you to define custom logic that will be executed whenever a new conversion is generated.

To use it, simply set the `onCompleted` parameter when defining a `MediaConversionDefinition`. For example:

```php
new MediaConversionDefinition(
name: '360',
onCompleted: function ($conversion, $media, $parent) {
// Example: Refresh your UI
// broadcast(new MyEvent($media));
}
);
```

This allows you to hook into the conversion process and execute additional logic, such as updating your UI or triggering other actions.

> [!TIP]
> The same behavior can be achieved by listening to `Elegantly\Media\Events\MediaConversionAddedEvent`.

### Regenerating Child Conversions When a Parent Runs

In some cases, you may want all **child conversions** of a given conversion to be **regenerated whenever the parent conversion is re-executed**.

There are two ways to achieve this, depending on where you handle conversions:

#### From the conversion definition

You can hook into the `onCompleted` callback of a conversion definition and instruct Laravel to delete all child conversions when the parent finishes.

```php
new MediaConversionDefinition(
name: '360',
onCompleted: function (?MediaConversion $conversion, Media $media, ?MediaConversion $parent) {
if ($conversion) {
// Delete any children so they’ll be regenerated automatically
$media->deleteChildrenConversions($conversion->conversion_name);
}
}
);
```

#### From `addConversion`

When adding a new conversion programmatically, you can explicitly tell it to remove children and regenerate them automatically:

```php
$mediaConversion = $media->addConversion(
file: $file,
deleteChildren: true, // ensures child conversions are refreshed
);

// regenerate children conversion
$this->media->generateConversions(
parent: $mediaConversion,
);
```

### Custom Conversions

Conversions can be anything: a variant of a file, a transcription of a video, a completely new file, or even just a string.

You can use built-in presets or define your own custom converter. To create a custom converter, start by creating a new class extending the `MediaConverter` class:

```php
namespace App\Media\Converters\Image;

use Elegantly\Media\Converters\MediaConverter;
use Elegantly\Media\Enums\MediaType;
use Elegantly\Media\Models\Media;
use Elegantly\Media\Models\MediaConversion;
use Illuminate\Contracts\Filesystem\Filesystem;
use Spatie\Image\Enums\Fit;
use Spatie\Image\Image;
use Spatie\ImageOptimizer\OptimizerChain;
use Spatie\TemporaryDirectory\TemporaryDirectory as SpatieTemporaryDirectory;

class MediaImageConverter extends MediaConverter
{
public function __construct(
public readonly Media $media,
public string $filename,
public ?int $width = null,
public ?int $height = null,
public Fit $fit = Fit::Max,
public ?OptimizerChain $optimizerChain = null,
) {}

public function shouldExecute(Media $media, ?MediaConversion $parent): bool
{
$source = $parent ?? $media;

return $source->type === MediaType::Image;
}

public function convert(
Media $media,
?MediaConversion $parent,
?string $file,
Filesystem $filesystem,
SpatieTemporaryDirectory $temporaryDirectory
): ?MediaConversion {

if (! $file) {
return null;
}

$input = $filesystem->path($file);
$output = $filesystem->path($this->filename);

Image::load($input)
->fit($this->fit, $this->width, $this->height)
->optimize($this->optimizerChain)
->save($output);

return $media->addConversion(
file: $output,
conversionName: $this->conversion,
parent: $parent,
);

}
}
```

The `convert` method is where the logic for the conversion is implemented. It provides the following parameters:

- **`$media`**: The Media model.
- **`$parent`**: The MediaConversion model, if the conversion is nested.
- **`$file`**: A local copy of the file associated with either `$media` or `$parent`.
- **`$filesystem`**: An instance of the local filesystem where the file copy is stored.
- **`$temporaryDirectory`**: An instance of `TemporaryDirectory` where the file copy is temporarily stored.

You don’t need to worry about cleaning up the files, as the `$temporaryDirectory` will be deleted automatically when the process completes or fails.

To finalize the conversion, ensure you save it by calling `$media->addConversion` or `$media->replaceConversion` at the end of the `handle` method.

### Manually Generate Conversions

You can manage your media conversions programmatically using the following methods:

```php
// Store a new file as a conversion
$media->addConversion(
file: $file, // Can be an HTTP File, URL, or file path
conversionName: 'transcript',
parent: $mediaConversion // (Optional) Specify a parent conversion
// Additional parameters...
);

// Replace an existing conversion safely
// If the same conversion already exists, it ensures the new file is stored before deleting the previous one.
$media->replaceConversion(
conversion: $mediaConversion
);

// Safely delete a specific conversion and all its children
$media->deleteConversion('360');

// Safely delete only the child conversions of a parent conversion
$media->deleteChildrenConversions('poster');

// Dispatch or execute a conversion
$media->dispatchConversion('360'); // Runs asynchronously as a job
$media->executeConversion('poster.360'); // Executes synchronously
$media->getOrExecuteConversion('poster.360'); // Retrieves or generates the conversion

// Retrieve conversion information
$media->getConversion('360'); // Fetch a specific conversion
$media->hasConversion('360'); // Check if a conversion exists
$media->getParentConversion('poster.360'); // Retrieve the parent (poster) of a conversion
$media->getChildrenConversions('poster'); // Retrieve child conversions
```

Additionally, you can use an Artisan command to generate conversions with various options:

```bash
php artisan media:generate-conversions
```

You can also use the following command to retry failed conversions:

```bash
php artisan media-conversions:retry
```

This provides a convenient way to process conversions in bulk or automate them within your workflows.

### Format Media URLs

Some cloud providers like Cloudflare, Bunny, or ImageKit allow you to create instant transformations of your images and videos using specially formatted URLs.

This package gives you a simple way to format your URLs so you can take advantage of these services.

When using the `$media->getUrl()` method, you can specify two parameters:

- `parameters`: An array of values
- `formatter`: The class name of the formatter you want to use

By combining these parameters, you can retrieve formatted URLs like this:

```php
use \Elegantly\Media\UrlFormatters\CloudflareImageUrlFormatter;

// Default formatter (query parameters)
$default = $media->getUrl(
parameters: ['width' => 360],
); // https://your-url.com?width=360

// Cloudflare formatter (path-based format)
$cloudflare = $media->getUrl(
parameters: ['width' => 360],
formatter: CloudflareImageUrlFormatter::class
); // /cdn-cgi/media/width=360/https://your-url.com
```

This package comes with 3 formatters out of the box:

- `\Elegantly\Media\UrlFormatters\DefaultUrlFormatter`
- `\Elegantly\Media\UrlFormatters\CloudflareImageUrlFormatter`
- `\Elegantly\Media\UrlFormatters\CloudflareVideoUrlFormatter`

Feel free to implement your own formatter by extending `\Elegantly\Media\UrlFormatters\AbstractUrlFormatter`.

## Conversions Presets

### Image Placeholder

When loading images on the web, it’s common to show a low-resolution blurred placeholder first, then swap it with the full-resolution image once it’s ready. This improves perceived performance, reduces layout shifts, and gives a polished feel to your UI.

This package includes a ready-to-use preset for generating these placeholders in Base64-encoded format. The placeholder is tiny in size, quick to load, and designed to be displayed while the actual image is being fetched.

#### Defining the Placeholder Conversion

To create a placeholder, simply define a media conversion using the `MediaImagePlaceholderConverter`:

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;
use Elegantly\Media\MediaCollection;
use Elegantly\Media\MediaConversionDefinition;
use Elegantly\Media\Converters\MediaImagePlaceholderConverter;

class User extends Model implements InteractWithMedia
{
use HasMedia;

public function registerMediaCollections(): array;
{
return [
new MediaCollection(
name: 'avatar',
single: true,
conversions: [
new MediaConversionDefinition(
name: 'placeholder',
converter: fn ($media) => new MediaImagePlaceholderConverter($media),
),
]
)
];
}
}
```

#### Using the Placeholder in Your Views

If you’re using the built-in Blade component, you can directly specify the placeholder conversion name:

```html

```

This will automatically load the blurred placeholder as a background-image.

#### Retrieving the Base64 Value Directly

If you need to manually work with the placeholder (e.g., for API responses or inline styles), you can retrieve it like this:

```php
$placeholder = $media->getConversion('placeholder');

$base64Image = $placeholder?->contents;
```

## Customization

### Custom Media Model

You can define your own Media model to use with the library.

First, create your own model class:

```php
namespace App\Models;

use Elegantly\Media\Models\Media as ElegantlyMedia;

class Media extends ElegantlyMedia
{
// ...
}
```

Then, update the `config` file:

```php
use App\Models\Media;

return [

'model' => Media::class,

// ...

];
```

The library is typed with generics, so you can use your own Media model seamlessly:

```php
namespace App\Models;

use App\Models\Media;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;

/**
* @implements InteractWithMedia
*/
class Post extends Model implements InteractWithMedia
{
/** @use HasMedia **/
use HasMedia;

// ...
}
```

## Troubleshooting

### Ghostscript and Imagick Issues

This package relies on the `spatie/pdf-to-image` library, which uses Ghostscript via Imagick to convert PDFs into images.

If you encounter errors while generating images from PDFs, such as:

- `attempt to perform an operation not allowed by the security policy 'PDF'`
- `Uncaught ImagickException: FailedToExecuteCommand 'gs'`

these issues are likely related to the configuration of Ghostscript or Imagick on your system.

For detailed guidance on resolving these errors, refer to the [spatie/pdf-to-image documentation on Ghostscript issues](https://github.com/spatie/pdf-to-image/blob/main/README.md#issues-regarding-ghostscript).

## Testing

```bash
composer test
```

## Changelog

Please see the [CHANGELOG](CHANGELOG.md) for more information on recent changes.

## Contributing

Feel free to open an issue or a discussion.

## Security Vulnerabilities

Please contact [me](https://github.com/QuentinGab) to report security vulnerabilities.

## Credits

- [Quentin Gabriele](https://github.com/QuentinGab)
- [All Contributors](../../contributors)

## License

The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.