Repository: yt-dlp/yt-dlp
Issue category: Bug Report

Issue:
[rutube] Connection to bl.rutube.ru timed out => No video formats found!
### 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

Russia

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

Current yt-dlp doesn't download any videos from rutube.ru at all. 

The errors are `Connection to bl.rutube.ru timed out. (connect timeout=20.0)` followed by `No video formats found!`. With and without VPN. 

Also tried `--user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0" --cookies-from-browser firefox`  with no help. But in Firefox video plays.

### 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] 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
[debug] Command-line config: ['https://rutube.ru/video/d144161b680dbc3a864c1587632b18f7/', '-vU']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version stable@2024.11.04 from yt-dlp/yt-dlp [197d0b03b] (zip)
[debug] Python 3.12.6 (CPython x86_64 64bit) - Linux-6.11.2-amd64-x86_64-with-glibc2.40 (OpenSSL 3.3.2 3 Sep 2024, glibc 2.40)
[debug] exe versions: ffmpeg 7.0.2-3 (setts), ffprobe 7.0.2-3, phantomjs 2.1.1
[debug] Optional libraries: Cryptodome-3.20.0, brotli-1.1.0, certifi-2024.08.30, mutagen-1.47.0, requests-2.32.3, sqlite3-3.46.1, urllib3-2.0.7, websockets-10.4
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests
[debug] Loaded 1838 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: stable@2024.11.04 from yt-dlp/yt-dlp
yt-dlp is up to date (stable@2024.11.04 from yt-dlp/yt-dlp)
[rutube] Extracting URL: https://rutube.ru/video/d144161b680dbc3a864c1587632b18f7/
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading video JSON
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading options JSON
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading m3u8 information
WARNING: [rutube] Failed to download m3u8 information: (<urllib3.connection.HTTPConnection object at 0x7fca20f0ccb0>, 'Connection to bl.rutube.ru timed out. (connect timeout=20.0)')
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading m3u8 information
WARNING: [rutube] Failed to download m3u8 information: (<urllib3.connection.HTTPConnection object at 0x7fca20f0d460>, 'Connection to bl.rutube.ru timed out. (connect timeout=20.0)')
ERROR: [rutube] d144161b680dbc3a864c1587632b18f7: No video formats found!; 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
Traceback (most recent call last):
  File "/usr/bin/yt-dlp/yt_dlp/YoutubeDL.py", line 1625, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/yt-dlp/yt_dlp/YoutubeDL.py", line 1781, in __extract_info
    return self.process_ie_result(ie_result, download, extra_info)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/yt-dlp/yt_dlp/YoutubeDL.py", line 1840, in process_ie_result
    ie_result = self.process_video_result(ie_result, download=download)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/yt-dlp/yt_dlp/YoutubeDL.py", line 2846, in process_video_result
    self.raise_no_formats(info_dict)
  File "/usr/bin/yt-dlp/yt_dlp/YoutubeDL.py", line 1122, in raise_no_formats
    raise ExtractorError(msg, video_id=info['id'], ie=info['extractor'],
yt_dlp.utils.ExtractorError: [rutube] d144161b680dbc3a864c1587632b18f7: No video formats found!; 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
```



Hints:
Exactly same behavior for both master and nightly:

```
[...]
Latest version: master@2024.11.06.215904 from yt-dlp/yt-dlp-master-builds
yt-dlp is up to date (master@2024.11.06.215904 from yt-dlp/yt-dlp-master-builds)
[...]
WARNING: [rutube] Failed to download m3u8 information: (<urllib3.connection.HTTPConnection object at 0x7fbde7180410>, 'Connection to bl.rutube.ru timed out. (connect timeout=20.0)')
[...]
ERROR: [rutube] d144161b680dbc3a864c1587632b18f7: No video formats found!
```

```
[...]
Latest version: nightly@2024.11.06.232826 from yt-dlp/yt-dlp-nightly-builds
yt-dlp is up to date (nightly@2024.11.06.232826 from yt-dlp/yt-dlp-nightly-builds)
[...]
WARNING: [rutube] Failed to download m3u8 information: (<urllib3.connection.HTTPConnection object at 0x7fbde7180410>, 'Connection to bl.rutube.ru timed out. (connect timeout=20.0)')
[...]
ERROR: [rutube] d144161b680dbc3a864c1587632b18f7: No video formats found!
```



The Firefox's Developer Tools are stating the browser does connect successfully to bl.rutube.ru:

```
GET https://bl.rutube.ru/route/d144161b680dbc3a864c1587632b18f7.m3u8?... 

HTTP/2 200 
server: QRATOR
date: Fri, 08 Nov 2024 08:05:52 GMT
content-type: text/html; charset=utf-8
temperature: {'short': 2, 'long': 4}
dest-server: river-2-301
client-ip: X.X.X.X
x-frontend-config-version: 3.8.2
access-control-allow-origin: *
access-control-allow-methods: GET, HEAD
access-control-allow-headers: Range
access-control-expose-headers: Range, Content-Length, Accept-Ranges, Date
access-control-allow-credentials: true
content-encoding: br
X-Firefox-Spdy: h2
```

Seems like QRATOR (rutube's anti-ddos service of choice) somehow detects yt-dlp's requests and block them?
Just tried to "wget" the m3u8 link directly from console and it works too. So it allows any user-agent, even wget. The problem must be somewhere inside the extractor or the urllib3:

```
$ wget "https://bl.rutube.ru/route/d144161b680dbc3a864c1587632b18f7.m3u8?..."
Resolving bl.rutube.ru (bl.rutube.ru)... 178.248.232.241
Connecting to bl.rutube.ru (bl.rutube.ru)|178.248.232.241|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1642 (1.6K) [text/html]

=> correct m3u8 file.
```

Seems like this fixes it:
```diff
diff --git a/yt_dlp/extractor/rutube.py b/yt_dlp/extractor/rutube.py
index 2c416811a..c77e0bf8a 100644
--- a/yt_dlp/extractor/rutube.py
+++ b/yt_dlp/extractor/rutube.py
@@ -61,7 +61,7 @@ def _download_api_options(self, video_id, query=None):
             query = {}
         query['format'] = 'json'
         return self._download_json(
-            f'http://rutube.ru/api/play/options/{video_id}/',
+            f'https://rutube.ru/api/play/options/{video_id}/',
             video_id, 'Downloading options JSON',
             'Unable to download options JSON',
             headers=self.geo_verification_headers(), query=query)
```
Yep, can confirm this patch does the job. It works with it:

```
[debug] Command-line config: ['https://rutube.ru/video/d144161b680dbc3a864c1587632b18f7/', '-vU']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version stable@2024.11.04 from yt-dlp/yt-dlp [197d0b03b] (linux_exe)
[debug] Python 3.12.6 (CPython x86_64 64bit) - Linux-6.11.2-amd64-x86_64-with-glibc2.40 (OpenSSL 3.3.2 3 Sep 2024, glibc 2.40)
[debug] exe versions: ffmpeg 7.0.2-3 (setts), ffprobe 7.0.2-3, phantomjs broken
[debug] Optional libraries: Cryptodome-3.21.0, brotli-1.1.0, certifi-2024.08.30, mutagen-1.47.0, requests-2.32.3, sqlite3-3.46.1, urllib3-2.2.3, websockets-13.1
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests, websockets
[debug] Loaded 1839 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: stable@2024.11.04 from yt-dlp/yt-dlp
yt-dlp is up to date (stable@2024.11.04 from yt-dlp/yt-dlp)
[rutube] Extracting URL: https://rutube.ru/video/d144161b680dbc3a864c1587632b18f7/
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading video JSON
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading options JSON
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading m3u8 information
[rutube] d144161b680dbc3a864c1587632b18f7: Downloading m3u8 information
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec, channels, acodec, size, br, asr, proto, vext, aext, hasaud, source, id
[debug] Default format spec: bestvideo*+bestaudio/best
[info] d144161b680dbc3a864c1587632b18f7: Downloading 1 format(s): m3u8-406-1
[debug] Invoking hlsnative downloader on "https://river-1.rutube.ru/hls-vod/QJgc4k_gFwQXtJK3H3QXaA/1731661417/1944/rutube-ds-origin-118-1/ebb02cbd26b04bf5a83e50c038e49b90.mp4.m3u8?i=1280x720_406"
[hlsnative] Downloading m3u8 manifest
[hlsnative] Total fragments: 45
[download] Destination: yt-dlp, the best youtube-dl alternative with more features [d144161b680dbc3a864c1587632b18f7].mp4
[download] 100% of   12.26MiB in 00:00:08 at 1.50MiB/s
```

Additional info: patch is working. Without the patch it's not possible to download any video.
Hello! Could you create a pull request? I have the same problem. If you can't do it, I can try to do it.
I will create a PR and merge it later today
I confirm that this works from UK. There are several other http: URLs used in the extractor that appear to be unaffected.
Maybe also adopt patch from #11415 ?
Maybe change ALL "http" to "https" in extractor?

Patch:
diff --git a/yt_dlp/extractor/rutube.py b/yt_dlp/extractor/rutube.py
index 2c416811af35..abf9aec7276b 100644
--- a/yt_dlp/extractor/rutube.py
+++ b/yt_dlp/extractor/rutube.py
@@ -2,15 +2,18 @@
 
 from .common import InfoExtractor
 from ..utils import (
+    UnsupportedError,
     bool_or_none,
     determine_ext,
     int_or_none,
+    js_to_json,
     parse_qs,
-    traverse_obj,
+    str_or_none,
     try_get,
     unified_timestamp,
     url_or_none,
 )
+from ..utils.traversal import traverse_obj
 
 
 class RutubeBaseIE(InfoExtractor):
@@ -19,7 +22,7 @@ def _download_api_info(self, video_id, query=None):
             query = {}
         query['format'] = 'json'
         return self._download_json(
-            f'http://rutube.ru/api/video/{video_id}/',
+            f'https://rutube.ru/api/video/{video_id}/',
             video_id, 'Downloading video JSON',
             'Unable to download video JSON', query=query)
 
@@ -61,18 +64,21 @@ def _download_api_options(self, video_id, query=None):
             query = {}
         query['format'] = 'json'
         return self._download_json(
-            f'http://rutube.ru/api/play/options/{video_id}/',
+            f'https://rutube.ru/api/play/options/{video_id}/',
             video_id, 'Downloading options JSON',
             'Unable to download options JSON',
             headers=self.geo_verification_headers(), query=query)
 
-    def _extract_formats(self, options, video_id):
+    def _extract_formats_and_subtitles(self, options, video_id):
         formats = []
+        subtitles = {}
         for format_id, format_url in options['video_balancer'].items():
             ext = determine_ext(format_url)
             if ext == 'm3u8':
-                formats.extend(self._extract_m3u8_formats(
-                    format_url, video_id, 'mp4', m3u8_id=format_id, fatal=False))
+                fmts, subs = self._extract_m3u8_formats_and_subtitles(
+                    format_url, video_id, 'mp4', m3u8_id=format_id, fatal=False)
+                formats.extend(fmts)
+                self._merge_subtitles(subs, target=subtitles)
             elif ext == 'f4m':
                 formats.extend(self._extract_f4m_formats(
                     format_url, video_id, f4m_id=format_id, fatal=False))
@@ -82,11 +88,19 @@ def _extract_formats(self, options, video_id):
                     'format_id': format_id,
                 })
         for hls_url in traverse_obj(options, ('live_streams', 'hls', ..., 'url', {url_or_none})):
-            formats.extend(self._extract_m3u8_formats(hls_url, video_id, ext='mp4', fatal=False))
-        return formats
-
-    def _download_and_extract_formats(self, video_id, query=None):
-        return self._extract_formats(
+            fmts, subs = self._extract_m3u8_formats_and_subtitles(
+                hls_url, video_id, 'mp4', fatal=False, m3u8_id='hls')
+            formats.extend(fmts)
+            self._merge_subtitles(subs, target=subtitles)
+        for caption in traverse_obj(options, ('captions', lambda _, v: url_or_none(v['file']))):
+            subtitles.setdefault(caption.get('code') or 'ru', []).append({
+                'url': caption['file'],
+                'name': caption.get('langTitle'),
+            })
+        return formats, subtitles
+
+    def _download_and_extract_formats_and_subtitles(self, video_id, query=None):
+        return self._extract_formats_and_subtitles(
             self._download_api_options(video_id, query=query), video_id)
 
 
@@ -97,8 +111,8 @@ class RutubeIE(RutubeBaseIE):
     _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//rutube\.ru/(?:play/)?embed/[\da-z]{32}.*?)\1']
 
     _TESTS = [{
-        'url': 'http://rutube.ru/video/3eac3b4561676c17df9132a9a1e62e3e/',
-        'md5': 'e33ac625efca66aba86cbec9851f2692',
+        'url': 'https://rutube.ru/video/3eac3b4561676c17df9132a9a1e62e3e/',
+        'md5': '3d73fdfe5bb81b9aef139e22ef3de26a',
         'info_dict': {
             'id': '3eac3b4561676c17df9132a9a1e62e3e',
             'ext': 'mp4',
@@ -111,26 +125,25 @@ class RutubeIE(RutubeBaseIE):
             'upload_date': '20131016',
             'age_limit': 0,
             'view_count': int,
-            'thumbnail': 'http://pic.rutubelist.ru/video/d2/a0/d2a0aec998494a396deafc7ba2c82add.jpg',
+            'thumbnail': 'https://pic.rutubelist.ru/video/d2/a0/d2a0aec998494a396deafc7ba2c82add.jpg',
             'categories': ['Новости и СМИ'],
             'chapters': [],
         },
-        'expected_warnings': ['Unable to download f4m'],
     }, {
-        'url': 'http://rutube.ru/play/embed/a10e53b86e8f349080f718582ce4c661',
+        'url': 'https://rutube.ru/play/embed/a10e53b86e8f349080f718582ce4c661',
         'only_matching': True,
     }, {
-        'url': 'http://rutube.ru/embed/a10e53b86e8f349080f718582ce4c661',
+        'url': 'https://rutube.ru/embed/a10e53b86e8f349080f718582ce4c661',
         'only_matching': True,
     }, {
-        'url': 'http://rutube.ru/video/3eac3b4561676c17df9132a9a1e62e3e/?pl_id=4252',
+        'url': 'https://rutube.ru/video/3eac3b4561676c17df9132a9a1e62e3e/?pl_id=4252',
         'only_matching': True,
     }, {
         'url': 'https://rutube.ru/video/10b3a03fc01d5bbcc632a2f3514e8aab/?pl_type=source',
         'only_matching': True,
     }, {
         'url': 'https://rutube.ru/video/private/884fb55f07a97ab673c7d654553e0f48/?p=x2QojCumHTS3rsKHWXN8Lg',
-        'md5': 'd106225f15d625538fe22971158e896f',
+        'md5': '4fce7b4fcc7b1bcaa3f45eb1e1ad0dd7',
         'info_dict': {
             'id': '884fb55f07a97ab673c7d654553e0f48',
             'ext': 'mp4',
@@ -143,11 +156,10 @@ class RutubeIE(RutubeBaseIE):
             'upload_date': '20221210',
             'age_limit': 0,
             'view_count': int,
-            'thumbnail': 'http://pic.rutubelist.ru/video/f2/d4/f2d42b54be0a6e69c1c22539e3152156.jpg',
+            'thumbnail': 'https://pic.rutubelist.ru/video/f2/d4/f2d42b54be0a6e69c1c22539e3152156.jpg',
             'categories': ['Видеоигры'],
             'chapters': [],
         },
-        'expected_warnings': ['Unable to download f4m'],
     }, {
         'url': 'https://rutube.ru/video/c65b465ad0c98c89f3b25cb03dcc87c6/',
         'info_dict': {
@@ -156,17 +168,16 @@ class RutubeIE(RutubeBaseIE):
             'chapters': 'count:4',
             'categories': ['Бизнес и предпринимательство'],
             'description': 'md5:252feac1305257d8c1bab215cedde75d',
-            'thumbnail': 'http://pic.rutubelist.ru/video/71/8f/718f27425ea9706073eb80883dd3787b.png',
+            'thumbnail': 'https://pic.rutubelist.ru/video/71/8f/718f27425ea9706073eb80883dd3787b.png',
             'duration': 782,
             'age_limit': 0,
             'uploader_id': '23491359',
             'timestamp': 1677153329,
             'view_count': int,
             'upload_date': '20230223',
-            'title': 'Бизнес с нуля: найм сотрудников. Интервью с директором строительной компании',
+            'title': 'Бизнес с нуля: найм сотрудников. Интервью с директором строительной компании #1',
             'uploader': 'Стас Быков',
         },
-        'expected_warnings': ['Unable to download f4m'],
     }, {
         'url': 'https://rutube.ru/live/video/c58f502c7bb34a8fcdd976b221fca292/',
         'info_dict': {
@@ -174,7 +185,7 @@ class RutubeIE(RutubeBaseIE):
             'ext': 'mp4',
             'categories': ['Телепередачи'],
             'description': '',
-            'thumbnail': 'http://pic.rutubelist.ru/video/14/19/14190807c0c48b40361aca93ad0867c7.jpg',
+            'thumbnail': 'https://pic.rutubelist.ru/video/14/19/14190807c0c48b40361aca93ad0867c7.jpg',
             'live_status': 'is_live',
             'age_limit': 0,
             'uploader_id': '23460655',
@@ -184,6 +195,24 @@ class RutubeIE(RutubeBaseIE):
             'title': r're:Первый канал. Прямой эфир \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
             'uploader': 'Первый канал',
         },
+    }, {
+        'url': 'https://rutube.ru/play/embed/03a9cb54bac3376af4c5cb0f18444e01/',
+        'info_dict': {
+            'id': '03a9cb54bac3376af4c5cb0f18444e01',
+            'ext': 'mp4',
+            'age_limit': 0,
+            'description': '',
+            'title': 'Церемония начала торгов акциями ПАО «ЕвроТранс»',
+            'chapters': [],
+            'upload_date': '20240829',
+            'duration': 293,
+            'uploader': 'MOEX - Московская биржа',
+            'timestamp': 1724946628,
+            'thumbnail': 'https://pic.rutubelist.ru/video/2e/24/2e241fddb459baf0fa54acfca44874f4.jpg',
+            'view_count': int,
+            'uploader_id': '38420507',
+            'categories': ['Интервью'],
+        },
     }, {
         'url': 'https://rutube.ru/video/5ab908fccfac5bb43ef2b1e4182256b0/',
         'only_matching': True,
@@ -192,40 +221,46 @@ class RutubeIE(RutubeBaseIE):
         'only_matching': True,
     }]
 
-    @classmethod
-    def suitable(cls, url):
-        return False if RutubePlaylistIE.suitable(url) else super().suitable(url)
-
     def _real_extract(self, url):
         video_id = self._match_id(url)
         query = parse_qs(url)
         info = self._download_and_extract_info(video_id, query)
-        info['formats'] = self._download_and_extract_formats(video_id, query)
-        return info
+        formats, subtitles = self._download_and_extract_formats_and_subtitles(video_id, query)
+        return {
+            **info,
+            'formats': formats,
+            'subtitles': subtitles,
+        }
 
 
 class RutubeEmbedIE(RutubeBaseIE):
     IE_NAME = 'rutube:embed'
     IE_DESC = 'Rutube embedded videos'
-    _VALID_URL = r'https?://rutube\.ru/(?:video|play)/embed/(?P<id>[0-9]+)'
+    _VALID_URL = r'https?://rutube\.ru/(?:video|play)/embed/(?P<id>[0-9]+)(?:[?#/]|$)'
 
     _TESTS = [{
-        'url': 'http://rutube.ru/video/embed/6722881?vk_puid37=&vk_puid38=',
+        'url': 'https://rutube.ru/video/embed/6722881?vk_puid37=&vk_puid38=',
         'info_dict': {
             'id': 'a10e53b86e8f349080f718582ce4c661',
             'ext': 'mp4',
             'timestamp': 1387830582,
             'upload_date': '20131223',
             'uploader_id': '297833',
-            'description': 'Видео группы ★http://vk.com/foxkidsreset★ музей Fox Kids и Jetix<br/><br/> восстановлено и сделано в шикоформате subziro89 http://vk.com/subziro89',
             'uploader': 'subziro89 ILya',
             'title': 'Мистический городок Эйри в Индиан 5 серия озвучка subziro89',
+            'age_limit': 0,
+            'duration': 1395,
+            'chapters': [],
+            'description': 'md5:a5acea57bbc3ccdc3cacd1f11a014b5b',
+            'view_count': int,
+            'thumbnail': 'https://pic.rutubelist.ru/video/d3/03/d3031f4670a6e6170d88fb3607948418.jpg',
+            'categories': ['Сериалы'],
         },
         'params': {
             'skip_download': True,
         },
     }, {
-        'url': 'http://rutube.ru/play/embed/8083783',
+        'url': 'https://rutube.ru/play/embed/8083783',
         'only_matching': True,
     }, {
         # private video
@@ -240,11 +275,12 @@ def _real_extract(self, url):
         query = parse_qs(url)
         options = self._download_api_options(embed_id, query)
         video_id = options['effective_video']
-        formats = self._extract_formats(options, video_id)
+        formats, subtitles = self._extract_formats_and_subtitles(options, video_id)
         info = self._download_and_extract_info(video_id, query)
         info.update({
             'extractor_key': 'Rutube',
             'formats': formats,
+            'subtitles': subtitles,
         })
         return info
 
@@ -295,14 +331,14 @@ class RutubeTagsIE(RutubePlaylistBaseIE):
     IE_DESC = 'Rutube tags'
     _VALID_URL = r'https?://rutube\.ru/tags/video/(?P<id>\d+)'
     _TESTS = [{
-        'url': 'http://rutube.ru/tags/video/1800/',
+        'url': 'https://rutube.ru/tags/video/1800/',
         'info_dict': {
             'id': '1800',
         },
         'playlist_mincount': 68,
     }]
 
-    _PAGE_TEMPLATE = 'http://rutube.ru/api/tags/video/%s/?page=%s&format=json'
+    _PAGE_TEMPLATE = 'https://rutube.ru/api/tags/video/%s/?page=%s&format=json'
 
 
 class RutubeMovieIE(RutubePlaylistBaseIE):
@@ -310,8 +346,8 @@ class RutubeMovieIE(RutubePlaylistBaseIE):
     IE_DESC = 'Rutube movies'
     _VALID_URL = r'https?://rutube\.ru/metainfo/tv/(?P<id>\d+)'
 
-    _MOVIE_TEMPLATE = 'http://rutube.ru/api/metainfo/tv/%s/?format=json'
-    _PAGE_TEMPLATE = 'http://rutube.ru/api/metainfo/tv/%s/video?page=%s&format=json'
+    _MOVIE_TEMPLATE = 'https://rutube.ru/api/metainfo/tv/%s/?format=json'
+    _PAGE_TEMPLATE = 'https://rutube.ru/api/metainfo/tv/%s/video?page=%s&format=json'
 
     def _real_extract(self, url):
         movie_id = self._match_id(url)
@@ -327,62 +363,82 @@ class RutubePersonIE(RutubePlaylistBaseIE):
     IE_DESC = 'Rutube person videos'
     _VALID_URL = r'https?://rutube\.ru/video/person/(?P<id>\d+)'
     _TESTS = [{
-        'url': 'http://rutube.ru/video/person/313878/',
+        'url': 'https://rutube.ru/video/person/313878/',
         'info_dict': {
             'id': '313878',
         },
-        'playlist_mincount': 37,
+        'playlist_mincount': 36,
     }]
 
-    _PAGE_TEMPLATE = 'http://rutube.ru/api/video/person/%s/?page=%s&format=json'
+    _PAGE_TEMPLATE = 'https://rutube.ru/api/video/person/%s/?page=%s&format=json'
 
 
 class RutubePlaylistIE(RutubePlaylistBaseIE):
     IE_NAME = 'rutube:playlist'
     IE_DESC = 'Rutube playlists'
-    _VALID_URL = r'https?://rutube\.ru/(?:video|(?:play/)?embed)/[\da-z]{32}/\?.*?\bpl_id=(?P<id>\d+)'
+    _VALID_URL = r'https?://rutube\.ru/plst/(?P<id>\d+)'
     _TESTS = [{
-        'url': 'https://rutube.ru/video/cecd58ed7d531fc0f3d795d51cee9026/?pl_id=3097&pl_type=tag',
+        'url': 'https://rutube.ru/plst/308547/',
         'info_dict': {
-            'id': '3097',
+            'id': '308547',
         },
-        'playlist_count': 27,
-    }, {
-        'url': 'https://rutube.ru/video/10b3a03fc01d5bbcc632a2f3514e8aab/?pl_id=4252&pl_type=source',
-        'only_matching': True,
+        'playlist_mincount': 22,
     }]
-
-    _PAGE_TEMPLATE = 'http://rutube.ru/api/playlist/%s/%s/?page=%s&format=json'
-
-    @classmethod
-    def suitable(cls, url):
-        from ..utils import int_or_none, parse_qs
-
-        if not super().suitable(url):
-            return False
-        params = parse_qs(url)
-        return params.get('pl_type', [None])[0] and int_or_none(params.get('pl_id', [None])[0])
-
-    def _next_page_url(self, page_num, playlist_id, item_kind):
-        return self._PAGE_TEMPLATE % (item_kind, playlist_id, page_num)
-
-    def _real_extract(self, url):
-        qs = parse_qs(url)
-        playlist_kind = qs['pl_type'][0]
-        playlist_id = qs['pl_id'][0]
-        return self._extract_playlist(playlist_id, item_kind=playlist_kind)
+    _PAGE_TEMPLATE = 'https://rutube.ru/api/playlist/custom/%s/videos?page=%s&format=json'
 
 
 class RutubeChannelIE(RutubePlaylistBaseIE):
     IE_NAME = 'rutube:channel'
     IE_DESC = 'Rutube channel'
-    _VALID_URL = r'https?://rutube\.ru/channel/(?P<id>\d+)/videos'
+    _VALID_URL = r'https?://rutube\.ru/(?:channel/(?P<id>\d+)|u/(?P<slug>\w+))(?:/(?P<section>videos|shorts|playlists))?'
     _TESTS = [{
         'url': 'https://rutube.ru/channel/639184/videos/',
         'info_dict': {
-            'id': '639184',
+            'id': '639184_videos',
+        },
+        'playlist_mincount': 129,
+    }, {
+        'url': 'https://rutube.ru/channel/25902603/shorts/',
+        'info_dict': {
+            'id': '25902603_shorts',
         },
-        'playlist_mincount': 133,
+        'playlist_mincount': 277,
+    }, {
+        'url': 'https://rutube.ru/channel/25902603/',
+        'info_dict': {
+            'id': '25902603',
+        },
+        'playlist_mincount': 406,
+    }, {
+        'url': 'https://rutube.ru/u/rutube/videos/',
+        'info_dict': {
+            'id': '23704195_videos',
+        },
+        'playlist_mincount': 113,
     }]
 
-    _PAGE_TEMPLATE = 'http://rutube.ru/api/video/person/%s/?page=%s&format=json'
+    _PAGE_TEMPLATE = 'https://rutube.ru/api/video/person/%s/?page=%s&format=json&origin__type=%s'
+
+    def _next_page_url(self, page_num, playlist_id, section):
+        origin_type = {
+            'videos': 'rtb,rst,ifrm,rspa',
+            'shorts': 'rshorts',
+            None: '',
+        }.get(section)
+        return self._PAGE_TEMPLATE % (playlist_id, page_num, origin_type)
+
+    def _real_extract(self, url):
+        playlist_id, slug, section = self._match_valid_url(url).group('id', 'slug', 'section')
+        if section == 'playlists':
+            raise UnsupportedError(url)
+        if slug:
+            webpage = self._download_webpage(url, slug)
+            redux_state = self._search_json(
+                r'window\.reduxState\s*=', webpage, 'redux state', slug, transform_source=js_to_json)
+            playlist_id = traverse_obj(redux_state, (
+                'api', 'queries', lambda k, _: k.startswith('channelIdBySlug'),
+                'data', 'channel_id', {int}, {str_or_none}, any))
+        playlist = self._extract_playlist(playlist_id, section=section)
+        if section:
+            playlist['id'] = f'{playlist_id}_{section}'
+        return playlist


Test patch: