Repository: yt-dlp/yt-dlp
Pull request #3168 (2022-03-23 04:17:05)

[youtube:tab] Use shorts video url
## Please follow the guide below

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes [ ] relevant to your *pull request* (like that [x])
- Use *Preview* tab to see how your *pull request* will actually look like

---

### 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
- [X] Checked the code with [flake8](https://pypi.python.org/pypi/flake8)

### 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 one of the following options:
- [X] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
- [ ] I am not the original author of this code but it is in public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)

### What is the purpose of your *pull request*?
- [ ] Bug fix
- [X] Improvement
- [ ] New extractor
- [ ] New feature

---

### Description of your *pull request* and other information

Explanation of your *pull request* in arbitrary form goes here. Please make sure the description explains the purpose and effect of your *pull request* and is worded well enough to be understood. Provide as much context and examples as possible.

Useful for allowing filtering of shorts by the shorts url.

Closes https://github.com/yt-dlp/yt-dlp/issues/3165

coletdjnz: ```
yt-dlp-dev https://www.youtube.com/playlist\?list\=PL7DTipO5UGUvaC47sIDxj5XVta12unWE9
[youtube:tab] PL7DTipO5UGUvaC47sIDxj5XVta12unWE9: Downloading webpage
[youtube:tab] PL7DTipO5UGUvaC47sIDxj5XVta12unWE9: Downloading API JSON with unavailable videos
[download] Downloading playlist: Divided
[youtube:tab] playlist Divided: Downloading 6 videos
[download] Downloading video 1 of 6
ERROR: no suitable InfoExtractor for URL https://www.youtube.com/watch?v=8MVIp48XBDY&list=PL7DTipO5UGUvaC47sIDxj5XVta12unWE9&index=1
[download] Downloading video 2 of 6
ERROR: no suitable InfoExtractor for URL https://www.youtube.com/watch?v=VJOm8EwuJ94&list=PL7DTipO5UGUvaC47sIDxj5XVta12unWE9&index=2
[download] Downloading video 3 of 6
ERROR: no suitable InfoExtractor for URL https://www.youtube.com/watch?v=F4kh1skvy5A&list=PL7DTipO5UGUvaC47sIDxj5XVta12unWE9&index=3
[download] Downloading video 4 of 6
ERROR: no suitable InfoExtractor for URL https://www.youtube.com/watch?v=AWG-dcTbKG0&list=PL7DTipO5UGUvaC47sIDxj5XVta12unWE9&index=4
[download] Downloading video 5 of 6
ERROR: no suitable InfoExtractor for URL https://www.youtube.com/watch?v=pK2SbuBb4RE&list=PL7DTipO5UGUvaC47sIDxj5XVta12unWE9&index=5
[download] Downloading video 6 of 6
ERROR: no suitable InfoExtractor for URL https://www.youtube.com/watch?v=BEbJMW0erzQ&list=PL7DTipO5UGUvaC47sIDxj5XVta12unWE9&index=6
[download] Finished downloading playlist: Divided
```

Patch:
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index 5771fbcf7..478bdacca 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -1099,10 +1099,11 @@ def prepare_outtmpl(self, outtmpl, info_dict, sanitize=False):
             (?P<fields>{field})
             (?P<maths>(?:{math_op}{math_field})*)
             (?:>(?P<strf_format>.+?))?
-            (?P<alternate>(?<!\\),[^|&)]+)?
-            (?:&(?P<replacement>.*?))?
-            (?:\|(?P<default>.*?))?
-            $'''.format(field=FIELD_RE, math_op=MATH_OPERATORS_RE, math_field=MATH_FIELD_RE))
+            (?P<remaining>
+                (?P<alternate>(?<!\\),[^|&)]+)?
+                (?:&(?P<replacement>.*?))?
+                (?:\|(?P<default>.*?))?
+            )$'''.format(field=FIELD_RE, math_op=MATH_OPERATORS_RE, math_field=MATH_FIELD_RE))
 
         def _traverse_infodict(k):
             k = k.split('.')
@@ -1173,7 +1174,7 @@ def create_key(outer_mobj):
                 value = get_value(mobj)
                 replacement = mobj['replacement']
                 if value is None and mobj['alternate']:
-                    mobj = re.match(INTERNAL_FORMAT_RE, mobj['alternate'][1:])
+                    mobj = re.match(INTERNAL_FORMAT_RE, mobj['remaining'][1:])
                 else:
                     break
 
diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py
index 8ee688798..0726e27b4 100644
--- a/yt_dlp/extractor/youtube.py
+++ b/yt_dlp/extractor/youtube.py
@@ -818,12 +818,17 @@ def _extract_video(self, renderer):
             renderer, ('thumbnailOverlays', ..., 'thumbnailOverlayTimeStatusRenderer', 'style'), get_all=False, expected_type=str)
         badges = self._extract_badges(renderer)
         thumbnails = self._extract_thumbnails(renderer, 'thumbnail')
+        navigation_url = urljoin('https://www.youtube.com/', traverse_obj(
+            renderer, ('navigationEndpoint', 'commandMetadata', 'webCommandMetadata', 'url'), expected_type=str))
+        url = f'https://www.youtube.com/watch?v={video_id}'
+        if overlay_style == 'SHORTS' or (navigation_url and '/shorts/' in navigation_url):
+            url = f'https://www.youtube.com/shorts/{video_id}'
 
         return {
             '_type': 'url',
             'ie_key': YoutubeIE.ie_key(),
             'id': video_id,
-            'url': f'https://www.youtube.com/watch?v={video_id}',
+            'url': url,
             'title': title,
             'description': description,
             'duration': duration,
diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py
index 234ddeff0..a5ed6d184 100644
--- a/yt_dlp/postprocessor/ffmpeg.py
+++ b/yt_dlp/postprocessor/ffmpeg.py
@@ -553,9 +553,9 @@ def _target_ext(self, source_ext):
 
     @staticmethod
     def _options(target_ext):
+        yield from FFmpegPostProcessor.stream_copy_opts(False)
         if target_ext == 'avi':
-            return ['-c:v', 'libxvid', '-vtag', 'XVID']
-        return []
+            yield from ('-c:v', 'libxvid', '-vtag', 'XVID')
 
     @PostProcessor._restrict_to(images=False)
     def run(self, info):
@@ -1129,6 +1129,8 @@ def __init__(self, downloader, only_multi_video=False):
         super().__init__(downloader)
 
     def concat_files(self, in_files, out_file):
+        if not self._downloader._ensure_dir_exists(out_file):
+            return
         if len(in_files) == 1:
             if os.path.realpath(in_files[0]) != os.path.realpath(out_file):
                 self.to_screen(f'Moving "{in_files[0]}" to "{out_file}"')


Test patch:
diff --git a/test/test_YoutubeDL.py b/test/test_YoutubeDL.py
index f9b40501d..c9108c5b6 100644
--- a/test/test_YoutubeDL.py
+++ b/test/test_YoutubeDL.py
@@ -818,6 +818,8 @@ def expect_same_infodict(out):
         test('%(id&foo)s.bar', 'foo.bar')
         test('%(title&foo)s.bar', 'NA.bar')
         test('%(title&foo|baz)s.bar', 'baz.bar')
+        test('%(x,id&foo|baz)s.bar', 'foo.bar')
+        test('%(x,title&foo|baz)s.bar', 'baz.bar')
 
         # Laziness
         def gen():