Repository: yt-dlp/yt-dlp

Issue:
support for eurosport.com
### Checklist

- [X] I'm reporting a new site support request
- [X] I've verified that I'm running yt-dlp version **2022.01.21**. ([update instructions](https://github.com/yt-dlp/yt-dlp#update))
- [X] I've checked that all provided URLs are alive and playable in a browser
- [X] I've checked that none of provided URLs [violate any copyrights](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
- [X] I've searched 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 am willing to share it if required

### Region

worldwide

### Example URLs

https://www.eurosport.com/tennis/australian-open/2022/nick-kyrgios-hits-kid-in-crowd-with-wild-shot-turns-his-tears-to-smiles-with-gift-at-australian-open_vid1614604/video.shtml

### Description

[Eurosport](https://en.wikipedia.org/wiki/Eurosport) is a pan-European television sports network and a subsidiary of Discovery.

### Verbose log

```shell
[debug] Command-line config: ['https://www.eurosport.com/tennis/australian-open/2022/nick-kyrgios-hits-kid-in-crowd-with-wild-shot-turns-his-tears-to-smiles-with-gift-at-australian-open_vid1614604/video.shtml', '--ignore-config', '-v']
[debug] Encodings: locale cp1252, fs utf-8, out utf-8, err utf-8, pref cp1252
[debug] yt-dlp version 2022.01.21 [f20d607] (win_exe)
[debug] Python version 3.8.10 (CPython 64bit) - Windows-10-10.0.19043-SP0
[debug] exe versions: ffmpeg N-105208-gb24f0c82b3-20220108 (setts), ffprobe N-105208-gb24f0c82b3-20220108
[debug] Optional libraries: Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
[debug] [generic] Extracting URL: https://www.eurosport.com/tennis/australian-open/2022/nick-kyrgios-hits-kid-in-crowd-with-wild-shot-turns-his-tears-to-smiles-with-gift-at-australian-open_vid1614604/video.shtml
[generic] video: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] video: Downloading webpage
[generic] video: Extracting information
[debug] Looking for video embeds
ERROR: Unsupported URL: https://www.eurosport.com/tennis/australian-open/2022/nick-kyrgios-hits-kid-in-crowd-with-wild-shot-turns-his-tears-to-smiles-with-gift-at-australian-open_vid1614604/video.shtml
Traceback (most recent call last):
  File "yt_dlp\YoutubeDL.py", line 1367, in wrapper
  File "yt_dlp\YoutubeDL.py", line 1437, in __extract_info
  File "yt_dlp\extractor\common.py", line 612, in extract
  File "yt_dlp\extractor\generic.py", line 3966, in _real_extract
yt_dlp.utils.UnsupportedError: Unsupported URL: https://www.eurosport.com/tennis/australian-open/2022/nick-kyrgios-hits-kid-in-crowd-with-wild-shot-turns-his-tears-to-smiles-with-gift-at-australian-open_vid1614604/video.shtml
```



Discussion:
I found url that not geo-blocked:
https://www.eurosport.com/tennis/roland-garros/2022/highlights-rafael-nadal-brushes-aside-caper-ruud-to-win-record-extending-14th-french-open-title_vid1694147/video.shtml
https://www.eurosport.com/tennis/roland-garros/2022/watch-the-top-five-shots-from-men-s-final-as-rafael-nadal-beats-casper-ruud-to-seal-14th-french-open_vid1694283/video.shtml

Resolving patch:
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -491,6 +491,7 @@
 from .esri import EsriVideoIE
 from .europa import EuropaIE
 from .europeantour import EuropeanTourIE
+from .eurosport import EurosportIE
 from .euscreen import EUScreenIE
 from .expotv import ExpoTVIE
 from .expressen import ExpressenIE
diff --git a/yt_dlp/extractor/eurosport.py b/yt_dlp/extractor/eurosport.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/eurosport.py
@@ -0,0 +1,99 @@
+from .common import InfoExtractor
+from ..utils import traverse_obj
+
+
+class EurosportIE(InfoExtractor):
+    _VALID_URL = r'https?://www\.eurosport\.com/\w+/[\w-]+/\d+/[\w-]+_(?P<id>vid\d+)'
+    _TESTS = [{
+        'url': 'https://www.eurosport.com/tennis/roland-garros/2022/highlights-rafael-nadal-brushes-aside-caper-ruud-to-win-record-extending-14th-french-open-title_vid1694147/video.shtml',
+        'info_dict': {
+            'id': '2480939',
+            'ext': 'mp4',
+            'title': 'Highlights: Rafael Nadal brushes aside Caper Ruud to win record-extending 14th French Open title',
+            'description': 'md5:b564db73ecfe4b14ebbd8e62a3692c76',
+            'thumbnail': 'https://imgresizer.eurosport.com/unsafe/1280x960/smart/filters:format(jpeg)/origin-imgresizer.eurosport.com/2022/06/05/3388285-69245968-2560-1440.png',
+            'duration': 195.0,
+            'display_id': 'vid1694147',
+            'timestamp': 1654446698,
+            'upload_date': '20220605',
+        }
+    }, {
+        'url': 'https://www.eurosport.com/tennis/roland-garros/2022/watch-the-top-five-shots-from-men-s-final-as-rafael-nadal-beats-casper-ruud-to-seal-14th-french-open_vid1694283/video.shtml',
+        'info_dict': {
+            'id': '2481254',
+            'ext': 'mp4',
+            'title': 'md5:149dcc5dfb38ab7352acc008cc9fb071',
+            'duration': 130.0,
+            'thumbnail': 'https://imgresizer.eurosport.com/unsafe/1280x960/smart/filters:format(jpeg)/origin-imgresizer.eurosport.com/2022/06/05/3388422-69248708-2560-1440.png',
+            'description': 'md5:a0c8a7f6b285e48ae8ddbe7aa85cfee6',
+            'display_id': 'vid1694283',
+            'timestamp': 1654456090,
+            'upload_date': '20220605',
+        }
+    }, {
+        # geo-fence but can bypassed by xff
+        'url': 'https://www.eurosport.com/cycling/tour-de-france-femmes/2022/incredible-ride-marlen-reusser-storms-to-stage-4-win-at-tour-de-france-femmes_vid1722221/video.shtml',
+        'info_dict': {
+            'id': '2582552',
+            'ext': 'mp4',
+            'title': '‘Incredible ride!’ - Marlen Reusser storms to Stage 4 win at Tour de France Femmes',
+            'duration': 188.0,
+            'display_id': 'vid1722221',
+            'timestamp': 1658936167,
+            'thumbnail': 'https://imgresizer.eurosport.com/unsafe/1280x960/smart/filters:format(jpeg)/origin-imgresizer.eurosport.com/2022/07/27/3423347-69852108-2560-1440.jpg',
+            'description': 'md5:32bbe3a773ac132c57fb1e8cca4b7c71',
+            'upload_date': '20220727',
+        }
+    }]
+
+    _TOKEN = None
+
+    # actually defined in https://netsport.eurosport.io/?variables={"databaseId":<databaseId>,"playoutType":"VDP"}&extensions={"persistedQuery":{"version":1 ..
+    # but this method require to get sha256 hash
+    _GEO_COUNTRIES = ['DE', 'NL', 'EU', 'IT', 'FR']  # Not complete list but it should work
+
+    def _real_initialize(self):
+        if EurosportIE._TOKEN is None:
+            EurosportIE._TOKEN = self._download_json(
+                'https://eu3-prod-direct.eurosport.com/token?realm=eurosport', None,
+                'Trying to get token')['data']['attributes']['token']
+
+    def _real_extract(self, url):
+        display_id = self._match_id(url)
+        webpage = self._download_webpage(url, display_id)
+
+        json_data = self._download_json(
+            f'https://eu3-prod-direct.eurosport.com/playback/v2/videoPlaybackInfo/sourceSystemId/eurosport-{display_id}',
+            display_id, query={'usePreAuth': True}, headers={'Authorization': f'Bearer {EurosportIE._TOKEN}'})['data']
+
+        json_ld_data = self._search_json_ld(webpage, display_id)
+
+        formats, subtitles = [], {}
+        for stream_type in json_data['attributes']['streaming']:
+            if stream_type == 'hls':
+                fmts, subs = self._extract_m3u8_formats_and_subtitles(
+                    traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id, ext='mp4')
+            elif stream_type == 'dash':
+                fmts, subs = self._extract_mpd_formats_and_subtitles(
+                    traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id)
+            elif stream_type == 'mss':
+                fmts, subs = self._extract_ism_formats_and_subtitles(
+                    traverse_obj(json_data, ('attributes', 'streaming', stream_type, 'url')), display_id)
+
+            formats.extend(fmts)
+            self._merge_subtitles(subs, target=subtitles)
+
+        self._sort_formats(formats)
+
+        return {
+            'id': json_data['id'],
+            'title': json_ld_data.get('title') or self._og_search_title(webpage),
+            'display_id': display_id,
+            'formats': formats,
+            'subtitles': subtitles,
+            'thumbnails': json_ld_data.get('thumbnails'),
+            'description': (json_ld_data.get('description')
+                            or self._html_search_meta(['og:description', 'description'], webpage)),
+            'duration': json_ld_data.get('duration'),
+            'timestamp': json_ld_data.get('timestamp'),
+        }


Test patch: