nhattuanbl/lara-media

Laravel media manager / convert

1.2 2024-09-23 20:52 UTC

This package is auto-updated.

Last update: 2024-12-23 21:19:55 UTC


README

Lara Media is a media library for Laravel. It provides a simple way to store, convert, and manipulate media files.

Preview

Features

  • Store media information in MongoDB (other connection is not tested)
  • Multiple disk storage
  • Multiple album/collection for each model
  • Convert media files to other formats / other resolution
  • Detect main color of photo
  • Add watermark to photo
  • Add watermark to video
  • Get thumbnail of media
  • Get multiple screenshot of video
  • Get multiple resolution of photo
  • Dashboard to manage media files, view statistic, tracking conversion status via queue
  • Url version
  • Temporary url

Requirements

Installation

composer require nhattuanbl/lara-media
#publish config
php artisan vendor:publish --provider="Nhattuanbl\LaraMedia\LaraMediaProvider" --tag="config"
#publish migration
php artisan vendor:publish --provider="Nhattuanbl\LaraMedia\LaraMediaProvider" --tag="migrations"
#publish view
php artisan vendor:publish --provider="Nhattuanbl\LaraMedia\LaraMediaProvider" --tag="assets"
#publish assets
php artisan vendor:publish --provider="Nhattuanbl\LaraMedia\LaraMediaProvider" --tag="views"
php artisan migrate
php artisan storage:link

Config

return [
    //default database connection - not tested on other connection
    'connection' => env('LARA_MEDIA_CONNECTION', 'mongodb'),

    //default model store media information
    'model' => \Nhattuanbl\LaraMedia\Models\LaraMedia::class,

    //default db table name
    'table_name' => 'media',

    //dashboard
    'web' => [
        'enabled' => true,
        'domain' => null,
        'prefix' => 'lara-media',
        'middleware' => ['web'],
    ],
    
    'public_assets' => [
        'root' => env('LARA_MEDIA_PUBLIC_ROOT', 'app'), //should be set in .env file
        'url' => env('LARA_MEDIA_PUBLIC_URL', '/assets/media'), //should be set in .env file
    ],
    
    'temporary' => [
        'enabled' => true,
        'route_path' => 'download',
        'route_name' => 'web.download',
    ],

    //default storage disk
    'disk' => env('LARA_MEDIA_DISK', 'local'),

    //dir name - can be date path - null for id / default: 'Y/m/d'
    'store_path' => 'Y/m/d',

    'conversion' => [
        'connection' => env('LARA_MEDIA_QUEUE_CONNECTION', 'mongodb'),

        'queue' => env('LARA_MEDIA_QUEUE_NAME', 'conversion'),

        //temporary directory
        'temp' => storage_path('temp'),

        //delete original file after convert
        'keep_original' => env('LARA_MEDIA_KEEP_ORIGINAL', true),

        //number of threads to use
        'threads' => env('LARA_MEDIA_THREAD', 16),

        //ffmpeg binary path
        'ffmpeg_path' => env('LARA_MEDIA_FFMPEG_PATH', '/usr/bin/ffmpeg'),
        'ffprobe_path' => env('LARA_MEDIA_FFPROBE_PATH', '/usr/bin/ffprobe'),
    ],

    'watermark' => [
        'path' => null, //storage_path('your watermark image.png')
        'position' => \Nhattuanbl\LaraMedia\Enums\PositionEnum::Center,
        'opacity' => 0.2,
        'height_percent' => 50,
    ],

    'photo' => [
        //get main color store in hex format
        'detect_main_color' => true,
    ],

    'video' => [
        //better quality at lower sizes
        '2pass_encoding' => false,

        //max timout for conversion
        'timeout' => 7200,

        'encoding' => [
            'mp4' => ['libx264', 'aac'],
            'webm' => ['libvpx', 'libvorbis'],
            'ts' => ['libx264', 'aac'],
            '3gp'  => ['libx264', 'aac'],
            '3g2'  => ['libx264', 'aac'],
            'mov'  => ['libx264', 'aac'],
            'fli'  => ['libx264', 'aac'],
            'mkv'  => ['libx264', 'aac'],
            'asf'  => ['wmv2', 'wmav2'],
            'wmv'  => ['wmv2', 'wmav2'],
        ],
    ],

    'audio' => [
        'timeout' => 7200,
        'encoding' => [
            'aac'  => 'aac', //required: yasm pkg-config libfdk-aac-dev
            'm4a'  => 'aac',
            'mp3'  => 'libmp3lame',
            'flac' => 'flac',
            'wav'  => 'pcm_s16le',
        ],
    ],
];

Usage

Prepare your model

//App\Models\User.php
use MongoDB\Laravel\Eloquent\HybridRelations;
use Nhattuanbl\LaraMedia\Contracts\AudioRegister;
use Nhattuanbl\LaraMedia\Contracts\HasMedia;
use Nhattuanbl\LaraMedia\Contracts\MediaRegister;
use Nhattuanbl\LaraMedia\Contracts\VideoRegister;
use Nhattuanbl\LaraMedia\Enums\PositionEnum;
use Nhattuanbl\LaraMedia\Enums\ResolutionEnum;

class User extends Model
{
    use HasMedia, HybridRelations;
    
    ...
    
    public function registerMediaAlbum(): MediaRegister
    {
        return $this->addMediaAlbum()
        ->acceptsMimeTypes(['image/*']) // use */* for all mime types
        ->album('defaultalbum') //default album name for all uploaded files
        ->limit(3); //global limit
    }

    public function registerVideoAlbum(): VideoRegister
    {
        return $this->addVideoAlbum([ResolutionEnum::H240, ResolutionEnum::H360])
            ->limit(5) // override registerMediaAlbum limit
            ->album('videoalbum') //override registerMediaAlbum album
            ->onQueue('default') //optional - override lara-media config
            ->onConnection('sync') //optional - override lara-media config
            ->format('mp4') //optional
            ->quality(55) //optional - quality video percent 0-100
            ->keepOrigin(false) //optional - keep original file after convert
            ->description('some desc') //optional - store video description
            ->watermark(
                storage_path('watermark.png'),
                 PositionEnum::BottomLeft, 
                 0.5, 
                 50
            ) //all optional - override lara-media config
            ->posters(
                5, //required - number of screenshot
                300, //optional - width of screenshot
            )
            ->thumbnail(4, 4) //optional - default 16 (row * col) images in thumbnail
            ->conversionDisk('s3') //optional - override lara-media config
            ->onDisk('local') //optional - override lara-media config
            ;
    }
    
    public function registerPhotoAlbum(): PhotoRegister
    {
        return $this->addPhotoAlbum()
            ->album('photoalbum')
            ->limit(3)
            ->responsive([600, 300])
            ->onQueue('default')
            ->onConnection('sync')
            ->watermark()
            ;
    }

    public function registerAudioAlbum(): AudioRegister
    {
        return $this->addAudioAlbum()
            ->album('audioalbum')
            ->limit(5)
            ->description('some desc')
            ->responsive(['wav', 'mp3'])
            ->conversionDisk('s3')
            ->onDisk('local')
            ->keepOrigin(false)
            ->quality(80)
            ;
    }
}

Prepare your controller

//App\Http\Controllers\
use Nhattuanbl\LaraMedia\Models\LaraMedia;

    //add media from local file
    $model->addMedia(storage_path('photo.webp'))->toAlbum();
    
    //add media from url
    $model->addMedia('https://example.com/photo.webp')
        ->onDisk('override model register disk')
        ->toAlbum('override model register album name');
    
    //add media from base64
    $model->addMedia('data:image/png;base64,....')
        ->onQueue('override model register')
        ->toAlbum();
    
    //copy media from LaraMedia
    $model->addMedia(LaraMedia::findOrFail($id))
        ->withoutResponsive() //optional - disable conversion
        ->toAlbum();
        
    //add media from request
    $model->addMedia(request()->file)->toAlbum();
    
    
    //get original media url
    LaraMedia::findOrFail($id)->url;
    
    //get media url with version
    LaraMedia::findOrFail($id)->getUrlAttribute('400');
    
    //get media temporary url
    LaraMedia::findOrFail($id)->temporaryUrl(
        now()->addHours(1)->timestamp, //expire time
        $version, //optional - null for original version
        ['views' => 1] //optional - allow accessible only 1 time
    );

Web access

http://localhost/lara-media

Custom uploaded file naming

//App/Providers/AppServiceProvider.php
public function boot()
{
    MediaRegister::$sanitizeFilename = function (string $filename) {
        $invalidChars = [
            '\\', '/', ':', '*', '?', '"', '<', '>', '|',
            "\0", "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08",
            "\x09", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F", "\x10", "\x11", "\x12",
            "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1A", "\x1B", "\x1C",
            "\x1D", "\x1E", "\x1F"
        ];

        $sanitized = str_replace($invalidChars, '_', $filename);
        $sanitized = trim($sanitized, " \t\n\r\0\x0B.");
        return strlen($sanitized) ? $sanitized : floor(microtime(true) * 1000);
    };
}

Custom media model

//App/Models/CustomMedia.php
namespace App\Models;

class CustomMedia extends \Nhattuanbl\LaraMedia\Models\LaraMedia
{
    protected $table = 'custom_media';
    protected $connection = 'mysql';
    
    public function get_idAttribute(): int|string
    {
        return $this->id;
    }
}

//lara-media.php
    ...
    'model' => \App\Models\CustomMedia::class,

Troubleshooting

In migration file you will see that im added 2 new column to customize imtigger/laravel-job-status package for tracking media conversion status so this migration file should run after imtigger/laravel-job-status migration file. Have fun!