diff --git a/test/test_utils.py b/test/test_utils.py index 0fa8731473..9a62322f01 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -47,6 +47,7 @@ from youtube_dl.utils import ( js_to_json, get_filesystem_encoding, intlist_to_bytes, + args_to_str, ) @@ -361,5 +362,11 @@ class TestUtil(unittest.TestCase): intlist_to_bytes([0, 1, 127, 128, 255]), b'\x00\x01\x7f\x80\xff') + def test_args_to_str(self): + self.assertEqual( + args_to_str(['foo', 'ba/r', '-baz', '2 be', '']), + 'foo ba/r -baz \'2 be\' \'\'' + ) + if __name__ == '__main__': unittest.main() diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index fde026fbf6..bfa0c6d431 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -29,6 +29,7 @@ from .compat import ( compat_str, compat_urllib_error, compat_urllib_request, + shlex_quote, ) from .utils import ( escape_url, @@ -60,6 +61,7 @@ from .utils import ( write_string, YoutubeDLHandler, prepend_extension, + args_to_str, ) from .cache import Cache from .extractor import get_info_extractor, gen_extractors @@ -253,6 +255,22 @@ class YoutubeDL(object): self.print_debug_header() self.add_default_info_extractors() + def warn_if_short_id(self, argv): + # short YouTube ID starting with dash? + idxs = [ + i for i, a in enumerate(argv) + if re.match(r'^-[0-9A-Za-z_-]{10}$', a)] + if idxs: + correct_argv = ( + ['youtube-dl'] + + [a for i, a in enumerate(argv) if i not in idxs] + + ['--'] + [argv[i] for i in idxs] + ) + self.report_warning( + 'Long argument string detected. ' + 'Use -- to separate parameters and URLs, like this:\n%s\n' % + args_to_str(correct_argv)) + def add_info_extractor(self, ie): """Add an InfoExtractor object to the end of the list.""" self._ies.append(ie) @@ -1410,3 +1428,4 @@ class YoutubeDL(object): if encoding is None: encoding = preferredencoding() return encoding + diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index c1323b4f35..f519fae3e4 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -334,11 +334,12 @@ def _real_main(argv=None): # Maybe do nothing if (len(all_urls) < 1) and (opts.load_info_filename is None): - if not (opts.update_self or opts.rm_cachedir): - parser.error('you must provide at least one URL') - else: + if opts.update_self or opts.rm_cachedir: sys.exit() + ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv) + parser.error('you must provide at least one URL') + try: if opts.load_info_filename is not None: retcode = ydl.download_with_info_file(opts.load_info_filename) diff --git a/youtube_dl/compat.py b/youtube_dl/compat.py index 9d33a8ec5f..5492065341 100644 --- a/youtube_dl/compat.py +++ b/youtube_dl/compat.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import getpass import optparse import os +import re import subprocess import sys @@ -174,7 +175,10 @@ try: from shlex import quote as shlex_quote except ImportError: # Python < 3.3 def shlex_quote(s): - return "'" + s.replace("'", "'\"'\"'") + "'" + if re.match(r'^[-_\w./]+$', s): + return s + else: + return "'" + s.replace("'", "'\"'\"'") + "'" def compat_ord(c): diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 5be7cf9920..c3d8bf8e9d 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -41,6 +41,7 @@ from .compat import ( compat_urllib_parse_urlparse, compat_urllib_request, compat_urlparse, + shlex_quote, ) @@ -1433,3 +1434,8 @@ def ytdl_is_updateable(): from zipimport import zipimporter return isinstance(globals().get('__loader__'), zipimporter) or hasattr(sys, 'frozen') + + +def args_to_str(args): + # Get a short string representation for a subprocess command + return ' '.join(shlex_quote(a) for a in args)