mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-30 22:25:19 +00:00 
			
		
		
		
	--recode-video option (Closes #18)
This commit is contained in:
		| @@ -81,6 +81,7 @@ class FileDownloader(object): | ||||
|     writesubtitles:    Write the video subtitles to a .srt file | ||||
|     subtitleslang:     Language of the subtitles to download | ||||
|     test:              Download only first bytes to test the downloader. | ||||
|     keepvideo:         Keep the video file after post-processing | ||||
|     """ | ||||
|  | ||||
|     params = None | ||||
| @@ -529,13 +530,27 @@ class FileDownloader(object): | ||||
|         return self._download_retcode | ||||
|  | ||||
|     def post_process(self, filename, ie_info): | ||||
|         """Run the postprocessing chain on the given file.""" | ||||
|         """Run all the postprocessors on the given file.""" | ||||
|         info = dict(ie_info) | ||||
|         info['filepath'] = filename | ||||
|         keep_video = None | ||||
|         for pp in self._pps: | ||||
|             info = pp.run(info) | ||||
|             if info is None: | ||||
|                 break | ||||
|             try: | ||||
|                 keep_video_wish,new_info = pp.run(info) | ||||
|                 if keep_video_wish is not None: | ||||
|                     if keep_video_wish: | ||||
|                         keep_video = keep_video_wish | ||||
|                     elif keep_video is None: | ||||
|                         # No clear decision yet, let IE decide | ||||
|                         keep_video = keep_video_wish | ||||
|             except PostProcessingError as e: | ||||
|                 self.to_stderr(u'ERROR: ' + e.msg) | ||||
|         if not keep_video and not self.params.get('keepvideo', False): | ||||
|             try: | ||||
|                 self.to_stderr(u'Deleting original file %s (pass -k to keep)' % filename) | ||||
|                 os.remove(encodeFilename(filename)) | ||||
|             except (IOError, OSError): | ||||
|                 self.to_stderr(u'WARNING: Unable to remove downloaded video file') | ||||
|  | ||||
|     def _download_with_rtmpdump(self, filename, url, player_url, page_url): | ||||
|         self.report_destination(filename) | ||||
|   | ||||
| @@ -45,25 +45,20 @@ class PostProcessor(object): | ||||
|         one has an extra field called "filepath" that points to the | ||||
|         downloaded file. | ||||
|  | ||||
|         When this method returns None, the postprocessing chain is | ||||
|         stopped. However, this method may return an information | ||||
|         dictionary that will be passed to the next postprocessing | ||||
|         object in the chain. It can be the one it received after | ||||
|         changing some fields. | ||||
|         This method returns a tuple, the first element of which describes | ||||
|         whether the original file should be kept (i.e. not deleted - None for | ||||
|         no preference), and the second of which is the updated information. | ||||
|  | ||||
|         In addition, this method may raise a PostProcessingError | ||||
|         exception that will be taken into account by the downloader | ||||
|         it was called from. | ||||
|         exception if post processing fails. | ||||
|         """ | ||||
|         return information # by default, do nothing | ||||
|         return None, information # by default, keep file and do nothing | ||||
|  | ||||
| class FFmpegPostProcessorError(BaseException): | ||||
|     def __init__(self, message): | ||||
|         self.message = message | ||||
| class FFmpegPostProcessorError(PostProcessingError): | ||||
|     pass | ||||
|  | ||||
| class AudioConversionError(BaseException): | ||||
|     def __init__(self, message): | ||||
|         self.message = message | ||||
| class AudioConversionError(PostProcessingError): | ||||
|     pass | ||||
|  | ||||
| class FFmpegPostProcessor(PostProcessor): | ||||
|     def __init__(self,downloader=None): | ||||
| @@ -83,7 +78,7 @@ class FFmpegPostProcessor(PostProcessor): | ||||
|  | ||||
|     def run_ffmpeg(self, path, out_path, opts): | ||||
|         if not self._exes['ffmpeg'] and not self._exes['avconv']: | ||||
|             raise FFmpegPostProcessorError('ffmpeg or avconv not found. Please install one.') | ||||
|             raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.') | ||||
|         cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y', '-i', encodeFilename(path)] | ||||
|                + opts + | ||||
|                [encodeFilename(self._ffmpeg_filename_argument(out_path))]) | ||||
| @@ -91,7 +86,7 @@ class FFmpegPostProcessor(PostProcessor): | ||||
|         stdout,stderr = p.communicate() | ||||
|         if p.returncode != 0: | ||||
|             msg = stderr.strip().split('\n')[-1] | ||||
|             raise FFmpegPostProcessorError(msg) | ||||
|             raise FFmpegPostProcessorError(msg.decode('utf-8', 'replace')) | ||||
|  | ||||
|     def _ffmpeg_filename_argument(self, fn): | ||||
|         # ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details | ||||
| @@ -100,13 +95,12 @@ class FFmpegPostProcessor(PostProcessor): | ||||
|         return fn | ||||
|  | ||||
| class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|     def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, keepvideo=False, nopostoverwrites=False): | ||||
|     def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False): | ||||
|         FFmpegPostProcessor.__init__(self, downloader) | ||||
|         if preferredcodec is None: | ||||
|             preferredcodec = 'best' | ||||
|         self._preferredcodec = preferredcodec | ||||
|         self._preferredquality = preferredquality | ||||
|         self._keepvideo = keepvideo | ||||
|         self._nopostoverwrites = nopostoverwrites | ||||
|  | ||||
|     def get_audio_codec(self, path): | ||||
| @@ -145,8 +139,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|  | ||||
|         filecodec = self.get_audio_codec(path) | ||||
|         if filecodec is None: | ||||
|             self._downloader.to_stderr(u'WARNING: unable to obtain file audio codec with ffprobe') | ||||
|             return None | ||||
|             raise PostProcessingError(u'WARNING: unable to obtain file audio codec with ffprobe') | ||||
|  | ||||
|         more_opts = [] | ||||
|         if self._preferredcodec == 'best' or self._preferredcodec == filecodec or (self._preferredcodec == 'm4a' and filecodec == 'aac'): | ||||
| @@ -204,10 +197,10 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|         except: | ||||
|             etype,e,tb = sys.exc_info() | ||||
|             if isinstance(e, AudioConversionError): | ||||
|                 self._downloader.to_stderr(u'ERROR: audio conversion failed: ' + e.message) | ||||
|                 msg = u'audio conversion failed: ' + e.message | ||||
|             else: | ||||
|                 self._downloader.to_stderr(u'ERROR: error running ' + (self._exes['avconv'] and 'avconv' or 'ffmpeg')) | ||||
|             return None | ||||
|                 msg = u'error running ' + (self._exes['avconv'] and 'avconv' or 'ffmpeg') | ||||
|             raise PostProcessingError(msg) | ||||
|  | ||||
|         # Try to update the date time for extracted audio file. | ||||
|         if information.get('filetime') is not None: | ||||
| @@ -216,29 +209,24 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|             except: | ||||
|                 self._downloader.to_stderr(u'WARNING: Cannot update utime of audio file') | ||||
|  | ||||
|         if not self._keepvideo: | ||||
|             try: | ||||
|                 os.remove(encodeFilename(path)) | ||||
|             except (IOError, OSError): | ||||
|                 self._downloader.to_stderr(u'WARNING: Unable to remove downloaded video file') | ||||
|                 return None | ||||
|  | ||||
|         information['filepath'] = new_path | ||||
|         return information | ||||
|         return False,information | ||||
|  | ||||
| class FFmpegVideoConvertor(FFmpegPostProcessor): | ||||
|     def __init__(self, downloader=None,preferedformat=None): | ||||
|         FFmpegPostProcessor.__init__(self,downloader) | ||||
|         super(FFmpegVideoConvertor, self).__init__(downloader) | ||||
|         self._preferedformat=preferedformat | ||||
|  | ||||
|     def run(self, information): | ||||
|         path = information['filepath'] | ||||
|         prefix, sep, ext = path.rpartition(u'.') | ||||
|         outpath = prefix + sep + self._preferedformat | ||||
|         if not self._preferedformat or information['format'] == self._preferedformat: | ||||
|             return information | ||||
|         self._downloader.to_screen(u'['+'ffmpeg'+'] Converting video from %s to %s, Destination: ' % (information['format'], self._preferedformat) +outpath) | ||||
|         if information['ext'] == self._preferedformat: | ||||
|             self._downloader.to_screen(u'[ffmpeg] Not converting video file %s - already is in target format %s' % (path, self._preferedformat)) | ||||
|             return True,information | ||||
|         self._downloader.to_screen(u'['+'ffmpeg'+'] Converting video from %s to %s, Destination: ' % (information['ext'], self._preferedformat) +outpath) | ||||
|         self.run_ffmpeg(path, outpath, []) | ||||
|         information['filepath'] = outpath | ||||
|         information['format'] = self._preferedformat | ||||
|         return information | ||||
|         information['ext'] = self._preferedformat | ||||
|         return False,information | ||||
|   | ||||
| @@ -175,7 +175,6 @@ def parseOpts(): | ||||
|             action='store', dest='subtitleslang', metavar='LANG', | ||||
|             help='language of the closed captions to download (optional) use IETF language tags like \'en\'') | ||||
|  | ||||
|  | ||||
|     verbosity.add_option('-q', '--quiet', | ||||
|             action='store_true', dest='quiet', help='activates quiet mode', default=False) | ||||
|     verbosity.add_option('-s', '--simulate', | ||||
| @@ -251,6 +250,8 @@ def parseOpts(): | ||||
|             help='"best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; best by default') | ||||
|     postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5', | ||||
|             help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)') | ||||
|     postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None, | ||||
|             help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)') | ||||
|     postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False, | ||||
|             help='keeps the video file on disk after the post-processing; the video is erased by default') | ||||
|     postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False, | ||||
| @@ -380,6 +381,9 @@ def _real_main(): | ||||
|         opts.audioquality = opts.audioquality.strip('k').strip('K') | ||||
|         if not opts.audioquality.isdigit(): | ||||
|             parser.error(u'invalid audio quality specified') | ||||
|     if opts.recodevideo is not None: | ||||
|         if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']: | ||||
|             parser.error(u'invalid video recode format specified') | ||||
|  | ||||
|     if sys.version_info < (3,): | ||||
|         # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems) | ||||
| @@ -436,6 +440,7 @@ def _real_main(): | ||||
|         'prefer_free_formats': opts.prefer_free_formats, | ||||
|         'verbose': opts.verbose, | ||||
|         'test': opts.test, | ||||
|         'keepvideo': opts.keepvideo, | ||||
|         }) | ||||
|  | ||||
|     if opts.verbose: | ||||
| @@ -457,7 +462,9 @@ def _real_main(): | ||||
|  | ||||
|     # PostProcessors | ||||
|     if opts.extractaudio: | ||||
|         fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, keepvideo=opts.keepvideo, nopostoverwrites=opts.nopostoverwrites)) | ||||
|         fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites)) | ||||
|     if opts.recodevideo: | ||||
|         fd.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo)) | ||||
|  | ||||
|     # Maybe do nothing | ||||
|     if len(all_urls) < 1: | ||||
|   | ||||
| @@ -450,7 +450,8 @@ class PostProcessingError(Exception): | ||||
|     This exception may be raised by PostProcessor's .run() method to | ||||
|     indicate an error in the postprocessing task. | ||||
|     """ | ||||
|     pass | ||||
|     def __init__(self, msg): | ||||
|         self.msg = msg | ||||
|  | ||||
| class MaxDownloadsReached(Exception): | ||||
|     """ --max-downloads limit has been reached. """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Hagemeister
					Philipp Hagemeister