Repository: yt-dlp/yt-dlp (python)

Issue:
"Unable to extract player;" error on certain OKRU videos (Odnoklassniki)
### 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), [the FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ), and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%3Aissue%20-label%3Aspam%20%20) for similar issues **including closed ones**. DO NOT post duplicates
- [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

Australia

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

Some OKRU videos produce this error, while others behave normally. Here are URLs which fail to download and give the error reported in the post title:

https://ok.ru/video/10003120720431
https://ok.ru/video/10003121244719
https://ok.ru/video/10002989648431
https://ok.ru/video/10009477122607
https://ok.ru/video/10009477319215

Here is one that works fine (most seem ok):

https://ok.ru/video/10029569477167


### 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://ok.ru/video/10009477122607', '-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 nightly@2025.05.30.233011 from yt-dlp/yt-dlp-nightly-builds [3fe72e9ee] (zip)
[debug] Python 3.9.6 (CPython x86_64 64bit) - macOS-10.16-x86_64-i386-64bit (OpenSSL 1.1.1k  25 Mar 2021)
[debug] exe versions: ffmpeg 4.4-tessus (setts), ffprobe 4.4-tessus, rtmpdump 2.4
[debug] Optional libraries: Cryptodome-3.17, brotli-1.0.9, certifi-2022.12.07, mutagen-1.46.0, sqlite3-3.35.5, websockets-10.4 (unsupported)
[debug] Proxy map: {}
[debug] Request Handlers: urllib
[debug] Plugin directories: none
[debug] Loaded 1860 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp-nightly-builds/releases/latest
Latest version: nightly@2025.05.30.233011 from yt-dlp/yt-dlp-nightly-builds
yt-dlp is up to date (nightly@2025.05.30.233011 from yt-dlp/yt-dlp-nightly-builds)
[Odnoklassniki] Extracting URL: https://ok.ru/video/10009477122607
[Odnoklassniki] 10009477122607: Downloading desktop webpage
[Odnoklassniki] 10009477122607: Downloading mobile webpage
ERROR: [Odnoklassniki] 10009477122607: Unable to extract player; 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
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 748, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/odnoklassniki.py", line 252, in _real_extract
    raise e
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/odnoklassniki.py", line 246, in _real_extract
    return self._extract_desktop(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/odnoklassniki.py", line 278, in _extract_desktop
    unescapeHTML(self._search_regex(
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 1339, in _search_regex
    raise RegexNotFoundError(f'Unable to extract {_name}')
```

Pull request:
[ie/odnoklassniki] Detect and raise when login is required
Closes #13360


<details open><summary>Template</summary> <!-- OPEN is intentional -->

### Before submitting a *pull request* make sure you have:
- [x] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [x] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check those that apply and remove the others:
- [x] I am the original author of the code in this PR, and I am willing to release it under [Unlicense](http://unlicense.org/)

### What is the purpose of your *pull request*? Check those that apply and remove the others:
- [x] Fix or improvement to an extractor (Make sure to add/update tests)

</details>


Patch:
diff --git a/yt_dlp/extractor/adobepass.py b/yt_dlp/extractor/adobepass.py
index 91c40b32e..8c2d9d934 100644
--- a/yt_dlp/extractor/adobepass.py
+++ b/yt_dlp/extractor/adobepass.py
@@ -1574,18 +1574,29 @@ def extract_redirect_url(html, url=None, fatal=False):
                             post_form(mvpd_confirm_page_res, 'Confirming Login')
                 elif mso_id == 'Philo':
                     # Philo has very unique authentication method
-                    self._download_webpage(
-                        'https://idp.philo.com/auth/init/login_code', video_id, 'Requesting auth code', data=urlencode_postdata({
+                    self._request_webpage(
+                        'https://idp.philo.com/auth/init/login_code', video_id,
+                        'Requesting Philo auth code', data=json.dumps({
                             'ident': username,
                             'device': 'web',
                             'send_confirm_link': False,
                             'send_token': True,
-                        }))
+                            'device_ident': f'web-{uuid.uuid4().hex}',
+                            'include_login_link': True,
+                        }).encode(), headers={
+                            'Content-Type': 'application/json',
+                            'Accept': 'application/json',
+                        })
+
                     philo_code = getpass.getpass('Type auth code you have received [Return]: ')
-                    self._download_webpage(
-                        'https://idp.philo.com/auth/update/login_code', video_id, 'Submitting token', data=urlencode_postdata({
-                            'token': philo_code,
-                        }))
+                    self._request_webpage(
+                        'https://idp.philo.com/auth/update/login_code', video_id,
+                        'Submitting token', data=json.dumps({'token': philo_code}).encode(),
+                        headers={
+                            'Content-Type': 'application/json',
+                            'Accept': 'application/json',
+                        })
+
                     mvpd_confirm_page_res = self._download_webpage_handle('https://idp.philo.com/idp/submit', video_id, 'Confirming Philo Login')
                     post_form(mvpd_confirm_page_res, 'Confirming Login')
                 elif mso_id == 'Verizon':
diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py
index d5607296d..1174bd4f5 100644
--- a/yt_dlp/extractor/common.py
+++ b/yt_dlp/extractor/common.py
@@ -1675,9 +1675,9 @@ def extract_video_object(e):
                 'ext': mimetype2ext(e.get('encodingFormat')),
                 'title': unescapeHTML(e.get('name')),
                 'description': unescapeHTML(e.get('description')),
-                'thumbnails': [{'url': unescapeHTML(url)}
-                               for url in variadic(traverse_obj(e, 'thumbnailUrl', 'thumbnailURL'))
-                               if url_or_none(url)],
+                'thumbnails': traverse_obj(e, (('thumbnailUrl', 'thumbnailURL', 'thumbnail_url'), (None, ...), {
+                    'url': ({str}, {unescapeHTML}, {self._proto_relative_url}, {url_or_none}),
+                })),
                 'duration': parse_duration(e.get('duration')),
                 'timestamp': unified_timestamp(e.get('uploadDate')),
                 # author can be an instance of 'Organization' or 'Person' types.
diff --git a/yt_dlp/extractor/odnoklassniki.py b/yt_dlp/extractor/odnoklassniki.py
index d27d1c3f0..18eba42e6 100644
--- a/yt_dlp/extractor/odnoklassniki.py
+++ b/yt_dlp/extractor/odnoklassniki.py
@@ -273,6 +273,8 @@ def _extract_desktop(self, url):
             return self._extract_desktop(smuggle_url(url, {'referrer': 'https://boosty.to'}))
         elif error:
             raise ExtractorError(error, expected=True)
+        elif '>Access to this video is restricted</div>' in webpage:
+            self.raise_login_required()
 
         player = self._parse_json(
             unescapeHTML(self._search_regex(
@@ -429,7 +431,7 @@ def _extract_mobile(self, url):
         video_id = self._match_id(url)
 
         webpage = self._download_webpage(
-            f'http://m.ok.ru/video/{video_id}', video_id,
+            f'https://m.ok.ru/video/{video_id}', video_id,
             note='Downloading mobile webpage')
 
         error = self._search_regex(


Test patch:
diff --git a/test/test_InfoExtractor.py b/test/test_InfoExtractor.py
index c6ff6209a..bc89b2955 100644
--- a/test/test_InfoExtractor.py
+++ b/test/test_InfoExtractor.py
@@ -314,6 +314,20 @@ def test_search_json_ld_realworld(self):
                 },
                 {},
             ),
+            (
+                # test thumbnail_url key without URL scheme
+                r'''
+<script type="application/ld+json">
+{
+"@context": "https://schema.org",
+"@type": "VideoObject",
+"thumbnail_url": "//www.nobelprize.org/images/12693-landscape-medium-gallery.jpg"
+}</script>''',
+                {
+                    'thumbnails': [{'url': 'https://www.nobelprize.org/images/12693-landscape-medium-gallery.jpg'}],
+                },
+                {},
+            ),
         ]
         for html, expected_dict, search_json_ld_kwargs in _TESTS:
             expect_dict(