Repository: yt-dlp/yt-dlp

Issue:
[Crunchyroll] Download Metadata Without Throwing Premium Error
### 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 requesting a site-specific feature
- [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 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

US

### Example URLs

https://www.crunchyroll.com/watch/G7PU4118Q/the-real-deal-is-convincing-as-hell
https://www.crunchyroll.com/watch/GD9UVMZZK/the-kaiju-who-defeats-kaiju

### Provide a description that is worded well enough to be understood

Crunchyroll thumbnails should always be downloadable regardless of whether the content is premium or not, since you don't need to be logged in to see the thumbnail on the website. Basic authentication should work to extract the subtitles regardless of whether the video endpoints work or not (if it throws a HTTP Error 403: Forbidden).

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [ ] 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
yt-dlp --write-thumbnail https://www.crunchyroll.com/watch/GD9UVMZZK/the-kaiju-who-defeats-kaiju                                      
[crunchyroll] Extracting URL: https://www.crunchyroll.com/watch/GD9UVMZZK/the-kaiju-who-defeats-kaiju
Extracting cookies from chrome
Extracted 485 cookies from chrome
[crunchyroll] Authenticating with grant_type=etp_rt_cookie
[crunchyroll] GD9UVMZZK: Downloading object info JSON (cms)
[crunchyroll] GD9UVMZZK: Downloading stream info
ERROR: [crunchyroll] GD9UVMZZK: Unable to download JSON metadata: HTTP Error 403: Forbidden (caused by <HTTPError 403: Forbidden>)

yt-dlp --write-thumbnail --write-sub --sub-langs en-US https://www.crunchyroll.com/watch/G7PU4118Q/the-real-deal-is-convincing-as-hell
[crunchyroll] Extracting URL: https://www.crunchyroll.com/watch/G7PU4118Q/the-real-deal-is-convincing-as-hell
Extracting cookies from chrome
Extracted 485 cookies from chrome
[crunchyroll] Authenticating with grant_type=etp_rt_cookie
[crunchyroll] G7PU4118Q: Downloading object info JSON (cms)
[crunchyroll] G7PU4118Q: Downloading stream info
ERROR: [crunchyroll] G7PU4118Q: Unable to download JSON metadata: HTTP Error 403: Forbidden (caused by <HTTPError 403: Forbidden>)
```



Discussion:
yeah we could return thumbnails and other metadata; however, we need to extract the subs from the stream info response now, so subs for premium content wouldn't be possible w/o premium creds

we could do something like this
```diff
diff --git a/yt_dlp/extractor/crunchyroll.py b/yt_dlp/extractor/crunchyroll.py
index 20cc8583c..2338c8ac8 100644
--- a/yt_dlp/extractor/crunchyroll.py
+++ b/yt_dlp/extractor/crunchyroll.py
@@ -391,10 +391,11 @@ def entries():
         if not self._IS_PREMIUM and traverse_obj(response, (f'{object_type}_metadata', 'is_premium_only')):
             message = f'This {object_type} is for premium members only'
             if CrunchyrollBaseIE._REFRESH_TOKEN:
-                raise ExtractorError(message, expected=True)
-            self.raise_login_required(message, method='password')
-
-        result['formats'], result['subtitles'] = self._extract_stream(internal_id)
+                self.raise_no_formats(message, expected=True, video_id=internal_id)
+            else:
+                self.raise_login_required(message, method='password', metadata_available=True)
+        else:
+            result['formats'], result['subtitles'] = self._extract_stream(internal_id)
 
         result['chapters'] = self._extract_chapters(internal_id)
 
@@ -580,14 +581,16 @@ def _real_extract(self, url):
         if not response:
             raise ExtractorError(f'No video with id {internal_id} could be found (possibly region locked?)', expected=True)
 
+        result = self._transform_music_response(response)
+
         if not self._IS_PREMIUM and response.get('isPremiumOnly'):
             message = f'This {response.get("type") or "media"} is for premium members only'
             if CrunchyrollBaseIE._REFRESH_TOKEN:
-                raise ExtractorError(message, expected=True)
-            self.raise_login_required(message, method='password')
-
-        result = self._transform_music_response(response)
-        result['formats'], _ = self._extract_stream(f'music/{internal_id}', internal_id)
+                self.raise_no_formats(message, expected=True, video_id=internal_id)
+            else:
+                self.raise_login_required(message, method='password', metadata_available=True)
+        else:
+            result['formats'], _ = self._extract_stream(f'music/{internal_id}', internal_id)
 
         return result

```
> so subs for premium content wouldn't be possible w/o premium creds

Yeah, that was meant more for the Thumbnail. I have Crunchyroll Premium so would I be able to download just the subtitles without it throwing me a 403 Error?  I've edited the issue and title to be more reflective of the problem.

Resolving patch:
diff --git a/yt_dlp/extractor/crunchyroll.py b/yt_dlp/extractor/crunchyroll.py
--- a/yt_dlp/extractor/crunchyroll.py
+++ b/yt_dlp/extractor/crunchyroll.py
@@ -394,10 +394,11 @@ def entries():
         if not self._IS_PREMIUM and traverse_obj(response, (f'{object_type}_metadata', 'is_premium_only')):
             message = f'This {object_type} is for premium members only'
             if CrunchyrollBaseIE._REFRESH_TOKEN:
-                raise ExtractorError(message, expected=True)
-            self.raise_login_required(message, method='password')
-
-        result['formats'], result['subtitles'] = self._extract_stream(internal_id)
+                self.raise_no_formats(message, expected=True, video_id=internal_id)
+            else:
+                self.raise_login_required(message, method='password', metadata_available=True)
+        else:
+            result['formats'], result['subtitles'] = self._extract_stream(internal_id)
 
         result['chapters'] = self._extract_chapters(internal_id)
 
@@ -583,14 +584,16 @@ def _real_extract(self, url):
         if not response:
             raise ExtractorError(f'No video with id {internal_id} could be found (possibly region locked?)', expected=True)
 
+        result = self._transform_music_response(response)
+
         if not self._IS_PREMIUM and response.get('isPremiumOnly'):
             message = f'This {response.get("type") or "media"} is for premium members only'
             if CrunchyrollBaseIE._REFRESH_TOKEN:
-                raise ExtractorError(message, expected=True)
-            self.raise_login_required(message, method='password')
-
-        result = self._transform_music_response(response)
-        result['formats'], _ = self._extract_stream(f'music/{internal_id}', internal_id)
+                self.raise_no_formats(message, expected=True, video_id=internal_id)
+            else:
+                self.raise_login_required(message, method='password', metadata_available=True)
+        else:
+            result['formats'], _ = self._extract_stream(f'music/{internal_id}', internal_id)
 
         return result
 


Test patch: