[ExtractAudio, cleanup] Refactor

This commit is contained in:
pukkandan 2022-06-06 21:49:57 +05:30
parent b7c47b7438
commit 35faefee5d
No known key found for this signature in database
GPG key ID: 7EEE9E1E817D0A39
5 changed files with 57 additions and 92 deletions

View file

@ -871,23 +871,22 @@ You can also fork the project on github and run your fork's [build workflow](.gi
## Post-Processing Options: ## Post-Processing Options:
-x, --extract-audio Convert video files to audio-only files -x, --extract-audio Convert video files to audio-only files
(requires ffmpeg and ffprobe) (requires ffmpeg and ffprobe)
--audio-format FORMAT Specify audio format to convert the audio to --audio-format FORMAT Format to convert the audio to when -x is
when -x is used. Currently supported formats used. (currently supported: best (default),
are: best (default) or one of aac, flac, mp3, aac, m4a, opus, vorbis, flac, alac, wav)
mp3, m4a, opus, vorbis, wav, alac
--audio-quality QUALITY Specify ffmpeg audio quality to use when --audio-quality QUALITY Specify ffmpeg audio quality to use when
converting the audio with -x. Insert a value converting the audio with -x. Insert a value
between 0 (best) and 10 (worst) for VBR or a between 0 (best) and 10 (worst) for VBR or a
specific bitrate like 128K (default 5) specific bitrate like 128K (default 5)
--remux-video FORMAT Remux the video into another container if --remux-video FORMAT Remux the video into another container if
necessary (currently supported: mp4, mkv, necessary (currently supported: mp4, mkv,
flv, webm, mov, avi, mka, ogg, aac, flac, flv, webm, mov, avi, mka, ogg, mp3, aac,
mp3, m4a, opus, vorbis, wav, alac). If m4a, opus, vorbis, flac, alac, wav). If
target container does not support the target container does not support the
video/audio codec, remuxing will fail. You video/audio codec, remuxing will fail. You
can specify multiple rules; Eg. can specify multiple rules; Eg.
"aac>m4a/mov>mp4/mkv" will remux aac to m4a, "aac>m4a/mov>mp4/mkv" will remux aac to m4a,
mov to mp4 and anything else to mkv. mov to mp4 and anything else to mkv
--recode-video FORMAT Re-encode the video into another format if --recode-video FORMAT Re-encode the video into another format if
necessary. The syntax and supported formats necessary. The syntax and supported formats
are the same as --remux-video are the same as --remux-video

View file

