mirror of https://github.com/yt-dlp/yt-dlp.git
parent
0bea4fd807
commit
00bbc5f177
|
@ -985,7 +985,9 @@ You can also fork the project on github and run your fork's [build workflow](.gi
|
||||||
(currently supported: srt, vtt, ass, lrc)
|
(currently supported: srt, vtt, ass, lrc)
|
||||||
(Alias: --convert-subtitles)
|
(Alias: --convert-subtitles)
|
||||||
--convert-thumbnails FORMAT Convert the thumbnails to another format
|
--convert-thumbnails FORMAT Convert the thumbnails to another format
|
||||||
(currently supported: jpg, png, webp)
|
(currently supported: jpg, png, webp). You
|
||||||
|
can specify multiple rules using similar
|
||||||
|
syntax as --remux-video
|
||||||
--split-chapters Split video into multiple files based on
|
--split-chapters Split video into multiple files based on
|
||||||
internal chapters. The "chapter:" prefix can
|
internal chapters. The "chapter:" prefix can
|
||||||
be used with "--paths" and "--output" to set
|
be used with "--paths" and "--output" to set
|
||||||
|
|
|
@ -215,13 +215,13 @@ def validate_options(opts):
|
||||||
# Postprocessor formats
|
# Postprocessor formats
|
||||||
validate_in('audio format', opts.audioformat, ['best'] + list(FFmpegExtractAudioPP.SUPPORTED_EXTS))
|
validate_in('audio format', opts.audioformat, ['best'] + list(FFmpegExtractAudioPP.SUPPORTED_EXTS))
|
||||||
validate_in('subtitle format', opts.convertsubtitles, FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)
|
validate_in('subtitle format', opts.convertsubtitles, FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)
|
||||||
validate_in('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)
|
for name, value, pp in (
|
||||||
if opts.recodevideo is not None:
|
('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP),
|
||||||
opts.recodevideo = opts.recodevideo.replace(' ', '')
|
('recode video format', opts.recodevideo, FFmpegVideoConvertorPP),
|
||||||
validate_regex('video recode format', opts.recodevideo, FFmpegVideoConvertorPP.FORMAT_RE)
|
('remux video format', opts.remuxvideo, FFmpegVideoRemuxerPP),
|
||||||
if opts.remuxvideo is not None:
|
):
|
||||||
opts.remuxvideo = opts.remuxvideo.replace(' ', '')
|
if value is not None:
|
||||||
validate_regex('video remux format', opts.remuxvideo, FFmpegVideoRemuxerPP.FORMAT_RE)
|
validate_regex(name, value.replace(' ', ''), pp.FORMAT_RE)
|
||||||
if opts.audioquality:
|
if opts.audioquality:
|
||||||
opts.audioquality = opts.audioquality.strip('k').strip('K')
|
opts.audioquality = opts.audioquality.strip('k').strip('K')
|
||||||
# int_or_none prevents inf, nan
|
# int_or_none prevents inf, nan
|
||||||
|
|
|
@ -1610,7 +1610,8 @@ def create_parser():
|
||||||
metavar='FORMAT', dest='convertthumbnails', default=None,
|
metavar='FORMAT', dest='convertthumbnails', default=None,
|
||||||
help=(
|
help=(
|
||||||
'Convert the thumbnails to another format '
|
'Convert the thumbnails to another format '
|
||||||
'(currently supported: %s) ' % ', '.join(FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)))
|
f'(currently supported: {", ".join(FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS)}). '
|
||||||
|
'You can specify multiple rules using similar syntax as --remux-video'))
|
||||||
postproc.add_option(
|
postproc.add_option(
|
||||||
'--split-chapters', '--split-tracks',
|
'--split-chapters', '--split-tracks',
|
||||||
dest='split_chapters', action='store_true', default=False,
|
dest='split_chapters', action='store_true', default=False,
|
||||||
|
|
|
@ -56,6 +56,25 @@ ACODECS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_mapping_re(supported):
|
||||||
|
return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(supported)))
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_mapping(source, mapping):
|
||||||
|
"""
|
||||||
|
Get corresponding item from a mapping string like 'A>B/C>D/E'
|
||||||
|
@returns (target, error_message)
|
||||||
|
"""
|
||||||
|
for pair in mapping.lower().split('/'):
|
||||||
|
kv = pair.split('>', 1)
|
||||||
|
if len(kv) == 1 or kv[0].strip() == source:
|
||||||
|
target = kv[-1].strip()
|
||||||
|
if target == source:
|
||||||
|
return target, f'already is in target format {source}'
|
||||||
|
return target, None
|
||||||
|
return None, f'could not find a mapping for {source}'
|
||||||
|
|
||||||
|
|
||||||
class FFmpegPostProcessorError(PostProcessingError):
|
class FFmpegPostProcessorError(PostProcessingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -542,18 +561,12 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
||||||
|
|
||||||
class FFmpegVideoConvertorPP(FFmpegPostProcessor):
|
class FFmpegVideoConvertorPP(FFmpegPostProcessor):
|
||||||
SUPPORTED_EXTS = ('mp4', 'mkv', 'flv', 'webm', 'mov', 'avi', 'mka', 'ogg', *FFmpegExtractAudioPP.SUPPORTED_EXTS)
|
SUPPORTED_EXTS = ('mp4', 'mkv', 'flv', 'webm', 'mov', 'avi', 'mka', 'ogg', *FFmpegExtractAudioPP.SUPPORTED_EXTS)
|
||||||
FORMAT_RE = re.compile(r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(SUPPORTED_EXTS)))
|
FORMAT_RE = create_mapping_re(SUPPORTED_EXTS)
|
||||||
_ACTION = 'converting'
|
_ACTION = 'converting'
|
||||||
|
|
||||||
def __init__(self, downloader=None, preferedformat=None):
|
def __init__(self, downloader=None, preferedformat=None):
|
||||||
super().__init__(downloader)
|
super().__init__(downloader)
|
||||||
self._preferedformats = preferedformat.lower().split('/')
|
self.mapping = preferedformat
|
||||||
|
|
||||||
def _target_ext(self, source_ext):
|
|
||||||
for pair in self._preferedformats:
|
|
||||||
kv = pair.split('>')
|
|
||||||
if len(kv) == 1 or kv[0].strip() == source_ext:
|
|
||||||
return kv[-1].strip()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _options(target_ext):
|
def _options(target_ext):
|
||||||
|
@ -564,11 +577,7 @@ class FFmpegVideoConvertorPP(FFmpegPostProcessor):
|
||||||
@PostProcessor._restrict_to(images=False)
|
@PostProcessor._restrict_to(images=False)
|
||||||
def run(self, info):
|
def run(self, info):
|
||||||
filename, source_ext = info['filepath'], info['ext'].lower()
|
filename, source_ext = info['filepath'], info['ext'].lower()
|
||||||
target_ext = self._target_ext(source_ext)
|
target_ext, _skip_msg = resolve_mapping(source_ext, self.mapping)
|
||||||
_skip_msg = (
|
|
||||||
f'could not find a mapping for {source_ext}' if not target_ext
|
|
||||||
else f'already is in target format {source_ext}' if source_ext == target_ext
|
|
||||||
else None)
|
|
||||||
if _skip_msg:
|
if _skip_msg:
|
||||||
self.to_screen(f'Not {self._ACTION} media file "{filename}"; {_skip_msg}')
|
self.to_screen(f'Not {self._ACTION} media file "{filename}"; {_skip_msg}')
|
||||||
return [], info
|
return [], info
|
||||||
|
@ -1068,10 +1077,11 @@ class FFmpegSplitChaptersPP(FFmpegPostProcessor):
|
||||||
|
|
||||||
class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor):
|
class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor):
|
||||||
SUPPORTED_EXTS = ('jpg', 'png', 'webp')
|
SUPPORTED_EXTS = ('jpg', 'png', 'webp')
|
||||||
|
FORMAT_RE = create_mapping_re(SUPPORTED_EXTS)
|
||||||
|
|
||||||
def __init__(self, downloader=None, format=None):
|
def __init__(self, downloader=None, format=None):
|
||||||
super().__init__(downloader)
|
super().__init__(downloader)
|
||||||
self.format = format
|
self.mapping = format
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_webp(cls, path):
|
def is_webp(cls, path):
|
||||||
|
@ -1115,18 +1125,17 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor):
|
||||||
continue
|
continue
|
||||||
has_thumbnail = True
|
has_thumbnail = True
|
||||||
self.fixup_webp(info, idx)
|
self.fixup_webp(info, idx)
|
||||||
_, thumbnail_ext = os.path.splitext(original_thumbnail)
|
thumbnail_ext = os.path.splitext(original_thumbnail)[1][1:].lower()
|
||||||
if thumbnail_ext:
|
|
||||||
thumbnail_ext = thumbnail_ext[1:].lower()
|
|
||||||
if thumbnail_ext == 'jpeg':
|
if thumbnail_ext == 'jpeg':
|
||||||
thumbnail_ext = 'jpg'
|
thumbnail_ext = 'jpg'
|
||||||
if thumbnail_ext == self.format:
|
target_ext, _skip_msg = resolve_mapping(thumbnail_ext, self.mapping)
|
||||||
self.to_screen('Thumbnail "%s" is already in the requested format' % original_thumbnail)
|
if _skip_msg:
|
||||||
|
self.to_screen(f'Not converting thumbnail "{original_thumbnail}"; {_skip_msg}')
|
||||||
continue
|
continue
|
||||||
thumbnail_dict['filepath'] = self.convert_thumbnail(original_thumbnail, self.format)
|
thumbnail_dict['filepath'] = self.convert_thumbnail(original_thumbnail, target_ext)
|
||||||
files_to_delete.append(original_thumbnail)
|
files_to_delete.append(original_thumbnail)
|
||||||
info['__files_to_move'][thumbnail_dict['filepath']] = replace_extension(
|
info['__files_to_move'][thumbnail_dict['filepath']] = replace_extension(
|
||||||
info['__files_to_move'][original_thumbnail], self.format)
|
info['__files_to_move'][original_thumbnail], target_ext)
|
||||||
|
|
||||||
if not has_thumbnail:
|
if not has_thumbnail:
|
||||||
self.to_screen('There aren\'t any thumbnails to convert')
|
self.to_screen('There aren\'t any thumbnails to convert')
|
||||||
|
|
Loading…
Reference in New Issue