Repository: yt-dlp/yt-dlp

Issue:
Download single video from tweet with multiple videos
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I remove or skip any mandatory\* field

### Checklist

- [X] I'm requesting a site-specific feature
- [X] I've verified that I'm running yt-dlp version **2022.11.11** ([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 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

_No response_

### Example URLs

https://twitter.com/CTVJLaidlaw/status/1600649710662213632
VS:

https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/1
AND
https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2



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

People can now put multiple videos into a tweet  
When I point to the URL of a tweet, yt-dlp downloads both videos correctly.  

In a browser, when you click on the first video it opens in the twitter video player with the URL: 
https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/1
..and when you click on the second video it opens in the twitter video player with the URL: 
https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2

However if you try either or these /video/ URLs yt-dlp downloads BOTH videos (the entire playlist?)

My Site Feature Request is for
https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/1
to ONLY download the first video @
https://video.twimg.com/ext_tw_video/1600649511827038209/pu/vid/720x1280/Gmnz36SAOTrDTzrK.mp4?tag=12

and for 
https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2
to ONLY download the second video @
https://video.twimg.com/ext_tw_video/1600649511827013632/pu/vid/720x1280/0B2s_vOTOwfpz4m8.mp4?tag=12


hope that makes sense....


### 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] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
>yt-dlp.py -vU "https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2"
[debug] Command-line config: ['-vU', 'https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2']
[debug] Encodings: locale cp1252, fs utf-8, pref cp1252, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2022.11.11 [8b64402] (zip)
[debug] Python 3.10.5 (CPython AMD64 64bit) - Windows-10-10.0.14393-SP0 (OpenSSL 1.1.1n  15 Mar 2022)
[debug] exe versions: ffmpeg 4.3.2-2021-02-02-full_build-www.gyan.dev, ffprobe 4.3.2-2021-02-02-full_build-www.gyan.dev, rtmpdump 2.4
[debug] Optional libraries: sqlite3-2.6.0
[debug] Proxy map: {}
[debug] Loaded 1723 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: 2022.11.11, Current version: 2022.11.11
yt-dlp is up to date (2022.11.11)
[debug] [twitter] Extracting URL: https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2
[twitter] 1600649710662213632: Downloading guest token
[twitter] 1600649710662213632: Downloading JSON metadata
[debug] [twitter] Extracting from video info: 1600649511827038209
[twitter] 1600649710662213632: Downloading m3u8 information
[debug] Sort order given by extractor: res, br, size, proto
[debug] Formats sorted by: hasvid, ie_pref, res, tbr, vbr, abr, filesize, fs_approx, proto, lang, quality, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, asr, vext, aext, hasaud, source, id
[debug] [twitter] Extracting from video info: 1600649511827013632
[twitter] 1600649710662213632: Downloading m3u8 information
[debug] Sort order given by extractor: res, br, size, proto
[debug] Formats sorted by: hasvid, ie_pref, res, tbr, vbr, abr, filesize, fs_approx, proto, lang, quality, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, asr, vext, aext, hasaud, source, id
[download] Downloading playlist: Jocelyn Laidlaw - How Kirstie Alley's tragic death inspired me to share more about my cancer diagnosis. #colorectalcancer #cancerjourney #imnotaquitter
[twitter] Playlist Jocelyn Laidlaw - How Kirstie Alley's tragic death inspired me to share more about my cancer diagnosis. #colorectalcancer #cancerjourney #imnotaquitter: Downloading 2 videos of 2
[download] Downloading video 1 of 2
[debug] Default format spec: bestvideo*+bestaudio/best
[info] 1600649511827038209: Downloading 1 format(s): http-2176
[debug] Invoking http downloader on "https://video.twimg.com/ext_tw_video/1600649511827038209/pu/vid/720x1280/Gmnz36SAOTrDTzrK.mp4?tag=12"
[debug] File locking is not supported. Proceeding without locking
[download] Destination: Jocelyn Laidlaw - How Kirstie Alley's tragic death inspired me to share more about my cancer diagnosis. #colorectalcancer #cancerjourney #imnotaquitter #1 [1600649511827038209].mp4
[download] 100% of   15.62MiB in 00:00:03 at 4.15MiB/s
[download] Downloading video 2 of 2
[debug] Default format spec: bestvideo*+bestaudio/best
[info] 1600649511827013632: Downloading 1 format(s): http-2176
[debug] Invoking http downloader on "https://video.twimg.com/ext_tw_video/1600649511827013632/pu/vid/720x1280/0B2s_vOTOwfpz4m8.mp4?tag=12"
[download] Destination: Jocelyn Laidlaw - How Kirstie Alley's tragic death inspired me to share more about my cancer diagnosis. #colorectalcancer #cancerjourney #imnotaquitter #2 [1600649511827013632].mp4
[download] 100% of   14.21MiB in 00:00:03 at 4.56MiB/s
[download] Finished downloading playlist: Jocelyn Laidlaw - How Kirstie Alley's tragic death inspired me to share more about my cancer diagnosis. #colorectalcancer #cancerjourney #imnotaquitter
```



Discussion:
> In a browser, when you click on the first video it opens in the twitter video player with the URL: [twitter.com/CTVJLaidlaw/status/1600649710662213632/video/1](https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/1) ..and when you click on the second video it opens in the twitter video player with the URL: [twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2](https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2)

It doesn't work both ways though. If you open these links in browser, it still takes you to the tweet's main page and does not open the correct video. So other users could expect yt-dlp to download both videos. To avoid confusion in this case, we'll have to make such URLs obey `--no-playlist`

Resolving patch:
diff --git a/yt_dlp/extractor/twitter.py b/yt_dlp/extractor/twitter.py
--- a/yt_dlp/extractor/twitter.py
+++ b/yt_dlp/extractor/twitter.py
@@ -293,7 +293,7 @@ def _real_extract(self, url):
 
 class TwitterIE(TwitterBaseIE):
     IE_NAME = 'twitter'
-    _VALID_URL = TwitterBaseIE._BASE_REGEX + r'(?:(?:i/web|[^/]+)/status|statuses)/(?P<id>\d+)'
+    _VALID_URL = TwitterBaseIE._BASE_REGEX + r'(?:(?:i/web|[^/]+)/status|statuses)/(?P<id>\d+)(?:/video/(?P<index>\d+))?'
 
     _TESTS = [{
         'url': 'https://twitter.com/freethenipple/status/643211948184596480',
@@ -336,7 +336,7 @@ class TwitterIE(TwitterBaseIE):
             'id': '665052190608723968',
             'display_id': '665052190608723968',
             'ext': 'mp4',
-            'title': 'md5:55fef1d5b811944f1550e91b44abb82e',
+            'title': 'md5:e99588f17b3dd0503814ffb560e64731',
             'description': 'A new beginning is coming December 18. Watch the official 60 second #TV spot for #StarWars: #TheForceAwakens. https://t.co/OkSqT2fjWJ',
             'uploader_id': 'starwars',
             'uploader': r're:Star Wars.*',
@@ -648,7 +648,7 @@ class TwitterIE(TwitterBaseIE):
             'uploader_url': 'https://twitter.com/Rizdraws',
             'upload_date': '20220928',
             'timestamp': 1664391723,
-            'thumbnail': 're:^https?://.*\\.jpg',
+            'thumbnail': r're:^https?://.+\.jpg',
             'like_count': int,
             'repost_count': int,
             'comment_count': int,
@@ -727,6 +727,48 @@ class TwitterIE(TwitterBaseIE):
         },
         'add_ie': ['TwitterSpaces'],
         'params': {'skip_download': 'm3u8'},
+    }, {
+        # URL specifies video number but --yes-playlist
+        'url': 'https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/1',
+        'playlist_mincount': 2,
+        'info_dict': {
+            'id': '1600649710662213632',
+            'title': 'md5:be05989b0722e114103ed3851a0ffae2',
+            'timestamp': 1670459604.0,
+            'description': 'md5:591c19ce66fadc2359725d5cd0d1052c',
+            'comment_count': int,
+            'uploader_id': 'CTVJLaidlaw',
+            'repost_count': int,
+            'tags': ['colorectalcancer', 'cancerjourney', 'imnotaquitter'],
+            'upload_date': '20221208',
+            'age_limit': 0,
+            'uploader': 'Jocelyn Laidlaw',
+            'uploader_url': 'https://twitter.com/CTVJLaidlaw',
+            'like_count': int,
+        },
+    }, {
+        # URL specifies video number and --no-playlist
+        'url': 'https://twitter.com/CTVJLaidlaw/status/1600649710662213632/video/2',
+        'info_dict': {
+            'id': '1600649511827013632',
+            'ext': 'mp4',
+            'title': 'md5:be05989b0722e114103ed3851a0ffae2',
+            'thumbnail': r're:^https?://.+\.jpg',
+            'timestamp': 1670459604.0,
+            'uploader_id': 'CTVJLaidlaw',
+            'uploader': 'Jocelyn Laidlaw',
+            'repost_count': int,
+            'comment_count': int,
+            'tags': ['colorectalcancer', 'cancerjourney', 'imnotaquitter'],
+            'duration': 102.226,
+            'uploader_url': 'https://twitter.com/CTVJLaidlaw',
+            'display_id': '1600649710662213632',
+            'like_count': int,
+            'description': 'md5:591c19ce66fadc2359725d5cd0d1052c',
+            'upload_date': '20221208',
+            'age_limit': 0,
+        },
+        'params': {'noplaylist': True},
     }, {
         # onion route
         'url': 'https://twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion/TwitterBlue/status/1484226494708662273',
@@ -828,7 +870,7 @@ def _build_graphql_query(self, media_id):
         }
 
     def _real_extract(self, url):
-        twid = self._match_id(url)
+        twid, selected_index = self._match_valid_url(url).group('id', 'index')
         if self.is_logged_in or self._configuration_arg('force_graphql'):
             self.write_debug(f'Using GraphQL API (Auth = {self.is_logged_in})')
             result = self._call_graphql_api('zZXycP0V6H7m-2r0mOnFcA/TweetDetail', twid)
@@ -998,6 +1040,13 @@ def get_binding_value(k):
 
         entries[0]['_old_archive_ids'] = [make_archive_id(self, twid)]
 
+        if not self._yes_playlist(twid, selected_index, video_label='URL-specified video number'):
+            index = int(selected_index) - 1
+            if index >= len(entries):
+                raise ExtractorError(f'Video #{selected_index} is unavailable', expected=True)
+
+            return entries[index]
+
         if len(entries) == 1:
             return entries[0]
 


Test patch: