Repository: yt-dlp/yt-dlp

Issue:
Getting 401 while trying to download videos from SlidesLive
### Checklist

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

Czech Republic

### Description

Hi, I'm not able to download any videos from SlidesLive as it gives me HTTP Error 401: Unauthorized. The video is playable directly from the browser, no login is required. I tried multiple different videos with the same output. Here's what I tried:

https://slideslive.com/38897546
https://slideslive.com/38900146
https://slideslive.com/38946802
https://slideslive.com/38922304

### Verbose log

```shell
yt-dlp -Uv https://slideslive.com/38900146
[debug] Command-line config: ['-Uv', 'https://slideslive.com/38900146']
[debug] Encodings: locale UTF-8, fs utf-8, out utf-8, err utf-8, pref UTF-8[debug] yt-dlp version 2021.10.22
[debug] Python version 3.9.7 (CPython 64bit) - macOS-11.6-x86_64-i386-64bit
[debug] exe versions: ffmpeg 4.4.1, ffprobe 4.4.1
[debug] Optional libraries: sqlite
[debug] Proxy map: {}
yt-dlp is up to date (2021.10.22)
[debug] [SlidesLive] Extracting URL: https://slideslive.com/38900146
[SlidesLive] 38900146: Downloading JSON metadata
ERROR: [SlidesLive] 38900146: Unable to download JSON metadata: HTTP Error 401: Unauthorized (caused by <HTTPError 401: 'Unauthorized'>); please report this issue on  https://github.com/yt-dlp/yt-dlp . Make sure you are using the latest version; see  https://github.com/yt-dlp/yt-dlp  on how to update. Be sure to call yt-dlp with the --verbose flag and include its complete output.
  File "/usr/local/Cellar/yt-dlp/2021.10.22/libexec/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 702, in _request_webpage
    return self._downloader.urlopen(url_or_request)
  File "/usr/local/Cellar/yt-dlp/2021.10.22/libexec/lib/python3.9/site-packages/yt_dlp/YoutubeDL.py", line 3252, in urlopen
    return self._opener.open(req, timeout=self._socket_timeout)
  File "/usr/local/Cellar/python@3.9/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 523, in open
    response = meth(req, response)
  File "/usr/local/Cellar/python@3.9/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 632, in http_response
    response = self.parent.error(
  File "/usr/local/Cellar/python@3.9/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 561, in error
    return self._call_chain(*args)
  File "/usr/local/Cellar/python@3.9/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 494, in _call_chain
    result = func(*args)
  File "/usr/local/Cellar/python@3.9/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 641, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
```



Discussion:
This seems to be caused by the addition of a query parameter named `player_token`. A valid parameter can be extracted from `<div>` containing the embedded player.

