Repository: yt-dlp/yt-dlp

Issue:
[Winsports.co] no mediastream embed found
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting that a **supported** site is broken
- [X] I've verified that I'm running yt-dlp version **2023.03.03** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

Colombia

### Provide a description that is worded well enough to be understood

This error started after the previous fix to the winsports ie 

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [X] If using API, add `'verbose': True` to `YoutubeDL` params instead
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', 'https://www.winsports.co/videos/bucaramanga-se-quedo-con-el-grito-de-gol-en-la-garganta']
[debug] Encodings: locale cp1252, fs utf-8, pref cp1252, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2023.03.03 [934496428] (win_exe)
[debug] Python 3.8.10 (CPython AMD64 64bit) - Windows-10-10.0.19045-SP0 (OpenSSL 1.1.1k  25 Mar 2021)
[debug] exe versions: ffmpeg N-109920-gac6eec1fc2-20230224 (setts), ffprobe N-109920-gac6eec1fc2-20230224
[debug] Optional libraries: Cryptodome-3.17, brotli-1.0.9, certifi-2022.12.07, mutagen-1.46.0, sqlite3-2.6.0, websockets-10.4
[debug] Proxy map: {}
[debug] Loaded 1782 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Available version: stable@2023.03.03, Current version: stable@2023.03.03
Current Build Hash: e46f99d497641d7bdb9180691e78a9d4a3525b964ed918c1a0a3a66a97b11ae1
yt-dlp is up to date (stable@2023.03.03)
[WinSportsVideo] Extracting URL: https://www.winsports.co/videos/bucaramanga-se-quedo-con-el-grito-de-gol-en-la-garganta
[WinSportsVideo] bucaramanga-se-quedo-con-el-grito-de-gol-en-la-garganta: Downloading webpage
ERROR: [WinSportsVideo] bucaramanga-se-quedo-con-el-grito-de-gol-en-la-garganta: No MediaStream embed found in webpage; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "yt_dlp\extractor\common.py", line 694, in extract
  File "yt_dlp\extractor\mediastream.py", line 174, in _real_extract
  File "yt_dlp\extractor\common.py", line 1172, in raise_no_formats
```



Discussion:
> This error started after the previous fix to the winsports ie

If you are saying older versions still work, pls show verbose of that too.
This URL was not matched by the WinSportsVideo extractor before the last patch, ~~but generic was detecting a MediaStream embed~~ (edit: whoops wrong about this, it just looked like it would be because of the iframe embed, but the MediaStream embed regex is actually broken! :laughing:). I'll PR in a bit

Resolving patch:
diff --git a/yt_dlp/extractor/mediastream.py b/yt_dlp/extractor/mediastream.py
--- a/yt_dlp/extractor/mediastream.py
+++ b/yt_dlp/extractor/mediastream.py
@@ -2,16 +2,44 @@
 
 from .common import InfoExtractor
 from ..utils import (
+    clean_html,
     remove_end,
-    str_or_none,
-    strip_or_none,
     traverse_obj,
     urljoin,
 )
 
 
-class MediaStreamIE(InfoExtractor):
-    _VALID_URL = r'https?://mdstrm.com/(?:embed|live-stream)/(?P<id>\w+)'
+class MediaStreamBaseIE(InfoExtractor):
+    _EMBED_BASE_URL = 'https://mdstrm.com/embed'
+    _BASE_URL_RE = r'https?://mdstrm\.com/(?:embed|live-stream)'
+
+    def _extract_mediastream_urls(self, webpage):
+        yield from traverse_obj(list(self._yield_json_ld(webpage, None)), (
+            lambda _, v: v['@type'] == 'VideoObject', ('embedUrl', 'contentUrl'),
+            {lambda x: x if re.match(rf'{self._BASE_URL_RE}/\w+', x) else None}))
+
+        for mobj in re.finditer(r'<script[^>]+>[^>]*playerMdStream\.mdstreamVideo\(\s*[\'"](?P<video_id>\w+)', webpage):
+            yield f'{self._EMBED_BASE_URL}/{mobj.group("video_id")}'
+
+        yield from re.findall(
+            rf'<iframe[^>]+\bsrc="({self._BASE_URL_RE}/\w+)', webpage)
+
+        for mobj in re.finditer(
+            r'''(?x)
+                <(?:div|ps-mediastream)[^>]+
+                (class="[^"]*MediaStreamVideoPlayer)[^"]*"[^>]+
+                data-video-id="(?P<video_id>\w+)"
+                (?:\s*data-video-type="(?P<video_type>[^"]+))?
+                (?:[^>]*>\s*<div[^>]+\1[^"]*"[^>]+data-mediastream=["\'][^>]+
+                    https://mdstrm\.com/(?P<live>live-stream))?
+                ''', webpage):
+
+            video_type = 'live-stream' if mobj.group('video_type') == 'live' or mobj.group('live') else 'embed'
+            yield f'https://mdstrm.com/{video_type}/{mobj.group("video_id")}'
+
+
+class MediaStreamIE(MediaStreamBaseIE):
+    _VALID_URL = MediaStreamBaseIE._BASE_URL_RE + r'/(?P<id>\w+)'
 
     _TESTS = [{
         'url': 'https://mdstrm.com/embed/6318e3f1d1d316083ae48831',
@@ -23,6 +51,7 @@ class MediaStreamIE(InfoExtractor):
             'thumbnail': r're:^https?://[^?#]+6318e3f1d1d316083ae48831',
             'ext': 'mp4',
         },
+        'params': {'skip_download': 'm3u8'},
     }]
 
     _WEBPAGE_TESTS = [{
@@ -35,9 +64,7 @@ class MediaStreamIE(InfoExtractor):
             'ext': 'mp4',
             'live_status': 'is_live',
         },
-        'params': {
-            'skip_download': 'Livestream'
-        },
+        'params': {'skip_download': 'Livestream'},
     }, {
         'url': 'https://www.multimedios.com/television/clases-de-llaves-y-castigos-quien-sabe-mas',
         'md5': 'de31f0b1ecc321fb35bf22d58734ea40',
@@ -48,6 +75,7 @@ class MediaStreamIE(InfoExtractor):
             'thumbnail': 're:^https?://[^?#]+63731bab8ec9b308a2c9ed28',
             'ext': 'mp4',
         },
+        'params': {'skip_download': 'm3u8'},
     }, {
         'url': 'https://www.americatv.com.pe/videos/esto-es-guerra/facundo-gonzalez-sufrio-fuerte-golpe-durante-competencia-frente-hugo-garcia-eeg-noticia-139120',
         'info_dict': {
@@ -57,6 +85,7 @@ class MediaStreamIE(InfoExtractor):
             'thumbnail': 're:^https?://[^?#]+63756df1c638b008a5659dec',
             'ext': 'mp4',
         },
+        'params': {'skip_download': 'm3u8'},
     }, {
         'url': 'https://www.americatv.com.pe/videos/al-fondo-hay-sitio/nuevas-lomas-town-bernardo-mata-se-enfrento-sujeto-luchar-amor-macarena-noticia-139083',
         'info_dict': {
@@ -66,26 +95,12 @@ class MediaStreamIE(InfoExtractor):
             'thumbnail': 're:^https?://[^?#]+637307669609130f74cd3a6e',
             'ext': 'mp4',
         },
+        'params': {'skip_download': 'm3u8'},
     }]
 
-    @classmethod
-    def _extract_embed_urls(cls, url, webpage):
-        for mobj in re.finditer(r'<script[^>]+>[^>]*playerMdStream.mdstreamVideo\(\s*[\'"](?P<video_id>\w+)', webpage):
-            yield f'https://mdstrm.com/embed/{mobj.group("video_id")}'
-
-        yield from re.findall(
-            r'<iframe[^>]src\s*=\s*"(https://mdstrm.com/[\w-]+/\w+)', webpage)
-
-        for mobj in re.finditer(
-            r'''(?x)
-                <(?:div|ps-mediastream)[^>]+
-                class\s*=\s*"[^"]*MediaStreamVideoPlayer[^"]*"[^>]+
-                data-video-id\s*=\s*"(?P<video_id>\w+)\s*"
-                (?:\s*data-video-type\s*=\s*"(?P<video_type>[^"]+))?
-                ''', webpage):
-
-            video_type = 'live-stream' if mobj.group('video_type') == 'live' else 'embed'
-            yield f'https://mdstrm.com/{video_type}/{mobj.group("video_id")}'
+    def _extract_from_webpage(self, url, webpage):
+        for embed_url in self._extract_mediastream_urls(webpage):
+            yield self.url_result(embed_url, MediaStreamIE, None)
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
@@ -94,7 +109,7 @@ def _real_extract(self, url):
         if 'Debido a tu ubicación no puedes ver el contenido' in webpage:
             self.raise_geo_restricted()
 
-        player_config = self._search_json(r'window.MDSTRM.OPTIONS\s*=', webpage, 'metadata', video_id)
+        player_config = self._search_json(r'window\.MDSTRM\.OPTIONS\s*=', webpage, 'metadata', video_id)
 
         formats, subtitles = [], {}
         for video_format in player_config['src']:
@@ -122,7 +137,7 @@ def _real_extract(self, url):
         }
 
 
-class WinSportsVideoIE(InfoExtractor):
+class WinSportsVideoIE(MediaStreamBaseIE):
     _VALID_URL = r'https?://www\.winsports\.co/videos/(?P<id>[\w-]+)'
 
     _TESTS = [{
@@ -158,21 +173,36 @@ class WinSportsVideoIE(InfoExtractor):
             'ext': 'mp4',
         },
         'params': {'skip_download': 'm3u8'},
+    }, {
+        'url': 'https://www.winsports.co/videos/bucaramanga-se-quedo-con-el-grito-de-gol-en-la-garganta',
+        'info_dict': {
+            'id': '6402adb62bbf3b18d454e1b0',
+            'display_id': 'bucaramanga-se-quedo-con-el-grito-de-gol-en-la-garganta',
+            'title': '⚽Bucaramanga se quedó con el grito de gol en la garganta',
+            'description': 'Gol anulado Bucaramanga',
+            'thumbnail': r're:^https?://[^?#]+6402adb62bbf3b18d454e1b0',
+            'ext': 'mp4',
+        },
+        'params': {'skip_download': 'm3u8'},
     }]
 
     def _real_extract(self, url):
         display_id = self._match_id(url)
         webpage = self._download_webpage(url, display_id)
-        json_ld = self._search_json_ld(webpage, display_id, expected_type='VideoObject', default={})
-        media_setting_json = self._search_json(
-            r'<script\s*[^>]+data-drupal-selector="drupal-settings-json">', webpage, 'drupal-setting-json', display_id)
-
-        mediastream_id = traverse_obj(
-            media_setting_json, ('settings', 'mediastream_formatter', ..., 'mediastream_id', {str_or_none}),
-            get_all=False) or json_ld.get('url')
-        if not mediastream_id:
+        data = self._search_json(
+            r'<script\s*[^>]+data-drupal-selector="drupal-settings-json">', webpage, 'data', display_id)
+
+        mediastream_url = urljoin(f'{self._EMBED_BASE_URL}/', (
+            traverse_obj(data, (
+                (('settings', 'mediastream_formatter', ..., 'mediastream_id'), 'url'), {str}), get_all=False)
+            or next(self._extract_mediastream_urls(webpage), None)))
+
+        if not mediastream_url:
             self.raise_no_formats('No MediaStream embed found in webpage')
 
+        title = clean_html(remove_end(
+            self._search_json_ld(webpage, display_id, expected_type='VideoObject', default={}).get('title')
+            or self._og_search_title(webpage), '| Win Sports'))
+
         return self.url_result(
-            urljoin('https://mdstrm.com/embed/', mediastream_id), MediaStreamIE, display_id, url_transparent=True,
-            display_id=display_id, video_title=strip_or_none(remove_end(json_ld.get('title'), '| Win Sports')))
+            mediastream_url, MediaStreamIE, display_id, url_transparent=True, display_id=display_id, video_title=title)


Test patch: