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

Ffmpeg Bundle Laravel Package

pulse00/ffmpeg-bundle

pulse00/ffmpeg-bundle integrates the PHP-FFMpeg library into Symfony applications, providing a simple service-based API for audio and video processing. Generate thumbnails, inspect media metadata, apply filters, and run conversions with FFmpeg.

View on GitHub
Deep Wiki
Context7

Getting Started

What 0.6.0 actually gives you: nothing new in the API — it widens the compatibility envelope (php >=5.5, PHP 7, Symfony 3) via PR #8. Treat it as a "you can now install this on a modern-ish stack" release, not a feature release.

Where to look first, in this order:

  1. DependencyInjection/Configuration.php — the whole public surface of the bundle is this config tree (binaries, ffmpeg.threads, ffmpeg.timeout, ffprobe.timeout). Read it before anything else.
  2. Resources/config/services.xml — shows you exactly one thing worth knowing: the ffmpeg service is a preconfigured FFMpeg\FFMpeg::create() instance. There is no magic beyond that.
  3. The underlying library, PHP-FFMpeg — 95% of your day-to-day work is its API (Media::open(), filters, formats), not the bundle's.

Install:

composer require pulse00/ffmpeg-bundle:^0.6

Configure (Symfony-style, as the bundle expects):

# config/packages/pulse00_ffmpeg.yaml
pulse00_ffmpeg:
    binaries:
        ffmpeg: /usr/bin/ffmpeg
        ffprobe: /usr/bin/ffprobe
    ffmpeg:
        threads: 4
        timeout: 3600
    ffprobe:
        timeout: 60

In Laravel there is no service provider. You have two options, and the honest answer is that option B is usually correct:

// A) Wire the bundle's service graph by hand
// app/Providers/AppServiceProvider.php
$this->app->singleton(\FFMpeg\FFMpeg::class, fn () => \FFMpeg\FFMpeg::create([
    'ffmpeg.binaries'  => config('services.ffmpeg.binaries'),
    'ffprobe.binaries' => config('services.ffprobe.binaries'),
    'timeout'          => config('services.ffmpeg.timeout', 3600),
    'threads'          => config('services.ffmpeg.threads', 4),
]));

// B) Skip the bundle entirely — it is a thin Symfony DI wrapper
// composer require php-ffmpeg/php-ffmpeg

First use case — transcode an uploaded video to a web-safe MP4:

$ffmpeg = app(\FFMpeg\FFMpeg::class);

$video = $ffmpeg->open(storage_path('app/uploads/raw.mov'));

$video
    ->filters()
    ->resize(new \FFMpeg\Coordinate\Dimension(1280, 720), \FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_INSET, true)
    ->synchronize();

$video->save(new \FFMpeg\Format\Video\X264('aac', 'libx264'), storage_path('app/public/out.mp4'));

If that runs once end-to-end, you've validated the binaries, the codecs, and your timeouts. That's the whole smoke test.

Implementation Patterns

Bind it as a singleton, never resolve per-call. FFMpeg::create() shells out to probe the binaries; doing that in a loop is pure waste. Inject the container binding into jobs and commands.

Never transcode inside a request. Every realistic pattern below belongs in a queued job:

class TranscodeVideo implements ShouldQueue
{
    public $timeout = 1800;      // must be > FFMpeg timeout config
    public $tries = 1;           // transcodes are not idempotent-cheap

    public function __construct(public int $videoId) {}

    public function handle(\FFMpeg\FFMpeg $ffmpeg, \FFMpeg\FFProbe $ffprobe): void
    {
        $video = $this->videoId ? Video::findOrFail($this->videoId) : null;
        $in  = Storage::disk('local')->path($video->source_path);
        $out = Storage::disk('local')->path("transcodes/{$video->id}.mp4");

        $ffmpeg->open($in)
            ->save(new X264('aac', 'libx264'), $out);

        $video->update(['transcoded_path' => "transcodes/{$video->id}.mp4"]);
    }
}

Probe before you commit. Read duration/resolution first, then pick a target bitrate and a resize strategy. This avoids transcoding a 4K source into a 1280-wide output with the wrong aspect handling.

