Repository: yt-dlp/yt-dlp

Issue:
https://nos.nl/
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting a broken site
- [X] I've verified that I'm running yt-dlp version **2022.08.08** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#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 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

Netherlands

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

https://nos.nl/
Videos from this website (the Dutch BBC) don't work: Unsupported URL..

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Encodings: locale cp1252, fs utf-8, pref cp1252, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2022.08.08 [3157158] (win32_exe)
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.19044-SP0
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: avconv -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] Checking exe version: avprobe -bsfs
[debug] exe versions: none
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
[debug] Proxy map: {}
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: 2022.08.08, Current version: 2022.08.08
yt-dlp is up to date (2022.08.08)
[debug] [generic] Extracting URL: https://nos.nl/nieuwsuur/artikel/2440353-verzakking-door-droogte-dreigt-tot-een-miljoen-kwetsbare-huizen
[generic] 2440353-verzakking-door-droogte-dreigt-tot-een-miljoen-kwetsbare-huizen: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] 2440353-verzakking-door-droogte-dreigt-tot-een-miljoen-kwetsbare-huizen: Extracting information
[debug] Looking for Brightcove embeds
[debug] Looking for embeds
ERROR: Unsupported URL: https://nos.nl/nieuwsuur/artikel/2440353-verzakking-door-droogte-dreigt-tot-een-miljoen-kwetsbare-huizen
Traceback (most recent call last):
  File "yt_dlp\YoutubeDL.py", line 1441, in wrapper
  File "yt_dlp\YoutubeDL.py", line 1517, in __extract_info
  File "yt_dlp\extractor\common.py", line 666, in extract
  File "yt_dlp\extractor\generic.py", line 3077, in _real_extract
yt_dlp.utils.UnsupportedError: Unsupported URL: https://nos.nl/nieuwsuur/artikel/2440353-verzakking-door-droogte-dreigt-tot-een-miljoen-kwetsbare-huizen
```



Discussion:
In theory it should be possible to write an extractor for this.
I was able to find the exact link to the video with "[The Stream Detector](https://github.com/rowrawer/stream-detector)" and yt-dlp was able to download the video when given that link.
From your url. i got the video url from nextjs data. We can extract many information from there. We can extract the video from `nextjs_json['props']['pageProps']['data']` with `type` equal to `"video"` then get video url from `source['url']`.
Here's the sample of `nextjs_json['props']['pageProps']['data']` with type equal to `video`:
<details><summary> Json sample </summary>

```json
{
                "id": "2440340",
                "hasCoreId": true,
                "type": "video",
                "title": "'We hebben een huis vol met scheuren'",
                "geoprotection": false,
                "description": "Door de aanhoudende droogte verzakken honderdduizenden huizen in Nederland. Zo ook die van Jan Bergmans en Francien van Hulten in Zevenaar. ",
                "imagesByRatio": {
                  "16:9": [{
                    "width": 128,
                    "height": 72,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/128x72a.jpg"
                    }
                  }, {
                    "width": 256,
                    "height": 144,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/256x144a.jpg"
                    }
                  }, {
                    "width": 384,
                    "height": 216,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/384x216a.jpg"
                    }
                  }, {
                    "width": 512,
                    "height": 288,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/512x288a.jpg"
                    }
                  }, {
                    "width": 640,
                    "height": 360,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/640x360a.jpg"
                    }
                  }, {
                    "width": 768,
                    "height": 432,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/768x432a.jpg"
                    }
                  }, {
                    "width": 1024,
                    "height": 576,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/1024x576a.jpg"
                    }
                  }, {
                    "width": 1280,
                    "height": 720,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/1280x720a.jpg"
                    }
                  }, {
                    "width": 1536,
                    "height": 864,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/1536x864a.jpg"
                    }
                  }, {
                    "width": 1920,
                    "height": 1080,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/1920x1080a.jpg"
                    }
                  }, {
                    "width": 2560,
                    "height": 1440,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/2560x1440a.jpg"
                    }
                  }, {
                    "width": 3200,
                    "height": 1800,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/3200x1800a.jpg"
                    }
                  }, {
                    "width": 3840,
                    "height": 2160,
                    "url": {
                      "jpg": "https://cdn.nos.nl/image/2022/08/12/887149/3840x2160a.jpg"
                    }
                  }]
                },
                "source": {
                  "aspectRatio": "16:9",
                  "url": "https://resolver.streaming.api.nos.nl/stream?stream=NOSC_28d7e138_b91d_4149_ba23_caa84a992ce3\u0026profile=hls_unencrypted\u0026policy=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb3JlLm5vcy5ubCIsInN1YiI6Ik5PU0NfMjhkN2UxMzhfYjkxZF80MTQ5X2JhMjNfY2FhODRhOTkyY2UzIiwiYXVkIjpbInJlc29sdmVyLnN0cmVhbWluZy5hcGkubm9zLm5sIl0sImlhdCI6MTY2MDM5Nzg3NywiYWxsb3dlZEFyZWFzIjpbXSwiaXNHZW9wcm90ZWN0ZWQiOmZhbHNlfQ.uvcvIsNVY2F7Uv6PGNrFy083TnLOwqVdodApgJwuuF0",
                  "mimetype": "application/vnd.apple.mpegurl"
                },
                "adConfig": "https://oxmvifnvqu.nl/pwidjie/[identifier]?ncc=[noCommercialCookies]\u0026cust_params=omroep%3Dnos%26genre%3Dnieuwsuur%26video_duur%3D95%26afleverings_titel%3Dwehebbeneenhuisvolmetscheuren%26programma_titel%3Dnieuwsuur",
                "duration": "1:35"
              }
