diff --git a/.gitignore b/.gitignore index 37b2fa8d3b..b8128fab17 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,6 @@ updates_key.pem *.m4a *.m4v *.part +*.swp test/testdata .tox diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index a96bf9b5cd..7cf5de43f0 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -73,6 +73,7 @@ __authors__ = ( 'Erik Johnson', 'Keith Beckman', 'Ole Ernst', + 'Aaron McDaniel (mcd1992)', ) __license__ = 'Public Domain' @@ -119,6 +120,7 @@ from .postprocessor import ( FFmpegExtractAudioPP, FFmpegEmbedSubtitlePP, XAttrMetadataPP, + ExecAfterDownloadPP, ) @@ -550,7 +552,8 @@ def parseOpts(overrideArguments=None): 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 {}\'' ) parser.add_option_group(general) parser.add_option_group(selection) @@ -831,6 +834,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, } with YoutubeDL(ydl_opts) as ydl: @@ -854,6 +858,12 @@ def _real_main(argv=None): ydl.add_post_processor(FFmpegAudioFixPP()) ydl.add_post_processor(AtomicParsleyPP()) + + # 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)) + # Update version if opts.update_self: update_self(ydl.to_screen, opts.verbose) diff --git a/youtube_dl/postprocessor/__init__.py b/youtube_dl/postprocessor/__init__.py index 08e6ddd00c..15aa0daa9b 100644 --- a/youtube_dl/postprocessor/__init__.py +++ b/youtube_dl/postprocessor/__init__.py @@ -9,6 +9,7 @@ from .ffmpeg import ( FFmpegEmbedSubtitlePP, ) from .xattrpp import XAttrMetadataPP +from .execafterdownload import ExecAfterDownloadPP __all__ = [ 'AtomicParsleyPP', @@ -19,4 +20,5 @@ __all__ = [ 'FFmpegExtractAudioPP', 'FFmpegEmbedSubtitlePP', 'XAttrMetadataPP', + 'ExecAfterDownloadPP', ] diff --git a/youtube_dl/postprocessor/execafterdownload.py b/youtube_dl/postprocessor/execafterdownload.py new file mode 100644 index 0000000000..e6f3cdfd22 --- /dev/null +++ b/youtube_dl/postprocessor/execafterdownload.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals +from .common import PostProcessor +from ..utils import PostProcessingError +import subprocess +import shlex + + +class ExecAfterDownloadPP(PostProcessor): + def __init__(self, downloader=None, verboseOutput=None, commandString=None): + self.verboseOutput = verboseOutput + self.commandString = commandString + + def run(self, information): + self.targetFile = information['filepath'].replace('\'', '\'\\\'\'') # Replace single quotes with '\'' + self.commandList = shlex.split(self.commandString) + self.commandString = '' + + # 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 + ' ' + + 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)) + + return None, information # by default, keep file and do nothing + + +class PostProcessingExecError(PostProcessingError): + pass