Source path: app/Http/Controllers/MediaController.php
Extracted from supplied specification. Review before copying into application.

<?php
namespace App\Http\Controllers;

use App\Jobs\{GenerateImage, GenerateVideo};
use App\Models\Media;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class MediaController extends Controller
{
    private const IMAGE_COST = 1;
    private const VIDEO_COST = 5;

    public function upload(Request $r)
    {
        $r->validate(['file' => 'required|file|mimes:jpg,jpeg,png,webp,mp4,mov|max:512000']);
        $file = $r->file('file');
        $type = str_starts_with($file->getMimeType(), 'video') ? 'video' : 'image';
        $path = $file->store("media/{$r->user()->id}", config('filesystems.default'));

        return Media::create([
            'user_id' => $r->user()->id, 'type' => $type,
            'source' => 'upload', 'path' => $path, 'status' => 'ready',
        ]);
    }

    public function generateImage(Request $r)
    {
        $data = $r->validate(['prompt' => 'required|string|max:1000', 'size' => 'in:1024x1024,1024x1536,1536x1024']);
        $this->charge($r, self::IMAGE_COST);

        $m = Media::create([
            'user_id' => $r->user()->id, 'type' => 'image', 'source' => 'ai',
            'prompt' => $data['prompt'], 'status' => 'pending', 'provider' => 'openai',
        ]);
        GenerateImage::dispatch($m, $data['size'] ?? '1024x1024');

        return response()->json($m, 202);
    }

    public function generateVideo(Request $r)
    {
        $data = $r->validate(['prompt' => 'required|string|max:1000']);
        $this->charge($r, self::VIDEO_COST);

        $m = Media::create([
            'user_id' => $r->user()->id, 'type' => 'video', 'source' => 'ai',
            'prompt' => $data['prompt'], 'status' => 'pending',
        ]);
        GenerateVideo::dispatch($m);

        return response()->json($m, 202);
    }

    public function show(Request $r, Media $media)
    {
        abort_if($media->user_id !== $r->user()->id, 403);
        return $media->append('url');
    }

    private function charge(Request $r, int $cost): void
    {
        $affected = \App\Models\User::where('id', $r->user()->id)
            ->where('ai_credits', '>=', $cost)->decrement('ai_credits', $cost);
        abort_if(!$affected, 402, 'Not enough AI credits.');
    }
}

In Media.php, make append('url') work by adding:

php
public function getUrlAttribute(): ?string { return $this->url(); }

(and rename the method to fileUrl() and update its calls to avoid a clash, or keep the method and drop the accessor and append).
