diff --git a/youtube_dl/PostProcessor.py b/youtube_dl/PostProcessor.py index 69aedf87a..9caed69a1 100644 --- a/youtube_dl/PostProcessor.py +++ b/youtube_dl/PostProcessor.py @@ -2,6 +2,7 @@ import os import subprocess import sys import time +import io from .utils import ( @@ -78,15 +79,15 @@ class FFmpegPostProcessor(PostProcessor): programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe'] return dict((program, executable(program)) for program in programs) - def run_ffmpeg_multiple_files(self, input_paths, out_path, opts): + def run_ffmpeg_multiple_files(self, input_paths, out_path, opts, input_opts=[]): if not self._exes['ffmpeg'] and not self._exes['avconv']: raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.') files_cmd = [] for path in input_paths: files_cmd.extend(['-i', encodeFilename(path)]) - cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y'] + files_cmd - + opts + + cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y'] + + input_opts + files_cmd + opts + [encodeFilename(self._ffmpeg_filename_argument(out_path))]) if self._downloader.params.get('verbose', False): @@ -98,8 +99,8 @@ class FFmpegPostProcessor(PostProcessor): msg = stderr.strip().split('\n')[-1] raise FFmpegPostProcessorError(msg) - def run_ffmpeg(self, path, out_path, opts): - self.run_ffmpeg_multiple_files([path], out_path, opts) + def run_ffmpeg(self, path, out_path, opts, input_opts=[]): + self.run_ffmpeg_multiple_files([path], out_path, opts, input_opts) def _ffmpeg_filename_argument(self, fn): # ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details @@ -509,3 +510,18 @@ class FFmpegMetadataPP(FFmpegPostProcessor): os.remove(encodeFilename(filename)) os.rename(encodeFilename(temp_filename), encodeFilename(filename)) return True, info + + +class FFmpegJoinVideos(FFmpegPostProcessor): + def join(self, final_video, videos): + files_file = u'%s.videos' % final_video + with io.open(encodeFilename(files_file), 'w', encoding='utf-8') as f: + for video in videos: + f.write(u'file \'%s\'\n' % video) + self._downloader.to_screen(u'[ffmpeg] Joining video parts, destination: %s' % final_video) + try: + self.run_ffmpeg(files_file, final_video, ['-c', 'copy'], ['-f', 'concat']) + except FFmpegPostProcessorError: + return False + os.remove(encodeFilename(files_file)) + return True diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index c393aa4cf..af9be750c 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -52,6 +52,7 @@ from .utils import ( from .extractor import get_info_extractor, gen_extractors from .FileDownloader import FileDownloader from .version import __version__ +from .PostProcessor import FFmpegJoinVideos class YoutubeDL(object): @@ -790,6 +791,16 @@ class YoutubeDL(object): parts_files.append(part_filename) parts_success.append(self.fd._do_download(part_filename, part_info)) success = all(parts_success) + if success: + video_joiner = FFmpegJoinVideos(self) + join_success = video_joiner.join(filename, parts_files) + if not join_success: + self.report_error(u'Could not join the video parts') + else: + self.to_screen(u'[info] Removing video parts') + for part_file in parts_files: + os.remove(encodeFilename(part_file)) + success = join_success except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: self.report_error(u'unable to download video data: %s' % str(err)) return