From 21792b88b791b16e3ab0a0fb2e26e5bb8a4e2ff3 Mon Sep 17 00:00:00 2001 From: dirkf Date: Sun, 3 Mar 2024 12:38:00 +0000 Subject: [PATCH] [external/FFmpeg] Fix and improve --ffmpeg-location handling * pass YoutubeDL (FileDownloader) to FFmpegPostProcessor constructor * consolidate path search in FFmpegPostProcessor * make availability of FFmpegFD depend on existence of FFmpegPostProcessor * detect ffmpeg executable on instantiation of FFmpegFD * resolves #32735 --- test/test_downloader_external.py | 16 ++++++++++++- youtube_dl/downloader/external.py | 17 +++++++++----- youtube_dl/postprocessor/ffmpeg.py | 36 +++++++++--------------------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/test/test_downloader_external.py b/test/test_downloader_external.py index 029f9b05f..4491bd9de 100644 --- a/test/test_downloader_external.py +++ b/test/test_downloader_external.py @@ -18,6 +18,7 @@ from test.helper import ( ) from youtube_dl import YoutubeDL from youtube_dl.compat import ( + compat_contextlib_suppress, compat_http_cookiejar_Cookie, compat_http_server, compat_kwargs, @@ -35,6 +36,9 @@ from youtube_dl.downloader.external import ( HttpieFD, WgetFD, ) +from youtube_dl.postprocessor import ( + FFmpegPostProcessor, +) import threading TEST_SIZE = 10 * 1024 @@ -227,7 +231,17 @@ class TestAria2cFD(unittest.TestCase): self.assertIn('--load-cookies=%s' % downloader._cookies_tempfile, cmd) -@ifExternalFDAvailable(FFmpegFD) +# Handle delegated availability +def ifFFmpegFDAvailable(externalFD): + # raise SkipTest, or set False! + avail = ifExternalFDAvailable(externalFD) and False + with compat_contextlib_suppress(Exception): + avail = FFmpegPostProcessor(downloader=None).available + return unittest.skipUnless( + avail, externalFD.get_basename() + ' not found') + + +@ifFFmpegFDAvailable(FFmpegFD) class TestFFmpegFD(unittest.TestCase): _args = [] diff --git a/youtube_dl/downloader/external.py b/youtube_dl/downloader/external.py index f22fa6013..4fbc0f520 100644 --- a/youtube_dl/downloader/external.py +++ b/youtube_dl/downloader/external.py @@ -13,7 +13,12 @@ from ..compat import ( compat_str, compat_subprocess_Popen, ) -from ..postprocessor.ffmpeg import FFmpegPostProcessor, EXT_TO_OUT_FORMATS + +try: + from ..postprocessor.ffmpeg import FFmpegPostProcessor, EXT_TO_OUT_FORMATS +except ImportError: + FFmpegPostProcessor = None + from ..utils import ( cli_option, cli_valueless_option, @@ -362,13 +367,14 @@ class FFmpegFD(ExternalFD): @classmethod def available(cls): - return FFmpegPostProcessor().available + # actual availability can only be confirmed for an instance + return bool(FFmpegPostProcessor) def _call_downloader(self, tmpfilename, info_dict): - url = info_dict['url'] - ffpp = FFmpegPostProcessor(downloader=self) + # `downloader` means the parent `YoutubeDL` + ffpp = FFmpegPostProcessor(downloader=self.ydl) if not ffpp.available: - self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.') + self.report_error('ffmpeg required for download but no ffmpeg (nor avconv) executable could be found. Please install one.') return False ffpp.check_version() @@ -397,6 +403,7 @@ class FFmpegFD(ExternalFD): # if end_time: # args += ['-t', compat_str(end_time - start_time)] + url = info_dict['url'] cookies = self.ydl.cookiejar.get_cookies_for_url(url) if cookies: args.extend(['-cookies', ''.join( diff --git a/youtube_dl/postprocessor/ffmpeg.py b/youtube_dl/postprocessor/ffmpeg.py index 801160e6c..e5ffdf378 100644 --- a/youtube_dl/postprocessor/ffmpeg.py +++ b/youtube_dl/postprocessor/ffmpeg.py @@ -96,6 +96,7 @@ class FFmpegPostProcessor(PostProcessor): self._paths = None self._versions = None + location = None if self._downloader: prefer_ffmpeg = self._downloader.params.get('prefer_ffmpeg', True) location = self._downloader.params.get('ffmpeg_location') @@ -118,32 +119,17 @@ class FFmpegPostProcessor(PostProcessor): location = os.path.dirname(os.path.abspath(location)) if basename in ('ffmpeg', 'ffprobe'): prefer_ffmpeg = True + self._paths = dict( + (p, p if location is None else os.path.join(location, p)) + for p in programs) + self._versions = dict( + x for x in ( + (p, get_ffmpeg_version(self._paths[p])) for p in programs) + if x[1] is not None) - self._paths = dict( - (p, os.path.join(location, p)) for p in programs) - self._versions = dict( - (p, get_ffmpeg_version(self._paths[p])) for p in programs) - if self._versions is None: - self._versions = dict( - (p, get_ffmpeg_version(p)) for p in programs) - self._paths = dict((p, p) for p in programs) - - if prefer_ffmpeg is False: - prefs = ('avconv', 'ffmpeg') - else: - prefs = ('ffmpeg', 'avconv') - for p in prefs: - if self._versions[p]: - self.basename = p - break - - if prefer_ffmpeg is False: - prefs = ('avprobe', 'ffprobe') - else: - prefs = ('ffprobe', 'avprobe') - for p in prefs: - if self._versions[p]: - self.probe_basename = p + for p in ('ffmpeg', 'avconv')[::-1 if prefer_ffmpeg is False else 1]: + if self._versions.get(p): + self.basename = self.probe_basename = p break @property