mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-30 22:25:19 +00:00 
			
		
		
		
	[cleanup] Refactor ffmpeg convertors
This commit is contained in:
		| @@ -31,7 +31,6 @@ from .utils import ( | ||||
|     preferredencoding, | ||||
|     read_batch_urls, | ||||
|     RejectedVideoReached, | ||||
|     REMUX_EXTENSIONS, | ||||
|     render_table, | ||||
|     SameFileError, | ||||
|     setproctitle, | ||||
| @@ -45,6 +44,13 @@ from .downloader import ( | ||||
| from .extractor import gen_extractors, list_extractors | ||||
| from .extractor.common import InfoExtractor | ||||
| from .extractor.adobepass import MSO_INFO | ||||
| from .postprocessor.ffmpeg import ( | ||||
|     FFmpegExtractAudioPP, | ||||
|     FFmpegSubtitlesConvertorPP, | ||||
|     FFmpegThumbnailsConvertorPP, | ||||
|     FFmpegVideoConvertorPP, | ||||
|     FFmpegVideoRemuxerPP, | ||||
| ) | ||||
| from .postprocessor.metadatafromfield import MetadataFromFieldPP | ||||
| from .YoutubeDL import YoutubeDL | ||||
|  | ||||
| @@ -209,25 +215,25 @@ def _real_main(argv=None): | ||||
|     if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart: | ||||
|         raise ValueError('Playlist end must be greater than playlist start') | ||||
|     if opts.extractaudio: | ||||
|         if opts.audioformat not in ['best', 'aac', 'flac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']: | ||||
|         if opts.audioformat not in ['best'] + list(FFmpegExtractAudioPP.SUPPORTED_EXTS): | ||||
|             parser.error('invalid audio format specified') | ||||
|     if opts.audioquality: | ||||
|         opts.audioquality = opts.audioquality.strip('k').strip('K') | ||||
|         if not opts.audioquality.isdigit(): | ||||
|             parser.error('invalid audio quality specified') | ||||
|     if opts.recodevideo is not None: | ||||
|         if opts.recodevideo not in REMUX_EXTENSIONS: | ||||
|             parser.error('invalid video recode format specified') | ||||
|         opts.recodevideo = opts.recodevideo.replace(' ', '') | ||||
|         if not re.match(FFmpegVideoConvertorPP.FORMAT_RE, opts.recodevideo): | ||||
|             parser.error('invalid video remux format specified') | ||||
|     if opts.remuxvideo is not None: | ||||
|         opts.remuxvideo = opts.remuxvideo.replace(' ', '') | ||||
|         remux_regex = r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(REMUX_EXTENSIONS)) | ||||
|         if not re.match(remux_regex, opts.remuxvideo): | ||||
|         if not re.match(FFmpegVideoRemuxerPP.FORMAT_RE, opts.remuxvideo): | ||||
|             parser.error('invalid video remux format specified') | ||||
|     if opts.convertsubtitles is not None: | ||||
|         if opts.convertsubtitles not in ('srt', 'vtt', 'ass', 'lrc'): | ||||
|         if opts.convertsubtitles not in FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS: | ||||
|             parser.error('invalid subtitle format specified') | ||||
|     if opts.convertthumbnails is not None: | ||||
|         if opts.convertthumbnails not in ('jpg', 'png'): | ||||
|         if opts.convertthumbnails not in FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS: | ||||
|             parser.error('invalid thumbnail format specified') | ||||
|  | ||||
|     if opts.date is not None: | ||||
| @@ -480,10 +486,10 @@ def _real_main(argv=None): | ||||
|         opts.postprocessor_args['default'] = opts.postprocessor_args['default-compat'] | ||||
|  | ||||
|     final_ext = ( | ||||
|         opts.recodevideo | ||||
|         or (opts.remuxvideo in REMUX_EXTENSIONS) and opts.remuxvideo | ||||
|         or (opts.extractaudio and opts.audioformat != 'best') and opts.audioformat | ||||
|         or None) | ||||
|         opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS | ||||
|         else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS | ||||
|         else opts.audioformat if (opts.extractaudio and opts.audioformat != 'best') | ||||
|         else None) | ||||
|  | ||||
|     match_filter = ( | ||||
|         None if opts.match_filter is None | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import optparse | ||||
| import re | ||||
| import sys | ||||
|  | ||||
| from .downloader.external import list_external_downloaders | ||||
| from .compat import ( | ||||
|     compat_expanduser, | ||||
|     compat_get_terminal_size, | ||||
| @@ -18,11 +17,18 @@ from .utils import ( | ||||
|     get_executable_path, | ||||
|     OUTTMPL_TYPES, | ||||
|     preferredencoding, | ||||
|     REMUX_EXTENSIONS, | ||||
|     write_string, | ||||
| ) | ||||
| from .version import __version__ | ||||
|  | ||||
| from .downloader.external import list_external_downloaders | ||||
| from .postprocessor.ffmpeg import ( | ||||
|     FFmpegExtractAudioPP, | ||||
|     FFmpegSubtitlesConvertorPP, | ||||
|     FFmpegThumbnailsConvertorPP, | ||||
|     FFmpegVideoRemuxerPP, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _hide_login_info(opts): | ||||
|     PRIVATE_OPTS = set(['-p', '--password', '-u', '--username', '--video-password', '--ap-password', '--ap-username']) | ||||
| @@ -1123,7 +1129,9 @@ def parseOpts(overrideArguments=None): | ||||
|         help='Convert video files to audio-only files (requires ffmpeg and ffprobe)') | ||||
|     postproc.add_option( | ||||
|         '--audio-format', metavar='FORMAT', dest='audioformat', default='best', | ||||
|         help='Specify audio format: "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "%default" by default; No effect without -x') | ||||
|         help=( | ||||
|             'Specify audio format to convert the audio to when -x is used. Currently supported formats are: ' | ||||
|             'best (default) or one of %s' % '|'.join(FFmpegExtractAudioPP.SUPPORTED_EXTS))) | ||||
|     postproc.add_option( | ||||
|         '--audio-quality', metavar='QUALITY', | ||||
|         dest='audioquality', default='5', | ||||
| @@ -1134,15 +1142,14 @@ def parseOpts(overrideArguments=None): | ||||
|         help=( | ||||
|             'Remux the video into another container if necessary (currently supported: %s). ' | ||||
|             'If target container does not support the video/audio codec, remuxing will fail. ' | ||||
|             'You can specify multiple rules; eg. "aac>m4a/mov>mp4/mkv" will remux aac to m4a, mov to mp4 ' | ||||
|             'and anything else to mkv.' % '|'.join(REMUX_EXTENSIONS))) | ||||
|             'You can specify multiple rules; Eg. "aac>m4a/mov>mp4/mkv" will remux aac to m4a, mov to mp4 ' | ||||
|             'and anything else to mkv.' % '|'.join(FFmpegVideoRemuxerPP.SUPPORTED_EXTS))) | ||||
|     postproc.add_option( | ||||
|         '--recode-video', | ||||
|         metavar='FORMAT', dest='recodevideo', default=None, | ||||
|         help=( | ||||
|             'Re-encode the video into another format if re-encoding is necessary. ' | ||||
|             'You can specify multiple rules similar to --remux-video. ' | ||||
|             'The supported formats are also the same as --remux-video')) | ||||
|             'The syntax and supported formats are the same as --remux-video')) | ||||
|     postproc.add_option( | ||||
|         '--postprocessor-args', '--ppa', | ||||
|         metavar='NAME:ARGS', dest='postprocessor_args', default={}, type='str', | ||||
| @@ -1250,11 +1257,15 @@ def parseOpts(overrideArguments=None): | ||||
|     postproc.add_option( | ||||
|         '--convert-subs', '--convert-sub', '--convert-subtitles', | ||||
|         metavar='FORMAT', dest='convertsubtitles', default=None, | ||||
|         help='Convert the subtitles to another format (currently supported: srt|ass|vtt|lrc) (Alias: --convert-subtitles)') | ||||
|         help=( | ||||
|             'Convert the subtitles to another format (currently supported: %s) ' | ||||
|             '(Alias: --convert-subtitles)' % '|'.join(FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS))) | ||||
|     postproc.add_option( | ||||
|         '--convert-thumbnails', | ||||
|         metavar='FORMAT', dest='convertthumbnails', default=None, | ||||
|         help='Convert the thumbnails to another format (currently supported: jpg, png)') | ||||
|         help=( | ||||
|             'Convert the thumbnails to another format ' | ||||
|             '(currently supported: %s) ' % '|'.join(FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS))) | ||||
|     postproc.add_option( | ||||
|         '--split-chapters', '--split-tracks', | ||||
|         dest='split_chapters', action='store_true', default=False, | ||||
|   | ||||
| @@ -290,13 +290,12 @@ class FFmpegPostProcessor(PostProcessor): | ||||
|  | ||||
|  | ||||
| class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|     COMMON_AUDIO_EXTENSIONS = ('wav', 'flac', 'm4a', 'aiff', 'mp3', 'ogg', 'mka', 'opus', 'wma') | ||||
|     COMMON_AUDIO_EXTS = ('wav', 'flac', 'm4a', 'aiff', 'mp3', 'ogg', 'mka', 'opus', 'wma') | ||||
|     SUPPORTED_EXTS = ('best', 'aac', 'flac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav') | ||||
|  | ||||
|     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._preferredcodec = preferredcodec or 'best' | ||||
|         self._preferredquality = preferredquality | ||||
|         self._nopostoverwrites = nopostoverwrites | ||||
|  | ||||
| @@ -315,7 +314,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|         path = information['filepath'] | ||||
|         orig_ext = information['ext'] | ||||
|  | ||||
|         if self._preferredcodec == 'best' and orig_ext in self.COMMON_AUDIO_EXTENSIONS: | ||||
|         if self._preferredcodec == 'best' and orig_ext in self.COMMON_AUDIO_EXTS: | ||||
|             self.to_screen('Skipping audio extraction since the file is already in a common audio format') | ||||
|             return [], information | ||||
|  | ||||
| @@ -400,6 +399,8 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): | ||||
|  | ||||
|  | ||||
| class FFmpegVideoConvertorPP(FFmpegPostProcessor): | ||||
|     SUPPORTED_EXTS = ('mp4', 'mkv', 'flv', 'webm', 'mov', 'avi', 'mp3', 'mka', 'm4a', 'ogg', 'opus') | ||||
|     FORMAT_RE = re.compile(r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(SUPPORTED_EXTS))) | ||||
|     _action = 'converting' | ||||
|  | ||||
|     def __init__(self, downloader=None, preferedformat=None): | ||||
| @@ -419,14 +420,14 @@ class FFmpegVideoConvertorPP(FFmpegPostProcessor): | ||||
|         return [] | ||||
|  | ||||
|     def run(self, information): | ||||
|         path = information['filepath'] | ||||
|         target_ext = self._target_ext(information['ext'].lower()) | ||||
|         path, source_ext = information['filepath'], information['ext'].lower() | ||||
|         target_ext = self._target_ext(source_ext) | ||||
|         _skip_msg = ( | ||||
|             'could not find a mapping for %s' if not target_ext | ||||
|             else 'already is in target format %s' if source_ext == target_ext | ||||
|             else None) | ||||
|         if _skip_msg: | ||||
|             self.to_screen('Not %s media file %s; %s' % (self._action, path, _skip_msg % source_ext)) | ||||
|             self.to_screen('Not %s media file "%s"; %s' % (self._action, path, _skip_msg % source_ext)) | ||||
|             return [], information | ||||
|  | ||||
|         prefix, sep, oldext = path.rpartition('.') | ||||
| @@ -708,6 +709,8 @@ class FFmpegFixupM3u8PP(FFmpegPostProcessor): | ||||
|  | ||||
|  | ||||
| class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor): | ||||
|     SUPPORTED_EXTS = ('srt', 'vtt', 'ass', 'lrc') | ||||
|  | ||||
|     def __init__(self, downloader=None, format=None): | ||||
|         super(FFmpegSubtitlesConvertorPP, self).__init__(downloader) | ||||
|         self.format = format | ||||
| @@ -816,6 +819,8 @@ class FFmpegSplitChaptersPP(FFmpegPostProcessor): | ||||
|  | ||||
|  | ||||
| class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor): | ||||
|     SUPPORTED_EXTS = ('jpg', 'png') | ||||
|  | ||||
|     def __init__(self, downloader=None, format=None): | ||||
|         super(FFmpegThumbnailsConvertorPP, self).__init__(downloader) | ||||
|         self.format = format | ||||
| @@ -841,31 +846,29 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor): | ||||
|                 info['__files_to_move'][webp_filename] = replace_extension( | ||||
|                     info['__files_to_move'].pop(thumbnail_filename), 'webp') | ||||
|  | ||||
|     def convert_thumbnail(self, thumbnail_filename, ext): | ||||
|         if ext == 'jpg': | ||||
|             format_name = 'JPEG' | ||||
|             opts = ['-bsf:v', 'mjpeg2jpeg'] | ||||
|         elif ext == 'png': | ||||
|             format_name = 'PNG' | ||||
|             opts = [] | ||||
|         else: | ||||
|             raise FFmpegPostProcessorError('Only conversion to either jpg or png is currently supported') | ||||
|     @staticmethod | ||||
|     def _options(target_ext): | ||||
|         if target_ext == 'jpg': | ||||
|             return ['-bsf:v', 'mjpeg2jpeg'] | ||||
|         return [] | ||||
|  | ||||
|     def convert_thumbnail(self, thumbnail_filename, target_ext): | ||||
|         # NB: % is supposed to be escaped with %% but this does not work | ||||
|         # for input files so working around with standard substitution | ||||
|         escaped_thumbnail_filename = thumbnail_filename.replace('%', '#') | ||||
|         os.rename(encodeFilename(thumbnail_filename), encodeFilename(escaped_thumbnail_filename)) | ||||
|         escaped_thumbnail_conv_filename = replace_extension(escaped_thumbnail_filename, ext) | ||||
|         self.to_screen('Converting thumbnail "%s" to %s' % (escaped_thumbnail_filename, format_name)) | ||||
|         self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_conv_filename, opts) | ||||
|         thumbnail_conv_filename = replace_extension(thumbnail_filename, ext) | ||||
|         escaped_thumbnail_conv_filename = replace_extension(escaped_thumbnail_filename, target_ext) | ||||
|  | ||||
|         self.to_screen('Converting thumbnail "%s" to %s' % (escaped_thumbnail_filename, target_ext)) | ||||
|         self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_conv_filename, self._options(target_ext)) | ||||
|  | ||||
|         # Rename back to unescaped | ||||
|         thumbnail_conv_filename = replace_extension(thumbnail_filename, target_ext) | ||||
|         os.rename(encodeFilename(escaped_thumbnail_filename), encodeFilename(thumbnail_filename)) | ||||
|         os.rename(encodeFilename(escaped_thumbnail_conv_filename), encodeFilename(thumbnail_conv_filename)) | ||||
|         return thumbnail_conv_filename | ||||
|  | ||||
|     def run(self, info): | ||||
|         if self.format not in ('jpg', 'png'): | ||||
|             raise FFmpegPostProcessorError('Only conversion to either jpg or png is currently supported') | ||||
|         files_to_delete = [] | ||||
|         has_thumbnail = False | ||||
|  | ||||
|   | ||||
| @@ -1716,8 +1716,6 @@ KNOWN_EXTENSIONS = ( | ||||
|     'wav', | ||||
|     'f4f', 'f4m', 'm3u8', 'smil') | ||||
|  | ||||
| REMUX_EXTENSIONS = ('mp4', 'mkv', 'flv', 'webm', 'mov', 'avi', 'mp3', 'mka', 'm4a', 'ogg', 'opus') | ||||
|  | ||||
| # needed for sanitizing filenames in restricted mode | ||||
| ACCENT_CHARS = dict(zip('ÂÃÄÀÁÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖŐØŒÙÚÛÜŰÝÞßàáâãäåæçèéêëìíîïðñòóôõöőøœùúûüűýþÿ', | ||||
|                         itertools.chain('AAAAAA', ['AE'], 'CEEEEIIIIDNOOOOOOO', ['OE'], 'UUUUUY', ['TH', 'ss'], | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 pukkandan
					pukkandan