{"id":27145461,"url":"https://github.com/elegantengineeringtech/laravel-media","last_synced_at":"2026-01-19T22:07:45.721Z","repository":{"id":213681740,"uuid":"695893595","full_name":"ElegantEngineeringTech/laravel-media","owner":"ElegantEngineeringTech","description":"Extremely powerful media library for Laravel 🖼️","archived":false,"fork":false,"pushed_at":"2024-11-23T14:06:14.000Z","size":1207,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-11-24T19:52:57.990Z","etag":null,"topics":["images","laravel","media","php","upload","videos"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ElegantEngineeringTech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"ElegantEngineeringTech"}},"created_at":"2023-09-24T14:53:19.000Z","updated_at":"2024-11-23T14:06:17.000Z","dependencies_parsed_at":"2024-04-03T18:00:52.243Z","dependency_job_id":"3cfdf9bc-3a96-4bef-a2b1-261917c70afc","html_url":"https://github.com/ElegantEngineeringTech/laravel-media","commit_stats":null,"previous_names":["finller/laravel-media","elegantengineeringtech/laravel-media"],"tags_count":83,"template":false,"template_full_name":"spatie/package-skeleton-laravel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElegantEngineeringTech%2Flaravel-media","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElegantEngineeringTech%2Flaravel-media/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElegantEngineeringTech%2Flaravel-media/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElegantEngineeringTech%2Flaravel-media/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ElegantEngineeringTech","download_url":"https://codeload.github.com/ElegantEngineeringTech/laravel-media/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247818862,"owners_count":21001385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["images","laravel","media","php","upload","videos"],"created_at":"2025-04-08T09:48:41.739Z","updated_at":"2026-01-04T00:25:31.426Z","avatar_url":"https://github.com/ElegantEngineeringTech.png","language":"PHP","funding_links":["https://github.com/sponsors/ElegantEngineeringTech"],"categories":[],"sub_categories":[],"readme":"# Extremely powerful media library for Laravel 🖼️\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/elegantly/laravel-media.svg?style=flat-square)](https://packagist.org/packages/elegantly/laravel-media)\n[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/ElegantEngineeringTech/laravel-media/run-tests.yml?branch=main\u0026label=tests\u0026style=flat-square)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3Arun-tests+branch%3Amain)\n[![GitHub Coverage Action Status](https://img.shields.io/github/actions/workflow/status/ElegantEngineeringTech/laravel-media/coverage.yml?branch=main\u0026label=coverage\u0026style=flat-square)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3Acoverage+branch%3Amain)\n[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/ElegantEngineeringTech/laravel-media/fix-php-code-style-issues.yml?branch=main\u0026label=code%20style\u0026style=flat-square)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3A\"Fix+PHP+code+style+issues\"+branch%3Amain)\n[![Total Downloads](https://img.shields.io/packagist/dt/elegantly/laravel-media.svg?style=flat-square)](https://packagist.org/packages/elegantly/laravel-media)\n\nThis package offers an extremely flexible media library, enabling you to store any type of file along with their conversions.\n\nIt provides advanced features such as:\n\n-   🌐 Supports any filesystem solutions (local or cloud), such as S3, R2, Bunny.net, DO...\n-   ⚡ Supports any file conversion solutions (local or cloud), such as ffmpeg, Transloadit, Cloudflare, Coconut, and others.\n-   🔄 Advanced nested media conversions\n-   🚀 Rich metadata automatically extracted\n-   🛠️ Highly flexible and customizable\n\nI 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.\n\n## Table of Contents\n\n1. [Requirements](#requirements)\n\n1. [Installation](#installation)\n\n1. [Basic Usage](#basic-usage)\n\n    - [Define Media Collection](#defining-media-collections)\n    - [Define Media Conversions](#defining-media-conversions)\n    - [Adding Media](#adding-media)\n    - [Retreiving Media](#adding-media)\n    - [Media properties](#media-properties)\n    - [Accessing Media Conversions](#accessing-media-conversions)\n    - [Blade components](#blade-components)\n\n1. [Advanced Usage](#advanced-usage)\n\n    - [Transforming a file before storing it](#transforming-a-file-before-storing-it)\n    - [Async vs Sync conversions](#async-vs-sync-conversions)\n    - [Delayed conversions](#delayed-conversions)\n    - [`onAdded` MediaCollection Callback](#onadded-mediacollection-callback)\n    - [`onCompleted` MediaConversionDefinition Callback](#oncompleted-mediaconversiondefinition-callback)\n    - [Regenerating Child Conversions When a Parent Runs](#regenerating-child-conversions-when-a-parent-runs)\n    - [Custom conversions](#custom-conversions)\n    - [Manually generate conversions](#manually-generate-conversions)\n    - [Format Media Url](#format-media-url)\n\n1. [Conversions Presets](#conversions-presets)\n\n    - [Image Placeholder](#image-placeholder)\n\n1. [Customization](#customization)\n\n    - [Custom Media Model](#custom-media-model)\n\n1. [Troubleshooting](#troubleshooting)\n    - [Ghostscript and Imagick Issues](#ghostscript-and-imagick-issues)\n\n## Requirements\n\n-   PHP 8.1+\n-   Laravel 11.0+\n-   `spatie/image` for image conversions\n-   `spatie/pdf-to-image` for PDF to image conversions\n-   `ffmpeg` for video/audio processing\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require elegantly/laravel-media\n```\n\nYou have to publish and run the migrations with:\n\n```bash\nphp artisan vendor:publish --tag=\"media-migrations\"\nphp artisan migrate\n```\n\nYou can publish the config file with:\n\n```bash\nphp artisan vendor:publish --tag=\"media-config\"\n```\n\nThis is the contents of the published config file:\n\n```php\nuse Elegantly\\Media\\Jobs\\DeleteModelMediaJob;\nuse Elegantly\\Media\\Models\\Media;\nuse Elegantly\\Media\\Models\\MediaConversion;\nuse Elegantly\\Media\\PathGenerators\\UuidPathGenerator;\nuse Elegantly\\Media\\UrlFormatters\\DefaultUrlFormatter;\n\nreturn [\n    /**\n     * The media model.\n     * Define your own model here by extending \\Elegantly\\Media\\Models\\Media::class.\n     */\n    'model' =\u003e Media::class,\n\n    /**\n     * The MediaConversion model.\n     * Define your own model here by extending \\Elegantly\\Media\\Models\\MediaConversion::class.\n     */\n    'media_conversion_model' =\u003e MediaConversion::class,\n\n    /**\n     * The path used to store temporary file copies for conversions.\n     * This will be used with the storage_path() function.\n     */\n    'temporary_storage_path' =\u003e 'app/tmp/media',\n\n    /**\n     * The default disk used for storing files.\n     */\n    'disk' =\u003e env('MEDIA_DISK', env('FILESYSTEM_DISK', 'local')),\n\n    /**\n     * Determine if media should be deleted with the model\n     * when using the HasMedia Trait.\n     */\n    'delete_media_with_model' =\u003e true,\n\n    /**\n     * Determine if media should be deleted with the model\n     * when it is soft deleted.\n     */\n    'delete_media_with_trashed_model' =\u003e false,\n\n    /**\n     * Job class responsible for deleting media when the model is deleted.\n     * This helps with performance and monitoring by queuing media deletions.\n     */\n    'delete_media_with_model_job' =\u003e DeleteModelMediaJob::class,\n\n    /**\n     * The default collection name assigned media.\n     */\n    'default_collection_name' =\u003e 'default',\n\n    /**\n     * The default URL formatter class.\n     * Used when calling `$media-\u003egetUrl()`.\n     */\n    'default_url_formatter' =\u003e DefaultUrlFormatter::class,\n\n    /**\n     * The default path generator class.\n     * Used when storing new files.\n     */\n    'default_path_generator' =\u003e UuidPathGenerator::class,\n\n    /**\n     * Prefix for the generated file path.\n     * Set to null to disable the prefix.\n     * Override the generateBasePath method in the Media model for full customization.\n     */\n    'generated_path_prefix' =\u003e null,\n\n    /**\n     * Queue connection name to use when dispatching media conversion jobs.\n     */\n    'queue_connection' =\u003e env('QUEUE_CONNECTION', 'sync'),\n\n    /**\n     * Queue name to use for media conversion jobs.\n     * Set to null to use the default Laravel queue.\n     */\n    'queue' =\u003e null,\n\n    /**\n     * Configuration for FFmpeg processing.\n     */\n    'ffmpeg' =\u003e [\n        /**\n         * The binary path to the FFmpeg executable.\n         */\n        'ffmpeg_binaries' =\u003e env('FFMPEG_BINARIES', 'ffmpeg'),\n\n        /**\n         * The binary path to the FFprobe executable.\n         */\n        'ffprobe_binaries' =\u003e env('FFPROBE_BINARIES', 'ffprobe'),\n\n        /**\n         * Optional log channel for FFmpeg operations.\n         * Set to null to disable logging.\n         */\n        'log_channel' =\u003e null,\n    ],\n];\n```\n\nOptionally, you can publish the views using\n\n```bash\nphp artisan vendor:publish --tag=\"media-views\"\n```\n\n## Basic Usage\n\n### Defining Media Collections\n\nMedia Collections define how media are stored, transformed, and processed for a specific model. They provide granular control over file handling, accepted types, and transformations.\n\nTo associate a media collection with a Model, start by adding the `InteractWithMedia` interface and the `HasMedia` trait.\n\nNext, define your collections in the `registerMediaCollections` method, as shown below:\n\n```php\nnamespace App\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Elegantly\\Media\\Concerns\\HasMedia;\nuse Elegantly\\Media\\Contracts\\InteractWithMedia;\nuse Elegantly\\Media\\MediaCollection;\n\nclass Channel extends Model implements InteractWithMedia\n{\n    use HasMedia;\n\n    public function registerMediaCollections(): array;\n    {\n        return [\n            new MediaCollection(\n                name: 'avatar',\n                single: true, // If true, only the latest file will be kept\n                disk: 's3', // (optional) Specify where the file will be stored\n                acceptedMimeTypes: [ // (optional) Specify accepted file types\n                    'image/jpeg',\n                    'image/png',\n                    'image/webp'\n                ]\n            )\n        ];\n    }\n}\n```\n\n### Defining Media Conversions\n\nMedia 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.\n\nThis package provides common converter to simplify your work:\n\n-   `MediaImageConverter`: This converter optimizes, resizes, or converts any image using `spatie/image`.\n-   `MediaMp4Converter`: This conversion optimizes, resizes, or converts any video or gif to mp4 video using `ffmpeg`.\n-   `MediaWebmConverter`: This conversion optimizes, resizes, or converts any video or gif to webm video using `ffmpeg`.\n-   `MediaWavConverter`: This conversion optimizes, resizes, converts or extract any audio in wav format using `ffmpeg`.\n-   `MediaMp3Converter`: This conversion optimizes, resizes, converts or extract any audio in mp3 format using `ffmpeg`.\n-   `MediaFrameConverter`: This conversion extracts a frame from a video using `ffmpeg`.\n-   `MediaPdfToImageConverter`: This conversion extracts an image from the PDF using `spatie/pdf-to-image`.\n\n```php\nnamespace App\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Elegantly\\Media\\Concerns\\HasMedia;\nuse Elegantly\\Media\\Contracts\\InteractWithMedia;\nuse Elegantly\\Media\\MediaCollection;\nuse Elegantly\\Media\\MediaConversionDefinition;\nuse Elegantly\\Media\\Converters\\MediaMp4Converter;\nuse Elegantly\\Media\\Converters\\MediaFrameConverter;\nuse Elegantly\\Media\\Converters\\MediaImageConverter;\n\nclass Channel extends Model implements InteractWithMedia\n{\n    use HasMedia;\n\n    public function registerMediaCollections(): array;\n    {\n        return [\n            new MediaCollection(\n                name: 'videos',\n                conversions: [\n                    new MediaConversionDefinition(\n                        name: 'poster',\n                        converter: fn ($media) =\u003e new MediaFrameConverter(\n                            media: $media,\n                            filename: \"{$media-\u003ename}-thumbnail.jpg\",\n                            timecode: 0,\n                        ),\n                        conversions: [\n                            new MediaConversionDefinition(\n                                name: '360p',\n                                converter: fn ($media) =\u003e new MediaImageConverter(\n                                    media: $media,\n                                    filename: \"{$media-\u003ename}.jpg\"\n                                    width: 360\n                                )\n                            ),\n                        ],\n                    ),\n                    new MediaConversionDefinition(\n                        name: '720p',\n                        converter: fn ($media) =\u003e new MediaMp4Converter(\n                            media: $media,\n                            filename: \"{$media-\u003ename}.mp4\"\n                            width: 720\n                        )\n                    ),\n                ]\n            )\n        ];\n    }\n}\n```\n\n### Adding Media\n\nAdd media to your model, using the `addMedia` method, from various sources:\n\n-   an url\n-   a resource or stream\n-   a `\\Illuminate\\Http\\UploadedFile` instance\n-   a `\\Illuminate\\Http\\File` instance\n\n#### From a Controller\n\n```php\nuse Elegantly\\Media\\Exceptions\\InvalidMimeTypeException;\n\npublic function store(Request $request, Channel $channel)\n{\n    try {\n        $channel-\u003eaddMedia(\n            file: $request-\u003efile('avatar'),\n            collectionName: 'avatar',\n            name: \"{$channel-\u003ename}-avatar\"\n        );\n    } catch (InvalidMimeTypeException $exception){\n        // Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.\n    }\n}\n```\n\n#### From a Livewire Component\n\n```php\nuse Livewire\\WithFileUploads;\nuse Elegantly\\Media\\Exceptions\\InvalidMimeTypeException;\nuse Livewire\\Features\\SupportFileUploads\\TemporaryUploadedFile;\n\nclass ImageUploader extends Component\n{\n    use WithFileUploads;\n\n    /** @var ?TemporaryUploadedFile */\n    public $avatar = null;\n\n    public function save()\n    {\n        try {\n            $this-\u003echannel-\u003eaddMedia(\n                file: $this-\u003eavatar-\u003egetRealPath(),\n                collectionName: 'avatar',\n                name: \"{$this-\u003echannel-\u003ename}-avatar\"\n            );\n        } catch (InvalidMimeTypeException $exception){\n            // Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.\n        }\n    }\n}\n```\n\n#### From an Url\n\n```php\nuse Elegantly\\Media\\Exceptions\\InvalidMimeTypeException;\n\n try {\n    $channel-\u003eaddMedia(\n        file: \"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\",\n        collectionName: 'videos',\n        name: \"BigBuckBunny\"\n    );\n} catch (InvalidMimeTypeException $exception){\n    // Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.\n}\n```\n\n### Retreiving Media\n\nRetrieve media from your model:\n\n```php\n// Get all media from a specific collection\n$avatars = $channel-\u003egetMedia('avatar');\n\n// Get the first media from a collection\n$avatar = $channel-\u003egetFirstMedia('avatar');\n\n// Check if media exists\n$hasAvatar = $channel-\u003ehasMedia('avatar');\n```\n\n### Media properties\n\nEach media item provides rich metadata automatically:\n\n```php\n$media = $channel-\u003egetFirstMedia('avatar');\n\n// File properties\n$media-\u003ename; // file_name without the extension\n$media-\u003efile_name;\n$media-\u003eextension;\n$media-\u003emime_type;\n$media-\u003esize; // in bytes\n$media-\u003ehumanReadableSize();\n\n// Image/Video specific properties\n$media-\u003ewidth;       // in pixels\n$media-\u003eheight;      // in pixels\n$media-\u003easpect_ratio;\n$media-\u003eduration;    // for video/audio\n```\n\nYou can use dot notation to access either the root properties or a specific conversion:\n\n```php\n// Get the original media URL\n$originalUrl = $media-\u003egetUrl();\n\n// Get a specific conversion URL\n$thumbnailUrl = $media-\u003egetUrl(\n    conversion: '360p',\n    fallback: true // Falls back to original if conversion doesn't exist\n);\n\n$posterUrl = $media-\u003egetUrl(\n    conversion: 'poster.360p',\n    fallback: 'poster' // Falls back to an other conversion if conversion doesn't exist\n);\n\n// Use the same logic with other properties such as\n$media-\u003egetPath();\n$media-\u003egetWidth();\n// ...\n```\n\n### Access Media Conversions\n\nTo directly access conversions, use:\n\n```php\n// Check if a conversion exists\n$hasThumbnail = $media-\u003ehasConversion('100p');\n\n// Get a specific conversion\n$thumbnailConversion = $media-\u003egetConversion('100p');\n\n// Get the 'poster' conversion\n$media-\u003egetParentConversion('poster.360p');\n\n// Only get children conversions of poster\n$media-\u003egetChildrenConversions('poster');\n```\n\n### Blade components\n\nThe package also provides blade components.\n\n```html\n\u003c!-- fallback to the root media url if the conversion doesn't exist --\u003e\n\u003c!-- allows you to specify query parameters --\u003e\n\u003cx-media::img\n    :media=\"$user-\u003egetFirstMedia('poster')\"\n    conversion=\"360p\"\n    fallback\n    parameters=\"['foo'=\u003e'bar']\"\n    alt=\"Video poster\"\n/\u003e\n```\n\n```html\n\u003c!-- fallback to the root media url if the conversion doesn't exist --\u003e\n\u003c!-- allows you to specify query parameters --\u003e\n\u003cx-media::video\n    :media=\"$user-\u003egetFirstMedia('videos')\"\n    conversion=\"720p\"\n    fallback\n    muted\n    playsinline\n    autoplay\n    loop\n/\u003e\n```\n\n## Advanced Usage\n\n### Transforming a file before storing it\n\nYou can define a transformation step that runs before a file is stored by using the `transform` method on a media collection.\n\nThis is useful if you want to **resize, optimize, or process an uploaded file** before it gets saved to disk.\n\nFor example, the snippet below shows how you can resize and optimize an uploaded image before storing it:\n\n```php\nuse Illuminate\\Http\\File;\nuse Spatie\\TemporaryDirectory\\TemporaryDirectory;\nuse Spatie\\Image\\Image;\nuse Spatie\\Image\\Enums\\Fit;\n\nnew MediaCollection(\n    name: 'avatar',\n    acceptedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],\n    transform: function (File $file, TemporaryDirectory $temporaryDirectory): File {\n        $input = $file-\u003egetRealPath();\n        $output = $temporaryDirectory-\u003epath('avatar.jpg');\n\n        Image::load($input)\n            -\u003efit(Fit::Contain, 500)\n            -\u003eoptimize()\n            -\u003esave($output);\n\n        return new File($output);\n    }\n);\n```\n\n### Async vs. Sync Conversions\n\nWhen adding new media, its conversions can be either dispatched asynchronously or generated synchronously.\n\nYou can configure the strategy in the conversion definition using the `queued` and `queue` parameters:\n\n```php\nnew MediaCollection(\n    name: 'avatar',\n    conversions: [\n        new MediaConversionDefinition(\n            name: '360p',\n            queued: true,  // (default) Dispatch as a background job\n            queue: 'slow' // (optional) Specify a custom queue\n            converter: fn ($media) =\u003e new MediaImageConverter(\n                media: $media,\n                filename: \"{$media-\u003ename}.jpg\"\n                width: 360\n            )\n        ),\n        new MediaConversionDefinition(\n            name: '180p',\n            queued: false,  // Generate the conversion synchronously\n            converter: fn ($media) =\u003e new MediaImageConverter(\n                media: $media,\n                filename: \"{$media-\u003ename}.jpg\"\n                width: 180\n            )\n        ),\n    ]\n)\n```\n\nSynchronous conversions can be particularly useful in specific use cases, such as generating a poster immediately upon upload.\n\n### Delayed Conversions\n\nThere 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.\n\nTo achieve this, configure the conversion with the `immediate` parameter set to `false`. This allows you to generate the conversion manually when needed:\n\n```php\nnew MediaCollection(\n    name: 'avatar',\n    conversions: [\n        new MediaConversionDefinition(\n            name: '360p',\n            immediate: false, // Conversion will not be generated at upload time\n            converter: fn ($media) =\u003e new MediaImageConverter(\n                media: $media,\n                filename: \"{$media-\u003ename}.jpg\"\n                width: 360\n            )\n        ),\n    ]\n)\n```\n\nTo generate the conversion later, you can use the following methods:\n\n```php\n// Generate the conversion synchronously\n$media-\u003eexecuteConversion(\n    conversion: '360',\n    force: false // Skips execution if the conversion already exists\n);\n\n// Dispatch the conversion as a background job\n$media-\u003edispatchConversion(\n    conversion: '360',\n    force: false // Skips execution if the conversion already exists\n);\n```\n\n### `onAdded` MediaCollection Callback\n\nThe `onAdded` callback allows you to define custom logic that will be executed whenever new media is added to your collection.\n\nTo use it, simply set the `onAdded` parameter when defining a `MediaCollection`. For example:\n\n```php\nnew MediaCollection(\n    name: 'avatar',\n    onAdded: function ($media) {\n        // Example: Notify the model when new media is added\n        // $media-\u003emodel-\u003enotify(new MediaAddedNotification($media));\n    }\n);\n```\n\nWith this, you can easily hook into the media addition process and trigger actions like sending notifications, logging, or other custom behavior.\n\n\u003e [!TIP]\n\u003e The same behavior can be achieved by listening to `Elegantly\\Media\\Events\\MediaAddedEvent`.\n\n### `onCompleted` MediaConversionDefinition Callback\n\nThe `onCompleted` callback allows you to define custom logic that will be executed whenever a new conversion is generated.\n\nTo use it, simply set the `onCompleted` parameter when defining a `MediaConversionDefinition`. For example:\n\n```php\nnew MediaConversionDefinition(\n    name: '360',\n    onCompleted: function ($conversion, $media, $parent) {\n        // Example: Refresh your UI\n        // broadcast(new MyEvent($media));\n    }\n);\n```\n\nThis allows you to hook into the conversion process and execute additional logic, such as updating your UI or triggering other actions.\n\n\u003e [!TIP]\n\u003e The same behavior can be achieved by listening to `Elegantly\\Media\\Events\\MediaConversionAddedEvent`.\n\n### Regenerating Child Conversions When a Parent Runs\n\nIn some cases, you may want all **child conversions** of a given conversion to be **regenerated whenever the parent conversion is re-executed**.\n\nThere are two ways to achieve this, depending on where you handle conversions:\n\n#### From the conversion definition\n\nYou can hook into the `onCompleted` callback of a conversion definition and instruct Laravel to delete all child conversions when the parent finishes.\n\n```php\nnew MediaConversionDefinition(\n    name: '360',\n    onCompleted: function (?MediaConversion $conversion, Media $media, ?MediaConversion $parent) {\n        if ($conversion) {\n            // Delete any children so they’ll be regenerated automatically\n            $media-\u003edeleteChildrenConversions($conversion-\u003econversion_name);\n        }\n    }\n);\n```\n\n#### From `addConversion`\n\nWhen adding a new conversion programmatically, you can explicitly tell it to remove children and regenerate them automatically:\n\n```php\n$mediaConversion = $media-\u003eaddConversion(\n    file: $file,\n    deleteChildren: true, // ensures child conversions are refreshed\n);\n\n// regenerate children conversion\n$this-\u003emedia-\u003egenerateConversions(\n    parent: $mediaConversion,\n);\n```\n\n### Custom Conversions\n\nConversions can be anything: a variant of a file, a transcription of a video, a completely new file, or even just a string.\n\nYou 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:\n\n```php\nnamespace App\\Media\\Converters\\Image;\n\nuse Elegantly\\Media\\Converters\\MediaConverter;\nuse Elegantly\\Media\\Enums\\MediaType;\nuse Elegantly\\Media\\Models\\Media;\nuse Elegantly\\Media\\Models\\MediaConversion;\nuse Illuminate\\Contracts\\Filesystem\\Filesystem;\nuse Spatie\\Image\\Enums\\Fit;\nuse Spatie\\Image\\Image;\nuse Spatie\\ImageOptimizer\\OptimizerChain;\nuse Spatie\\TemporaryDirectory\\TemporaryDirectory as SpatieTemporaryDirectory;\n\nclass MediaImageConverter extends MediaConverter\n{\n    public function __construct(\n        public readonly Media $media,\n        public string $filename,\n        public ?int $width = null,\n        public ?int $height = null,\n        public Fit $fit = Fit::Max,\n        public ?OptimizerChain $optimizerChain = null,\n    ) {}\n\n    public function shouldExecute(Media $media, ?MediaConversion $parent): bool\n    {\n        $source = $parent ?? $media;\n\n        return $source-\u003etype === MediaType::Image;\n    }\n\n    public function convert(\n        Media $media,\n        ?MediaConversion $parent,\n        ?string $file,\n        Filesystem $filesystem,\n        SpatieTemporaryDirectory $temporaryDirectory\n    ): ?MediaConversion {\n\n        if (! $file) {\n            return null;\n        }\n\n        $input = $filesystem-\u003epath($file);\n        $output = $filesystem-\u003epath($this-\u003efilename);\n\n        Image::load($input)\n            -\u003efit($this-\u003efit, $this-\u003ewidth, $this-\u003eheight)\n            -\u003eoptimize($this-\u003eoptimizerChain)\n            -\u003esave($output);\n\n        return $media-\u003eaddConversion(\n            file: $output,\n            conversionName: $this-\u003econversion,\n            parent: $parent,\n        );\n\n    }\n}\n```\n\nThe `convert` method is where the logic for the conversion is implemented. It provides the following parameters:\n\n-   **`$media`**: The Media model.\n-   **`$parent`**: The MediaConversion model, if the conversion is nested.\n-   **`$file`**: A local copy of the file associated with either `$media` or `$parent`.\n-   **`$filesystem`**: An instance of the local filesystem where the file copy is stored.\n-   **`$temporaryDirectory`**: An instance of `TemporaryDirectory` where the file copy is temporarily stored.\n\nYou don’t need to worry about cleaning up the files, as the `$temporaryDirectory` will be deleted automatically when the process completes or fails.\n\nTo finalize the conversion, ensure you save it by calling `$media-\u003eaddConversion` or `$media-\u003ereplaceConversion` at the end of the `handle` method.\n\n### Manually Generate Conversions\n\nYou can manage your media conversions programmatically using the following methods:\n\n```php\n// Store a new file as a conversion\n$media-\u003eaddConversion(\n    file: $file, // Can be an HTTP File, URL, or file path\n    conversionName: 'transcript',\n    parent: $mediaConversion // (Optional) Specify a parent conversion\n    // Additional parameters...\n);\n\n// Replace an existing conversion safely\n// If the same conversion already exists, it ensures the new file is stored before deleting the previous one.\n$media-\u003ereplaceConversion(\n    conversion: $mediaConversion\n);\n\n// Safely delete a specific conversion and all its children\n$media-\u003edeleteConversion('360');\n\n// Safely delete only the child conversions of a parent conversion\n$media-\u003edeleteChildrenConversions('poster');\n\n// Dispatch or execute a conversion\n$media-\u003edispatchConversion('360'); // Runs asynchronously as a job\n$media-\u003eexecuteConversion('poster.360'); // Executes synchronously\n$media-\u003egetOrExecuteConversion('poster.360'); // Retrieves or generates the conversion\n\n// Retrieve conversion information\n$media-\u003egetConversion('360'); // Fetch a specific conversion\n$media-\u003ehasConversion('360'); // Check if a conversion exists\n$media-\u003egetParentConversion('poster.360'); // Retrieve the parent (poster) of a conversion\n$media-\u003egetChildrenConversions('poster'); // Retrieve child conversions\n```\n\nAdditionally, you can use an Artisan command to generate conversions with various options:\n\n```bash\nphp artisan media:generate-conversions\n```\n\nYou can also use the following command to retry failed conversions:\n\n```bash\nphp artisan media-conversions:retry\n```\n\nThis provides a convenient way to process conversions in bulk or automate them within your workflows.\n\n### Format Media URLs\n\nSome cloud providers like Cloudflare, Bunny, or ImageKit allow you to create instant transformations of your images and videos using specially formatted URLs.\n\nThis package gives you a simple way to format your URLs so you can take advantage of these services.\n\nWhen using the `$media-\u003egetUrl()` method, you can specify two parameters:\n\n-   `parameters`: An array of values\n-   `formatter`: The class name of the formatter you want to use\n\nBy combining these parameters, you can retrieve formatted URLs like this:\n\n```php\nuse \\Elegantly\\Media\\UrlFormatters\\CloudflareImageUrlFormatter;\n\n// Default formatter (query parameters)\n$default = $media-\u003egetUrl(\n    parameters: ['width' =\u003e 360],\n); // https://your-url.com?width=360\n\n// Cloudflare formatter (path-based format)\n$cloudflare = $media-\u003egetUrl(\n    parameters: ['width' =\u003e 360],\n    formatter: CloudflareImageUrlFormatter::class\n); // /cdn-cgi/media/width=360/https://your-url.com\n```\n\nThis package comes with 3 formatters out of the box:\n\n-   `\\Elegantly\\Media\\UrlFormatters\\DefaultUrlFormatter`\n-   `\\Elegantly\\Media\\UrlFormatters\\CloudflareImageUrlFormatter`\n-   `\\Elegantly\\Media\\UrlFormatters\\CloudflareVideoUrlFormatter`\n\nFeel free to implement your own formatter by extending `\\Elegantly\\Media\\UrlFormatters\\AbstractUrlFormatter`.\n\n## Conversions Presets\n\n### Image Placeholder\n\nWhen 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.\n\nThis 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.\n\n#### Defining the Placeholder Conversion\n\nTo create a placeholder, simply define a media conversion using the `MediaImagePlaceholderConverter`:\n\n```php\nnamespace App\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Elegantly\\Media\\Concerns\\HasMedia;\nuse Elegantly\\Media\\Contracts\\InteractWithMedia;\nuse Elegantly\\Media\\MediaCollection;\nuse Elegantly\\Media\\MediaConversionDefinition;\nuse Elegantly\\Media\\Converters\\MediaImagePlaceholderConverter;\n\nclass User extends Model implements InteractWithMedia\n{\n    use HasMedia;\n\n    public function registerMediaCollections(): array;\n    {\n        return [\n            new MediaCollection(\n                name: 'avatar',\n                single: true,\n                conversions: [\n                    new MediaConversionDefinition(\n                        name: 'placeholder',\n                        converter: fn ($media) =\u003e new MediaImagePlaceholderConverter($media),\n                    ),\n                ]\n            )\n        ];\n    }\n}\n```\n\n#### Using the Placeholder in Your Views\n\nIf you’re using the built-in \u003cx-media::img\u003e Blade component, you can directly specify the placeholder conversion name:\n\n```html\n\u003cx-media::img :media=\"$user-\u003egetFirstMedia('avatar')\" placeholder /\u003e\n\n\u003cx-media::img\n    :media=\"$user-\u003egetFirstMedia('avatar')\"\n    placeholder=\"custom-placeholder-name\"\n/\u003e\n```\n\nThis will automatically load the blurred placeholder as a background-image.\n\n#### Retrieving the Base64 Value Directly\n\nIf you need to manually work with the placeholder (e.g., for API responses or inline styles), you can retrieve it like this:\n\n```php\n$placeholder = $media-\u003egetConversion('placeholder');\n\n$base64Image = $placeholder?-\u003econtents;\n```\n\n## Customization\n\n### Custom Media Model\n\nYou can define your own Media model to use with the library.\n\nFirst, create your own model class:\n\n```php\nnamespace App\\Models;\n\nuse Elegantly\\Media\\Models\\Media as ElegantlyMedia;\n\nclass Media extends ElegantlyMedia\n{\n    // ...\n}\n```\n\nThen, update the `config` file:\n\n```php\nuse App\\Models\\Media;\n\nreturn [\n\n    'model' =\u003e Media::class,\n\n    // ...\n\n];\n```\n\nThe library is typed with generics, so you can use your own Media model seamlessly:\n\n```php\nnamespace App\\Models;\n\nuse App\\Models\\Media;\nuse Elegantly\\Media\\Concerns\\HasMedia;\nuse Elegantly\\Media\\Contracts\\InteractWithMedia;\n\n/**\n * @implements InteractWithMedia\u003cMedia\u003e\n */\nclass Post extends Model implements InteractWithMedia\n{\n    /** @use HasMedia\u003cMedia\u003e **/\n    use HasMedia;\n\n    // ...\n}\n```\n\n## Troubleshooting\n\n### Ghostscript and Imagick Issues\n\nThis package relies on the `spatie/pdf-to-image` library, which uses Ghostscript via Imagick to convert PDFs into images.\n\nIf you encounter errors while generating images from PDFs, such as:\n\n-   `attempt to perform an operation not allowed by the security policy 'PDF'`\n-   `Uncaught ImagickException: FailedToExecuteCommand 'gs'`\n\nthese issues are likely related to the configuration of Ghostscript or Imagick on your system.\n\nFor 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).\n\n## Testing\n\n```bash\ncomposer test\n```\n\n## Changelog\n\nPlease see the [CHANGELOG](CHANGELOG.md) for more information on recent changes.\n\n## Contributing\n\nFeel free to open an issue or a discussion.\n\n## Security Vulnerabilities\n\nPlease contact [me](https://github.com/QuentinGab) to report security vulnerabilities.\n\n## Credits\n\n-   [Quentin Gabriele](https://github.com/QuentinGab)\n-   [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see the [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felegantengineeringtech%2Flaravel-media","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felegantengineeringtech%2Flaravel-media","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felegantengineeringtech%2Flaravel-media/lists"}