Source path: app/Jobs/GenerateVideo.php
Extracted from supplied specification. Review before copying into application.

<?php
namespace App\Jobs;

use App\Models\Media;
use App\Services\Ai\AiManager;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class GenerateVideo implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public Media $media, public string $aspect = '9:16') {}

    public function handle(AiManager $ai): void
    {
        $errors = [];
        foreach ($ai->chain('video', $this->media->organization) as [$driver, $cfg, $name]) {
            try {
                $meta = $driver->submit($this->media->prompt, ['aspect' => $this->aspect], $cfg);
                $this->media->update([
                    'provider' => $name, 'model' => $cfg['model'] ?? null,
                    'provider_meta' => $meta, 'status' => 'processing', 'aspect_ratio' => $this->aspect,
                ]);
                PollVideo::dispatch($this->media)->delay(now()->addSeconds(20));
                return;
            } catch (\Throwable $e) {
                $errors[] = "$name: " . substr($e->getMessage(), 0, 200);
            }
        }

        $this->media->update(['status' => 'failed', 'error' => implode(' | ', $errors) ?: 'No video provider configured.']);
        $this->media->organization->refund(config('ai.cost.video'));
    }
}
