christyoga123 / video-optimizer
A fluent Laravel package for optimizing videos - resize, convert formats (MP4, WebM, AVI), compress and reduce file size while maintaining quality
Requires
- php: ^8.1
- illuminate/support: ^10.0|^11.0|^12.0
- php-ffmpeg/php-ffmpeg: ^1.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
A fluent Laravel package for optimizing videos - resize, convert formats (MP4, WebM, AVI), compress and reduce file size while maintaining quality.
Features
-
๐ฌ Format Conversion - Convert videos to MP4, WebM, OGG, MOV, MKV, AVI
-
๐ Smart Resize - Resize with max width/height while maintaining aspect ratio
-
๐๏ธ Quality Control - Adjustable bitrate, CRF, and preset settings
-
๐ง Fluent API - Chain methods for clean, readable code
-
โ๏ธ Configurable - Publish and customize default settings
-
๐ผ๏ธ Thumbnail Generation - Extract thumbnails from videos
-
๐ Audio Control - Option to remove or keep audio
-
๐ต Smart Audio Codecs - Auto-detects best audio codecs (fixes WebM/Ogg issues on macOS/Homebrew)
-
๐ Video Info - Get duration, dimensions, codec info
-
๐งน Auto Cleanup - Automatic temporary file cleanup with
dontCleanup()option -
๐ฆ Queueable Job - Built-in job for background processing
Requirements
- PHP 8.1+
- Laravel 10.x, 11.x, or 12.x
- FFmpeg installed on your server
Installing FFmpeg
macOS:
brew install ffmpeg
Ubuntu/Debian:
sudo apt update sudo apt install ffmpeg
Windows: Download from ffmpeg.org and add to PATH.
Installation
Install the package via Composer:
composer require christyoga123/video-optimizer
The package will automatically register its service provider.
Publish Configuration (Optional)
php artisan vendor:publish --tag=video-optimizer-config
This will create a config/video-optimizer.php file with all available options.
Usage
Basic Usage
use Christyoga123\VideoOptimizer\VideoOptimizer; // Process uploaded file and get optimized temp path $tempPath = VideoOptimizer::make() ->toMp4() ->maxDimensions(1920, 1080) ->videoBitrate(1500) ->process($uploadedFile); // Do something with the optimized file Storage::disk('public')->put('videos/video.mp4', file_get_contents($tempPath));
Using Facade
use Christyoga123\VideoOptimizer\Facades\VideoOptimizer; $tempPath = VideoOptimizer::make() ->toMp4() ->crf(23) ->preset('fast') ->process($request->file('video'));
From Request Input
$tempPath = VideoOptimizer::make() ->toWebm() ->videoBitrate(2000) ->processFromRequest('video'); if ($tempPath) { // File was uploaded and processed $post->update(['video' => Storage::putFile('videos', $tempPath)]); }
From File Path
$tempPath = VideoOptimizer::make() ->toMp4() ->maxDimensions(1280, 720) ->crf(28) ->processFromPath('/path/to/original/video.mov');
With Default Settings
Apply all defaults from config at once:
$tempPath = VideoOptimizer::make() ->withDefaults() ->process($uploadedFile);
With Thumbnail Generation
$optimizer = VideoOptimizer::make() ->toMp4() ->withThumbnail(5); // Capture at 5 seconds $videoPath = $optimizer->process($uploadedFile); $thumbnailPath = $optimizer->getThumbnailPath(); // Store both video and thumbnail Storage::disk('public')->put('videos/video.mp4', file_get_contents($videoPath)); Storage::disk('public')->put('thumbnails/thumb.jpg', file_get_contents($thumbnailPath));
Remove Audio
$tempPath = VideoOptimizer::make() ->toMp4() ->noAudio() ->process($uploadedFile);
Get Video Information
$optimizer = VideoOptimizer::make(); // Get video info before processing $info = $optimizer->getVideoInfo($uploadedFile->getRealPath()); // Result: // [ // 'duration' => 120.5, // 'bitrate' => 2500000, // 'size' => 37500000, // 'format' => 'mov,mp4,m4a,3gp,3g2,mj2', // 'width' => 1920, // 'height' => 1080, // 'video_codec' => 'h264', // 'audio_codec' => 'aac', // 'frame_rate' => '30/1', // ]
Get Size Comparison
$optimizer = VideoOptimizer::make() ->toMp4() ->crf(28) ->maxDimensions(1280, 720); $tempPath = $optimizer->process($uploadedFile); $comparison = $optimizer->getSizeComparison($uploadedFile->getRealPath()); // Result: // [ // 'original_size' => 50000000, // 'optimized_size' => 12500000, // 'saved_bytes' => 37500000, // 'saved_percentage' => 75.0 // ] echo "Saved: " . $optimizer->formatFileSize($comparison['saved_bytes']); // Output: "Saved: 35.76 MB"
Helper Methods
$optimizer = VideoOptimizer::make(); // Get sanitized filename for storage $filename = $optimizer->getOptimizedFilename($uploadedFile); // "my_video.mp4" // Get current format $format = $optimizer->getFormat(); // "mp4" // Get temp file path $path = $optimizer->getTempPath(); // Get thumbnail path (if generated) $thumbnail = $optimizer->getThumbnailPath(); // Get video duration $duration = $optimizer->getDuration($uploadedFile->getRealPath()); // Get video dimensions $dimensions = $optimizer->getDimensions($uploadedFile->getRealPath()); // ['width' => 1920, 'height' => 1080] // Manual cleanup (usually not needed - destructor handles this) $optimizer->cleanup();
Available Methods
Format Methods
| Method | Description |
|---|---|
format(string $format) |
Set output format (mp4, webm, ogg, etc.) |
toMp4() |
Convert to MP4 format (H.264 + AAC) |
toWebm() |
Convert to WebM format (VP9 + Vorbis) |
toOgg() |
Convert to OGG format (Theora + Vorbis) |
toMov() |
Convert to MOV format (H.264 + AAC) |
toMkv() |
Convert to MKV format (H.264 + AAC) |
toAvi() |
Convert to AVI format (H.264 + MP3) |
Codec Methods
| Method | Description |
|---|---|
videoCodec(string $codec) |
Set video codec |
audioCodec(string $codec) |
Set audio codec |
Resize Methods
| Method | Description |
|---|---|
maxWidth(int $width) |
Set maximum width (maintains aspect ratio) |
maxHeight(int $height) |
Set maximum height (maintains aspect ratio) |
maxDimensions(int $width, int $height) |
Set both max width and height |
Quality & Settings
| Method | Description |
|---|---|
videoBitrate(int $kbps) |
Set video bitrate in kbps |
audioBitrate(int $kbps) |
Set audio bitrate in kbps |
crf(int $value) |
Set CRF (0-51, lower = better quality) |
preset(string $preset) |
Set encoding preset (ultrafast to veryslow) |
tempDir(string $path) |
Set custom temporary directory |
timeout(int $seconds) |
Set processing timeout |
threads(int $count) |
Set number of threads |
withDefaults() |
Apply all defaults from config |
Audio Control
| Method | Description |
|---|---|
noAudio() |
Remove audio from video |
withAudio() |
Keep audio in video |
Thumbnail Methods
| Method | Description |
|---|---|
withThumbnail(int $atSecond) |
Enable thumbnail generation at specific time |
getThumbnailPath() |
Get generated thumbnail path |
Process Methods
| Method | Description |
|---|---|
process(UploadedFile $file) |
Process uploaded file |
processUploadedFile(UploadedFile $file) |
Alias for process() |
processFromPath(string $path) |
Process from file path |
processFromRequest(string $inputName) |
Process from request input name |
Info Methods
| Method | Description |
|---|---|
getDuration(string $path) |
Get video duration in seconds |
getDimensions(string $path) |
Get video width and height |
getVideoInfo(string $path) |
Get complete video info |
Configuration
After publishing, you can customize these options in config/video-optimizer.php:
return [ // FFmpeg binary paths (null = auto-detect) 'ffmpeg_path' => env('VIDEO_OPTIMIZER_FFMPEG_PATH', null), 'ffprobe_path' => env('VIDEO_OPTIMIZER_FFPROBE_PATH', null), // Processing timeout in seconds 'timeout' => env('VIDEO_OPTIMIZER_TIMEOUT', 3600), // Number of threads (0 = auto) 'threads' => env('VIDEO_OPTIMIZER_THREADS', 0), // Default output format 'format' => env('VIDEO_OPTIMIZER_FORMAT', 'mp4'), // Default codecs 'video_codec' => env('VIDEO_OPTIMIZER_VIDEO_CODEC', 'libx264'), 'audio_codec' => env('VIDEO_OPTIMIZER_AUDIO_CODEC', 'aac'), // Default bitrates 'video_bitrate' => env('VIDEO_OPTIMIZER_VIDEO_BITRATE', 1500), 'audio_bitrate' => env('VIDEO_OPTIMIZER_AUDIO_BITRATE', 128), // Default max dimensions 'max_width' => env('VIDEO_OPTIMIZER_MAX_WIDTH', 1920), 'max_height' => env('VIDEO_OPTIMIZER_MAX_HEIGHT', 1080), // CRF (Constant Rate Factor) - lower = better quality 'crf' => env('VIDEO_OPTIMIZER_CRF', 23), // Encoding preset 'preset' => env('VIDEO_OPTIMIZER_PRESET', 'medium'), // Temporary directory 'temp_dir' => env('VIDEO_OPTIMIZER_TEMP_DIR', storage_path('app/temp')), // Thumbnail generation 'generate_thumbnail' => env('VIDEO_OPTIMIZER_GENERATE_THUMBNAIL', false), 'thumbnail_time' => env('VIDEO_OPTIMIZER_THUMBNAIL_TIME', 1), ];
Recommended Settings
For Web Streaming (Balance)
VideoOptimizer::make() ->toMp4() ->crf(23) ->preset('medium') ->maxDimensions(1920, 1080) ->process($file);
For Small File Size
VideoOptimizer::make() ->toMp4() ->crf(28) ->preset('slow') ->maxDimensions(1280, 720) ->videoBitrate(1000) ->process($file);
For High Quality
VideoOptimizer::make() ->toMp4() ->crf(18) ->preset('slower') ->videoBitrate(4000) ->process($file);
For Fast Processing
VideoOptimizer::make() ->toMp4() ->preset('ultrafast') ->process($file);
Integration Examples
With Spatie Media Library
use Christyoga123\VideoOptimizer\VideoOptimizer; // Optimize before adding to media collection $tempPath = VideoOptimizer::make() ->toMp4() ->maxDimensions(1920, 1080) ->crf(23) ->process($request->file('video')); $model->addMedia($tempPath) ->usingFileName('optimized-video.mp4') ->toMediaCollection('videos');
With Laravel Storage
$optimizer = VideoOptimizer::make() ->toMp4() ->crf(23) ->withThumbnail(3); $tempPath = $optimizer->process($request->file('video')); $filename = $optimizer->getOptimizedFilename($request->file('video')); // Store video $videoPath = Storage::disk('public')->putFileAs('uploads/videos', $tempPath, $filename); // Store thumbnail $thumbnailFilename = pathinfo($filename, PATHINFO_FILENAME) . '.jpg'; $thumbnailPath = Storage::disk('public')->putFileAs('uploads/thumbnails', $optimizer->getThumbnailPath(), $thumbnailFilename); return response()->json([ 'video' => $videoPath, 'thumbnail' => $thumbnailPath, ]);
In Form Request
// app/Http/Requests/StoreVideoRequest.php public function passedValidation() { if ($this->hasFile('video')) { $optimizer = VideoOptimizer::make() ->withDefaults() ->withThumbnail(1); $tempPath = $optimizer->process($this->file('video')); $this->merge([ 'optimized_video_path' => $tempPath, 'thumbnail_path' => $optimizer->getThumbnailPath(), ]); } } } }
Queueable Job
The package includes a ready-to-use job for background processing:
use Christyoga123\VideoOptimizer\Jobs\OptimizeVideoJob; // Dispatch job OptimizeVideoJob::dispatch( $sourcePath, // Full path to source file $destinationPath, // Full path to save result (optional, overwrites source if null) [ // Options array 'format' => 'mp4', 'crf' => 23, 'preset' => 'fast' ], true // Delete source file after optimization? (default: false) );
Manual Cleanup Control
By default, the optimizer cleans up temporary files when the object is destroyed. If you need to keep the file longer (e.g., passing between jobs), use dontCleanup():
$optimizer = VideoOptimizer::make() ->toMp4() ->dontCleanup() // Prevent auto-deletion ->process($file); // ... do something time consuming ... // Manually cleanup when done $optimizer->cleanup();
Custom Job Implementation
In Queue Job
// app/Jobs/OptimizeVideo.php class OptimizeVideo implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function __construct( public string $videoPath, public int $postId ) {} public function handle(): void { $optimizer = VideoOptimizer::make() ->toMp4() ->crf(23) ->maxDimensions(1920, 1080) ->withThumbnail(3); $tempPath = $optimizer->processFromPath($this->videoPath); // Store optimized video $filename = 'video_' . $this->postId . '.mp4'; Storage::disk('public')->put('videos/' . $filename, file_get_contents($tempPath)); // Store thumbnail $thumbFilename = 'thumb_' . $this->postId . '.jpg'; Storage::disk('public')->put('thumbnails/' . $thumbFilename, file_get_contents($optimizer->getThumbnailPath())); // Update post Post::find($this->postId)->update([ 'video_path' => 'videos/' . $filename, 'thumbnail_path' => 'thumbnails/' . $thumbFilename, 'video_processed' => true, ]); // Delete original file @unlink($this->videoPath); } }
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email christianuswibisono@gmail.com instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.