Source path: app/Services/Publishers/YouTubePublisher.php
Extracted from supplied specification. Review before copying into application.

<?php
namespace App\Services\Publishers;

use App\Models\PostTarget;
use App\Models\SocialAccount;
use GuzzleHttp\Psr7\Utils;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class YouTubePublisher extends BasePublisher
{
    public function discoverAccounts(string $t): array
    {
        $items = Http::withToken($t)->get('https://www.googleapis.com/youtube/v3/channels', ['part' => 'snippet', 'mine' => 'true'])
            ->throw()->json('items', []);
        return array_map(fn ($c) => ['id' => $c['id'], 'name' => $c['snippet']['title']], $items);
    }

    public function refreshToken(SocialAccount $a): void
    {
        $c = config('social.youtube');
        $r = Http::asForm()->post($c['token_url'], [
            'client_id' => $c['client_id'], 'client_secret' => $c['client_secret'],
            'refresh_token' => $a->refresh_token, 'grant_type' => 'refresh_token',
        ])->throw();
        $this->saveTokens($a, $r->json());
    }

    public function publish(PostTarget $t): array
    {
        $tok = $this->token($t->socialAccount);
        $video = $this->media($t)->firstWhere('type', 'video');
        if (!$video) throw new \RuntimeException('YouTube requires a video.');

        $post = $t->post;
        $title = $post->title ?: Str::limit(strtok($post->caption, "\n"), 100, '');
        $f = $this->tmp($video);

        try {
            $size = filesize($f);
            $init = Http::withToken($tok)->withHeaders([
                'X-Upload-Content-Type' => 'video/mp4',
                'X-Upload-Content-Length' => $size,
            ])->post('https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status', [
                'snippet' => ['title' => $title, 'description' => $post->caption],
                'status' => [
                    'privacyStatus' => config('social.youtube.privacy'),
                    'selfDeclaredMadeForKids' => false,
                ],
            ])->throw();

            $res = Http::withToken($tok)->timeout(0)
                ->withBody(Utils::streamFor(fopen($f, 'r')), 'video/mp4')
                ->put($init->header('Location'))->throw();

            $id = $res->json('id');
            return ['id' => $id, 'url' => "https://www.youtube.com/watch?v=$id"];
        } finally { @unlink($f); }
    }
}

A vertical video of 3 minutes or less with #Shorts in the description is treated as a Short.

PublisherFactory (updated):

php
return match ($platform) {
    'facebook'  => app(FacebookPublisher::class),
    'instagram' => app(InstagramPublisher::class),
    'x'         => app(XPublisher::class),
    'linkedin'  => app(LinkedInPublisher::class),
    'tiktok'    => app(TikTokPublisher::class),
    'youtube'   => app(YouTubePublisher::class),
    default     => throw new \InvalidArgumentException("No publisher for {$platform}"),
};

Add a per-platform validation in PostController::store before saving. It would reject a YouTube or TikTok target with no video, an Instagram target with no media, and an X caption over 280 characters. It saves companies from failures at publish time.