```
</details>

Resolving patch:
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -1180,6 +1180,7 @@
 from .noovo import NoovoIE
 from .normalboots import NormalbootsIE
 from .nosvideo import NosVideoIE
+from .nosnl import NOSNLArticleIE
 from .nova import (
     NovaEmbedIE,
     NovaIE,
diff --git a/yt_dlp/extractor/nosnl.py b/yt_dlp/extractor/nosnl.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/nosnl.py
@@ -0,0 +1,95 @@
+from .common import InfoExtractor
+from ..utils import parse_duration, parse_iso8601, traverse_obj
+
+
+class NOSNLArticleIE(InfoExtractor):
+    _VALID_URL = r'https?://nos\.nl/((?!video)(\w+/)?\w+/)\d+-(?P<display_id>[\w-]+)'
+    _TESTS = [
+        {
+            # only 1 video
+            'url': 'https://nos.nl/nieuwsuur/artikel/2440353-verzakking-door-droogte-dreigt-tot-een-miljoen-kwetsbare-huizen',
+            'info_dict': {
+                'id': '2440340',
+                'ext': 'mp4',
+                'description': 'md5:5f83185d902ac97af3af4bed7ece3db5',
+                'title': '\'We hebben een huis vol met scheuren\'',
+                'duration': 95.0,
+                'thumbnail': 'https://cdn.nos.nl/image/2022/08/12/887149/3840x2160a.jpg',
+            }
+        }, {
+            # more than 1 video
+            'url': 'https://nos.nl/artikel/2440409-vannacht-sliepen-weer-enkele-honderden-asielzoekers-in-ter-apel-buiten',
+            'info_dict': {
+                'id': '2440409',
+                'title': 'Vannacht sliepen weer enkele honderden asielzoekers in Ter Apel buiten',
+                'description': 'Er werd wel geprobeerd om kwetsbare migranten onderdak te bieden, zegt het COA.',
+                'tags': ['aanmeldcentrum', 'Centraal Orgaan opvang asielzoekers', 'COA', 'asielzoekers', 'Ter Apel'],
+                'modified_timestamp': 1660452773,
+                'modified_date': '20220814',
+                'upload_date': '20220813',
+                'thumbnail': 'https://cdn.nos.nl/image/2022/07/18/880346/1024x576a.jpg',
+                'timestamp': 1660401384,
+            },
+            'playlist_count': 2,
+        }, {
+            # audio + video
+            'url': 'https://nos.nl/artikel/2440789-wekdienst-16-8-groningse-acties-tien-jaar-na-zware-aardbeving-femke-bol-in-actie-op-ek-atletiek',
+            'info_dict': {
+                'id': '2440789',
+                'title': 'Wekdienst 16/8: Groningse acties tien jaar na zware aardbeving • Femke Bol in actie op EK atletiek ',
+                'description': 'Nieuws, weer, verkeer: met dit overzicht begin je geïnformeerd aan de dag.',
+                'tags': ['wekdienst'],
+                'modified_date': '20220816',
+                'modified_timestamp': 1660625449,
+                'timestamp': 1660625449,
+                'upload_date': '20220816',
+                'thumbnail': 'https://cdn.nos.nl/image/2022/08/16/888178/1024x576a.jpg',
+            },
+            'playlist_count': 2,
+        }
+    ]
+
+    def _entries(self, nextjs_json, display_id):
+        for item in nextjs_json['items']:
+            if item.get('type') == 'video':
+                formats, subtitle = self._extract_m3u8_formats_and_subtitles(
+                    traverse_obj(item, ('source', 'url')), display_id, ext='mp4')
+                yield {
+                    'id': str(item['id']),
+                    'title': item.get('title'),
+                    'description': item.get('description'),
+                    'formats': formats,
+                    'subtitles': subtitle,
+                    'duration': parse_duration(item.get('duration')),
+                    'thumbnails': [{
+                        'url': traverse_obj(image, ('url', ...), get_all=False),
+                        'width': image.get('width'),
+                        'height': image.get('height')
+                    } for image in traverse_obj(item, ('imagesByRatio', ...))[0]],
+                }
+
+            elif item.get('type') == 'audio':
+                yield {
+                    'id': str(item['id']),
+                    'title': item.get('title'),
+                    'url': traverse_obj(item, ('media', 'src')),
+                    'ext': 'mp3',
+                }
+
+    def _real_extract(self, url):
+        display_id = self._match_valid_url(url).group('display_id')
+        webpage = self._download_webpage(url, display_id)
+
+        nextjs_json = self._search_nextjs_data(webpage, display_id)['props']['pageProps']['data']
+        return {
+            '_type': 'playlist',
+            'entries': self._entries(nextjs_json, display_id),
+            'id': str(nextjs_json['id']),
+            'title': nextjs_json.get('title') or self._html_search_meta(['title', 'og:title', 'twitter:title'], webpage),
+            'description': (nextjs_json.get('description')
+                            or self._html_search_meta(['description', 'twitter:description', 'og:description'], webpage)),
+            'tags': nextjs_json.get('keywords'),
+            'modified_timestamp': parse_iso8601(nextjs_json.get('modifiedAt')),
+            'thumbnail': nextjs_json.get('shareImageSrc') or self._html_search_meta(['og:image', 'twitter:image'], webpage),
+            'timestamp': parse_iso8601(nextjs_json.get('publishedAt'))
+        }


Test patch: