php-ffmpeg/extras
php-ffmpeg/extras extends PHP-FFMpeg with additional filters, formats, and media tools, making it easy to add advanced audio and video processing to your PHP applications without writing low-level FFmpeg commands.
This package is a thin class library, not a framework extension. There is no service provider, no config file, and no facade. It simply ships additional FFMpeg\Format\* classes that php-ffmpeg/php-ffmpeg can consume.
Install:
composer require php-ffmpeg/php-ffmpeg php-ffmpeg/extras
Look first at: vendor/php-ffmpeg/extras/src/FFMpeg/Format/ — that directory is the package. Confirm which classes exist and which codecs they emit before designing anything around them. Typical classes include Video\WebM, Video\Wmv, Video\Ogg, Audio\Flac, Audio\Opus.
First use case — transcode an uploaded MP4 to WebM inside a controller or job:
use FFMpeg\FFMpeg;
use FFMpeg\Format\Video\WebM;
$ffmpeg = FFMpeg::create([
'ffmpeg.binaries' => config('ffmpeg.ffmpeg'),
'ffprobe.binaries' => config('ffmpeg.ffprobe'),
'timeout' => 3600,
]);
$video = $ffmpeg->open(storage_path('app/tmp/input.mp4'));
$video->save(
(new WebM())->setVideoCodec('libvpx-vp9')->setAudioCodec('libvorbis'),
storage_path('app/public/output.webm')
);
Register FFMpeg as a singleton in a service provider so the binaries and timeout config are resolved once:
$this->app->singleton(FFMpeg::class, fn () => FFMpeg::create(config('ffmpeg')));
Format registry / enum. Because formats are plain classes, the cleanest Laravel integration is a small mapping so callers request 'webm' rather than a class name:
enum TargetFormat: string {
case WebM = 'webm';
case Wmv = 'wmv';
case Flac = 'flac';
public function formatter(): FormatInterface {
return match ($this) {
self::WebM => new WebM(),
self::Wmv => new Wmv(),
self::Flac => new Flac(),
};
}
}
Wrap it in a single service. Keep FFMpeg out of controllers. A TranscodeService that takes a source path, a TargetFormat, and a destination path gives you one seam to test, mock, and instrument.
Queue the work. Transcoding is the archetypal queued job. Type-hint the service in the job, set public int $timeout = 3600; and public int $tries = 1; — retries on a half-written output file cause more harm than good. Use a dedicated queue so long transcodes don't starve short jobs.
Temp-file + disk pattern for S3/R2. FFMpeg only accepts local paths. Pull the source to a storage/app/tmp path, transcode to a second temp path, upload to the destination disk, then unlink both in a finally block. Never hand it a stream or a URL.
Multi-output in one pass. If you need WebM and a thumbnail, open the video once and reuse the Video object for save(), frame(), and waveform() calls rather than re-opening for each target.
Progress reporting. Attach ->on('progress', fn ($video, $format, $pct) => Cache::put("job:{$id}", $pct)). Works the same for extras formats; the callback is driven by the core library, not the format class.
Compose with core filters. Extras formats are interchangeable with core formats, so $video->filters()->resize(new Dimension(1280, 720))->synchronize() and format chaining work exactly as documented upstream.
Fake it in tests. In feature tests, bind a stub FFMpeg that records the requested format and copy a fixture file instead of spawning a process — the extras package adds nothing here, so the standard php-ffmpeg mocking approach applies.
No service provider — don't run vendor:publish. If you're expecting Laravel-style ergonomics (a config/ffmpeg.php shipped by the package), you'll need to write it yourself.
Version skew with the core library. This package is updated rarely and has historically tracked older php-ffmpeg/php-ffmpeg constraints. Check composer why-not php-ffmpeg/php-ffmpeg before assuming it works with the latest core release; you may need to pin the core to a matching minor. This is the single most likely thing to break your install.
Verify codecs in your actual binary, not the class. WebM may default to a codec your ffmpeg build lacks. Run ffmpeg -codecs | grep -E 'vp9|vorbis|wmv3' on the container image, and explicitly set setVideoCodec() / setAudioCodec() rather than trusting defaults.
License is NOASSERTION. Composer will not tell you what you're allowed to do. Get this cleared before shipping in a commercial product; if it can't be cleared, the format classes are trivial to copy — each is roughly 30 lines extending VideoAbstractFormat or AudioAbstractFormat and declaring getAvailableAudioCodecs() / getAvailableVideoCodecs(). Writing your own is a legitimate fallback.
Binary paths and ExecutableNotFoundException. Always source ffmpeg.binaries and ffprobe.binaries from env/config. Catch FFMpeg\Exception\ExecutableNotFoundException separately from RuntimeException — the first is a deployment problem, the second is usually a bad input file.
Debug by logging the command. When a transcode silently produces a 0-byte file, drop -loglevel debug into ffmpeg.threads-adjacent config or reproduce the command manually. The exception message from the wrapper is often just "Encoding failed" with no stderr.
Temp-disk exhaustion is the usual production failure. Large uploads plus a timeout of 0 (unlimited) will fill /tmp. Set an explicit timeout, cap input duration via ffprobe before queueing, and clean up in finally.
Don't hold FFMpeg in a long-lived container with a per-request timeout. The timeout is set at construction; if you vary it per job, instantiate per job instead of injecting the singleton.
Given 180 stars and essentially no dependents, treat this as vendored code. Pin it exactly in composer.json, and be ready to fork or inline the two or three format classes you actually use.
How can I help you explore Laravel packages today?