Also, the information coming from the `https://ben.slideslive.com/player/` url isn't  a JSON.
I have the same issue and indeed, there is a authenticity_token in the player frame sourcecode (I don't know if this is the same culprit as above mentioned):

<html>
<body>
<!--StartFragment-->

<meta name="csrf-param" content="authenticity_token" />
--
  | <meta name="csrf-token" content="p-CmDKIkwtBeOYHIXWP1jidCzy3k2NdiVJXlbe9pNrILukH7K6uvlRzLwmCLLNIsvYbRNpnGupjbBhfNd-5Bag" />

<!--EndFragment-->
</body>
</html>
From this link (https://slideslive.com/38897546/special-metaprednaska-petra-ludwiga-hodnoty-pro-lepsi-spolecnost), we can get `video_id` from `https://ben.slideslive.com/player/ `.

Here's the output from `https://ben.slideslive.com/player/38897546?player_token=eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NTU3MjQ1MzUsImV4cCI6MTY1NTczMTczNSwiaXAiOiIxMjUuMTYyLjI0Ni4xMDciLCJ1IjpudWxsLCJwIjozODg5NzU0NiwibSI6OTM3MywibV9uX2EiOmZhbHNlfQ.jKCpnTonVTaGEzM6240hcf4ro4DwgwCy9EUuBkIOEgA`:
```
#EXTM3U
#EXT-SL-VERSION:1
#EXT-SL-PRESENTATION-ID:38897546
#EXT-SL-PRESENTATION-TITLE:SPECIÁL: Meta-přednáška Petra Ludwiga - Hodnoty pro lepší společnost
#EXT-SL-PRESENTATION-UPDATED-AT:2021-04-18T22:58:35Z
#EXT-SL-PLAYER-TYPE:video_slideshow
#EXT-SL-PRESENTATION-THUMBNAIL:https://cdn.slideslive.com/data/presentations/38897546/slideslive_petr-ludwig_special-metaprednaska-petra-ludwiga-hodnoty-pro-lepsi-spolecnost.jpg?1618786713
#EXT-SL-PLAYLIST-TYPE:vod
#EXT-SL-PRESENTATION-MEDIA-SET-ID:9373
#EXT-SL-VOD-VIDEO-SERVICE-NAME:youtube
#EXT-SL-VOD-VIDEO-ID:jmg02wCJD5M
#EXT-SL-VOD-VIDEO-KEN-ENABLED:
#EXT-SL-VOD-SLIDES-XML-URL:https://slides.slideslive.com/38897546/38897546.xml?1642065757
#EXT-SL-VOD-SLIDES-JSON-URL:https://slides.slideslive.com/38897546/slides.json?1642065757
```
btw, the output in plain not in json like @\stepp1 said
From there we can check the video provider in `#EXT-SL-VOD-VIDEO-SERVICE-NAME` and if the result is `youtube`, we can pass this id in Youtube Extractor. 

We can get the video from webpage too but i think it's not too reliable. The link is in `iframe` tag and we can get the link from `src`

PS: i don't check other link so correct me if i'm wrong 
I'm getting this error too,

> ERROR: [SlidesLive] xxxxxxxx: Unable to download JSON metadata: HTTP Error 401: Unauthorized (caused by <HTTPError 401: 'Unauthorized'>); please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U

Is there a way to "bring your own" json? I tried the `--load-info-json` option but that appears to be for something else.

Unfortunately, one year later this problem remains. In my opinion, it's a bit bold to keep listing SlidesLive as supported here: https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md 
🧐

Resolving patch:
diff --git a/yt_dlp/extractor/slideslive.py b/yt_dlp/extractor/slideslive.py
--- a/yt_dlp/extractor/slideslive.py
+++ b/yt_dlp/extractor/slideslive.py
@@ -1,92 +1,176 @@
 from .common import InfoExtractor
 from ..utils import (
-    bool_or_none,
     smuggle_url,
-    try_get,
+    traverse_obj,
+    unified_timestamp,
     url_or_none,
 )
 
 
 class SlidesLiveIE(InfoExtractor):
     _VALID_URL = r'https?://slideslive\.com/(?P<id>[0-9]+)'
-    _WORKING = False
     _TESTS = [{
-        # video_service_name = YOUTUBE
+        # service_name = yoda
         'url': 'https://slideslive.com/38902413/gcc-ia16-backend',
-        'md5': 'b29fcd6c6952d0c79c5079b0e7a07e6f',
         'info_dict': {
-            'id': 'LMtgR8ba0b0',
+            'id': '38902413',
             'ext': 'mp4',
             'title': 'GCC IA16 backend',
-            'description': 'Watch full version of this video at https://slideslive.com/38902413.',
-            'uploader': 'SlidesLive Videos - A',
-            'uploader_id': 'UC62SdArr41t_-_fX40QCLRw',
-            'timestamp': 1597615266,
-            'upload_date': '20170925',
-        }
+            'timestamp': 1648189972,
+            'upload_date': '20220325',
+            'thumbnail': r're:^https?://.*\.jpg',
+        },
+        'params': {
+            'skip_download': 'm3u8',
+        },
     }, {
-        # video_service_name = yoda
+        # service_name = yoda
         'url': 'https://slideslive.com/38935785',
-        'md5': '575cd7a6c0acc6e28422fe76dd4bcb1a',
         'info_dict': {
-            'id': 'RMraDYN5ozA_',
+            'id': '38935785',
             'ext': 'mp4',
             'title': 'Offline Reinforcement Learning: From Algorithms to Practical Challenges',
+            'upload_date': '20211115',
+            'timestamp': 1636996003,
+            'thumbnail': r're:^https?://.*\.jpg',
+        },
+        'params': {
+            'skip_download': 'm3u8',
+        },
+    }, {
+        # service_name = yoda
+        'url': 'https://slideslive.com/38973182/how-should-a-machine-learning-researcher-think-about-ai-ethics',
+        'info_dict': {
+            'id': '38973182',
+            'ext': 'mp4',
+            'title': 'How Should a Machine Learning Researcher Think About AI Ethics?',
+            'upload_date': '20220201',
+            'thumbnail': r're:^https?://.*\.jpg',
+            'timestamp': 1643728135,
+        },
+        'params': {
+            'skip_download': 'm3u8',
         },
     }, {
-        # video_service_name = youtube
+        # service_name = youtube
+        'url': 'https://slideslive.com/38897546/special-metaprednaska-petra-ludwiga-hodnoty-pro-lepsi-spolecnost',
+        'md5': '8a79b5e3d700837f40bd2afca3c8fa01',
+        'info_dict': {
+            'id': 'jmg02wCJD5M',
+            'display_id': '38897546',
+            'ext': 'mp4',
+            'title': 'SPECIÁL: Meta-přednáška Petra Ludwiga - Hodnoty pro lepší společnost',
+            'description': 'Watch full version of this video at https://slideslive.com/38897546.',
+            'channel_url': 'https://www.youtube.com/channel/UCZWdAkNYFncuX0khyvhqnxw',
+            'channel': 'SlidesLive Videos - G1',
+            'channel_id': 'UCZWdAkNYFncuX0khyvhqnxw',
+            'uploader_id': 'UCZWdAkNYFncuX0khyvhqnxw',
+            'uploader': 'SlidesLive Videos - G1',
+            'uploader_url': 'http://www.youtube.com/channel/UCZWdAkNYFncuX0khyvhqnxw',
+            'live_status': 'not_live',
+            'upload_date': '20160710',
+            'timestamp': 1618786715,
+            'duration': 6827,
+            'like_count': int,
+            'view_count': int,
+            'comment_count': int,
+            'channel_follower_count': int,
+            'age_limit': 0,
+            'thumbnail': r're:^https?://.*\.jpg',
+            'playable_in_embed': True,
+            'availability': 'unlisted',
+            'tags': [],
+            'categories': ['People & Blogs'],
+        },
+    }, {
+        # service_name = youtube
         'url': 'https://slideslive.com/38903721/magic-a-scientific-resurrection-of-an-esoteric-legend',
         'only_matching': True,
     }, {
-        # video_service_name = url
+        # service_name = url
         'url': 'https://slideslive.com/38922070/learning-transferable-skills-1',
         'only_matching': True,
     }, {
-        # video_service_name = vimeo
+        # service_name = vimeo
         'url': 'https://slideslive.com/38921896/retrospectives-a-venue-for-selfreflection-in-ml-research-3',
         'only_matching': True,
     }]
 
+    def _extract_custom_m3u8_info(self, m3u8_data):
+        m3u8_dict = {}
+
+        lookup = {
+            'PRESENTATION-TITLE': 'title',
+            'PRESENTATION-UPDATED-AT': 'timestamp',
+            'PRESENTATION-THUMBNAIL': 'thumbnail',
+            'PLAYLIST-TYPE': 'playlist_type',
+            'VOD-VIDEO-SERVICE-NAME': 'service_name',
+            'VOD-VIDEO-ID': 'service_id',
+            'VOD-VIDEO-SERVERS': 'video_servers',
+            'VOD-SUBTITLES': 'subtitles',
+        }
+
+        for line in m3u8_data.splitlines():
+            if not line.startswith('#EXT-SL-'):
+                continue
+            tag, _, value = line.partition(':')
+            key = lookup.get(tag.lstrip('#EXT-SL-'))
+            if not key:
+                continue
+            m3u8_dict[key] = value
+
+        # Some values are stringified JSON arrays
+        for key in ('video_servers', 'subtitles'):
+            if key in m3u8_dict:
+                m3u8_dict[key] = self._parse_json(m3u8_dict[key], None, fatal=False) or []
+
+        return m3u8_dict
+
     def _real_extract(self, url):
         video_id = self._match_id(url)
-        video_data = self._download_json(
-            'https://ben.slideslive.com/player/' + video_id, video_id)
-        service_name = video_data['video_service_name'].lower()
+        webpage = self._download_webpage(url, video_id)
+        player_token = self._search_regex(r'data-player-token="([^"]+)"', webpage, 'player token')
+        player_data = self._download_webpage(
+            f'https://ben.slideslive.com/player/{video_id}', video_id,
+            note='Downloading player info', query={'player_token': player_token})
+        player_info = self._extract_custom_m3u8_info(player_data)
+
+        service_name = player_info['service_name'].lower()
         assert service_name in ('url', 'yoda', 'vimeo', 'youtube')
-        service_id = video_data['video_service_id']
+        service_id = player_info['service_id']
+
         subtitles = {}
-        for sub in try_get(video_data, lambda x: x['subtitles'], list) or []:
-            if not isinstance(sub, dict):
-                continue
+        for sub in traverse_obj(player_info, ('subtitles', ...), expected_type=dict):
             webvtt_url = url_or_none(sub.get('webvtt_url'))
             if not webvtt_url:
                 continue
-            lang = sub.get('language') or 'en'
-            subtitles.setdefault(lang, []).append({
+            subtitles.setdefault(sub.get('language') or 'en', []).append({
                 'url': webvtt_url,
+                'ext': 'vtt',
             })
+
         info = {
             'id': video_id,
-            'thumbnail': video_data.get('thumbnail'),
-            'is_live': bool_or_none(video_data.get('is_live')),
+            'title': player_info.get('title') or self._html_search_meta('title', webpage, default=''),
+            'timestamp': unified_timestamp(player_info.get('timestamp')),
+            'is_live': player_info.get('playlist_type') != 'vod',
+            'thumbnail': url_or_none(player_info.get('thumbnail')),
             'subtitles': subtitles,
         }
+
         if service_name in ('url', 'yoda'):
-            info['title'] = video_data['title']
             if service_name == 'url':
                 info['url'] = service_id
             else:
+                cdn_hostname = player_info['video_servers'][0]
                 formats = []
-                _MANIFEST_PATTERN = 'https://01.cdn.yoda.slideslive.com/%s/master.%s'
-                # use `m3u8` entry_protocol until EXT-X-MAP is properly supported by `m3u8_native` entry_protocol
                 formats.extend(self._extract_m3u8_formats(
-                    _MANIFEST_PATTERN % (service_id, 'm3u8'),
-                    service_id, 'mp4', m3u8_id='hls', fatal=False))
+                    f'https://{cdn_hostname}/{service_id}/master.m3u8',
+                    video_id, 'mp4', m3u8_id='hls', fatal=False, live=True))
                 formats.extend(self._extract_mpd_formats(
-                    _MANIFEST_PATTERN % (service_id, 'mpd'), service_id,
-                    mpd_id='dash', fatal=False))
+                    f'https://{cdn_hostname}/{service_id}/master.mpd',
+                    video_id, mpd_id='dash', fatal=False))
                 info.update({
-                    'id': service_id,
                     'formats': formats,
                 })
         else:
@@ -94,10 +178,11 @@ def _real_extract(self, url):
                 '_type': 'url_transparent',
                 'url': service_id,
                 'ie_key': service_name.capitalize(),
-                'title': video_data.get('title'),
+                'display_id': video_id,
             })
             if service_name == 'vimeo':
                 info['url'] = smuggle_url(
-                    'https://player.vimeo.com/video/' + service_id,
+                    f'https://player.vimeo.com/video/{service_id}',
                     {'http_headers': {'Referer': url}})
+
         return info


Test patch: