ac/transcoding-bundle
Symfony bundle that wires AC\Transcoding\Transcoder as a container service. Configure FFmpeg and/or HandBrakeCLI paths/timeouts, register custom adapters/presets/listeners via tags, and run CLI commands to transcode files or check adapter/preset status.
Installation:
composer require ac/transcoding-bundle
Enable the bundle in config/bundles.php:
return [
// ...
AmericanCouncils\TranscodingBundle\ACTranscodingBundle::class => ['all' => true],
];
Configure config/packages/ac_transcoding.yaml (or app/config.yml in older Laravel/Symfony):
ac_transcoding:
ffmpeg:
enabled: true
path: /usr/bin/ffmpeg
timeout: 30
handbrake:
enabled: true
path: /usr/local/bin/HandBrakeCLI
timeout: 60
First Use Case:
Inject the transcoder service into a Laravel service container or controller:
use AC\Transcoding\Transcoder;
public function __construct(Transcoder $transcoder) {
$this->transcoder = $transcoder;
}
public function transcodeVideo(Request $request) {
$inputFile = $request->file('video')->getRealPath();
$outputFile = storage_path('app/transcoded/' . uniqid() . '.mp4');
$this->transcoder->transcodeWithPreset($inputFile, 'handbrake.classic', $outputFile);
return response()->json(['success' => true]);
}
Basic Transcoding:
// Transcode with a built-in preset
$transcoder->transcodeWithPreset($inputPath, 'ffmpeg.h264', $outputPath);
// Transcode with custom options (array)
$transcoder->transcode($inputPath, [
'preset' => 'handbrake.classic',
'output' => $outputPath,
'custom_flags' => ['-vf', 'scale=1280:720'],
]);
Dynamic Preset Management:
transcoding.preset:
# config/services.yaml
services:
app.transcoding.preset.custom:
class: App\Transcoding\CustomPreset
tags: ['transcoding.preset']
$transcoder->transcodeWithPreset($inputPath, 'app.transcoding.preset.custom', $outputPath);
Event-Driven Extensions:
TranscodingEvent::PRE_TRANSCODE):
use AC\Transcoding\Event\TranscodingEvent;
public function onPreTranscode(TranscodingEvent $event) {
// Log or modify event data
$event->setCustomFlag('--some-flag');
}
services:
app.transcoding.listener:
class: App\Transcoding\PreTranscodeListener
tags: ['transcoding.listener']
CLI Integration:
transcoder:transcode command for batch processing:
php bin/console transcoder:transcode /path/to/input.mp4 handbrake.classic /path/to/output.mp4
TranscodingCommand.Laravel-Specific Patterns:
public function handleUpload(Request $request) {
$file = $request->file('video');
$tempPath = $file->getRealPath();
$outputPath = storage_path('app/transcoded/' . $file->hashName());
$this->transcoder->transcodeWithPreset($tempPath, 'ffmpeg.h264', $outputPath);
return response()->file($outputPath);
}
use App\Jobs\TranscodeVideoJob;
TranscodeVideoJob::dispatch($inputPath, 'ffmpeg.h264', $outputPath)->onQueue('transcoding');
Binary Paths:
ffmpeg/HandBrakeCLI paths are correct. Use absolute paths (e.g., /usr/bin/ffmpeg).which ffmpeg # Linux/Mac
where ffmpeg # Windows
Timeouts:
timeout in config to avoid hanging (e.g., timeout: 30 for 30 seconds).0 means no timeout (risk of server overload).File Permissions:
www-data, apache) has write permissions for output directories:
chmod -R 775 storage/app/transcoded
chown -R www-data:www-data storage/app/transcoded
Preset Naming:
ffmpeg.h264, handbrake.classic, handbrake.web.transcoding.preset tag.Error Handling:
try {
$transcoder->transcodeWithPreset($input, 'ffmpeg.h264', $output);
} catch (\AC\Transcoding\Exception\TranscodingException $e) {
Log::error('Transcoding failed: ' . $e->getMessage());
return response()->json(['error' => 'Transcoding failed'], 500);
}
Log Output:
$transcoder->transcode($input, [
'preset' => 'ffmpeg.h264',
'output' => $output,
'verbose' => true, // Logs full command output
]);
Check Command Line:
ffmpeg -i input.mp4 -c:v libx264 output.mp4
HandBrakeCLI -i input.mp4 -o output.mp4
Service Dumping:
php bin/console debug:container | grep transcoding
Custom Adapters:
AC\Transcoding\Adapter\AdapterInterface and tag as transcoding.adapter:
services:
app.transcoding.adapter.custom:
class: App\Transcoding\CustomAdapter
arguments: ['@logger']
tags: ['transcoding.adapter']
Dynamic Preset Loading:
$presetConfig = $this->fetchPresetFromDatabase($presetName);
$transcoder->transcode($input, [
'preset' => new \AC\Transcoding\Preset\DynamicPreset($presetConfig),
'output' => $output,
]);
Laravel Events:
use Illuminate\Http\Events\FileUploaded;
FileUploaded::listen(function ($event) {
if ($event->file->getClientOriginalExtension() === 'mp4') {
$this->transcoder->transcodeWithPreset(
$event->file->getRealPath(),
'ffmpeg.h264',
storage_path('app/transcoded/' . $event->file->hashName())
);
}
});
Artisan Commands:
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use AC\Transcoding\Transcoder;
class CustomTranscodeCommand extends Command {
protected static $defaultName = 'transcoder:custom';
public function __construct(Transcoder $transcoder) {
$this->transcoder = $transcoder;
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output) {
$this->transcoder->transcodeWithPreset(
$input->getArgument('input'),
'handbrake.web',
$input->getArgument('output')
);
$output->writeln('Transcoding complete!');
return Command::SUCCESS;
}
}
Register in config/services.yaml:
services:
App\Console\Commands\CustomTranscodeCommand:
tags: ['console.command']
How can I help you explore Laravel packages today?