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

lakshmaji/thumbnail

Lakshmaji Thumbnail is a Laravel package for generating image thumbnails with a simple, fluent API. It wraps Intervention Image to resize, crop, and cache thumbnails on the fly, making it easy to serve optimized image sizes in Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites (non-negotiable). The package is a thin wrapper around the ffmpeg binary — it does not ship FFmpeg. Confirm ffmpeg -version works from the shell under the same user PHP runs as before touching composer.

# Debian/Ubuntu
sudo apt-get install ffmpeg
# macOS
brew install ffmpeg

Install.

composer require lakshmaji/thumbnail

Because the last release is from 2017, expect a platform-constraint fight on PHP 8 / Laravel 10+. If composer refuses:

composer require lakshmaji/thumbnail --ignore-platform-reqs
# or pin the constraint already present in your lockfile

Then sanity-check the service provider/facade registered in config/app.php (older Laravel) or that auto-discovery picked it up (php artisan package:discover).

Configure the binary path first. Publish the config so FFmpeg's location is explicit rather than relying on PATH:

php artisan vendor:publish --provider="Lakshmaji\Thumbnail\ThumbnailServiceProvider"

In config/thumbnail.php set the FFmpeg (and FFprobe, if present) paths to the absolute binary location (/usr/bin/ffmpeg), and prefer an .env value (FFMPEG_PATH) so staging/production can differ.

First use case — grab a frame on upload:

use Lakshmaji\Thumbnail\Facade\Thumbnail;

Thumbnail::src(storage_path('app/videos/clip.mp4'))
    ->smart(true)
    ->save(storage_path('app/thumbnails'), 'clip.jpg');

Where to look first, in order:

  1. config/thumbnail.php — binary paths and defaults; almost every failure traces back here.
  2. The facade class (namespace is Lakshmaji\Thumbnail\Facade\Thumbnail; if your alias differs, import the class directly rather than debugging the alias).
  3. The README's method list — src(), smart(), timeToImage(), save().
  4. The package source for the exact ffmpeg command it builds — you will need it when debugging (see Gotchas).

Implementation Patterns

Pattern 1 — Never generate in the request cycle. FFmpeg is CPU-bound and can take seconds to minutes. Queue it:

// app/Jobs/GenerateVideoThumbnail.php
public function handle(): void
{
    $video = storage_path("app/videos/{$this->filename}");
    $dir   = storage_path('app/thumbnails');

    Thumbnail::src($video)
        ->timeToImage(3)          // deterministic & fast
        ->save($dir, "{$this->basename}.jpg");
}

Dispatch from the controller/observer after the upload is persisted, and store the resulting path on the model.

Pattern 2 — Smart vs. timed capture, chosen per content type.

  • smart(true) — scans the video with FFmpeg's thumbnail filter and picks a "representative" frame. Best for user-generated content with black fades/credits at the head. Slower (it decodes multiple frames).
  • timeToImage($seconds) — single seek. Use for known-format content (screen recordings, trailers, camera footage) where a fixed offset is reliable. Much faster and predictable.

Rule of thumb in production: timeToImage() by default, smart(true) only for UGC where the intro is untrusted.

Pattern 3 — Wrap it in a small service, not scattered facade calls. This isolates the 2017-era API so you can swap it later:

class Thumbnailer
{
    public function make(string $videoAbsPath, string $outDir, string $name, bool $smart = false): string
    {
        if (!is_file($videoAbsPath)) {
            throw new InvalidArgumentException("Missing video: {$videoAbsPath}");
        }
        if (!is_dir($outDir)) {
            mkdir($outDir, 0755, true);
        }

        Thumbnail::src($videoAbsPath)
            ->smart($smart)
            ->save($outDir, $name);

        return $outDir . DIRECTORY_SEPARATOR . $name;
    }
}

Pattern 4 — Model observer on upload. Attach to created on your Video model, dispatch the job, and have the job write thumbnail_path back. Keeps controllers thin and makes regeneration a matter of re-dispatching.

Pattern 5 — Batch/backfill command. Add an artisan command iterating over rows where thumbnail_path IS NULL, calling the same service. Wrap each iteration in try/catch so one corrupt file doesn't kill the batch; log failures with the file path.

Pattern 6 — Post-process with Intervention Image. This package only extracts a frame; it does not resize or crop. Feed its output into intervention/image to produce the actual 16:9 card, webp variants, etc. Treat the FFmpeg output as an intermediate artifact.

Integration tips.

  • Always pass absolute paths. storage_path()/public_path(); never a bare filename or a URL.
  • Generate into a temp directory, then move into place, so a killed worker never leaves a half-written file the UI references.
  • Persist the relative path in the DB and resolve it to absolute at render/queue time — keeps storage disks portable.

Gotchas and Tips

save() does not create parent directories. If the destination folder is missing, FFmpeg fails with a cryptic exit code. mkdir($dir, 0755, true) before calling.

Filename must carry the extension. ->save($dir, 'thumb.jpg'), not 'thumb'. The extension determines the encoder FFmpeg picks, and jpg/png behave differently on odd-dimension frames.

Return value is easy to misread. save() typically returns $this (for chaining) rather than the path. Don't return Thumbnail::...->save(...) and expect a string — construct the path yourself from the pieces you passed in.

The binary path is the #1 failure. Symptoms: empty/black output, exit code 127, or "not found" in logs. Debug by copying the exact command out of the package source and running it manually as the web/queue user:

sudo -u www-data /usr/bin/ffmpeg -i /var/www/app/storage/app/videos/clip.mp4 -ss 3 -vframes 1 /tmp/out.jpg

If that works and the package doesn't, the config path is wrong.

Smart mode on long videos is slow and memory-hungry. A 2-hour upload run through smart(true) can exceed PHP's max_execution_time/memory_limit even in a queue worker. Set generous timeouts on the job ($this->timeout = 600) and consider timeToImage() for anything over ~10 minutes.

Paths with spaces or non-ASCII characters are a classic breakage point for shell-wrapped FFmpeg calls. Keep storage filenames slugged/Str::random()-based; never pass raw user-supplied original filenames.

Black-frame results usually mean seeking landed on an intro fade. Bump timeToImage() from 0/1s to 3–5s, or switch to smart(true). Verify by actually eyeballing a sample — automated checks won't catch it.

Package age is the real risk. PHP 7-era code, no active maintenance, no Laravel 10/11 updates guaranteed. Treat it as an implementation detail behind your Thumbnailer service: if it breaks on a framework upgrade, replacing it with a direct Symfony\Component\Process\Process call to FFmpeg is ~20 lines and gives you far better error output and cancellation. Plan for that migration; don't let facade calls leak into controllers.

Extension point worth knowing: since it shells out, any FFmpeg option you need (quality -q:v, scaling via -vf scale=640:-1, sprite sheets) has to be done either by editing the package (don't) or by post-processing / replacing the call. Budget for the direct-FFmpeg path as soon as you need non-default output.

Logging: wrap calls in try/catch and log $e->getMessage() plus the source path and FFmpeg exit code. The package's exceptions are terse; the exit code plus a manual rerun of the command is what actually gets you to the cause.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums