Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Thumbnail Laravel Package

lakshmajim/thumbnail

Laravel package for generating and caching image thumbnails on the fly. Define sizes, crop or resize, and serve optimized images through simple routes and helpers, with built-in cache support for fast repeated requests.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal path from zero to first thumbnail:

  1. Install FFmpeg on the host (not just PHP). The package shells out to the ffmpeg binary, so nothing works without it:
    # Debian/Ubuntu
    sudo apt-get install ffmpeg
    # macOS
    brew install ffmpeg
    # verify
    ffmpeg -version
    
  2. Require the package:
    composer require lakshmajim/thumbnail
    
  3. Register the provider/facade (needed on Laravel < 5.5; on 5.5+ auto-discovery may or may not be present given the 2017 release — check composer.json for extra.laravel):
    // config/app.php
    'providers' => [
        Lakshmajim\Thumbnail\ThumbnailServiceProvider::class,
    ],
    'aliases' => [
        'Thumbnail' => Lakshmajim\Thumbnail\Facades\Thumbnail::class,
    ],
    
  4. First use case — grab a poster frame from an uploaded video:
    use Thumbnail;
    
    $video  = $request->file('video')->store('videos');
    $path   = storage_path('app/public/thumbnails/'.$name.'.jpg');
    
    Thumbnail::get($videoPath, $path); // confirm exact signature against the README
    

Where to look first: the repo's README.md for the exact method names/signatures (the package is small, so the source in src/ is 1–2 files and worth reading — it is just an FFmpeg command wrapper). Then check composer.json for the supported Laravel/PHP constraint before wiring it into a modern app.

First realistic use case: an upload endpoint that accepts a video, stores the original on a disk, and generates a thumbnail alongside it for listing pages / cards in the UI.

Implementation Patterns

Wrap it in a small application service. Because this is a thin FFmpeg wrapper, keep the vendor call behind your own class so you can swap implementations (e.g., php-ffmpeg/php-ffmpeg, spatie/image) later without touching controllers:

class VideoThumbnailer
{
    public function generate(string $videoPath, string $outputPath, ?string $atSecond = null): string
    {
        $outputPath = $this->ensureDirectory($outputPath);

        if (! Thumbnail::get($videoPath, $outputPath, $atSecond)) {
            throw new ThumbnailFailedException("Could not thumbnail {$videoPath}");
        }

        return $outputPath;
    }
}

Generate asynchronously on upload. Thumbnailing is blocking I/O and can take seconds per file — never do it inline in the request for anything larger than a small clip:

// Controller
$video = $request->file('video')->store('videos', 'public');
GenerateVideoThumbnail::dispatch($video);

// Job
public function handle(VideoThumbnailer $thumbnailer)
{
    $thumbnailer->generate(
        Storage::disk('public')->path($this->videoPath),
        Storage::disk('public')->path('thumbnails/'.pathinfo($this->videoPath, PATHINFO_FILENAME).'.jpg')
    );
}

Deterministic, content-addressed filenames. Don't trust the uploaded filename; hash it so re-uploads and cache busting behave:

$key = hash_file('sha1', $tmpPath).'.jpg';

Choose a sensible timestamp. Frame 0 is often a black frame or a fade-in. Seek ~10% into the video (capped) for a representative poster:

$duration   = $this->probeDuration($videoPath); // via ffprobe, packaged separately
$seekSecond = min(10, (int) floor($duration * 0.1));

Model integration via accessor / attribute. Store the thumbnail path on the media model and expose a URL accessor so views stay dumb:

public function getThumbnailUrlAttribute(): ?string
{
    return $this->thumbnail_path
        ? Storage::disk('public')->url($this->thumbnail_path)
        : null;
}

Pipeline pattern. Chain with other media processing steps using Laravel's Bus::chain so the thumbnail only runs after transcoding succeeds, and so failures bubble up in one place.

Integration tip — storage disks. Resolve absolute paths with Storage::disk(...)->path(); the FFmpeg wrapper needs a real filesystem path, not a URL or relative path.

Backfill / batch regeneration. Because it's a simple ->get() call, an artisan command that iterates existing rows and regenerates missing thumbnails is trivial — useful after a storage migration:

Video::whereNull('thumbnail_path')->chunkById(100, function ($videos) {
    foreach ($videos as $video) { /* dispatch job */ }
});

Gotchas and Tips

  • The package is essentially unmaintained (last release 2017). Verify it installs against your Laravel/PHP version before committing to it. If composer require fails, either fork it (a one-file change to the composer.json constraint usually suffices) or drop it and call ffmpeg directly — that's all the package does.
  • FFmpeg must be on the PATH of the process that runs PHP. This bites hardest with queue workers, PHP-FPM pools, or containers: php-fpm and your SSH shell often have different environments. Use an absolute binary path (/usr/bin/ffmpeg) if the package/config allows it, or symlink into a known location.
  • Escaping and spaces in filenames. A thin wrapper builds a shell command string; paths with spaces, quotes, or unicode are a common failure. Normalize uploads to safe ASCII names before passing them in.
  • Empty/failed output is silent by default. Always check the return value and, ideally, file_exists($outputPath) && filesize($outputPath) > 0 — a failed FFmpeg invocation can return true or produce a zero-byte file depending on how the wrapper shells out.
  • Timeouts and memory. FFmpeg decoding is CPU/memory heavy; job timeouts and max_execution_time need to accommodate it. Set your queue worker --timeout higher than php.ini's limit, otherwise the worker is killed mid-run and the job is retried forever.
  • Temp files leak. If your code writes to a temp path before moving into storage, wrap in try/finally with unlink() — a throwing job leaves orphans in /tmp and these fill up fast in containers.
  • Permissions. The web/queue user must be able to write the output directory and read the input directory. storage/ ownership issues after deploys (chown -R www-data storage) are the most common "it worked locally" bug.
  • Seeking accuracy. Fast seek (-ss before -i) is quicker but can land a few frames off; exact seek (-ss after -i) is slower. For posters it rarely matters, but if you need a specific frame, test it.
  • Video formats. FFmpeg handles almost anything, but very new codecs may fail on old distro binaries. Test with real production files, not just MP4s from a phone.
  • Configuration/publish quirks. If the package ships a config, publish it with php artisan vendor:publish --provider="Lakshmajim\Thumbnail\ThumbnailServiceProvider" and check the tag; older packages sometimes use a tag-less --provider publish that overwrites silently. Commit the config and, importantly, make the binary path configurable via .env.
  • Extension points are thin. There isn't much to hook into — realistically your extension point is your own VideoThumbnailer service. Treat the vendor package as an implementation detail so a future migration is a one-file change.
  • Debugging tip. When a call fails, log the exact command the wrapper ran (or replicate it manually in the shell) and run it verbatim. FFmpeg's real error is almost always in its stderr, so capture and log stderr if the wrapper exposes it.
  • Test in CI. Add a small sample video to your test suite and make a @requires ffmpeg test; otherwise your thumbnail tests are skipped/inconclusive and regressions slip through.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route