Repository: yt-dlp/yt-dlp

Issue:
[Discovery+] Using US XFR for other countries
### Checklist

- [X] I'm reporting a new site support request
- [X] I've verified that I'm running yt-dlp version **2021.11.10.1**. ([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

Brazil

### Example URLs

- Single video: https://www.discoveryplus.com/br/video/90-dias-para-casar-antes-dos-90-dias-tlc-br/triangulo-amoroso


### Description

Support Request for Discoveryplus Brazil

### Verbose log

```shell
[debug] Command-line config: ['--cookies', 'cookies.txt', '--verbose', 'https://www.discoveryplus.com/br/video/90-dias-para-casar-antes-dos-90-dias-tlc-br/triangulo-amoroso']
[debug] Encodings: locale cp1252, fs utf-8, out utf-8, err utf-8, pref cp1252
[debug] yt-dlp version 2021.11.10.1 (win_exe)
[debug] Python version 3.8.10 (CPython 64bit) - Windows-10-10.0.19044-SP0
[debug] exe versions: none
[debug] Optional libraries: Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
[debug] [DiscoveryPlus] Extracting URL: https://www.discoveryplus.com/br/video/90-dias-para-casar-antes-dos-90-dias-tlc-br/triangulo-amoroso
[debug] Using fake IP 6.252.32.205 (US) as X-Forwarded-For
[DiscoveryPlus] 90-dias-para-casar-antes-dos-90-dias-tlc-br/triangulo-amoroso: Downloading JSON metadata
ERROR: [DiscoveryPlus] 90-dias-para-casar-antes-dos-90-dias-tlc-br/triangulo-amoroso: Unable to download JSON metadata: HTTP Error 404: Not Found (caused by <HTTPError 404: 'Not Found'>); please report this issue on  https://github.com/yt-dlp/yt-dlp . Make sure you are using the latest version; type  yt-dlp -U  to update. Be sure to call yt-dlp with the --verbose flag and include its complete output. (caused by <HTTPError 404: 'Not Found'>); please report this issue on  https://github.com/yt-dlp/yt-dlp . Make sure you are using the latest version; type  yt-dlp -U  to update. Be sure to call yt-dlp with the --verbose flag and include its complete output.
  File "yt_dlp\extractor\common.py", line 713, in _request_webpage
  File "yt_dlp\YoutubeDL.py", line 3295, in urlopen
  File "urllib\request.py", line 531, in open
  File "urllib\request.py", line 640, in http_response
  File "urllib\request.py", line 569, in error
  File "urllib\request.py", line 502, in _call_chain
  File "urllib\request.py", line 649, in http_error_default
```

[InvestigationDiscovery] "This video is only available for registered users. You may want to use --cookies."
### 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 yt-dlp is broken on a **supported** site
- [X] I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
- [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

United States of America

### Provide a description that is worded well enough to be understood

Hello! I'm trying to download a video from the Investigation Discovery website, but I get an error that the video is only available to registered users. This is confusing because the same video plays perfectly fine in my browser without requiring an account. I've tried multiple things, including using --cookies, --source-address, and setting the User-Agent header, but nothing works.

Here's the URL of the video I'm trying to download: https://www.investigationdiscovery.com/video/deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [ ] 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
user1@debian:~/Desktop$ yt-dlp -vU -F -S "vbr,abr" "https://www.investigationdiscovery.com/video/deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca"
[debug] Command-line config: ['-vU', '-F', '-S', 'vbr,abr', 'https://www.investigationdiscovery.com/video/deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version nightly@2024.07.10.232707 from yt-dlp/yt-dlp-nightly-builds [705f5b84d] (zip)
[debug] Python 3.11.2 (CPython x86_64 64bit) - Linux-6.1.0-22-amd64-x86_64-with-glibc2.36 (OpenSSL 3.0.13 30 Jan 2024, glibc 2.36)
[debug] exe versions: none
[debug] Optional libraries: certifi-2022.09.24, mutagen-1.46.0, requests-2.28.1, sqlite3-3.40.1, urllib3-1.26.12
[debug] Proxy map: {}
[debug] Request Handlers: urllib
[debug] Loaded 1834 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp-nightly-builds/releases/latest
Latest version: nightly@2024.07.10.232707 from yt-dlp/yt-dlp-nightly-builds
yt-dlp is up to date (nightly@2024.07.10.232707 from yt-dlp/yt-dlp-nightly-builds)
[InvestigationDiscovery] Extracting URL: https://www.investigationdiscovery.com/video/deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca
[debug] Using fake IP 6.158.231.125 (US) as X-Forwarded-For
[InvestigationDiscovery] deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca: Downloading JSON metadata
ERROR: [InvestigationDiscovery] deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca: This video is only available for registered users. You may want to use --cookies.
  File "/home/user1/.local/bin/yt-dlp/yt_dlp/extractor/common.py", line 740, in extract
    ie_result = self._real_extract(url)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user1/.local/bin/yt-dlp/yt_dlp/extractor/dplay.py", line 365, in _real_extract
    return self._get_disco_api_info(url, self._match_id(url), **self._DISCO_API_PARAMS)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user1/.local/bin/yt-dlp/yt_dlp/extractor/dplay.py", line 91, in _get_disco_api_info
    self._process_errors(e, geo_countries)
  File "/home/user1/.local/bin/yt-dlp/yt_dlp/extractor/dplay.py", line 48, in _process_errors
    raise ExtractorError(
```


Discussion:

Patch:
diff --git a/README.md b/README.md
index 08cca0a45..243b44344 100644
--- a/README.md
+++ b/README.md
@@ -1859,6 +1859,9 @@ #### orfon (orf:on)
 #### bilibili
 * `prefer_multi_flv`: Prefer extracting flv formats over mp4 for older videos that still provide legacy formats
 
+#### digitalconcerthall
+* `prefer_combined_hls`: Prefer extracting combined/pre-merged video and audio HLS formats. This will exclude 4K/HEVC video and lossless/FLAC audio formats, which are only available as split video/audio HLS formats
+
 **Note**: These options may be changed/removed in the future without concern for backward compatibility
 
 <!-- MANPAGE: MOVE "INSTALLATION" SECTION HERE -->
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
index fc917ff06..d2140bc52 100644
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -504,7 +504,6 @@
 from .digitalconcerthall import DigitalConcertHallIE
 from .digiteka import DigitekaIE
 from .discogs import DiscogsReleasePlaylistIE
-from .discovery import DiscoveryIE
 from .disney import DisneyIE
 from .dispeak import DigitallySpeakingIE
 from .dlf import (
@@ -532,16 +531,12 @@
     DiscoveryPlusIndiaShowIE,
     DiscoveryPlusItalyIE,
     DiscoveryPlusItalyShowIE,
-    DIYNetworkIE,
     DPlayIE,
     FoodNetworkIE,
-    GlobalCyclingNetworkPlusIE,
     GoDiscoveryIE,
     HGTVDeIE,
     HGTVUsaIE,
     InvestigationDiscoveryIE,
-    MotorTrendIE,
-    MotorTrendOnDemandIE,
     ScienceChannelIE,
     TravelChannelIE,
 )
diff --git a/yt_dlp/extractor/digitalconcerthall.py b/yt_dlp/extractor/digitalconcerthall.py
index 8b4d5c0fc..edb6fa9c0 100644
--- a/yt_dlp/extractor/digitalconcerthall.py
+++ b/yt_dlp/extractor/digitalconcerthall.py
@@ -1,6 +1,8 @@
 from .common import InfoExtractor
+from ..networking.exceptions import HTTPError
 from ..utils import (
     ExtractorError,
+    parse_codecs,
     try_get,
     url_or_none,
     urlencode_postdata,
@@ -12,6 +14,7 @@ class DigitalConcertHallIE(InfoExtractor):
     IE_DESC = 'DigitalConcertHall extractor'
     _VALID_URL = r'https?://(?:www\.)?digitalconcerthall\.com/(?P<language>[a-z]+)/(?P<type>film|concert|work)/(?P<id>[0-9]+)-?(?P<part>[0-9]+)?'
     _OAUTH_URL = 'https://api.digitalconcerthall.com/v2/oauth2/token'
+    _USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15'
     _ACCESS_TOKEN = None
     _NETRC_MACHINE = 'digitalconcerthall'
     _TESTS = [{
@@ -68,33 +71,42 @@ class DigitalConcertHallIE(InfoExtractor):
     }]
 
     def _perform_login(self, username, password):
-        token_response = self._download_json(
+        login_token = self._download_json(
             self._OAUTH_URL,
             None, 'Obtaining token', errnote='Unable to obtain token', data=urlencode_postdata({
                 'affiliate': 'none',
                 'grant_type': 'device',
                 'device_vendor': 'unknown',
+                # device_model 'Safari' gets split streams of 4K/HEVC video and lossless/FLAC audio
+                'device_model': 'unknown' if self._configuration_arg('prefer_combined_hls') else 'Safari',
                 'app_id': 'dch.webapp',
-                'app_version': '1.0.0',
+                'app_distributor': 'berlinphil',
+                'app_version': '1.84.0',
                 'client_secret': '2ySLN+2Fwb',
             }), headers={
-                'Content-Type': 'application/x-www-form-urlencoded',
-            })
-        self._ACCESS_TOKEN = token_response['access_token']
+                'Accept': 'application/json',
+                'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
+                'User-Agent': self._USER_AGENT,
+            })['access_token']
         try:
-            self._download_json(
+            login_response = self._download_json(
                 self._OAUTH_URL,
                 None, note='Logging in', errnote='Unable to login', data=urlencode_postdata({
                     'grant_type': 'password',
                     'username': username,
                     'password': password,
                 }), headers={
-                    'Content-Type': 'application/x-www-form-urlencoded',
+                    'Accept': 'application/json',
+                    'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
                     'Referer': 'https://www.digitalconcerthall.com',
-                    'Authorization': f'Bearer {self._ACCESS_TOKEN}',
+                    'Authorization': f'Bearer {login_token}',
+                    'User-Agent': self._USER_AGENT,
                 })
-        except ExtractorError:
-            self.raise_login_required(msg='Login info incorrect')
+        except ExtractorError as error:
+            if isinstance(error.cause, HTTPError) and error.cause.status == 401:
+                raise ExtractorError('Invalid username or password', expected=True)
+            raise
+        self._ACCESS_TOKEN = login_response['access_token']
 
     def _real_initialize(self):
         if not self._ACCESS_TOKEN:
@@ -108,11 +120,15 @@ def _entries(self, items, language, type_, **kwargs):
                     'Accept': 'application/json',
                     'Authorization': f'Bearer {self._ACCESS_TOKEN}',
                     'Accept-Language': language,
+                    'User-Agent': self._USER_AGENT,
                 })
 
             formats = []
             for m3u8_url in traverse_obj(stream_info, ('channel', ..., 'stream', ..., 'url', {url_or_none})):
-                formats.extend(self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', fatal=False))
+                formats.extend(self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
+            for fmt in formats:
+                if fmt.get('format_note') and fmt.get('vcodec') == 'none':
+                    fmt.update(parse_codecs(fmt['format_note']))
 
             yield {
                 'id': video_id,
@@ -140,13 +156,15 @@ def _real_extract(self, url):
             f'https://api.digitalconcerthall.com/v2/{api_type}/{video_id}', video_id, headers={
                 'Accept': 'application/json',
                 'Accept-Language': language,
+                'User-Agent': self._USER_AGENT,
+                'Authorization': f'Bearer {self._ACCESS_TOKEN}',
             })
-        album_artists = traverse_obj(vid_info, ('_links', 'artist', ..., 'name'))
         videos = [vid_info] if type_ == 'film' else traverse_obj(vid_info, ('_embedded', ..., ...))
 
         if type_ == 'work':
             videos = [videos[int(part) - 1]]
 
+        album_artists = traverse_obj(vid_info, ('_links', 'artist', ..., 'name', {str}))
         thumbnail = traverse_obj(vid_info, (
             'image', ..., {self._proto_relative_url}, {url_or_none},
             {lambda x: x.format(width=0, height=0)}, any))  # NB: 0x0 is the original size
diff --git a/yt_dlp/extractor/discovery.py b/yt_dlp/extractor/discovery.py
deleted file mode 100644
index b98279d67..000000000
--- a/yt_dlp/extractor/discovery.py
+++ /dev/null
@@ -1,115 +0,0 @@
-import random
-import string
-import urllib.parse
-
-from .discoverygo import DiscoveryGoBaseIE
-from ..networking.exceptions import HTTPError
-from ..utils import ExtractorError
-
-
-class DiscoveryIE(DiscoveryGoBaseIE):
-    _VALID_URL = r'''(?x)https?://
-        (?P<site>
-            go\.discovery|
-            www\.
-                (?:
-                    investigationdiscovery|
-                    discoverylife|
-                    animalplanet|
-                    ahctv|
-                    destinationamerica|
-                    sciencechannel|
-                    tlc
-                )|
-            watch\.
-                (?:
-                    hgtv|
-                    foodnetwork|
-                    travelchannel|
-                    diynetwork|
-                    cookingchanneltv|
-                    motortrend
-                )
-        )\.com/tv-shows/(?P<show_slug>[^/]+)/(?:video|full-episode)s/(?P<id>[^./?#]+)'''
-    _TESTS = [{
-        'url': 'https://go.discovery.com/tv-shows/cash-cab/videos/riding-with-matthew-perry',
-        'info_dict': {
-            'id': '5a2f35ce6b66d17a5026e29e',
-            'ext': 'mp4',
-            'title': 'Riding with Matthew Perry',
-            'description': 'md5:a34333153e79bc4526019a5129e7f878',
-            'duration': 84,
-        },
-        'params': {
-            'skip_download': True,  # requires ffmpeg
-        },
-    }, {
-        'url': 'https://www.investigationdiscovery.com/tv-shows/final-vision/full-episodes/final-vision',
-        'only_matching': True,
-    }, {
-        'url': 'https://go.discovery.com/tv-shows/alaskan-bush-people/videos/follow-your-own-road',
-        'only_matching': True,
-    }, {
-        # using `show_slug` is important to get the correct video data
-        'url': 'https://www.sciencechannel.com/tv-shows/mythbusters-on-science/full-episodes/christmas-special',
-        'only_matching': True,
-    }]
-    _GEO_COUNTRIES = ['US']
-    _GEO_BYPASS = False
-    _API_BASE_URL = 'https://api.discovery.com/v1/'
-
-    def _real_extract(self, url):
-        site, show_slug, display_id = self._match_valid_url(url).groups()
-
-        access_token = None
-        cookies = self._get_cookies(url)
-
-        # prefer Affiliate Auth Token over Anonymous Auth Token
-        auth_storage_cookie = cookies.get('eosAf') or cookies.get('eosAn')
-        if auth_storage_cookie and auth_storage_cookie.value:
-            auth_storage = self._parse_json(urllib.parse.unquote(
-                urllib.parse.unquote(auth_storage_cookie.value)),
-                display_id, fatal=False) or {}
-            access_token = auth_storage.get('a') or auth_storage.get('access_token')
-
-        if not access_token:
-            access_token = self._download_json(
-                f'https://{site}.com/anonymous', display_id,
-                'Downloading token JSON metadata', query={
-                    'authRel': 'authorization',
-                    'client_id': '3020a40c2356a645b4b4',
-                    'nonce': ''.join(random.choices(string.ascii_letters, k=32)),
-                    'redirectUri': 'https://www.discovery.com/',
-                })['access_token']
-
-        headers = self.geo_verification_headers()
-        headers['Authorization'] = 'Bearer ' + access_token
-
-        try:
-            video = self._download_json(
-                self._API_BASE_URL + 'content/videos',
-                display_id, 'Downloading content JSON metadata',
-                headers=headers, query={
-                    'embed': 'show.name',
-                    'fields': 'authenticated,description.detailed,duration,episodeNumber,id,name,parental.rating,season.number,show,tags',
-                    'slug': display_id,
-                    'show_slug': show_slug,
-                })[0]
-            video_id = video['id']
-            stream = self._download_json(
-                self._API_BASE_URL + 'streaming/video/' + video_id,
-                display_id, 'Downloading streaming JSON metadata', headers=headers)
-        except ExtractorError as e:
-            if isinstance(e.cause, HTTPError) and e.cause.status in (401, 403):
-                e_description = self._parse_json(
-                    e.cause.response.read().decode(), display_id)['description']
-                if 'resource not available for country' in e_description:
-                    self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
-                if 'Authorized Networks' in e_description:
-                    raise ExtractorError(
-                        'This video is only available via cable service provider subscription that'
-                        ' is not currently supported. You may want to use --cookies.', expected=True)
-                raise ExtractorError(e_description)
-            raise
-
-        return self._extract_video_info(video, stream, display_id)
diff --git a/yt_dlp/extractor/discoverygo.py b/yt_dlp/extractor/discoverygo.py
deleted file mode 100644
index 964948548..000000000
--- a/yt_dlp/extractor/discoverygo.py
+++ /dev/null
@@ -1,171 +0,0 @@
-import re
-
-from .common import InfoExtractor
-from ..utils import (
-    ExtractorError,
-    determine_ext,
-    extract_attributes,
-    int_or_none,
-    parse_age_limit,
-    remove_end,
-    unescapeHTML,
-    url_or_none,
-)
-
-
-class DiscoveryGoBaseIE(InfoExtractor):
-    _VALID_URL_TEMPLATE = r'''(?x)https?://(?:www\.)?(?:
-            discovery|
-            investigationdiscovery|
-            discoverylife|
-            animalplanet|
-            ahctv|
-            destinationamerica|
-            sciencechannel|
-            tlc|
-            velocitychannel
-        )go\.com/%s(?P<id>[^/?#&]+)'''
-
-    def _extract_video_info(self, video, stream, display_id):
-        title = video['name']
-
-        if not stream:
-            if video.get('authenticated') is True:
-                raise ExtractorError(
-                    'This video is only available via cable service provider subscription that'
-                    ' is not currently supported. You may want to use --cookies.', expected=True)
-            else:
-                raise ExtractorError('Unable to find stream')
-        STREAM_URL_SUFFIX = 'streamUrl'
-        formats = []
-        for stream_kind in ('', 'hds'):
-            suffix = STREAM_URL_SUFFIX.capitalize() if stream_kind else STREAM_URL_SUFFIX
-            stream_url = stream.get(f'{stream_kind}{suffix}')
-            if not stream_url:
-                continue
-            if stream_kind == '':
-                formats.extend(self._extract_m3u8_formats(
-                    stream_url, display_id, 'mp4', entry_protocol='m3u8_native',
-                    m3u8_id='hls', fatal=False))
-            elif stream_kind == 'hds':
-                formats.extend(self._extract_f4m_formats(
-                    stream_url, display_id, f4m_id=stream_kind, fatal=False))
-
-        video_id = video.get('id') or display_id
-        description = video.get('description', {}).get('detailed')
-        duration = int_or_none(video.get('duration'))
-
-        series = video.get('show', {}).get('name')
-        season_number = int_or_none(video.get('season', {}).get('number'))
-        episode_number = int_or_none(video.get('episodeNumber'))
-
-        tags = video.get('tags')
-        age_limit = parse_age_limit(video.get('parental', {}).get('rating'))
-
-        subtitles = {}
-        captions = stream.get('captions')
-        if isinstance(captions, list):
-            for caption in captions:
-                subtitle_url = url_or_none(caption.get('fileUrl'))
-                if not subtitle_url or not subtitle_url.startswith('http'):
-                    continue
-                lang = caption.get('fileLang', 'en')
-                ext = determine_ext(subtitle_url)
-                subtitles.setdefault(lang, []).append({
-                    'url': subtitle_url,
-                    'ext': 'ttml' if ext == 'xml' else ext,
-                })
-
-        return {
-            'id': video_id,
-            'display_id': display_id,
-            'title': title,
-            'description': description,
-            'duration': duration,
-            'series': series,
-            'season_number': season_number,
-            'episode_number': episode_number,
-            'tags': tags,
-            'age_limit': age_limit,
-            'formats': formats,
-            'subtitles': subtitles,
-        }
-
-
-class DiscoveryGoIE(DiscoveryGoBaseIE):
-    _VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % r'(?:[^/]+/)+'
-    _GEO_COUNTRIES = ['US']
-    _TEST = {
-        'url': 'https://www.discoverygo.com/bering-sea-gold/reaper-madness/',
-        'info_dict': {
-            'id': '58c167d86b66d12f2addeb01',
-            'ext': 'mp4',
-            'title': 'Reaper Madness',
-            'description': 'md5:09f2c625c99afb8946ed4fb7865f6e78',
-            'duration': 2519,
-            'series': 'Bering Sea Gold',
-            'season_number': 8,
-            'episode_number': 6,
-            'age_limit': 14,
-        },
-    }
-
-    def _real_extract(self, url):
-        display_id = self._match_id(url)
-
-        webpage = self._download_webpage(url, display_id)
-
-        container = extract_attributes(
-            self._search_regex(
-                r'(<div[^>]+class=["\']video-player-container[^>]+>)',
-                webpage, 'video container'))
-
-        video = self._parse_json(
-            container.get('data-video') or container.get('data-json'),
-            display_id)
-
-        stream = video.get('stream')
-
-        return self._extract_video_info(video, stream, display_id)
-
-
-class DiscoveryGoPlaylistIE(DiscoveryGoBaseIE):
-    _VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % ''
-    _TEST = {
-        'url': 'https://www.discoverygo.com/bering-sea-gold/',
-        'info_dict': {
-            'id': 'bering-sea-gold',
-            'title': 'Bering Sea Gold',
-            'description': 'md5:cc5c6489835949043c0cc3ad66c2fa0e',
-        },
-        'playlist_mincount': 6,
-    }
-
-    @classmethod
-    def suitable(cls, url):
-        return False if DiscoveryGoIE.suitable(url) else super().suitable(url)
-
-    def _real_extract(self, url):
-        display_id = self._match_id(url)
-
-        webpage = self._download_webpage(url, display_id)
-
-        entries = []
-        for mobj in re.finditer(r'data-json=(["\'])(?P<json>{.+?})\1', webpage):
-            data = self._parse_json(
-                mobj.group('json'), display_id,
-                transform_source=unescapeHTML, fatal=False)
-            if not isinstance(data, dict) or data.get('type') != 'episode':
-                continue
-            episode_url = data.get('socialUrl')
-            if not episode_url:
-                continue
-            entries.append(self.url_result(
-                episode_url, ie=DiscoveryGoIE.ie_key(),
-                video_id=data.get('id')))
-
-        return self.playlist_result(
-            entries, display_id,
-            remove_end(self._og_search_title(
-                webpage, fatal=False), ' | Discovery GO'),
-            self._og_search_description(webpage))
diff --git a/yt_dlp/extractor/dplay.py b/yt_dlp/extractor/dplay.py
index 48eae1088..e9f9357ad 100644
--- a/yt_dlp/extractor/dplay.py
+++ b/yt_dlp/extractor/dplay.py
@@ -346,8 +346,16 @@ def _real_extract(self, url):
 
 
 class DiscoveryPlusBaseIE(DPlayBaseIE):
+    """Subclasses must set _PRODUCT, _DISCO_API_PARAMS"""
+
+    _DISCO_CLIENT_VER = '27.43.0'
+
     def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
-        headers['x-disco-client'] = f'WEB:UNKNOWN:{self._PRODUCT}:25.2.6'
+        headers.update({
+            'x-disco-params': f'realm={realm},siteLookupKey={self._PRODUCT}',
+            'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:{self._DISCO_CLIENT_VER}',
+            'Authorization': self._get_auth(disco_base, display_id, realm),
+        })
 
     def _download_video_playback_info(self, disco_base, video_id, headers):
         return self._download_json(
@@ -368,6 +376,26 @@ def _real_extract(self, url):
 class GoDiscoveryIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:go\.)?discovery\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://go.discovery.com/video/in-the-eye-of-the-storm-discovery-atve-us/trapped-in-a-twister',
+        'info_dict': {
+            'id': '5352642',
+            'display_id': 'in-the-eye-of-the-storm-discovery-atve-us/trapped-in-a-twister',
+            'ext': 'mp4',
+            'title': 'Trapped in a Twister',
+            'description': 'Twisters destroy Midwest towns, trapping spotters in the eye of the storm.',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'season_number': 1,
+            'season': 'Season 1',
+            'series': 'In The Eye Of The Storm',
+            'duration': 2490.237,
+            'upload_date': '20240715',
+            'timestamp': 1721008800,
+            'tags': [],
+            'creators': ['Discovery'],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2024/07/10/5e39637d-cabf-3ab3-8e9a-f4e9d37bc036.jpeg',
+        },
+    }, {
         'url': 'https://go.discovery.com/video/dirty-jobs-discovery-atve-us/rodbuster-galvanizer',
         'info_dict': {
             'id': '4164906',
@@ -395,6 +423,26 @@ class GoDiscoveryIE(DiscoveryPlusBaseIE):
 class TravelChannelIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:watch\.)?travelchannel\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://watch.travelchannel.com/video/the-dead-files-travel-channel/protect-the-children',
+        'info_dict': {
+            'id': '4710177',
+            'display_id': 'the-dead-files-travel-channel/protect-the-children',
+            'ext': 'mp4',
+            'title': 'Protect the Children',
+            'description': 'An evil presence threatens an Ohio woman\'s children and marriage.',
+            'season_number': 14,
+            'season': 'Season 14',
+            'episode_number': 10,
+            'episode': 'Episode 10',
+            'series': 'The Dead Files',
+            'duration': 2550.481,
+            'timestamp': 1664510400,
+            'upload_date': '20220930',
+            'tags': [],
+            'creators': ['Travel Channel'],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2022/03/17/5e45eace-de5d-343a-9293-f400a2aa77d5.jpeg',
+        },
+    }, {
         'url': 'https://watch.travelchannel.com/video/ghost-adventures-travel-channel/ghost-train-of-ely',
         'info_dict': {
             'id': '2220256',
@@ -422,6 +470,26 @@ class TravelChannelIE(DiscoveryPlusBaseIE):
 class CookingChannelIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:watch\.)?cookingchanneltv\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://watch.cookingchanneltv.com/video/bobbys-triple-threat-food-network-atve-us/titans-vs-marcus-samuelsson',
+        'info_dict': {
+            'id': '5350005',
+            'ext': 'mp4',
+            'display_id': 'bobbys-triple-threat-food-network-atve-us/titans-vs-marcus-samuelsson',
+            'title': 'Titans vs Marcus Samuelsson',
+            'description': 'Marcus Samuelsson throws his legendary global tricks at the Titans.',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'season_number': 3,
+            'season': 'Season 3',
+            'series': 'Bobby\'s Triple Threat',
+            'duration': 2520.851,
+            'upload_date': '20240710',
+            'timestamp': 1720573200,
+            'tags': [],
+            'creators': ['Food Network'],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2024/07/04/529cd095-27ec-35c5-84e9-90ebd3e5d2da.jpeg',
+        },
+    }, {
         'url': 'https://watch.cookingchanneltv.com/video/carnival-eats-cooking-channel/the-postman-always-brings-rice-2348634',
         'info_dict': {
             'id': '2348634',
@@ -449,6 +517,22 @@ class CookingChannelIE(DiscoveryPlusBaseIE):
 class HGTVUsaIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:watch\.)?hgtv\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://watch.hgtv.com/video/flip-or-flop-the-final-flip-hgtv-atve-us/flip-or-flop-the-final-flip',
+        'info_dict': {
+            'id': '5025585',
+            'display_id': 'flip-or-flop-the-final-flip-hgtv-atve-us/flip-or-flop-the-final-flip',
+            'ext': 'mp4',
+            'title': 'Flip or Flop: The Final Flip',
+            'description': 'Tarek and Christina are going their separate ways after one last flip!',
+            'series': 'Flip or Flop: The Final Flip',
+            'duration': 2580.644,
+            'upload_date': '20231101',
+            'timestamp': 1698811200,
+            'tags': [],
+            'creators': ['HGTV'],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2022/11/27/455caa6c-1462-3f14-b63d-a026d7a5e6d3.jpeg',
+        },
+    }, {
         'url': 'https://watch.hgtv.com/video/home-inspector-joe-hgtv-atve-us/this-mold-house',
         'info_dict': {
             'id': '4289736',
@@ -476,6 +560,26 @@ class HGTVUsaIE(DiscoveryPlusBaseIE):
 class FoodNetworkIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:watch\.)?foodnetwork\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://watch.foodnetwork.com/video/guys-grocery-games-food-network/wild-in-the-aisles',
+        'info_dict': {
+            'id': '2152549',
+            'display_id': 'guys-grocery-games-food-network/wild-in-the-aisles',
+            'ext': 'mp4',
+            'title': 'Wild in the Aisles',
+            'description': 'The chefs make spaghetti and meatballs with "Out of Stock" ingredients.',
+            'season_number': 1,
+            'season': 'Season 1',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'series': 'Guy\'s Grocery Games',
+            'tags': [],
+            'creators': ['Food Network'],
+            'duration': 2520.651,
+            'upload_date': '20230623',
+            'timestamp': 1687492800,
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2022/06/15/37fb5333-cad2-3dbb-af7c-c20ec77c89c6.jpeg',
+        },
+    }, {
         'url': 'https://watch.foodnetwork.com/video/kids-baking-championship-food-network/float-like-a-butterfly',
         'info_dict': {
             'id': '4116449',
@@ -503,6 +607,26 @@ class FoodNetworkIE(DiscoveryPlusBaseIE):
 class DestinationAmericaIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:www\.)?destinationamerica\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://www.destinationamerica.com/video/bbq-pit-wars-destination-america/smoke-on-the-water',
+        'info_dict': {
+            'id': '2218409',
+            'display_id': 'bbq-pit-wars-destination-america/smoke-on-the-water',
+            'ext': 'mp4',
+            'title': 'Smoke on the Water',
+            'description': 'The pitmasters head to Georgia for the Smoke on the Water BBQ Festival.',
+            'season_number': 2,
+            'season': 'Season 2',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'series': 'BBQ Pit Wars',
+            'tags': [],
+            'creators': ['Destination America'],
+            'duration': 2614.878,
+            'upload_date': '20230623',
+            'timestamp': 1687492800,
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2020/05/11/c0f8e85d-9a10-3e6f-8e43-f6faafa81ba2.jpeg',
+        },
+    }, {
         'url': 'https://www.destinationamerica.com/video/alaska-monsters-destination-america-atve-us/central-alaskas-bigfoot',
         'info_dict': {
             'id': '4210904',
@@ -530,6 +654,26 @@ class DestinationAmericaIE(DiscoveryPlusBaseIE):
 class InvestigationDiscoveryIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:www\.)?investigationdiscovery\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://www.investigationdiscovery.com/video/deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca',
+        'info_dict': {
+            'id': '5341132',
+            'display_id': 'deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca',
+            'ext': 'mp4',
+            'title': 'RIP Bianca',
+            'description': 'A teenage influencer discovers an online world of threat, harm and danger.',
+            'season_number': 1,
+            'season': 'Season 1',
+            'episode_number': 3,
+            'episode': 'Episode 3',
+            'series': 'Deadly Influence: The Social Media Murders',
+            'creators': ['Investigation Discovery'],
+            'tags': [],
+            'duration': 2490.888,
+            'upload_date': '20240618',
+            'timestamp': 1718672400,
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2024/06/15/b567c774-9e44-3c6c-b0ba-db860a73e812.jpeg',
+        },
+    }, {
         'url': 'https://www.investigationdiscovery.com/video/unmasked-investigation-discovery/the-killer-clown',
         'info_dict': {
             'id': '2139409',
@@ -557,6 +701,26 @@ class InvestigationDiscoveryIE(DiscoveryPlusBaseIE):
 class AmHistoryChannelIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:www\.)?ahctv\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://www.ahctv.com/video/blood-and-fury-americas-civil-war-ahc/battle-of-bull-run',
+        'info_dict': {
+            'id': '2139199',
+            'display_id': 'blood-and-fury-americas-civil-war-ahc/battle-of-bull-run',
+            'ext': 'mp4',
+            'title': 'Battle of Bull Run',
+            'description': 'Two untested armies clash in the first real battle of the Civil War.',
+            'season_number': 1,
+            'season': 'Season 1',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'series': 'Blood and Fury: America\'s Civil War',
+            'duration': 2612.509,
+            'upload_date': '20220923',
+            'timestamp': 1663905600,
+            'creators': ['AHC'],
+            'tags': [],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2020/05/11/4af61bd7-d705-3108-82c4-1a6e541e20fa.jpeg',
+        },
+    }, {
         'url': 'https://www.ahctv.com/video/modern-sniper-ahc/army',
         'info_dict': {
             'id': '2309730',
@@ -584,6 +748,26 @@ class AmHistoryChannelIE(DiscoveryPlusBaseIE):
 class ScienceChannelIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:www\.)?sciencechannel\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://www.sciencechannel.com/video/spaces-deepest-secrets-science-atve-us/mystery-of-the-dead-planets',
+        'info_dict': {
+            'id': '2347335',
+            'display_id': 'spaces-deepest-secrets-science-atve-us/mystery-of-the-dead-planets',
+            'ext': 'mp4',
+            'title': 'Mystery of the Dead Planets',
+            'description': 'Astronomers unmask the truly destructive nature of the cosmos.',
+            'season_number': 7,
+            'season': 'Season 7',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'series': 'Space\'s Deepest Secrets',
+            'duration': 2524.989,
+            'upload_date': '20230128',
+            'timestamp': 1674882000,
+            'creators': ['Science'],
+            'tags': [],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2021/03/30/3796829d-aead-3f9a-bd8d-e49048b3cdca.jpeg',
+        },
+    }, {
         'url': 'https://www.sciencechannel.com/video/strangest-things-science-atve-us/nazi-mystery-machine',
         'info_dict': {
             'id': '2842849',
@@ -608,36 +792,29 @@ class ScienceChannelIE(DiscoveryPlusBaseIE):
     }
 
 
-class DIYNetworkIE(DiscoveryPlusBaseIE):
-    _VALID_URL = r'https?://(?:watch\.)?diynetwork\.com/video' + DPlayBaseIE._PATH_REGEX
+class DiscoveryLifeIE(DiscoveryPlusBaseIE):
+    _VALID_URL = r'https?://(?:www\.)?discoverylife\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
-        'url': 'https://watch.diynetwork.com/video/pool-kings-diy-network/bringing-beach-life-to-texas',
+        'url': 'https://www.discoverylife.com/video/er-files-discovery-life-atve-us/sweet-charity',
         'info_dict': {
-            'id': '2309730',
-            'display_id': 'pool-kings-diy-network/bringing-beach-life-to-texas',
+            'id': '2347614',
+            'display_id': 'er-files-discovery-life-atve-us/sweet-charity',
             'ext': 'mp4',
-            'title': 'Bringing Beach Life to Texas',
-            'description': 'The Pool Kings give a family a day at the beach in their own backyard.',
-            'season_number': 10,
-            'episode_number': 2,
+            'title': 'Sweet Charity',
+            'description': 'The staff at Charity Hospital treat a serious foot infection.',
+            'season_number': 1,
+            'season': 'Season 1',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'series': 'ER Files',
+            'duration': 2364.261,
+            'upload_date': '20230721',
+            'timestamp': 1689912000,
+            'creators': ['Discovery Life'],
+            'tags': [],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2021/03/16/4b6f0124-360b-3546-b6a4-5552db886b86.jpeg',
         },
-        'skip': 'Available for Premium users',
     }, {
-        'url': 'https://watch.diynetwork.com/video/pool-kings-diy-network/bringing-beach-life-to-texas',
-        'only_matching': True,
-    }]
-
-    _PRODUCT = 'diy'
-    _DISCO_API_PARAMS = {
-        'disco_host': 'us1-prod-direct.watch.diynetwork.com',
-        'realm': 'go',
-        'country': 'us',
-    }
-
-
-class DiscoveryLifeIE(DiscoveryPlusBaseIE):
-    _VALID_URL = r'https?://(?:www\.)?discoverylife\.com/video' + DPlayBaseIE._PATH_REGEX
-    _TESTS = [{
         'url': 'https://www.discoverylife.com/video/surviving-death-discovery-life-atve-us/bodily-trauma',
         'info_dict': {
             'id': '2218238',
@@ -665,6 +842,26 @@ class DiscoveryLifeIE(DiscoveryPlusBaseIE):
 class AnimalPlanetIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:www\.)?animalplanet\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://www.animalplanet.com/video/mysterious-creatures-with-forrest-galante-animal-planet-atve-us/the-demon-of-peru',
+        'info_dict': {
+            'id': '4650835',
+            'display_id': 'mysterious-creatures-with-forrest-galante-animal-planet-atve-us/the-demon-of-peru',
+            'ext': 'mp4',
+            'title': 'The Demon of Peru',
+            'description': 'In Peru, a farming village is being terrorized by a “man-like beast.”',
+            'season_number': 1,
+            'season': 'Season 1',
+            'episode_number': 4,
+            'episode': 'Episode 4',
+            'series': 'Mysterious Creatures with Forrest Galante',
+            'duration': 2490.488,
+            'upload_date': '20230111',
+            'timestamp': 1673413200,
+            'creators': ['Animal Planet'],
+            'tags': [],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2022/03/01/6dbaa833-9a2e-3fee-9381-c19eddf67c0c.jpeg',
+        },
+    }, {
         'url': 'https://www.animalplanet.com/video/north-woods-law-animal-planet/squirrel-showdown',
         'info_dict': {
             'id': '3338923',
@@ -692,6 +889,26 @@ class AnimalPlanetIE(DiscoveryPlusBaseIE):
 class TLCIE(DiscoveryPlusBaseIE):
     _VALID_URL = r'https?://(?:go\.)?tlc\.com/video' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
+        'url': 'https://go.tlc.com/video/90-day-the-last-resort-tlc-atve-us/the-last-chance',
+        'info_dict': {
+            'id': '5186422',
+            'display_id': '90-day-the-last-resort-tlc-atve-us/the-last-chance',
+            'ext': 'mp4',
+            'title': 'The Last Chance',
+            'description': 'Infidelity shakes Kalani and Asuelu\'s world, and Angela threatens divorce.',
+            'season_number': 1,
+            'season': 'Season 1',
+            'episode_number': 1,
+            'episode': 'Episode 1',
+            'series': '90 Day: The Last Resort',
+            'duration': 5123.91,
+            'upload_date': '20230815',
+            'timestamp': 1692061200,
+            'creators': ['TLC'],
+            'tags': [],
+            'thumbnail': 'https://us1-prod-images.disco-api.com/2023/08/08/0ee367e2-ac76-334d-bf23-dbf796696a24.jpeg',
+        },
+    }, {
         'url': 'https://go.tlc.com/video/my-600-lb-life-tlc/melissas-story-part-1',
         'info_dict': {
             'id': '2206540',
@@ -716,93 +933,8 @@ class TLCIE(DiscoveryPlusBaseIE):
     }
 
 
-class MotorTrendIE(DiscoveryPlusBaseIE):
-    _VALID_URL = r'https?://(?:watch\.)?motortrend\.com/video' + DPlayBaseIE._PATH_REGEX
-    _TESTS = [{
-        'url': 'https://watch.motortrend.com/video/car-issues-motortrend-atve-us/double-dakotas',
-        'info_dict': {
-            'id': '"4859182"',
-            'display_id': 'double-dakotas',
-            'ext': 'mp4',
-            'title': 'Double Dakotas',
-            'description': 'Tylers buy-one-get-one Dakota deal has the Wizard pulling double duty.',
-            'season_number': 2,
-            'episode_number': 3,
-        },
-        'skip': 'Available for Premium users',
-    }, {
-        'url': 'https://watch.motortrend.com/video/car-issues-motortrend-atve-us/double-dakotas',
-        'only_matching': True,
-    }]
-
-    _PRODUCT = 'vel'
-    _DISCO_API_PARAMS = {
-        'disco_host': 'us1-prod-direct.watch.motortrend.com',
-        'realm': 'go',
-        'country': 'us',
-    }
-
-
-class MotorTrendOnDemandIE(DiscoveryPlusBaseIE):
-    _VALID_URL = r'https?://(?:www\.)?motortrend(?:ondemand\.com|\.com/plus)/detail' + DPlayBaseIE._PATH_REGEX
-    _TESTS = [{
-        'url': 'https://www.motortrendondemand.com/detail/wheelstanding-dump-truck-stubby-bobs-comeback/37699/784',
-        'info_dict': {
-            'id': '37699',
-            'display_id': 'wheelstanding-dump-truck-stubby-bobs-comeback/37699',
-            'ext': 'mp4',
-            'title': 'Wheelstanding Dump Truck! Stubby Bob’s Comeback',
-            'description': 'md5:996915abe52a1c3dfc83aecea3cce8e7',
-            'season_number': 5,
-            'episode_number': 52,
-            'episode': 'Episode 52',
-            'season': 'Season 5',
-            'thumbnail': r're:^https?://.+\.jpe?g$',
-            'timestamp': 1388534401,
-            'duration': 1887.345,
-            'creator': 'Originals',
-            'series': 'Roadkill',
-            'upload_date': '20140101',
-            'tags': [],
-        },
-    }, {
-        'url': 'https://www.motortrend.com/plus/detail/roadworthy-rescues-teaser-trailer/4922860/',
-        'info_dict': {
-            'id': '4922860',
-            'ext': 'mp4',
-            'title': 'Roadworthy Rescues | Teaser Trailer',
-            'description': 'Derek Bieri helps Freiburger and Finnegan with their \'68 big-block Dart.',
-            'display_id': 'roadworthy-rescues-teaser-trailer/4922860',
-            'creator': 'Originals',
-            'series': 'Roadworthy Rescues',
-            'thumbnail': r're:^https?://.+\.jpe?g$',
-            'upload_date': '20220907',
-            'timestamp': 1662523200,
-            'duration': 1066.356,
-            'tags': [],
-        },
-    }, {
-        'url': 'https://www.motortrend.com/plus/detail/ugly-duckling/2450033/12439',
-        'only_matching': True,
-    }]
-
-    _PRODUCT = 'MTOD'
-    _DISCO_API_PARAMS = {
-        'disco_host': 'us1-prod-direct.motortrendondemand.com',
-        'realm': 'motortrend',
-        'country': 'us',
-    }
-
-    def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
-        headers.update({
-            'x-disco-params': f'realm={realm}',
-            'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:4.39.1-gi1',
-            'Authorization': self._get_auth(disco_base, display_id, realm),
-        })
-
-
 class DiscoveryPlusIE(DiscoveryPlusBaseIE):
-    _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/(?!it/)(?:\w{2}/)?video' + DPlayBaseIE._PATH_REGEX
+    _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/(?!it/)(?:(?P<country>[a-z]{2})/)?video(?:/sport)?' + DPlayBaseIE._PATH_REGEX
     _TESTS = [{
         'url': 'https://www.discoveryplus.com/video/property-brothers-forever-home/food-and-family',
         'info_dict': {
@@ -823,14 +955,42 @@ class DiscoveryPlusIE(DiscoveryPlusBaseIE):
     }, {
         'url': 'https://discoveryplus.com/ca/video/bering-sea-gold-discovery-ca/goldslingers',
         'only_matching': True,
+    }, {
+        'url': 'https://www.discoveryplus.com/gb/video/sport/eurosport-1-british-eurosport-1-british-sport/6-hours-of-spa-review',
+        'only_matching': True,
     }]
 
-    _PRODUCT = 'dplus_us'
-    _DISCO_API_PARAMS = {
-        'disco_host': 'us1-prod-direct.discoveryplus.com',
-        'realm': 'go',
-        'country': 'us',
-    }
+    _PRODUCT = None
+    _DISCO_API_PARAMS = None
+
+    def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
+        headers.update({
+            'x-disco-params': f'realm={realm},siteLookupKey={self._PRODUCT}',
+            'x-disco-client': f'WEB:UNKNOWN:dplus_us:{self._DISCO_CLIENT_VER}',
+            'Authorization': self._get_auth(disco_base, display_id, realm),
+        })
+
+    def _real_extract(self, url):
+        video_id, country = self._match_valid_url(url).group('id', 'country')
+        if not country:
+            country = 'us'
+
+        self._PRODUCT = f'dplus_{country}'
+
+        if country in ('br', 'ca', 'us'):
+            self._DISCO_API_PARAMS = {
+                'disco_host': 'us1-prod-direct.discoveryplus.com',
+                'realm': 'go',
+                'country': country,
+            }
+        else:
+            self._DISCO_API_PARAMS = {
+                'disco_host': 'eu1-prod-direct.discoveryplus.com',
+                'realm': 'dplay',
+                'country': country,
+            }
+
+        return self._get_disco_api_info(url, video_id, **self._DISCO_API_PARAMS)
 
 
 class DiscoveryPlusIndiaIE(DiscoveryPlusBaseIE):
@@ -993,7 +1153,7 @@ class DiscoveryPlusItalyIE(DiscoveryPlusBaseIE):
         'only_matching': True,
     }]
 
-    _PRODUCT = 'dplus_us'
+    _PRODUCT = 'dplus_it'
     _DISCO_API_PARAMS = {
         'disco_host': 'eu1-prod-direct.discoveryplus.com',
         'realm': 'dplay',
@@ -1002,8 +1162,8 @@ class DiscoveryPlusItalyIE(DiscoveryPlusBaseIE):
 
     def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
         headers.update({
-            'x-disco-params': f'realm={realm}',
-            'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:25.2.6',
+            'x-disco-params': f'realm={realm},siteLookupKey={self._PRODUCT}',
+            'x-disco-client': f'WEB:UNKNOWN:dplus_us:{self._DISCO_CLIENT_VER}',
             'Authorization': self._get_auth(disco_base, display_id, realm),
         })
 
@@ -1044,39 +1204,3 @@ class DiscoveryPlusIndiaShowIE(DiscoveryPlusShowBaseIE):
     _SHOW_STR = 'show'
     _INDEX = 4
     _VIDEO_IE = DiscoveryPlusIndiaIE
-
-
-class GlobalCyclingNetworkPlusIE(DiscoveryPlusBaseIE):
-    _VALID_URL = r'https?://plus\.globalcyclingnetwork\.com/watch/(?P<id>\d+)'
-    _TESTS = [{
-        'url': 'https://plus.globalcyclingnetwork.com/watch/1397691',
-        'info_dict': {
-            'id': '1397691',
-            'ext': 'mp4',
-            'title': 'The Athertons: Mountain Biking\'s Fastest Family',
-            'description': 'md5:75a81937fcd8b989eec6083a709cd837',
-            'thumbnail': 'https://us1-prod-images.disco-api.com/2021/03/04/eb9e3026-4849-3001-8281-9356466f0557.png',
-            'series': 'gcn',
-            'creator': 'Gcn',
-            'upload_date': '20210309',
-            'timestamp': 1615248000,
-            'duration': 2531.0,
-            'tags': [],
-        },
-        'skip': 'Subscription required',
-        'params': {'skip_download': 'm3u8'},
-    }]
-
-    _PRODUCT = 'web'
-    _DISCO_API_PARAMS = {
-        'disco_host': 'disco-api-prod.globalcyclingnetwork.com',
-        'realm': 'gcn',
-        'country': 'us',
-    }
-
-    def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
-        headers.update({
-            'x-disco-params': f'realm={realm}',
-            'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:27.3.2',
-            'Authorization': self._get_auth(disco_base, display_id, realm),
-        })
diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py
index b582b7d4e..2c23b3ed5 100644
--- a/yt_dlp/utils/_utils.py
+++ b/yt_dlp/utils/_utils.py
@@ -2984,6 +2984,7 @@ def parse_codecs(codecs_str):
         str.strip, codecs_str.strip().strip(',').split(','))))
     vcodec, acodec, scodec, hdr = None, None, None, None
     for full_codec in split_codecs:
+        full_codec = re.sub(r'^([^.]+)', lambda m: m.group(1).lower(), full_codec)
         parts = re.sub(r'0+(?=\d)', '', full_codec).split('.')
         if parts[0] in ('avc1', 'avc2', 'avc3', 'avc4', 'vp9', 'vp8', 'hev1', 'hev2',
                         'h263', 'h264', 'mp4v', 'hvc1', 'av1', 'theora', 'dvh1', 'dvhe'):


Test patch:
diff --git a/test/test_utils.py b/test/test_utils.py
index 3ff1f8b55..e82f551bc 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -929,6 +929,11 @@ def test_parse_codecs(self):
             'acodec': 'none',
             'dynamic_range': 'DV',
         })
+        self.assertEqual(parse_codecs('fLaC'), {
+            'vcodec': 'none',
+            'acodec': 'flac',
+            'dynamic_range': None,
+        })
         self.assertEqual(parse_codecs('theora, vorbis'), {
             'vcodec': 'theora',
             'acodec': 'vorbis',