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

<?php
namespace App\Jobs;

use App\Events\VideoReady;
use App\Models\Media;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

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

    public int $tries = 1;
    public int $timeout = 600;

    public function __construct(public Media $media, public int $round = 0) {}

    public function handle(): void
    {
        $res = Http::withToken(config('services.replicate.token'))
            ->get("https://api.replicate.com/v1/predictions/{$this->media->provider_job_id}")->throw();

        $status = $res->json('status');

        if (in_array($status, ['starting', 'processing'])) {
            if ($this->round > 120) { // ~40 min cap
                $this->fail('timeout');
                return $this->markFailed('Generation timed out.');
            }
            self::dispatch($this->media, $this->round + 1)->delay(now()->addSeconds(20));
            return;
        }

        if ($status !== 'succeeded') {
            $this->markFailed($res->json('error') ?? $status);
            return;
        }

        $output = $res->json('output');
        $url = is_array($output) ? $output[0] : $output;

        $disk = Storage::disk(config('filesystems.default'));
        $raw = 'tmp/' . Str::uuid() . '.mp4';
        $out = 'media/' . $this->media->user_id . '/' . Str::uuid() . '.mp4';
        $disk->put($raw, Http::timeout(300)->get($url)->body());

        // Normalize for social platforms: H.264/AAC, faststart
        $r = Process::timeout(300)->run([
            'ffmpeg', '-y', '-i', $disk->path($raw),
            '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-preset', 'veryfast',
            '-c:a', 'aac', '-movflags', '+faststart',
            $disk->path($out),
        ]);

        $disk->delete($raw);

        if ($r->failed()) {
            $this->markFailed('FFmpeg failed: ' . Str::limit($r->errorOutput(), 500));
            return;
        }

        $this->media->update(['path' => $out, 'status' => 'ready']);
        VideoReady::dispatch($this->media);
    }

    private function markFailed(string $msg): void
    {
        $this->media->update(['status' => 'failed', 'error' => $msg]);
        $this->media->user()->increment('ai_credits', 5); // refund
    }
}