@ -215,13 +215,9 @@ 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)
for name, value, pp in ( validate_regex('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP.FORMAT_RE)
('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP), validate_regex('recode video format', opts.recodevideo, FFmpegVideoConvertorPP.FORMAT_RE)
('recode video format', opts.recodevideo, FFmpegVideoConvertorPP), validate_regex('remux video format', opts.remuxvideo, FFmpegVideoRemuxerPP.FORMAT_RE)
('remux video format', opts.remuxvideo, FFmpegVideoRemuxerPP),
):
if value is not None:
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
@ -653,7 +649,7 @@ def parse_options(argv=None):
final_ext = ( final_ext = (
opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS
else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS
else opts.audioformat if (opts.extractaudio and opts.audioformat != 'best') else opts.audioformat if (opts.extractaudio and opts.audioformat in FFmpegExtractAudioPP.SUPPORTED_EXTS)
else None) else None)
return parser, opts, urls, { return parser, opts, urls, {

View file

@ -1423,20 +1423,22 @@ def create_parser():
postproc.add_option( postproc.add_option(
'--audio-format', metavar='FORMAT', dest='audioformat', default='best', '--audio-format', metavar='FORMAT', dest='audioformat', default='best',
help=( help=(
'Specify audio format to convert the audio to when -x is used. Currently supported formats are: ' 'Format to convert the audio to when -x is used. '
'best (default) or one of %s' % ', '.join(FFmpegExtractAudioPP.SUPPORTED_EXTS))) f'(currently supported: best (default), {", ".join(FFmpegExtractAudioPP.SUPPORTED_EXTS)})'))
postproc.add_option( postproc.add_option(
'--audio-quality', metavar='QUALITY', '--audio-quality', metavar='QUALITY',
dest='audioquality', default='5', dest='audioquality', default='5',
help='Specify ffmpeg audio quality to use when converting the audio with -x. Insert a value between 0 (best) and 10 (worst) for VBR or a specific bitrate like 128K (default %default)') help=(
'Specify ffmpeg audio quality to use when converting the audio with -x. '
'Insert a value between 0 (best) and 10 (worst) for VBR or a specific bitrate like 128K (default %default)'))
postproc.add_option( postproc.add_option(
'--remux-video', '--remux-video',
metavar='FORMAT', dest='remuxvideo', default=None, metavar='FORMAT', dest='remuxvideo', default=None,
help=( help=(
'Remux the video into another container if necessary (currently supported: %s). ' 'Remux the video into another container if necessary '
'If target container does not support the video/audio codec, remuxing will fail. ' f'(currently supported: {", ".join(FFmpegVideoRemuxerPP.SUPPORTED_EXTS)}). '
'You can specify multiple rules; Eg. "aac>m4a/mov>mp4/mkv" will remux aac to m4a, mov to mp4 ' 'If target container does not support the video/audio codec, remuxing will fail. You can specify multiple rules; '
'and anything else to mkv.' % ', '.join(FFmpegVideoRemuxerPP.SUPPORTED_EXTS))) 'Eg. "aac>m4a/mov>mp4/mkv" will remux aac to m4a, mov to mp4 and anything else to mkv'))
postproc.add_option( postproc.add_option(
'--recode-video', '--recode-video',
metavar='FORMAT', dest='recodevideo', default=None, metavar='FORMAT', dest='recodevideo', default=None,

View file

@ -216,5 +216,5 @@ class PostProcessor(metaclass=PostProcessorMetaClass):
raise PostProcessingError(f'Unable to communicate with {self.PP_NAME} API: {e}') raise PostProcessingError(f'Unable to communicate with {self.PP_NAME} API: {e}')
class AudioConversionError(PostProcessingError): class AudioConversionError(PostProcessingError): # Deprecated
pass pass

View file

@ -6,7 +6,7 @@ import re
import subprocess import subprocess
import time import time
from .common import AudioConversionError, PostProcessor from .common import PostProcessor
from ..compat import functools, imghdr from ..compat import functools, imghdr
from ..utils import ( from ..utils import (
ISO639Utils, ISO639Utils,
@ -45,19 +45,20 @@ EXT_TO_OUT_FORMATS = {
'vtt': 'webvtt', 'vtt': 'webvtt',
} }
ACODECS = { ACODECS = {
'mp3': 'libmp3lame', # name: (ext, encoder, opts)
'aac': 'aac', 'mp3': ('mp3', 'libmp3lame', ()),
'flac': 'flac', 'aac': ('m4a', 'aac', ('-f', 'adts')),
'm4a': 'aac', 'm4a': ('m4a', 'aac', ('-bsf:a', 'aac_adtstoasc')),
'opus': 'libopus', 'opus': ('opus', 'libopus', ()),
'vorbis': 'libvorbis', 'vorbis': ('ogg', 'libvorbis', ()),
'wav': None, 'flac': ('flac', 'flac', ()),
'alac': None, 'alac': ('m4a', None, ('-acodec', 'alac')),
'wav': ('wav', None, ('-f', 'wav')),
} }
def create_mapping_re(supported): def create_mapping_re(supported):
return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(supported))) return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\s*\w+\s*>)?\s*(?:%s)\s*' % '|'.join(supported)))
def resolve_mapping(source, mapping): def resolve_mapping(source, mapping):
@ -424,7 +425,7 @@ class FFmpegPostProcessor(PostProcessor):
class FFmpegExtractAudioPP(FFmpegPostProcessor): class FFmpegExtractAudioPP(FFmpegPostProcessor):
COMMON_AUDIO_EXTS = ('wav', 'flac', 'm4a', 'aiff', 'mp3', 'ogg', 'mka', 'opus', 'wma') COMMON_AUDIO_EXTS = ('wav', 'flac', 'm4a', 'aiff', 'mp3', 'ogg', 'mka', 'opus', 'wma')
SUPPORTED_EXTS = ('aac', 'flac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav', 'alac') SUPPORTED_EXTS = tuple(ACODECS.keys())
def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False): def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False):
FFmpegPostProcessor.__init__(self, downloader) FFmpegPostProcessor.__init__(self, downloader)
@ -463,71 +464,45 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
try: try:
FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts) FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts)
except FFmpegPostProcessorError as err: except FFmpegPostProcessorError as err:
raise AudioConversionError(err.msg) raise PostProcessingError(f'audio conversion failed: {err.msg}')
@PostProcessor._restrict_to(images=False) @PostProcessor._restrict_to(images=False)
def run(self, information): def run(self, information):
orig_path = path = information['filepath'] orig_path = path = information['filepath']
orig_ext = information['ext'] target_format = self._preferredcodec
if target_format == 'best' and information['ext'] in self.COMMON_AUDIO_EXTS:
if self._preferredcodec == 'best' and orig_ext in self.COMMON_AUDIO_EXTS: self.to_screen(f'Not converting audio {orig_path}; the file is already in a common audio format')
self.to_screen('Skipping audio extraction since the file is already in a common audio format')
return [], information return [], information
filecodec = self.get_audio_codec(path) filecodec = self.get_audio_codec(path)
if filecodec is None: if filecodec is None:
raise PostProcessingError('WARNING: unable to obtain file audio codec with ffprobe') raise PostProcessingError('WARNING: unable to obtain file audio codec with ffprobe')
more_opts = [] if filecodec == 'aac' and target_format in ('m4a', 'best'):
if self._preferredcodec == 'best' or self._preferredcodec == filecodec or (self._preferredcodec == 'm4a' and filecodec == 'aac'): # Lossless, but in another container
if filecodec == 'aac' and self._preferredcodec in ['m4a', 'best']: extension, _, more_opts, acodec = *ACODECS['m4a'], 'copy'
# Lossless, but in another container elif target_format == 'best' or target_format == filecodec:
acodec = 'copy' # Lossless if possible
extension = 'm4a' try:
more_opts = ['-bsf:a', 'aac_adtstoasc'] extension, _, more_opts, acodec = *ACODECS[filecodec], 'copy'
elif filecodec in ['aac', 'flac', 'mp3', 'vorbis', 'opus']: except KeyError:
# Lossless if possible extension, acodec, more_opts = ACODECS['mp3']
acodec = 'copy'
extension = filecodec
if filecodec == 'aac':
more_opts = ['-f', 'adts']
if filecodec == 'vorbis':
extension = 'ogg'
elif filecodec == 'alac':
acodec = None
extension = 'm4a'
more_opts += ['-acodec', 'alac']
else:
# MP3 otherwise.
acodec = 'libmp3lame'
extension = 'mp3'
more_opts = self._quality_args(acodec)
else: else:
# We convert the audio (lossy if codec is lossy) # We convert the audio (lossy if codec is lossy)
acodec = ACODECS[self._preferredcodec] extension, acodec, more_opts = ACODECS[target_format]
if acodec == 'aac' and self._features.get('fdk'): if acodec == 'aac' and self._features.get('fdk'):
acodec = 'libfdk_aac' acodec, more_opts = 'libfdk_aac', []
extension = self._preferredcodec
more_opts = self._quality_args(acodec)
if self._preferredcodec == 'aac':
more_opts += ['-f', 'adts']
elif self._preferredcodec == 'm4a':
more_opts += ['-bsf:a', 'aac_adtstoasc']
elif self._preferredcodec == 'vorbis':
extension = 'ogg'
elif self._preferredcodec == 'wav':
extension = 'wav'
more_opts += ['-f', 'wav']
elif self._preferredcodec == 'alac':
extension = 'm4a'
more_opts += ['-acodec', 'alac']
prefix, sep, ext = path.rpartition('.') # not os.path.splitext, since the latter does not work on unicode in all setups more_opts = list(more_opts)
temp_path = new_path = prefix + sep + extension if acodec != 'copy':
more_opts = self._quality_args(acodec)
# not os.path.splitext, since the latter does not work on unicode in all setups
temp_path = new_path = f'{path.rpartition(".")[0]}.{extension}'
if new_path == path: if new_path == path:
if acodec == 'copy': if acodec == 'copy':
self.to_screen(f'File is already in target format {self._preferredcodec}, skipping') self.to_screen(f'Not converting audio {orig_path}; file is already in target format {target_format}')
return [], information return [], information
orig_path = prepend_extension(path, 'orig') orig_path = prepend_extension(path, 'orig')
temp_path = prepend_extension(path, 'temp') temp_path = prepend_extension(path, 'temp')
@ -536,14 +511,8 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
self.to_screen('Post-process file %s exists, skipping' % new_path) self.to_screen('Post-process file %s exists, skipping' % new_path)
return [], information return [], information
try: self.to_screen(f'Destination: {new_path}')
self.to_screen(f'Destination: {new_path}') self.run_ffmpeg(path, temp_path, acodec, more_opts)
self.run_ffmpeg(path, temp_path, acodec, more_opts)
except AudioConversionError as e:
raise PostProcessingError(
'audio conversion failed: ' + e.msg)
except Exception:
raise PostProcessingError('error running ' + self.basename)
os.replace(path, orig_path) os.replace(path, orig_path)
os.replace(temp_path, new_path) os.replace(temp_path, new_path)
@ -553,8 +522,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
# 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:
self.try_utime( self.try_utime(
new_path, time.time(), information['filetime'], new_path, time.time(), information['filetime'], errnote='Cannot update utime of audio file')
errnote='Cannot update utime of audio file')
return [orig_path], information return [orig_path], information