$format = $ffprobe->format($path);
$duration = (float) $format->get('duration');

$streams = $ffprobe->streams($path);
$videoStream = $streams->videos()->first();
$width  = $videoStream->get('width');

Thumbnails are cheap; keep them synchronous-ish or in their own small job.

$ffmpeg->open($path)
    ->frame(\FFMpeg\Coordinate\TimeCode::fromSeconds($duration * 0.1))
    ->save(storage_path("app/thumbs/{$id}.jpg"));

Progress reporting requires the save() callback form and a place to persist it (cache/DB). It does not work in a synchronous HTTP handler because the client has gone away:

$format = (new X264('aac', 'libx264'))->setKiloBitrate(1200);

$video->save($format, $out, function ($audio, $video, $percent) use ($id) {
    Cache::put("transcode:{$id}", $percent, 3600);
});

Concat / HLS: open each segment as its own Media, then use the concat filter with the appropriate format. This is where php-ffmpeg's abstractions start leaking — you'll often end up dropping to a raw ->save() with a custom format, or shelling out.

Testing: ship a 2–3 second fixture clip in tests/Fixtures/. Faking ffmpeg is not worth it; a real 40 KB clip runs in well under a second.

Gotchas and Tips

The Laravel framing is the biggest gotcha. This is a Symfony bundle. It registers pulse00_ffmpeg config, a Pulse00FFMpegBundle, and an ffmpeg service alias. In Laravel you get none of that — no auto-discovery, no config publishing, no facade. Budget for either manual wiring (Pattern A above) or just depending on php-ffmpeg/php-ffmpeg directly. In a fresh Laravel app the bundle buys you almost nothing.

0.6.0 is a constraint-widening release. Expect changes in composer.json only (symfony/symfony widened to allow ^3.0, PHP 7 allowed). Run composer update pulse00/ffmpeg-bundle -W and check composer.lock — the risk isn't this package, it's whatever php-ffmpeg version Composer resolves alongside it. Pin php-ffmpeg/php-ffmpeg explicitly.

Binary paths fail silently-ish. The common failure is a wrong ffmpeg.binaries producing a RuntimeException deep in Symfony\Component\Process with a truncated command line. Debug with:

which ffmpeg ffprobe
ffmpeg -version

and log the full command before execution when you're chasing a failure. If you're on Forge/Vapor/Docker, the binary is usually /usr/bin/ffmpeg — but only if you installed it in the image.

Three timeouts stack up, and only one of them is the bundle's.

  • pulse00_ffmpeg.ffmpeg.timeout (passed to php-ffmpeg)
  • your queue worker's --timeout
  • PHP max_execution_time if you ever run it sync

If the queue worker's timeout is the smallest, your job is killed mid-write and you get a truncated, unplayable MP4 with no exception. Set queue $timeout strictly greater than the ffmpeg timeout, and let ffmpeg be the thing that fails first.

Codec availability is build-dependent. X264('aac', 'libx264') requires an ffmpeg compiled with libx264. On minimal Alpine images you'll get "Unknown encoder 'libx264'" at save() time — not at open() time, which makes it feel like your filters are broken. Verify with:

ffmpeg -codecs | grep -E 'libx264|aac'

Filters are lazy. Nothing happens until save() — but ->synchronize() forces resolution and gives you a much better error location when a filter fails. Use it in development.

Temp disk and memory. php-ffmpeg writes intermediary files to the system temp dir. Containerized workers with a 64 MB /tmp will fail on long inputs. Point sys_temp_dir at a real volume and set a size-based cleanup policy. Long-running save() calls also hold progress state in memory — batch big files, don't fan out 50 at once.

Service decoration is the extension point. If you need custom binary resolution (per-tenant ffmpeg, GPU builds), don't fork the bundle — decorate or replace the ffmpeg service definition in your own container config. In Laravel this is trivial since you own the binding anyway.

Prefer modern php-ffmpeg on greenfield work. 0.6.0 dates from the Symfony 3 era. It's fine for maintenance on an existing Symfony 2.7/3.x app, but the underlying library has moved on in API polish and PHP version support; a new Laravel project should just take the direct dependency and skip the bundle.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource