Repository: ytdl-org/youtube-dl

Issue:
itvhub download stopped working
When I try to download from itvhub now I get this response
```
$ yt-dlp https://www.itv.com/watch/the-amazing-spider-man-2/2a3878
[generic] 2a3878: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] 2a3878: Extracting information
ERROR: Unsupported URL: https://www.itv.com/watch/the-amazing-spider-man-2/2a3878
```
everything was fine until today.

Im running MX-Linux and yt-dlp version
```
$ yt-dlp -U
Latest version: 2022.11.11, Current version: 2022.11.11
yt-dlp is up to date (2022.11.11)
```
TIA

Brian
[itv] fixed extraction
## Please follow the guide below

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes [ ] relevant to your *pull request* (like that [x])
- Use *Preview* tab to see how your *pull request* will actually look like

---

### Before submitting a *pull request* make sure you have:
- [x] [Searched](https://github.com/ytdl-org/youtube-dl/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
- [x] Read [adding new extractor tutorial](https://github.com/ytdl-org/youtube-dl#adding-support-for-a-new-site)
- [x] Read [youtube-dl coding conventions](https://github.com/ytdl-org/youtube-dl#youtube-dl-coding-conventions) and adjusted the code to meet them
- [x] Covered the code with tests (note that PRs without tests will be REJECTED)
- [x] Checked the code with [flake8](https://pypi.python.org/pypi/flake8)

### In order to be accepted and merged into youtube-dl each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check one of the following options:
- [x] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
- [ ] I am not the original author of this code but it is in public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)

### What is the purpose of your *pull request*?
- [x] Bug fix
- [ ] Improvement
- [ ] New extractor
- [ ] New feature

---

### Description of your *pull request* and other information

ITV changes now require media stream and subtitles to be taken from different playlist resources.

Closes #28906 
Closes #29337 
Closes #29190
Closes #28939
Closes #29620
Closes #29951

**Use yt-dlp version 2021.12.01 or later instead**

https://github.com/yt-dlp/yt-dlp/releases/latest

ITV Hub JSON error
<!--

######################################################################
  WARNING!
  IGNORING THE FOLLOWING TEMPLATE WILL RESULT IN ISSUE CLOSED AS INCOMPLETE
######################################################################

-->


## Checklist

<!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dl:
- First of, make sure you are using the latest version of youtube-dl. Run `youtube-dl --version` and ensure your version is 2021.04.26. If it's not, see https://yt-dl.org/update on how to update. Issues with outdated version will be REJECTED.
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in http://yt-dl.org/escape.
- Search the bugtracker for similar issues: http://yt-dl.org/search-issues. DO NOT post duplicates.
- Finally, put x into all relevant boxes (like this [x])
-->

- [x] I'm reporting a broken site support
- [x] I've verified that I'm running youtube-dl version **2021.04.26**
- [x] I've checked that all provided URLs are alive and playable in a browser
- [x] I've checked that all URLs and arguments with special characters are properly quoted or escaped
- [x] I've searched the bugtracker for similar issues including closed ones


## Verbose log

<!--
Provide the complete verbose output of youtube-dl that clearly demonstrates the problem.
Add the `-v` flag to your command line you run youtube-dl with (`youtube-dl -v <your command line>`), copy the WHOLE output and insert it below. It should look similar to this:
 [debug] System config: []
 [debug] User config: []
 [debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
 [debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
 [debug] youtube-dl version 2021.04.26
 [debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
 [debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
 [debug] Proxy map: {}
 <more lines>
-->

```
[debug] System config: []
[debug] User config: []
[debug] Custom config: []
[debug] Command-line args: ['https://www.itv.com/hub/tipping-point/2a1875a1123', '--verbose']
[debug] Encodings: locale cp1252, fs mbcs, out cp437, pref cp1252
[debug] youtube-dl version 2021.04.26
[debug] Python version 3.4.4 (CPython) - Windows-10-10.0.19041
[debug] exe versions: none
[debug] Proxy map: {}
[debug] Using fake IP 25.161.160.85 (GB) as X-Forwarded-For.
[ITV] 2a1875a1123: Downloading webpage
[ITV] 2a1875a1123: Downloading JSON metadata
ERROR: Unable to download JSON metadata: HTTP Error 500: Internal Server Error (caused by HTTPError()); please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; type  youtube-dl -U  to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\extractor\common.py", line 634, in _request_webpage
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\YoutubeDL.py", line 2279, in urlopen
  File "C:\Python\Python34\lib\urllib\request.py", line 470, in open
  File "C:\Python\Python34\lib\urllib\request.py", line 580, in http_response
  File "C:\Python\Python34\lib\urllib\request.py", line 508, in error
  File "C:\Python\Python34\lib\urllib\request.py", line 442, in _call_chain
  File "C:\Python\Python34\lib\urllib\request.py", line 588, in http_error_default
```


## Description

<!--
Provide an explanation of your issue in an arbitrary form. Provide any additional information, suggested solution and as much context and examples as possible.
If work on your issue requires account credentials please provide them or explain how one can obtain them.
-->

Several programmes on ITV Hub now fail to download. URL's such as

https://www.itv.com/hub/the-chase/1a7842a1761 
https://www.itv.com/hub/tipping-point/2a1875a1123
https://www.itv.com/hub/tenable/10a0805a0021

fail to download though some like 

https://www.itv.com/hub/lorraine/1a9360a2830

continue to work



Discussion:

Any chance of getting this merged?
Apparently you have to say this

> closes #28906 

or 

> resolves #28906 

in the text of the PR to get GH to link the issue.

Same here, hopefully it's not DRM.

``` F:\Temp\youtubedl>youtube-dl.exe --verbose "https://www.itv.com/hub/heartbeat/Ya0757a0043"
[debug] System config: []
[debug] User config: ['-o', 'F:/Temp/youtubedl/%(title)s.%(ext)s']
[debug] Custom config: []
[debug] Command-line args: ['--verbose', 'https://www.itv.com/hub/heartbeat/Ya0757a0043']
[debug] Encodings: locale cp1252, fs mbcs, out cp850, pref cp1252
[debug] youtube-dl version 2021.04.26
[debug] Python version 3.4.4 (CPython) - Windows-10-10.0.19041
[debug] exe versions: ffmpeg N-95216-ge6625ca41f, ffprobe N-95216-ge6625ca41f, rtmpdump 2.4
[debug] Proxy map: {}
[debug] Using fake IP 25.151.6.58 (GB) as X-Forwarded-For.
[ITV] Ya0757a0043: Downloading webpage
[ITV] Ya0757a0043: Downloading JSON metadata
ERROR: Unable to download JSON metadata: HTTP Error 500: Internal Server Error (caused by HTTPError()); please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; type  youtube-dl -U  to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\extractor\common.py", line 634, in _request_webpage
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\YoutubeDL.py", line 2279, in urlopen
  File "C:\Python\Python34\lib\urllib\request.py", line 470, in open
  File "C:\Python\Python34\lib\urllib\request.py", line 580, in http_response
  File "C:\Python\Python34\lib\urllib\request.py", line 508, in error
  File "C:\Python\Python34\lib\urllib\request.py", line 442, in _call_chain
  File "C:\Python\Python34\lib\urllib\request.py", line 588, in http_error_default
```
I first came across this problem on the 28th April when trying to download the ITV news programmes for that day. Other programmes prior to the 28th April I haven't had a problem with the completion of the download. But then programmes such as,

youtube-dl https://www.itv.com/hub/good-morning-britain/2a3211a3033
youtube-dl https://www.itv.com/hub/lorraine/1a9360a2831
youtube-dl https://www.itv.com/hub/this-morning/2a6292a0581
for the 28th April as well comes up with the error & doesn't start downloading at all as well.

C:\Users\User\Videos\youtube-dl Downloads>youtube-dl https://www.itv.com/hub/lorraine/1a9360a2831 --verbose
[debug] System config: []
[debug] User config: []
[debug] Custom config: []
[debug] Command-line args: ['https://www.itv.com/hub/lorraine/1a9360a2831', '--verbose']
[debug] Encodings: locale cp1252, fs mbcs, out cp850, pref cp1252
[debug] youtube-dl version 2021.04.26
[debug] Python version 3.4.4 (CPython) - Windows-10-10.0.19041
[debug] exe versions: ffmpeg 4.0.2, ffprobe 3.4.1
[debug] Proxy map: {}
[debug] Using fake IP 25.168.96.112 (GB) as X-Forwarded-For.
[ITV] 1a9360a2831: Downloading webpage
[ITV] 1a9360a2831: Downloading JSON metadata
ERROR: Unable to download JSON metadata: HTTP Error 500: Internal Server Error (caused by HTTPError()); 
please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; type  youtube-dl -U  to update. 
Be sure to call youtube-dl with the --verbose flag and include its complete output.
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\extractor\common.py", line 634, in _request_webpage
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\YoutubeDL.py", line 2279, in urlopen
  File "C:\Python\Python34\lib\urllib\request.py", line 470, in open
  File "C:\Python\Python34\lib\urllib\request.py", line 580, in http_response
  File "C:\Python\Python34\lib\urllib\request.py", line 508, in error
  File "C:\Python\Python34\lib\urllib\request.py", line 442, in _call_chain
  File "C:\Python\Python34\lib\urllib\request.py", line 588, in http_error_default

C:\Users\User\Videos\youtube-dl Downloads>
Luckily at the moment, STV player still works, but anything from ITV2, 3, 4 or CITV would not be downloadable because STV is only the Channel 3 contractor for Scotland. And you have to hope they haven't opted out of the ITV schedule and are showing the show you want.
> Same here, hopefully it's not DRM.

500 Internal Server Errors are usually temporary. It means that for some reason, the server was unable to process the request that was sent to it.

If it was DRM related, I would not expect a 500 error.

Someone suggested in the other thread that if you kept trying, the programme would eventually download. That works for 403 Forbidden errors but doesn't appear to work for this.

The problem seems to be that for some programmes the server is just finding it impossible to send the JSON playlist back which means that YT-DL has no way to attempt to download the programme. Why this only seems to affect some programmes and not others though is a bit of a mystery.
It probably does not help, but I too am receiving this error.
I've been noticing the same thing over the past day or two also. Here's another example of an identical failure. It doesn't appear retrying works, just flat out refuses to download however many times over a 12 hour window. Running same versions as @TIGHazard1. And, as reported, some downloads do work, just the ones that don't never seem to work. Have also tried with `--no-geo-bypass` giving identical failure. I am UK based.


```
[debug] System config: []
[debug] User config: []
[debug] Custom config: []
[debug] Command-line args: ['-v', 'https://www.itv.com/hub/viewpoint/10a0556a0003']
[debug] Encodings: locale cp1252, fs mbcs, out cp437, pref cp1252
[debug] youtube-dl version 2021.04.26
[debug] Python version 3.4.4 (CPython) - Windows-10-10.0.19041
[debug] exe versions: ffmpeg git-2019-11-01-53c21c2, ffprobe git-2019-11-01-53c21c2
[debug] Proxy map: {}
[debug] Using fake IP 25.41.255.169 (GB) as X-Forwarded-For.
[ITV] 10a0556a0003: Downloading webpage
[ITV] 10a0556a0003: Downloading JSON metadata
ERROR: Unable to download JSON metadata: HTTP Error 500: Internal Server Error (caused by HTTPError()); please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; type  youtube-dl -U  to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\extractor\common.py", line 634, in _request_webpage
  File "C:\Users\dst\AppData\Roaming\Build archive\youtube-dl\ytdl-org\tmpeav8wi2f\build\youtube_dl\YoutubeDL.py", line 2279, in urlopen
  File "C:\Python\Python34\lib\urllib\request.py", line 470, in open
  File "C:\Python\Python34\lib\urllib\request.py", line 580, in http_response
  File "C:\Python\Python34\lib\urllib\request.py", line 508, in error
  File "C:\Python\Python34\lib\urllib\request.py", line 442, in _call_chain
  File "C:\Python\Python34\lib\urllib\request.py", line 588, in http_error_default
 ```


this comment applies to the two issues [here](https://github.com/Ascoware/get-iplayer-automator/issues/331) and [here](https://github.com/ytdl-org/youtube-dl/issues/27582) too, can someone please see whether physical old ios devices are failing on these videos? if so and if itv are still supporting them then can be reported as a bug 
I don't own any ios devices so sadly can't test that. Is anyone else able to?
I agree that many programs on ITV Hub now fail to download.

On 28th April I was able to download URL's

https://www.itv.com/hub/viewpoint/10a0556a0001
https://www.itv.com/hub/viewpoint/10a0556a0002

On 29th April I was unable to download URL

https://www.itv.com/hub/viewpoint/10a0556a0003

Moreover, if I now try to download the first two above URL's they now fail with the same error message that others are reporting, namely:

```
[ITV] 2a7855a0082: Downloading JSON metadata
ERROR: Unable to download JSON metadata: HTTP Error 500: Internal Server Error (caused by HTTPError()); please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; type  youtube-dl -U  to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.
```

So ITV have changed something server side. I don't think it's DRM (yet), but maybe a missing header or auth token?

> So ITV have changed something server side. I don't think it's DRM (yet), but maybe a missing header or auth token?

I cannot see a 500 error being DRM related. The error shows before the download has even been attempted. If DRM made the videos impossible to download then they would fail with some sort of unable to download error not a server error.
@gewyvoso   I can confirm you get the http 500 error from your first link when I try yt-dl on OSX. I can also confirm that I can access the link from the itv hub with an old iphone using ios.  In both cases using a VPN.  Is this the info that you want?
@dstftw Sorry but geo-restriction isn't the issue in this case. Unless there is something we have missed? That seems like an unfair label to have put on it. We can all stream direct from the website but get errors from yt-dl.

@gewyvoso I can also confirm I have tried it on an iPad with no issues. Which is annoying.

@Vangelis66 Any ideas what might be happening here?
@dstftw I tend to agree with @torbs723 that this isn't geo-restriction, it's something else.
is anyone able to set up man in the middle to view the traffic on working old ios? perhaps it's working in a different way from itv.py 

@dstftw, giving a geo tag to this is off topic, not the above two comments. one of my two linked threads gives detailed info about geo behaviour and that is different from what is occurring here. could be that it is more and different geo restriction and that you know something about it, so then please tell us that 

@LFS1 & @torbs723, i was surprised it did work. thank you for doing. that was one logical avenue to investigate. like i said, maybe the ytdl plugin is working differently from what itv are doing for real. if so, that could be a fix 

all this is academic to me, i'm not fussed, although i have heavily cached itv shows using this hls method til now. is there a place where people discuss the workings of these plugins? people seem to present good knowledge in these threads, which seems to have been picked up elsewhere. i would like to chat there, this forum here does not seem right for it 
@gewyvoso Thank you for the support

>i was surprised it did work. thank you for doing. that was one logical avenue to investigate. like i said, maybe the ytdl plugin is working differently from what itv are doing for real. if so, that could be a fix

It is possible though that the streams we are having problems with are used by other devices possibly old old iPhones, iPads etc that maybe don't support ITV's app?
this is what i mean. looking at the plugin code, it was like it was magicked into being. there was no explanation of its workings except by reading between the lines. happy to do that though, but what i would like to see is where discussions took place leading to that. that is where i can contribute and would like to, not here. until then i find myself holding back my thoughts 
. . . . 
thanks for discussing though. i am well interested in this kind of thing. there are people of many different mindsets here. good to have a more focussed group of like minds on it. then can talk freely. do you know what i mean? 
(28934 is another one. looks like i commented there same minute you commented at 28939) 
If only people would spend 30 seconds doing a search before they post a new issue.
Same problem here, but for me it only happens with videos that have appeared in the last day or two. I can still D/L older videos. For example, https://www.itv.com/hub/robin-of-sherwood/ROBa03a010, from 28 April, can be downloaded OK, but https://www.itv.com/hub/robin-of-sherwood/ROBa03a011, from 29 April, and https://www.itv.com/hub/robin-of-sherwood/ROBa03a012, from 30 April, both fail with the HTTP Error 500 with latest youtube-dl version 2021.04.26. e.g.

[ITV] ROBa03a011: Downloading webpage
[ITV] ROBa03a011: Downloading JSON metadata
ERROR: Unable to download JSON metadata: HTTP Error 500: Internal Server Error (caused by HTTPError()); please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; type  youtube-dl -U  to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.
  File "/usr/local/bin/youtube-dl/youtube_dl/extractor/common.py", line 634, in _request_webpage
    return self._downloader.urlopen(url_or_request)
  File "/usr/local/bin/youtube-dl/youtube_dl/YoutubeDL.py", line 2279, in urlopen
    return self._opener.open(req, timeout=self._socket_timeout)
  File "/usr/lib/python2.7/urllib2.py", line 435, in open
    response = meth(req, response)
  File "/usr/lib/python2.7/urllib2.py", line 548, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python2.7/urllib2.py", line 473, in error
    return self._call_chain(*args)
  File "/usr/lib/python2.7/urllib2.py", line 407, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 556, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
`
@torbs723 wrote:

> @Vangelis66 Any ideas what might be happening here?

I was tagged (in a now **hidden** comment) to troubleshoot this... 😄 
It is now already _Orthodox Easter Sunday_ here, I won't be available for the most part of the next two days... However, since I, too, have a vested interest in **ITVHub** shows fetched via `yt-dl`, I took a stab at it...

But first, let's discuss the `geo-restricted` label attributed to this issue... 
In general, **it is not a good idea to aggravate the main developer(s) of** `yt-dl` 😉 ; perhaps the label was just put there to highlight the fact that **for anyone to even start troubleshooting this, an ITV Hub **whitelisted** IP address is required**; sometimes, a **generic UK IP** won't cut it, you need an "ITV zone" one (e.g. excluding Scotland) ... 
My analysis has indeed proven (to me, at least, but in accordance to comments by other people here) that this new ITV HUB issue is **not related to geo-fencing**...

 While I am discussing geo-fencing, the ITV Hub people, some time in the month of March (2021), **imposed even stricter geo-blocking schemes**... 💢 
For many years, their **Playlist API**s (= the ones that yield **stream data**) would only enforce a "soft" geo-block, that could be circumvented via an `X-Forwarded-For:UK IP` request header; if you look closely at logs people have posted, it's line 
`[debug] Using fake IP 25.161.160.85 (GB) as X-Forwarded-For.`
From mid-March, if you happen to be **overseas**, you need a whitelisted UK HTTPS/SOCKS proxy/UK VPN to even get the master HLS playlist URI ("Downloading JSON metadata" in yt-dl's logs); of course, the HLS stream itself is **fully geo-blocked**, you have to use the same "means" to dump if overseas... Again, **this new "issue" isn't related to stricter geo-fencing practices imposed last March** (as it also occurs for people within the UK...). 

It well appears that the ITV Hub people made some changes during these last days, certainly **during last week**, that make **recently uploaded ITV hub shows inaccessible to** `yt-dl`; case in point is the **Jonathan Ross** show: 
Last week's episode
```
youtube-dl -F https://www.itv.com/hub/the-jonathan-ross-show/2a1166a0204

[ITV] 2a1166a0204: Downloading webpage
[ITV] 2a1166a0204: Downloading JSON metadata
[ITV] 2a1166a0204: Downloading m3u8 information
[info] Available formats for 2a1166a0204:
format code  extension  resolution note
hls-105      mp4        audio only  105k , mp4a.40.2@ 98k
hls-565      mp4        512x288     565k , avc1.64001F@ 434k, mp4a.40.2@ 98k
hls-762      mp4        512x288     762k , avc1.64001F@ 620k, mp4a.40.2@ 98k
hls-1058     mp4        896x504    1058k , avc1.64001F@ 899k, mp4a.40.2@ 98k
hls-1344     mp4        896x504    1344k , avc1.64001F@1169k, mp4a.40.2@ 98k
hls-1633     mp4        896x504    1633k , avc1.64001F@1442k, mp4a.40.2@ 98k (be
st)
```
Yerterday's episode: 
```
youtube-dl -F https://www.itv.com/hub/the-jonathan-ross-show/2a1166a0205

[ITV] 2a1166a0205: Downloading webpage
[ITV] 2a1166a0205: Downloading JSON metadata
ERROR: Unable to download JSON metadata: HTTP Error 500: Internal Server Error (
caused by <HTTPError 500: 'Internal Server Error'>); please report this issue on
 https://yt-dl.org/bug . Make sure you are using the latest version; type  youtu
be-dl -U  to update. Be sure to call youtube-dl with the --verbose flag and incl
ude its complete output.
```
(the test was done using the very same connection/IP).

Now, my **deeper** analysis was done focusing on **Viewpoint-SE01EP03** (sadly, the whole series is due to expire imminently): 
[https://www.itv.com/hub/viewpoint/10a0556a0003](https://www.itv.com/hub/viewpoint/10a0556a0003)
As most of you may know already, **desktop** browsers are being served **MPEG-DASH** streams with Common Encryption (cenc=**DRM**), the Google-owned **WidevineCDM** is required for playback 👎 💢 ; yt-dl can't/won't handle cenc, instead it has to resort to **HLSe** (AES-128 encrypted) streams, targeting older iOS devices... 

So the very first thing I did was to emulate such a device in my browser, by setting up **a site-specific-user-agent-override (SSUAO) for itv.com**; depending on browser used, this can be achieved via an **about:config** setting (Firefox derived browsers) or, more commonly, via an extension (Chromium browsers) that spoofs your user-agent; please, don't ask for instructions/how-to here, it isn't the appropriate place... 
I chose to use the iPhone5 UA below: 

`Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3`

Of course, ITVHub requires you to be logged-in to send any stream data to your browser (this hasn't been yet a requirement for yt-dl 👍 ); being logged in and with the above "mobile" UA, I inspected browser's Web Console (CTRL+SHIFT+K) and I clearly saw the **POST XHR** request to their playlist API: 
`https://magni.itv.com/playlist/itvonline/ITV/10_0556_0003.001`
(don't try this directly with a **GET** request), the response of which I am attaching here: [POST-ITV.json.txt](https://github.com/ytdl-org/youtube-dl/files/6410472/POST-ITV.json.txt)
BTW, this is equivalent to **yt-dl** "Downloading JSON metadata"; as one can see, no `HTTP Error 500: Internal Server Error` was generated in my test; this rules out intermittent server issues on the ITV side...

From that (see attached) JSON response, I extracted the master HLSe playlist URI: 
```
https://itvpnpmobile.content.itv.com/10-0556-0003-001/6/2/VAR003/10-0556-0003-001_6_2_VAR003.ism/.m3u8?hdnea=st=1619890241~exp=1619911841~acl=/10-0556-0003-001/*~data=nohubplus~hmac=ec46cb4825a413f0681ce4b94f72c5f82c9fb42fb6d424e746976a5d2c26aa99
```

(the same URI can be seen and copied from within the browser's Web Console 😉 ), which looks like below: 

```
#EXTM3U
#EXT-X-VERSION:4
## Created with OEM(version=1.7.25)


# AUDIO groups
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-aacl-128",NAME="audio",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2"

# variants
#EXT-X-STREAM-INF:BANDWIDTH=313000,CODECS="mp4a.40.2,avc1.4D401E",RESOLUTION=512x288,FRAME-RATE=50,AUDIO="audio-aacl-128",CLOSED-CAPTIONS=NONE
10-0556-0003-001_6_2_VAR003-audio=128000-video=167161.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=596000,CODECS="mp4a.40.2,avc1.4D401F",RESOLUTION=512x288,FRAME-RATE=50,AUDIO="audio-aacl-128",CLOSED-CAPTIONS=NONE
10-0556-0003-001_6_2_VAR003-audio=128000-video=433735.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=794000,CODECS="mp4a.40.2,avc1.4D401F",RESOLUTION=512x288,FRAME-RATE=50,AUDIO="audio-aacl-128",CLOSED-CAPTIONS=NONE
10-0556-0003-001_6_2_VAR003-audio=128000-video=620155.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1064000,CODECS="mp4a.40.2,avc1.4D401F",RESOLUTION=896x504,FRAME-RATE=50,AUDIO="audio-aacl-128",CLOSED-CAPTIONS=NONE
10-0556-0003-001_6_2_VAR003-audio=128000-video=875433.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1655000,CODECS="mp4a.40.2,avc1.4D401F",RESOLUTION=896x504,FRAME-RATE=50,AUDIO="audio-aacl-128",CLOSED-CAPTIONS=NONE
10-0556-0003-001_6_2_VAR003-audio=128000-video=1432734.m3u8

# variants
#EXT-X-STREAM-INF:BANDWIDTH=136000,CODECS="mp4a.40.2",AUDIO="audio-aacl-128"
10-0556-0003-001_6_2_VAR003-audio=128000.m3u8

# keyframes
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=23000,CODECS="avc1.4D401E",RESOLUTION=512x288,URI="keyframes/10-0556-0003-001_6_2_VAR003-video=167161.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=116000,CODECS="avc1.4D401F",RESOLUTION=896x504,URI="keyframes/10-0556-0003-001_6_2_VAR003-video=875433.m3u8"

```
This URI is **short-lived**, you have to act swiftly before its auth-token expires... Feeding that directly to yt-dl, I can initiate a **successful** download: 

`youtube-dl --console-title --hls-prefer-native --hls-use-mpegts -c --fixup never "https://itvpnpmobile.content.itv.com/10-0556-0003-001/6/2/VAR003/10-0556-0003-001_6_2_VAR003.ism/.m3u8?hdnea=st=1619890241~exp=1619911841~acl=/10-0556-0003-001/*~data=nohubplus~hmac=ec46cb4825a413f0681ce4b94f72c5f82c9fb42fb6d424e746976a5d2c26aa99" -o "ITV_Viewpoint_Se1Ep3_202104282100[10a0556a0003].ts"` => 
```
[generic] *~data=nohubplus~hmac=ec46cb4825a413f0681ce4b94f72c5f82c9fb42fb6d424e7
46976a5d2c26aa99: Requesting header
[generic] *~data=nohubplus~hmac=ec46cb4825a413f0681ce4b94f72c5f82c9fb42fb6d424e7
46976a5d2c26aa99: Downloading m3u8 information
[hlsnative] Downloading m3u8 manifest
[hlsnative] Total fragments: 461
[download] Destination: ITV_Viewpoint_Se1Ep3_202104282100[10a0556a0003].ts
[download]   2.6% of ~543.70MiB at 942.18KiB/s ETA 12:38
```
I did spend a lot of time trying to emulate the **above POST XHR my browser sent** in a yt-dl command, by **specifying all needed request headers** (referer/user-agent/cookies etc.), I even tried the **cookies.txt** method, sadly all **to no avail** 😭 ... It's a real pity that I can't code (in Python or other language), I hope my findings can be put to good use by actual coders... 

**In conclusion**: ITV staff have implemented in recent days "changes" (I wouldn't rule out this having been done **deliberately to thwart downloaders**) that would render current itvIE useless once old shows expire, perhaps sooner if these changes propagate to all ITVhub content... 
At the time of this writing, this isn't related to **an implementation of pure DRM** (e.g. Apple FairPlay), nor to further changes to their geo-fencing practices...
Therefore, I humbly concluded **it's a genuine itv.py bug that needs to be addressed in code**... 
@remitamine, you seem to be the defacto maintainer of the itvIE, please, when your schedules permit, could you take a deeper look into this? 
Also pinging @dirkf who happens to be **UK-based**, a Python coder and quite knowledgeable 😄 in UK media sites... 

My best wishes to all, my **deepest thanks to the yt-dl dev team** 👍 , Happy Easter Sunday to all those that observe it today... 😃 
@Vangelis66 As always Vangelis, thank you so much for your detailed analysis of this issue. And thank you for your confirmation that this doesn't relate to DRM.
Really appreciate the analysis @Vangelis66. Learnt something about spoofing the user agent as well. Taking it from here is beyond me but hopefuly the coding heroes can find some time and motivation to pick it up. Happy Easter Sunday.
I too have just learnt about spoofing user agents and can concur with @Vangelis66 that downloads from ITV (UK based) are still possible as described above.   Happy Easter. 
I asked a friend of mine in London to test yt-dl again fridays episode of the chase.  He has confirmed that the same 500 error occurs irrespective of where you are located. I hope this helps. 
> I asked a friend of mine in London to test yt-dl again fridays episode of the chase. He has confirmed that the same 500 error occurs irrespective of where you are located. I hope this helps.

But is available with the @Vangelis66 method

![image](https://user-images.githubusercontent.com/35435828/116815715-b52b8f00-ab56-11eb-9fc6-163d2ccceac2.png)


Resolving patch:
diff --git a/youtube_dl/extractor/itv.py b/youtube_dl/extractor/itv.py
--- a/youtube_dl/extractor/itv.py
+++ b/youtube_dl/extractor/itv.py
@@ -3,123 +3,266 @@
 
 import json
 import re
+import sys
 
 from .common import InfoExtractor
 from .brightcove import BrightcoveNewIE
+from ..compat import (
+    compat_HTTPError,
+    compat_integer_types,
+    compat_kwargs,
+    compat_urlparse,
+)
 from ..utils import (
     clean_html,
     determine_ext,
+    error_to_compat_str,
     extract_attributes,
-    get_element_by_class,
-    JSON_LD_RE,
+    ExtractorError,
+    get_element_by_attribute,
+    int_or_none,
     merge_dicts,
     parse_duration,
+    parse_iso8601,
+    remove_start,
     smuggle_url,
+    strip_or_none,
+    traverse_obj,
     url_or_none,
+    urljoin,
 )
 
 
-class ITVIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?itv\.com/hub/[^/]+/(?P<id>[0-9a-zA-Z]+)'
-    _GEO_COUNTRIES = ['GB']
+class ITVBaseIE(InfoExtractor):
+
+    def _search_nextjs_data(self, webpage, video_id, **kw):
+        transform_source = kw.pop('transform_source', None)
+        fatal = kw.pop('fatal', True)
+        return self._parse_json(
+            self._search_regex(
+                r'''<script\b[^>]+\bid=('|")__NEXT_DATA__\1[^>]*>(?P<js>[^<]+)</script>''',
+                webpage, 'next.js data', group='js', fatal=fatal, **kw),
+            video_id, transform_source=transform_source, fatal=fatal)
+
+    def __handle_request_webpage_error(self, err, video_id=None, errnote=None, fatal=True):
+        if errnote is False:
+            return False
+        if errnote is None:
+            errnote = 'Unable to download webpage'
+
+        errmsg = '%s: %s' % (errnote, error_to_compat_str(err))
+        if fatal:
+            raise ExtractorError(errmsg, sys.exc_info()[2], cause=err, video_id=video_id)
+        else:
+            self._downloader.report_warning(errmsg)
+            return False
+
+    @staticmethod
+    def _vanilla_ua_header():
+        return {'User-agent': 'Mozilla/5.0'}
+
+    def _download_webpage_handle(self, url, video_id, *args, **kwargs):
+        # specialised to (a) use vanilla UA (b) detect geo-block
+        params = self._downloader.params
+        nkwargs = {}
+        if (
+                'user_agent' not in params
+                and not any(re.match(r'(?i)user-agent\s*:', h)
+                            for h in (params.get('headers') or []))
+                and 'User-agent' not in (kwargs.get('headers') or {})):
+
+            kwargs.setdefault('headers', {})
+            kwargs['headers'] = self._vanilla_ua_header()
+            nkwargs = kwargs
+        if kwargs.get('expected_status') is not None:
+            exp = kwargs['expected_status']
+            if isinstance(exp, compat_integer_types):
+                exp = [exp]
+            if isinstance(exp, (list, tuple)) and 403 not in exp:
+                kwargs['expected_status'] = [403]
+                kwargs['expected_status'].extend(exp)
+                nkwargs = kwargs
+        else:
+            kwargs['expected_status'] = 403
+            nkwargs = kwargs
+
+        if nkwargs:
+            kwargs = compat_kwargs(kwargs)
+
+        ret = super(ITVBaseIE, self)._download_webpage_handle(url, video_id, *args, **kwargs)
+        if ret is False:
+            return ret
+        webpage, urlh = ret
+
+        if urlh.getcode() == 403:
+            # geo-block error is like this, with an unnecessary 'Of':
+            # '{\n  "Message" : "Request Originated Outside Of Allowed Geographic Region",\
+            # \n  "TransactionId" : "oas-magni-475082-xbYF0W"\n}'
+            if '"Request Originated Outside Of Allowed Geographic Region"' in webpage:
+                self.raise_geo_restricted(countries=['GB'])
+            ret = self.__handle_request_webpage_error(
+                compat_HTTPError(urlh.geturl(), 403, 'HTTP Error 403: Forbidden', urlh.headers, urlh),
+                fatal=kwargs.get('fatal'))
+
+        return ret
+
+
+class ITVIE(ITVBaseIE):
+    _VALID_URL = r'https?://(?:www\.)?itv\.com/(?:(?P<w>watch)|hub)/[^/]+/(?(w)[\w-]+/)(?P<id>\w+)'
+    _IE_DESC = 'ITVX'
     _TESTS = [{
+        'note': 'Hub URLs redirect to ITVX',
         'url': 'https://www.itv.com/hub/liar/2a4547a0012',
-        'info_dict': {
-            'id': '2a4547a0012',
-            'ext': 'mp4',
-            'title': 'Liar - Series 2 - Episode 6',
-            'description': 'md5:d0f91536569dec79ea184f0a44cca089',
-            'series': 'Liar',
-            'season_number': 2,
-            'episode_number': 6,
-        },
-        'params': {
-            # m3u8 download
-            'skip_download': True,
-        },
+        'only_matching': True,
     }, {
-        # unavailable via data-playlist-url
+        'note': 'Hub page unavailable via data-playlist-url (404 now)',
         'url': 'https://www.itv.com/hub/through-the-keyhole/2a2271a0033',
         'only_matching': True,
     }, {
-        # InvalidVodcrid
+        'note': 'Hub page with InvalidVodcrid (404 now)',
         'url': 'https://www.itv.com/hub/james-martins-saturday-morning/2a5159a0034',
         'only_matching': True,
     }, {
-        # ContentUnavailable
+        'note': 'Hub page with ContentUnavailable (404 now)',
         'url': 'https://www.itv.com/hub/whos-doing-the-dishes/2a2898a0024',
         'only_matching': True,
-    }]
+    }, {
+        'note': 'ITVX, or itvX, show',
+        'url': 'https://www.itv.com/watch/vera/1a7314/1a7314a0014',
+        'md5': 'bd0ad666b2c058fffe7d036785880064',
+        'info_dict': {
+            'id': '1a7314a0014',
+            'ext': 'mp4',
+            'title': 'Vera - Series 3 - Episode 4 - Prodigal Son',
+            'description': 'Vera and her team investigate the fatal stabbing of an ex-Met police officer outside a busy Newcastle nightclub - but there aren\'t many clues.',
+            'timestamp': 1653591600,
+            'upload_date': '20220526',
+            'uploader': 'ITVX',
+            'thumbnail': r're:https://\w+\.itv\.com/images/(?:\w+/)+\d+x\d+\?',
+            'duration': 5340.8,
+            'age_limit': 16,
+            'series': 'Vera',
+            'series_number': 3,
+            'episode': 'Prodigal Son',
+            'episode_number': 4,
+            'channel': 'ITV3',
+            'categories': list,
+        },
+        'params': {
+            # m3u8 download
+            # 'skip_download': True,
+        },
+        'skip': 'only available in UK',
+    }, {
+        'note': 'Latest ITV news bulletin: details change daily',
+        'url': 'https://www.itv.com/watch/news/varies-but-is-not-checked/6js5d0f',
+        'info_dict': {
+            'id': '6js5d0f',
+            'ext': 'mp4',
+            'title': r're:The latest ITV News headlines - \S.+',
+            'description': r'''re:.* today's top stories from the ITV News team.$''',
+            'timestamp': int,
+            'upload_date': r're:2\d\d\d(?:0[1-9]|1[0-2])(?:[012][1-9]|3[01])',
+            'uploader': 'ITVX',
+            'thumbnail': r're:https://images\.ctfassets\.net/(?:\w+/)+[\w.]+\.(?:jpg|png)',
+            'duration': float,
+            'age_limit': None,
+        },
+        'params': {
+            # variable download
+            # 'skip_download': True,
+        },
+        'skip': 'only available in UK',
+    }
+    ]
+
+    def _og_extract(self, webpage, require_title=False):
+        return {
+            'title': self._og_search_title(webpage, fatal=require_title),
+            'description': self._og_search_description(webpage, default=None),
+            'thumbnail': self._og_search_thumbnail(webpage, default=None),
+            'uploader': self._og_search_property('site_name', webpage, default=None),
+        }
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
+
         webpage = self._download_webpage(url, video_id)
+
+        # now quite different params!
         params = extract_attributes(self._search_regex(
-            r'(?s)(<[^>]+id="video"[^>]*>)', webpage, 'params'))
+            r'''(<[^>]+\b(?:class|data-testid)\s*=\s*("|')genie-container\2[^>]*>)''',
+            webpage, 'params'))
+
+        ios_playlist_url = traverse_obj(
+            params, 'data-video-id', 'data-video-playlist',
+            get_all=False, expected_type=url_or_none)
 
-        ios_playlist_url = params.get('data-video-playlist') or params['data-video-id']
-        hmac = params['data-video-hmac']
         headers = self.geo_verification_headers()
         headers.update({
             'Accept': 'application/vnd.itv.vod.playlist.v2+json',
             'Content-Type': 'application/json',
-            'hmac': hmac.upper(),
         })
         ios_playlist = self._download_json(
             ios_playlist_url, video_id, data=json.dumps({
                 'user': {
-                    'itvUserId': '',
                     'entitlements': [],
-                    'token': ''
                 },
                 'device': {
-                    'manufacturer': 'Safari',
-                    'model': '5',
+                    'manufacturer': 'Mobile Safari',
+                    'model': '5.1',
                     'os': {
-                        'name': 'Windows NT',
-                        'version': '6.1',
-                        'type': 'desktop'
+                        'name': 'iOS',
+                        'version': '5.0',
+                        'type': ' mobile'
                     }
                 },
                 'client': {
                     'version': '4.1',
-                    'id': 'browser'
+                    'id': 'browser',
+                    'supportsAdPods': True,
+                    'service': 'itv.x',
+                    'appversion': '2.43.28',
                 },
                 'variantAvailability': {
+                    'player': 'hls',
                     'featureset': {
                         'min': ['hls', 'aes', 'outband-webvtt'],
                         'max': ['hls', 'aes', 'outband-webvtt']
                     },
-                    'platformTag': 'dotcom'
+                    'platformTag': 'mobile'
                 }
             }).encode(), headers=headers)
         video_data = ios_playlist['Playlist']['Video']
-        ios_base_url = video_data.get('Base')
+        ios_base_url = traverse_obj(video_data, 'Base', expected_type=url_or_none)
+
+        media_url = (
+            (lambda u: url_or_none(urljoin(ios_base_url, u)))
+            if ios_base_url else url_or_none)
 
         formats = []
-        for media_file in (video_data.get('MediaFiles') or []):
-            href = media_file.get('Href')
+        for media_file in traverse_obj(video_data, 'MediaFiles', expected_type=list) or []:
+            href = traverse_obj(media_file, 'Href', expected_type=media_url)
             if not href:
                 continue
-            if ios_base_url:
-                href = ios_base_url + href
             ext = determine_ext(href)
             if ext == 'm3u8':
                 formats.extend(self._extract_m3u8_formats(
-                    href, video_id, 'mp4', entry_protocol='m3u8_native',
+                    href, video_id, 'mp4', entry_protocol='m3u8',
                     m3u8_id='hls', fatal=False))
+
             else:
                 formats.append({
                     'url': href,
                 })
         self._sort_formats(formats)
+        for f in formats:
+            f.setdefault('http_headers', {})
+            f['http_headers'].update(self._vanilla_ua_header())
 
         subtitles = {}
-        subs = video_data.get('Subtitles') or []
-        for sub in subs:
-            if not isinstance(sub, dict):
-                continue
-            href = url_or_none(sub.get('Href'))
+        for sub in traverse_obj(video_data, 'Subtitles', expected_type=list) or []:
+            href = traverse_obj(sub, 'Href', expected_type=url_or_none)
             if not href:
                 continue
             subtitles.setdefault('en', []).append({
@@ -127,59 +270,132 @@ def _real_extract(self, url):
                 'ext': determine_ext(href, 'vtt'),
             })
 
-        info = self._search_json_ld(webpage, video_id, default={})
-        if not info:
-            json_ld = self._parse_json(self._search_regex(
-                JSON_LD_RE, webpage, 'JSON-LD', '{}',
-                group='json_ld'), video_id, fatal=False)
-            if json_ld and json_ld.get('@type') == 'BreadcrumbList':
-                for ile in (json_ld.get('itemListElement:') or []):
-                    item = ile.get('item:') or {}
-                    if item.get('@type') == 'TVEpisode':
-                        item['@context'] = 'http://schema.org'
-                        info = self._json_ld(item, video_id, fatal=False) or {}
-                        break
+        next_data = self._search_nextjs_data(webpage, video_id, fatal=False, default='{}')
+        video_data.update(traverse_obj(next_data, ('props', 'pageProps', ('title', 'episode')), expected_type=dict)[0] or {})
+        title = traverse_obj(video_data, 'headerTitle', 'episodeTitle')
+        info = self._og_extract(webpage, require_title=not title)
+        tn = info.pop('thumbnail', None)
+        if tn:
+            info['thumbnails'] = [{'url': tn}]
+
+        # num. episode title
+        num_ep_title = video_data.get('numberedEpisodeTitle')
+        if not num_ep_title:
+            num_ep_title = clean_html(get_element_by_attribute('data-testid', 'episode-hero-description-strong', webpage))
+            num_ep_title = num_ep_title and num_ep_title.rstrip(' -')
+        ep_title = strip_or_none(
+            video_data.get('episodeTitle')
+            or (num_ep_title.split('.', 1)[-1] if num_ep_title else None))
+        title = title or re.sub(r'\s+-\s+ITVX$', '', info['title'])
+        if ep_title and ep_title != title:
+            title = title + ' - ' + ep_title
+
+        def get_thumbnails():
+            tns = []
+            for w, x in (traverse_obj(video_data, ('imagePresets'), expected_type=dict) or {}).items():
+                if isinstance(x, dict):
+                    for y, z in x.items():
+                        tns.append({'id': w + '_' + y, 'url': z})
+            return tns or None
+
+        video_str = lambda *x: traverse_obj(
+            video_data, *x, get_all=False, expected_type=strip_or_none)
 
         return merge_dicts({
             'id': video_id,
-            'title': self._html_search_meta(['og:title', 'twitter:title'], webpage),
+            'title': title,
             'formats': formats,
             'subtitles': subtitles,
-            'duration': parse_duration(video_data.get('Duration')),
-            'description': clean_html(get_element_by_class('episode-info__synopsis', webpage)),
+            # parsing hh:mm:ss:nnn not yet patched
+            'duration': parse_duration(re.sub(r'(\d{2})(:)(\d{3}$)', r'\1.\3', video_data.get('Duration') or '')),
+            'description': video_str('synopsis'),
+            'timestamp': traverse_obj(video_data, 'broadcastDateTime', 'dateTime', expected_type=parse_iso8601),
+            'thumbnails': get_thumbnails(),
+            'series': video_str('showTitle', 'programmeTitle'),
+            'series_number': int_or_none(video_data.get('seriesNumber')),
+            'episode': ep_title,
+            'episode_number': int_or_none((num_ep_title or '').split('.')[0]),
+            'channel': video_str('channel'),
+            'categories': traverse_obj(video_data, ('categories', 'formatted'), expected_type=list),
+            'age_limit': {False: 16, True: 0}.get(video_data.get('isChildrenCategory')),
         }, info)
 
 
-class ITVBTCCIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?itv\.com/btcc/(?:[^/]+/)*(?P<id>[^/?#&]+)'
-    _TEST = {
-        'url': 'http://www.itv.com/btcc/races/btcc-2018-all-the-action-from-brands-hatch',
+class ITVBTCCIE(ITVBaseIE):
+    _VALID_URL = r'https?://(?:www\.)?itv\.com/(?!(?:watch|hub)/)(?:[^/]+/)+(?P<id>[^/?#&]+)'
+    _IE_DESC = 'ITV articles: News, British Touring Car Championship'
+    _TESTS = [{
+        'note': 'British Touring Car Championship',
+        'url': 'https://www.itv.com/btcc/articles/btcc-2018-all-the-action-from-brands-hatch',
         'info_dict': {
             'id': 'btcc-2018-all-the-action-from-brands-hatch',
             'title': 'BTCC 2018: All the action from Brands Hatch',
         },
         'playlist_mincount': 9,
-    }
-    BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/1582188683001/HkiHLnNRx_default/index.html?videoId=%s'
+    }, {
+        'note': 'redirects to /btcc/articles/...',
+        'url': 'http://www.itv.com/btcc/races/btcc-2018-all-the-action-from-brands-hatch',
+        'only_matching': True,
+    }, {
+        'note': 'news article',
+        'url': 'https://www.itv.com/news/wales/2020-07-23/sean-fletcher-shows-off-wales-coastline-in-new-itv-series-as-british-tourists-opt-for-staycations',
+        'info_dict': {
+            'id': 'sean-fletcher-shows-off-wales-coastline-in-new-itv-series-as-british-tourists-opt-for-staycations',
+            'title': '''Sean Fletcher on why Wales' coastline should be your 'staycation' destination | ITV News''',
+        },
+        'playlist_mincount': 1,
+    }]
+
+    # should really be a class var of the BC IE
+    BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_default/index.html?videoId=%s'
+    BRIGHTCOVE_ACCOUNT = '1582188683001'
+    BRIGHTCOVE_PLAYER = 'HkiHLnNRx'
 
     def _real_extract(self, url):
         playlist_id = self._match_id(url)
 
-        webpage = self._download_webpage(url, playlist_id)
-
-        entries = [
-            self.url_result(
-                smuggle_url(self.BRIGHTCOVE_URL_TEMPLATE % video_id, {
-                    # ITV does not like some GB IP ranges, so here are some
-                    # IP blocks it accepts
-                    'geo_ip_blocks': [
-                        '193.113.0.0/16', '54.36.162.0/23', '159.65.16.0/21'
-                    ],
-                    'referrer': url,
-                }),
-                ie=BrightcoveNewIE.ie_key(), video_id=video_id)
-            for video_id in re.findall(r'data-video-id=["\'](\d+)', webpage)]
+        webpage, urlh = self._download_webpage_handle(url, playlist_id)
+        link = compat_urlparse.urlparse(urlh.geturl()).path.strip('/')
+
+        next_data = self._search_nextjs_data(webpage, playlist_id, fatal=False, default='{}')
+        path_prefix = compat_urlparse.urlparse(next_data.get('assetPrefix') or '').path.strip('/')
+        link = remove_start(link, path_prefix).strip('/')
+
+        content = traverse_obj(
+            next_data, ('props', 'pageProps', Ellipsis),
+            expected_type=lambda x: x if x['link'] == link else None,
+            get_all=False, default={})
+        content = traverse_obj(
+            content, ('body', 'content', Ellipsis, 'data'),
+            expected_type=lambda x: x if x.get('name') == 'Brightcove' or x.get('type') == 'Brightcove' else None)
+
+        contraband = {
+            # ITV does not like some GB IP ranges, so here are some
+            # IP blocks it accepts
+            'geo_ip_blocks': [
+                '193.113.0.0/16', '54.36.162.0/23', '159.65.16.0/21'
+            ],
+            'referrer': urlh.geturl(),
+        }
+
+        def entries():
+
+            for data in content or []:
+                video_id = data.get('id')
+                if not video_id:
+                    continue
+                account = data.get('accountId') or self.BRIGHTCOVE_ACCOUNT
+                player = data.get('playerId') or self.BRIGHTCOVE_PLAYER
+                yield self.url_result(
+                    smuggle_url(self.BRIGHTCOVE_URL_TEMPLATE % (account, player, video_id), contraband),
+                    ie=BrightcoveNewIE.ie_key(), video_id=video_id)
+
+            # obsolete ?
+            for video_id in re.findall(r'''data-video-id=["'](\d+)''', webpage):
+                yield self.url_result(
+                    smuggle_url(self.BRIGHTCOVE_URL_TEMPLATE % (self.BRIGHTCOVE_ACCOUNT, self.BRIGHTCOVE_PLAYER, video_id), contraband),
+                    ie=BrightcoveNewIE.ie_key(), video_id=video_id)
 
         title = self._og_search_title(webpage, fatal=False)
 
-        return self.playlist_result(entries, playlist_id, title)
+        return self.playlist_result(entries(), playlist_id, title)


Test patch: