Repository: yt-dlp/yt-dlp
File: yt_dlp/postprocessor/ffmpeg.py (Python)

Code before:
    'm4a': ('m4a', 'aac', ('-bsf:a', 'aac_adtstoasc')),
    'opus': ('opus', 'libopus', ()),
    'vorbis': ('ogg', 'libvorbis', ()),
    'flac': ('flac', 'flac', ()),
    'alac': ('m4a', None, ('-acodec', 'alac')),
    'wav': ('wav', None, ('-f', 'wav')),
}


def create_mapping_re(supported):
    return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\s*\w+\s*>)?\s*(?:{})\s*'.format('|'.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}'
            if target == 'none':
                return source, 'explicitly disabled'
            return target, None
    return None, f'could not find a mapping for {source}'


class FFmpegPostProcessorError(PostProcessingError):
    pass


class FFmpegPostProcessor(PostProcessor):
    _ffmpeg_location = contextvars.ContextVar('ffmpeg_location', default=None)

    def __init__(self, downloader=None):
        PostProcessor.__init__(self, downloader)
        self._prefer_ffmpeg = self.get_param('prefer_ffmpeg', True)
        self._paths = self._determine_executables()

    @staticmethod
    def get_versions_and_features(downloader=None):
        pp = FFmpegPostProcessor(downloader)
        return pp._versions, pp._features

    @staticmethod
    def get_versions(downloader=None):
        return FFmpegPostProcessor.get_versions_and_features(downloader)[0]


Review comment (suggestion):
This is not _strictly_ required as the logic in `convert_thumbnail` does the real work, but it does provide a nice message to the user so I thought I'd add it.

Just let me know if you'd like this handled differently 🤙 

Code after:
    'm4a': ('m4a', 'aac', ('-bsf:a', 'aac_adtstoasc')),
    'opus': ('opus', 'libopus', ()),
    'vorbis': ('ogg', 'libvorbis', ()),
    'flac': ('flac', 'flac', ()),
    'alac': ('m4a', None, ('-acodec', 'alac')),
    'wav': ('wav', None, ('-f', 'wav')),
}


def create_mapping_re(supported):
    return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\s*\w+\s*>)?\s*(?:{})\s*'.format('|'.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):
    pass


class FFmpegPostProcessor(PostProcessor):
    _ffmpeg_location = contextvars.ContextVar('ffmpeg_location', default=None)

    def __init__(self, downloader=None):
        PostProcessor.__init__(self, downloader)
        self._prefer_ffmpeg = self.get_param('prefer_ffmpeg', True)
        self._paths = self._determine_executables()

    @staticmethod
    def get_versions_and_features(downloader=None):
        pp = FFmpegPostProcessor(downloader)
        return pp._versions, pp._features

    @staticmethod
    def get_versions(downloader=None):
        return FFmpegPostProcessor.get_versions_and_features(downloader)[0]

    _ffmpeg_to_avconv = {'ffmpeg': 'avconv', 'ffprobe': 'avprobe'}