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