Repository: yt-dlp/yt-dlp

Issue:
Site request: Rotten Tomatoes (rottentomatoes.com)
### 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 a new site support request
- [X] I've verified that I'm running yt-dlp version **2023.03.04** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [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 none of provided URLs [violate any copyrights](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
- [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)
- [ ] 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

### Example URLs

* movie: https://www.rottentomatoes.com/m/santa_claus_conquers_the_martians
* television series: https://www.rottentomatoes.com/tv/the_beverly_hillbillies

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

The movie (`m`) and television (`tv`) URLs provided include galleries of videos, including trailers, clips, interviews, promotions, etc. There are also `celebrity` pages, but they don't contain videos to my knowledge.

There doesn't appear to be any easy way to copy the link of a single video using the frontend, but each video does have a unique ID.

Each video's info is inside `VideosCarousel__item`–class divs, with some generally self-explanatory divs inside that, containing video IDs, a thumbnail, a duration string, and a title.

The video interface uses JW Player. There are several formats for each video. They stream over HLS from "[link.theplatform.com](https://link.theplatform.com)".

### 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 https://www.rottentomatoes.com/m/santa_claus_conquers_the_martians -vU
[debug] Command-line config: ['https://www.rottentomatoes.com/m/santa_claus_conquers_the_martians', '-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@2023.03.04 [392389b7d] (pip)
[debug] Python 3.8.16 (CPython x86_64 64bit) - CYGWIN_NT-10.0-19044-3.4.6-1.x86_64-x86_64-64bit-WindowsPE (OpenSSL 1.1.1t  7 Feb 2023)
[debug] exe versions: ffmpeg 5.1.2-essentials_build-www.gyan.dev (setts), ffprobe 5.1.2-essentials_build-www.gyan.dev
[debug] Optional libraries: Cryptodome-3.17, brotli-1.0.9, certifi-2021.10.08, mutagen-1.45.1, sqlite3-2.6.0, websockets-11.0.1
[debug] Proxy map: {}
[debug] Loaded 1786 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Available version: stable@2023.03.04, Current version: stable@2023.03.04
yt-dlp is up to date (stable@2023.03.04)
[generic] Extracting URL: https://www.rottentomatoes.com/m/santa_claus_conquers_the_martians
[generic] santa_claus_conquers_the_martians: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] santa_claus_conquers_the_martians: Extracting information
[debug] Looking for embeds
ERROR: Unsupported URL: https://www.rottentomatoes.com/m/santa_claus_conquers_the_martians
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/yt_dlp/YoutubeDL.py", line 1518, in wrapper
    return func(self, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/yt_dlp/YoutubeDL.py", line 1594, in __extract_info
    ie_result = ie.extract(url)
  File "/usr/local/lib/python3.8/site-packages/yt_dlp/extractor/common.py", line 694, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/lib/python3.8/site-packages/yt_dlp/extractor/generic.py", line 2510, in _real_extract
    raise UnsupportedError(url)
yt_dlp.utils.UnsupportedError: Unsupported URL: https://www.rottentomatoes.com/m/santa_claus_conquers_the_martians
```



Discussion:
In the Santa page, find the JSON in this `<script>` element
```html
 <video-player-overlay-manager>
        <script id="heroVideos" type="application/json">...
</script>
</video-player-overlay-manager>
```
It looks like this:
```py
[
  {
    'file': 'https://link.theplatform.com/s/NGweTC/media/knQh_gt11TmR?formats=MPEG-DASH+widevine,M3U+appleHlsEncryption,M3U+none,MPEG-DASH+none,MPEG4,MP3',
    'type': 'hls',
    'description': '',
    'image': 'https://statcdn.fandango.com/MPX/image/NBCU_Fandango/532/579/thumb_B0463DF3-9AC4-4A23-87A1-37025CD7AA43.jpg',
    'isRedBand': false,
    'mediaid': '1819476035665',
    'mpxId': '1819476035665',
    'publicId': 'knQh_gt11TmR',
    'title': 'Santa Claus Conquers the Martians: Trailer 1',
    'default': false,
    'label': '0',
    'duration': '1:15',
    'durationInSeconds': '75.576',
    'emsMediaType': 'Movie',
    'emsId': 'fc709dda-6b8d-3f6e-bcf4-3a4a3faa7bb9',
    'publishDate': 'November 13, 2020',
    'overviewPageUrl': '/m/santa_claus_conquers_the_martians',
    'videoPageUrl': '/m/santa_claus_conquers_the_martians/trailers/knQh_gt11TmR',
    'videoType': 'TRAILER',
    'adobeDataLayer': {
      'content': {
        'id': 'rottentomatoes_1819476035665',
        'length': '75.576',
        'type': 'vod',
        'player_name': 'jw',
        'sdk_version': 'web: 6.51.0',
        'channel': 'movie',
        'originator': 'sinister cinema, something weird video',
        'name': 'santa claus conquers the martians: trailer 1',
        'rating': 'not adult',
        'stream_type': 'video'
      },
      'media_params': {
        'genre': 'holiday, sci-fi',
        'show_type': 1
      }
    },
    'comscore': {
      'labelmapping': 'c3=rottentomatoes.com, ns_st_st=Rotten Tomatoes, ns_st_pu=Sinister Cinema,Something Weird Video, ns_st_pr=Santa Claus Conquers the Martians, ns_st_sn=*null, ns_st_en=*null, ns_st_ge=HolidaySci-Fi, ns_st_ia=0, ns_st_ce=0, ns_st_ddt=1964, ns_st_tdt=1964'
    }
  }
]
```
The `[0].file` member can be extracted with yt-dlp (or yt-dl).

Resolving patch:
diff --git a/yt_dlp/extractor/rottentomatoes.py b/yt_dlp/extractor/rottentomatoes.py
--- a/yt_dlp/extractor/rottentomatoes.py
+++ b/yt_dlp/extractor/rottentomatoes.py
@@ -1,30 +1,80 @@
 from .common import InfoExtractor
-from .internetvideoarchive import InternetVideoArchiveIE
+from ..utils import (
+    ExtractorError,
+    clean_html,
+    float_or_none,
+    get_element_by_class,
+    join_nonempty,
+    traverse_obj,
+    url_or_none,
+)
 
 
 class RottenTomatoesIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/[^/]+/trailers/(?P<id>\d+)'
+    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/(?P<playlist>[^/]+)(?:/(?P<tr>trailers)(?:/(?P<id>\w+))?)?'
 
-    _TEST = {
+    _TESTS = [{
         'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
         'info_dict': {
             'id': '11028566',
             'ext': 'mp4',
             'title': 'Toy Story 3',
-            'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.',
+            'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.'
+        },
+        'skip': 'No longer available',
+    }, {
+        'url': 'https://www.rottentomatoes.com/m/toy_story_3/trailers/VycaVoBKhGuk',
+        'info_dict': {
+            'id': 'VycaVoBKhGuk',
+            'ext': 'mp4',
+            'title': 'Toy Story 3: Trailer 2',
+            'description': '',
             'thumbnail': r're:^https?://.*\.jpg$',
+            'duration': 149.941
+        },
+    }, {
+        'url': 'http://www.rottentomatoes.com/m/toy_story_3',
+        'info_dict': {
+            'id': 'toy_story_3',
+            'title': 'Toy Story 3',
+        },
+        'playlist_mincount': 4,
+    }, {
+        'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers',
+        'info_dict': {
+            'id': 'toy_story_3-trailers',
         },
-    }
+        'playlist_mincount': 5,
+    }]
+
+    def _extract_videos(self, data, display_id):
+        for video in traverse_obj(data, (lambda _, v: v['publicId'] and v['file'] and v['type'] == 'hls')):
+            yield {
+                'formats': self._extract_m3u8_formats(
+                    video['file'], display_id, 'mp4', m3u8_id='hls', fatal=False),
+                **traverse_obj(video, {
+                    'id': 'publicId',
+                    'title': 'title',
+                    'description': 'description',
+                    'duration': ('durationInSeconds', {float_or_none}),
+                    'thumbnail': ('image', {url_or_none}),
+                }),
+            }
 
     def _real_extract(self, url):
-        video_id = self._match_id(url)
-        webpage = self._download_webpage(url, video_id)
-        iva_id = self._search_regex(r'publishedid=(\d+)', webpage, 'internet video archive id')
+        playlist_id, trailers, video_id = self._match_valid_url(url).group('playlist', 'tr', 'id')
+        playlist_id = join_nonempty(playlist_id, trailers)
+        webpage = self._download_webpage(url, playlist_id)
+        data = self._search_json(
+            r'<script[^>]+\bid=["\'](?:heroV|v)ideos["\'][^>]*>', webpage,
+            'data', playlist_id, contains_pattern=r'\[{(?s:.+)}\]')
+
+        if video_id:
+            video_data = traverse_obj(data, lambda _, v: v['publicId'] == video_id)
+            if not video_data:
+                raise ExtractorError('Unable to extract video from webpage')
+            return next(self._extract_videos(video_data, video_id))
 
-        return {
-            '_type': 'url_transparent',
-            'url': 'http://video.internetvideoarchive.net/player/6/configuration.ashx?domain=www.videodetective.com&customerid=69249&playerid=641&publishedid=' + iva_id,
-            'ie_key': InternetVideoArchiveIE.ie_key(),
-            'id': video_id,
-            'title': self._og_search_title(webpage),
-        }
+        return self.playlist_result(
+            self._extract_videos(data, playlist_id), playlist_id,
+            clean_html(get_element_by_class('scoreboard__title', webpage)))


Test patch: