Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/floatplane.py (Python)

Code before:
    }]

    def _real_initialize(self):
        if not self._get_cookies('https://www.floatplane.com').get('sails.sid'):
            self.raise_login_required()

    def _real_extract(self, url):
        post_id = self._match_id(url)

        post_data = self._download_json(
            'https://www.floatplane.com/api/v3/content/post', post_id, query={'id': post_id},
            note='Downloading post data', errnote='Unable to download post data')

        if not any(traverse_obj(post_data, ('metadata', ('hasVideo', 'hasAudio')))):
            raise ExtractorError('Post does not contain a video or audio track', expected=True)

        uploader_url = format_field(
            post_data, [('creator', 'urlname')], 'https://www.floatplane.com/channel/%s/home') or None
        channel_url = urljoin(f'{uploader_url}/', traverse_obj(post_data, ('channel', 'urlname')))

        common_info = {
            **traverse_obj(post_data, {
                'uploader': ('creator', 'title'),
                'uploader_id': ('creator', 'id'),
                'channel': ('channel', 'title'),
                'channel_id': ('channel', 'id'),
                'release_timestamp': ('releaseDate', {parse_iso8601}),
            }),
            'uploader_url': uploader_url,
            'channel_url': channel_url,
            'availability': self._availability(needs_subscription=True),
        }

        items = []
        for media in traverse_obj(post_data, (('videoAttachments', 'audioAttachments'), ...)):
            media_id = media['id']
            media_typ = media.get('type') or 'video'

            metadata = self._download_json(
                f'https://www.floatplane.com/api/v3/content/{media_typ}', media_id, query={'id': media_id},
                note=f'Downloading {media_typ} metadata')

            stream = self._download_json(
                'https://www.floatplane.com/api/v2/cdn/delivery', media_id, query={
                    'type': 'vod' if media_typ == 'video' else 'aod',
                    'guid': metadata['guid']
                }, note=f'Downloading {media_typ} stream data')

            path_template = traverse_obj(stream, ('resource', 'uri', {str}))

            def format_path(params):

Review comment (suggestion):
I know this wasn't there initially, but to be safe we might as well add:
```suggestion
                'uploader': ('creator', 'title', {str}),
                'uploader_id': ('creator', 'id', {str}),
                'channel': ('channel', 'title', {str}),
                'channel_id': ('channel', 'id', {str}),
```

Code after:
        if not self._get_cookies('https://www.floatplane.com').get('sails.sid'):
            self.raise_login_required()

    def _real_extract(self, url):
        post_id = self._match_id(url)

        post_data = self._download_json(
            'https://www.floatplane.com/api/v3/content/post', post_id, query={'id': post_id},
            note='Downloading post data', errnote='Unable to download post data')

        if not any(traverse_obj(post_data, ('metadata', ('hasVideo', 'hasAudio')))):
            raise ExtractorError('Post does not contain a video or audio track', expected=True)

        uploader_url = format_field(
            post_data, [('creator', 'urlname')], 'https://www.floatplane.com/channel/%s/home') or None

        common_info = {
            'uploader_url': uploader_url,
            'channel_url': urljoin(f'{uploader_url}/', traverse_obj(post_data, ('channel', 'urlname'))),
            'availability': self._availability(needs_subscription=True),
            **traverse_obj(post_data, {
                'uploader': ('creator', 'title', {str}),
                'uploader_id': ('creator', 'id', {str}),
                'channel': ('channel', 'title', {str}),
                'channel_id': ('channel', 'id', {str}),
                'release_timestamp': ('releaseDate', {parse_iso8601}),
            }),
        }

        items = []
        for media in traverse_obj(post_data, (('videoAttachments', 'audioAttachments'), ...)):
            media_id = media['id']
            media_typ = media.get('type') or 'video'

            metadata = self._download_json(
                f'https://www.floatplane.com/api/v3/content/{media_typ}', media_id, query={'id': media_id},
                note=f'Downloading {media_typ} metadata')

            stream = self._download_json(
                'https://www.floatplane.com/api/v2/cdn/delivery', media_id, query={
                    'type': 'vod' if media_typ == 'video' else 'aod',
                    'guid': metadata['guid']
                }, note=f'Downloading {media_typ} stream data')

            path_template = traverse_obj(stream, ('resource', 'uri', {str}))

            def format_path(params):
                path = path_template
                for i, val in (params or {}).items():
                    path = path.replace(f'{{qualityLevelParams.{i}}}', val)
                return path