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

Code before:

        return f'https://{username}.substack.com/{params}'

    def _extract_video_formats(self, video_id, username):
        formats, subtitles = [], {}
        for video_format in ('hls', 'mp4'):
            video_url = f'https://{username}.substack.com/api/v1/video/upload/{video_id}/src?type={video_format}'

            if video_format == 'hls':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(video_url, video_id, 'mp4', fatal=False)
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)
            else:
                formats.append({
                    'url': video_url,
                    'ext': video_format,
                })

        return formats, subtitles

    def _real_extract(self, url):
        display_id, username = self._match_valid_url(url).group('id', 'username')
        webpage = self._download_webpage(url, display_id)

        post_info = self._search_json(
            r'<script[^>]*>\s*window\._preloads\s*=\s*', webpage, 'preloads', display_id)['post']

        post_type = post_info.get('type')
        formats, subtitles = [], {}
        if post_type == 'podcast':
            formats, subtitles = [{'url': post_info['podcast_url']}], {}
        elif post_type == 'video':
            formats, subtitles = self._extract_video_formats(post_info['videoUpload']['id'], username)
        else:
            self.raise_no_formats(f'Page type "{post_type}" is not supported')

        self._sort_formats(formats)
        return {
            'id': str(post_info['id']),
            'formats': formats,
            'subtitles': subtitles,
            'title': post_info.get('title'),
            'description': post_info.get('description'),
            'thumbnail': post_info.get('cover_image'),
            'uploader': username,
        }


Review comment (suggestion):
```suggestion
            r'<script[^>]*>\s*window\._preloads\s*=', webpage, 'preloads', display_id)['post']
```

Code after:
        mobj = re.search(r'{[^}]*["\']subdomain["\']\s*:\s*["\'](?P<subdomain>[^"]+)', webpage)
        if mobj:
            parsed = urllib.parse.urlparse(url)
            return parsed._replace(netloc=f'{mobj.group("subdomain")}.substack.com').geturl()

    def _extract_video_formats(self, video_id, username):
        formats, subtitles = [], {}
        for video_format in ('hls', 'mp4'):
            video_url = f'https://{username}.substack.com/api/v1/video/upload/{video_id}/src?type={video_format}'

            if video_format == 'hls':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(video_url, video_id, 'mp4', fatal=False)
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)
            else:
                formats.append({
                    'url': video_url,
                    'ext': video_format,
                })

        return formats, subtitles

    def _real_extract(self, url):
        display_id, username = self._match_valid_url(url).group('id', 'username')
        webpage = self._download_webpage(url, display_id)

        webpage_info = self._search_json(r'<script[^>]*>\s*window\._preloads\s*=', webpage, 'preloads', display_id)

        post_type = webpage_info['post']['type']
        formats, subtitles = [], {}
        if post_type == 'podcast':
            formats, subtitles = [{'url': webpage_info['post']['podcast_url']}], {}
        elif post_type == 'video':
            formats, subtitles = self._extract_video_formats(webpage_info['post']['videoUpload']['id'], username)
        else:
            self.raise_no_formats(f'Page type "{post_type}" is not supported')

        self._sort_formats(formats)
        return {
            'id': str(webpage_info['post']['id']),
            'formats': formats,
            'subtitles': subtitles,
            'title': traverse_obj(webpage_info, ('post', 'title')),
            'description': traverse_obj(webpage_info, ('post', 'description')),
            'thumbnail': traverse_obj(webpage_info, ('post', 'cover_image')),
            'uploader': traverse_obj(webpage_info, ('pub', 'name')),
            'uploader_id': str_or_none(traverse_obj(webpage_info, ('post', 'publication_id'))),
        }