Repository: yt-dlp/yt-dlp

Issue:
[RFE] Add support for ruv.is
### Checklist

- [X] I'm reporting a new site support request
- [X] I've verified that I'm running yt-dlp version **2021.12.25**. ([update instructions](https://github.com/yt-dlp/yt-dlp#update))
- [X] I've checked that all provided URLs are alive and playable in a browser
- [X] I've checked that none of provided URLs [violate any copyrights](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and am willing to share it if required

### Region

Worldwide; parts accessible from Iceland only

### Example URLs

https://www.ruv.is/sjonvarp/spila/jolin-koma/32539/9ma3lh
https://www.ruv.is/sjonvarp/spila/kastljos/30772/95erup
https://www.ruv.is/utvarp/spila/jolatonleikar-valdimars-gudmundssonar/32480/9lo3g1
https://www.ruv.is/utvarp/spila/gerpla/21137/69ig0h

### Description

Please add support for downloading video/audio from the media library of RÚV, Iceland´s state broadcaster. Thank you!

### Verbose log

```shell
[debug] Command-line config: ['-Uv', '-F', 'https://www.ruv.is/sjonvarp/spila/jolin-koma/32539/9ma3lh']
[debug] Encodings: locale cp1252, fs utf-8, out cp1252 (No ANSI), err cp1252 (No ANSI), pref cp1252
[debug] yt-dlp version 2021.12.25 [87e0499] (win_exe)
[debug] Python version 3.8.10 (CPython 64bit)
[debug] exe versions: ffmpeg 4.2.3
[debug] Optional libraries: Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
Latest version: 2021.12.25, Current version: 2021.12.25
yt-dlp is up to date (2021.12.25)
[debug] [generic] Extracting URL: https://www.ruv.is/sjonvarp/spila/jolin-koma/32539/9ma3lh
[generic] 9ma3lh: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] 9ma3lh: Downloading webpage
[generic] 9ma3lh: Extracting information
[debug] Looking for video embeds
ERROR: Unsupported URL: https://www.ruv.is/sjonvarp/spila/jolin-koma/32539/9ma3lh
Traceback (most recent call last):
  File "yt_dlp\YoutubeDL.py", line 1352, in wrapper
  File "yt_dlp\YoutubeDL.py", line 1421, in __extract_info
  File "yt_dlp\extractor\common.py", line 599, in extract
  File "yt_dlp\extractor\generic.py", line 3817, in _real_extract
yt_dlp.utils.UnsupportedError: Unsupported URL: https://www.ruv.is/sjonvarp/spila/jolin-koma/32539/9ma3lh
```



Discussion:
to devs
here's the req to get the stuff
```
curl 'https://www.ruv.is/gql/?operationName=getSerie&variables=%7B%22episodeID%22%3A%5B%229ma3lh%22%5D%2C%22programID%22%3A32539%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22fd7fcb985afe3f26ea4cf46419772e9b33a7e34c903a6cfc95eb0caf81450690%22%7D%7D' \
  -H 'accept: */*' \
  -H 'Referer: https://www.ruv.is/sjonvarp/spila/jolin-koma/32539/9ma3lh' \
  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36' \
  -H 'content-type: application/json' \
  --compressed
```

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
@@ -1377,7 +1377,10 @@
 )
 from .rutv import RUTVIE
 from .ruutu import RuutuIE
-from .ruv import RuvIE
+from .ruv import (
+    RuvIE,
+    RuvSpilaIE
+)
 from .safari import (
     SafariIE,
     SafariApiIE,
diff --git a/yt_dlp/extractor/ruv.py b/yt_dlp/extractor/ruv.py
--- a/yt_dlp/extractor/ruv.py
+++ b/yt_dlp/extractor/ruv.py
@@ -4,6 +4,8 @@
 from .common import InfoExtractor
 from ..utils import (
     determine_ext,
+    parse_duration,
+    traverse_obj,
     unified_timestamp,
 )
 
@@ -99,3 +101,89 @@ def _real_extract(self, url):
             'timestamp': timestamp,
             'formats': formats,
         }
+
+
+class RuvSpilaIE(InfoExtractor):
+    IE_NAME = 'ruv.is:spila'
+    _VALID_URL = r'https?://(?:www\.)?ruv\.is/(?:(?:sjon|ut)varp|(?:krakka|ung)ruv)/spila/.+/(?P<series_id>[0-9]+)/(?P<id>[a-z0-9]+)'
+    _TESTS = [{
+        'url': 'https://www.ruv.is/sjonvarp/spila/ithrottir/30657/9jcnd4',
+        'info_dict': {
+            'id': '9jcnd4',
+            'ext': 'mp4',
+            'title': '01.02.2022',
+            'chapters': 'count:4',
+            'timestamp': 1643743500,
+            'upload_date': '20220201',
+            'thumbnail': 'https://d38kdhuogyllre.cloudfront.net/fit-in/1960x/filters:quality(65)/hd_posters/94boog-iti3jg.jpg',
+            'description': 'Íþróttafréttir.',
+            'age_limit': 0,
+        },
+    }, {
+        'url': 'https://www.ruv.is/utvarp/spila/i-ljosi-sogunnar/23795/7hqkre',
+        'info_dict': {
+            'id': '7hqkre',
+            'ext': 'mp3',
+            'thumbnail': 'https://d38kdhuogyllre.cloudfront.net/fit-in/1960x/filters:quality(65)/hd_posters/7hqkre-7uepao.jpg',
+            'description': 'md5:8d7046549daff35e9a3190dc9901a120',
+            'chapters': [],
+            'upload_date': '20220204',
+            'timestamp': 1643965500,
+            'title': 'Nellie Bly II',
+            'age_limit': 0,
+        },
+    }, {
+        'url': 'https://www.ruv.is/ungruv/spila/ungruv/28046/8beuph',
+        'only_matching': True
+    }, {
+        'url': 'https://www.ruv.is/krakkaruv/spila/krakkafrettir/30712/9jbgb0',
+        'only_matching': True
+    }]
+
+    def _real_extract(self, url):
+        display_id, series_id = self._match_valid_url(url).group('id', 'series_id')
+        program = self._download_json(
+            'https://www.ruv.is/gql/', display_id, query={'query': '''{
+                Program(id: %s){
+                    title image description short_description
+                    episodes(id: {value: "%s"}) {
+                        rating title duration file image firstrun description
+                        clips {
+                            time text
+                        }
+                        subtitles {
+                            name value
+                        }
+                    }
+                }
+            }''' % (series_id, display_id)})['data']['Program']
+        episode = program['episodes'][0]
+
+        subs = {}
+        for trk in episode.get('subtitles'):
+            if trk.get('name') and trk.get('value'):
+                subs.setdefault(trk['name'], []).append({'url': trk['value'], 'ext': 'vtt'})
+
+        media_url = episode['file']
+        if determine_ext(media_url) == 'm3u8':
+            formats = self._extract_m3u8_formats(media_url, display_id)
+        else:
+            formats = [{'url': media_url}]
+
+        clips = [
+            {'start_time': parse_duration(c.get('time')), 'title': c.get('text')}
+            for c in episode.get('clips') or []]
+
+        return {
+            'id': display_id,
+            'title': traverse_obj(program, ('episodes', 0, 'title'), 'title'),
+            'description': traverse_obj(
+                program, ('episodes', 0, 'description'), 'description', 'short_description',
+                expected_type=lambda x: x or None),
+            'subtitles': subs,
+            'thumbnail': episode.get('image', '').replace('$$IMAGESIZE$$', '1960') or None,
+            'timestamp': unified_timestamp(episode.get('firstrun')),
+            'formats': formats,
+            'age_limit': episode.get('rating'),
+            'chapters': clips
+        }


Test patch: