diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index a671d64504..e0cb1ef75f 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -172,6 +172,7 @@ class YoutubeDL(object): The following options are used by the post processors: prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available, otherwise prefer avconv. + exec_cmd: Arbitrary command to run after downloading """ params = None diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 7cf5de43f0..b156950536 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -552,8 +552,9 @@ def _hide_login_info(opts): help='Prefer avconv over ffmpeg for running the postprocessors (default)') postproc.add_option('--prefer-ffmpeg', action='store_true', dest='prefer_ffmpeg', help='Prefer ffmpeg over avconv for running the postprocessors') - postproc.add_option('--exec', metavar='', action='store', dest='execstring', - help='Execute a command on the file after downloading, similar to find\'s -exec syntax. Must be enclosed in quotes. Example: --exec \'adb push {} /sdcard/Music/ && rm {}\'' ) + postproc.add_option( + '--exec', metavar='CMD', dest='exec_cmd', + help='Execute a command on the file after downloading, similar to find\'s -exec syntax. Example: --exec \'adb push {} /sdcard/Music/ && rm {}\'' ) parser.add_option_group(general) parser.add_option_group(selection) @@ -834,7 +835,7 @@ def _real_main(argv=None): 'default_search': opts.default_search, 'youtube_include_dash_manifest': opts.youtube_include_dash_manifest, 'encoding': opts.encoding, - 'execstring': opts.execstring, + 'exec_cmd': opts.exec_cmd, } with YoutubeDL(ydl_opts) as ydl: @@ -861,8 +862,9 @@ def _real_main(argv=None): # Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way. # So if the user is able to remove the file before your postprocessor runs it might cause a few problems. - if opts.execstring: - ydl.add_post_processor(ExecAfterDownloadPP(verboseOutput=opts.verbose,commandString=opts.execstring)) + if opts.exec_cmd: + ydl.add_post_processor(ExecAfterDownloadPP( + verboseOutput=opts.verbose, exec_cmd=opts.exec_cmd)) # Update version if opts.update_self: diff --git a/youtube_dl/postprocessor/execafterdownload.py b/youtube_dl/postprocessor/execafterdownload.py index e6f3cdfd22..08419a3d4b 100644 --- a/youtube_dl/postprocessor/execafterdownload.py +++ b/youtube_dl/postprocessor/execafterdownload.py @@ -1,39 +1,31 @@ from __future__ import unicode_literals -from .common import PostProcessor -from ..utils import PostProcessingError + import subprocess -import shlex + +from .common import PostProcessor +from ..utils import ( + shlex_quote, + PostProcessingError, +) class ExecAfterDownloadPP(PostProcessor): - def __init__(self, downloader=None, verboseOutput=None, commandString=None): + def __init__(self, downloader=None, verboseOutput=None, exec_cmd=None): self.verboseOutput = verboseOutput - self.commandString = commandString + self.exec_cmd = exec_cmd def run(self, information): - self.targetFile = information['filepath'].replace('\'', '\'\\\'\'') # Replace single quotes with '\'' - self.commandList = shlex.split(self.commandString) - self.commandString = '' + cmd = self.exec_cmd + if not '{}' in cmd: + cmd += ' {}' - # Replace all instances of '{}' with the file name and convert argument list to single string. - for index, arg in enumerate(self.commandList): - if(arg == '{}'): - self.commandString += '\'' + self.targetFile + '\' ' - else: - self.commandString += arg + ' ' + cmd = cmd.replace('{}', shlex_quote(information['filepath'])) - if self.targetFile not in self.commandString: # Assume user wants the file appended to the end of the command if no {}'s were given. - self.commandString += '\'' + self.targetFile + '\'' - - print("[exec] Executing command: " + self.commandString) - self.retCode = subprocess.call(self.commandString, shell=True) - if(self.retCode < 0): - print("[exec] WARNING: Command exited with a negative return code, the process was killed externally. Your command may not of completed succesfully!") - elif(self.verboseOutput): - print("[exec] Command exited with return code: " + str(self.retCode)) + self._downloader.to_screen("[exec] Executing command: %s" % cmd) + retCode = subprocess.call(cmd, shell=True) + if retCode != 0: + raise PostProcessingError( + 'Command returned error code %d' % retCode) return None, information # by default, keep file and do nothing - -class PostProcessingExecError(PostProcessingError): - pass diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 8095400d03..2b05fd7b7f 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -192,6 +192,13 @@ def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False, except ImportError: # Python 2.6 from xml.parsers.expat import ExpatError as compat_xml_parse_error +try: + from shlex import quote as shlex_quote +except ImportError: # Python < 3.3 + def shlex_quote(s): + return "'" + s.replace("'", "'\"'\"'") + "'" + + def compat_ord(c): if type(c) is int: return c else: return ord(c)