|
|
@ -30,54 +30,97 @@ class FFmpegPostProcessorError(PostProcessingError):
|
|
|
|
class FFmpegPostProcessor(PostProcessor):
|
|
|
|
class FFmpegPostProcessor(PostProcessor):
|
|
|
|
def __init__(self, downloader=None, deletetempfiles=False):
|
|
|
|
def __init__(self, downloader=None, deletetempfiles=False):
|
|
|
|
PostProcessor.__init__(self, downloader)
|
|
|
|
PostProcessor.__init__(self, downloader)
|
|
|
|
self._versions = self.get_versions()
|
|
|
|
|
|
|
|
self._deletetempfiles = deletetempfiles
|
|
|
|
self._deletetempfiles = deletetempfiles
|
|
|
|
|
|
|
|
self._determine_executables()
|
|
|
|
|
|
|
|
|
|
|
|
def check_version(self):
|
|
|
|
def check_version(self):
|
|
|
|
if not self._executable:
|
|
|
|
if not self.available():
|
|
|
|
raise FFmpegPostProcessorError('ffmpeg or avconv not found. Please install one.')
|
|
|
|
raise FFmpegPostProcessorError('ffmpeg or avconv not found. Please install one.')
|
|
|
|
|
|
|
|
|
|
|
|
required_version = '10-0' if self._uses_avconv() else '1.0'
|
|
|
|
required_version = '10-0' if self._uses_avconv() else '1.0'
|
|
|
|
if is_outdated_version(
|
|
|
|
if is_outdated_version(
|
|
|
|
self._versions[self._executable], required_version):
|
|
|
|
self._versions[self.basename], required_version):
|
|
|
|
warning = 'Your copy of %s is outdated, update %s to version %s or newer if you encounter any errors.' % (
|
|
|
|
warning = 'Your copy of %s is outdated, update %s to version %s or newer if you encounter any errors.' % (
|
|
|
|
self._executable, self._executable, required_version)
|
|
|
|
self.basename, self.basename, required_version)
|
|
|
|
if self._downloader:
|
|
|
|
if self._downloader:
|
|
|
|
self._downloader.report_warning(warning)
|
|
|
|
self._downloader.report_warning(warning)
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@staticmethod
|
|
|
|
def get_versions():
|
|
|
|
def get_versions(downloader=None):
|
|
|
|
programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
|
|
|
|
return FFmpegPostProcessor(downloader)._versions
|
|
|
|
return dict((p, get_exe_version(p, args=['-version'])) for p in programs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def available(self):
|
|
|
|
|
|
|
|
return self._executable is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _determine_executables(self):
|
|
|
|
def _executable(self):
|
|
|
|
programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
|
|
|
|
if self._downloader.params.get('prefer_ffmpeg', False):
|
|
|
|
prefer_ffmpeg = self._downloader.params.get('prefer_ffmpeg', False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.basename = None
|
|
|
|
|
|
|
|
self.probe_basename = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._paths = None
|
|
|
|
|
|
|
|
self._versions = None
|
|
|
|
|
|
|
|
if self._downloader:
|
|
|
|
|
|
|
|
location = self._downloader.params.get('ffmpeg_location')
|
|
|
|
|
|
|
|
if location is not None:
|
|
|
|
|
|
|
|
if not os.path.exists(location):
|
|
|
|
|
|
|
|
self._downloader.report_warning(
|
|
|
|
|
|
|
|
'ffmpeg-location %s does not exist! '
|
|
|
|
|
|
|
|
'Continuing without avconv/ffmpeg.' % (location))
|
|
|
|
|
|
|
|
self._versions = {}
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
elif not os.path.isdir(location):
|
|
|
|
|
|
|
|
basename = os.path.splitext(os.path.basename(location))[0]
|
|
|
|
|
|
|
|
if basename not in programs:
|
|
|
|
|
|
|
|
self._downloader.report_warning(
|
|
|
|
|
|
|
|
'Cannot identify executable %s, its basename should be one of %s. '
|
|
|
|
|
|
|
|
'Continuing without avconv/ffmpeg.' %
|
|
|
|
|
|
|
|
(location, ', '.join(programs)))
|
|
|
|
|
|
|
|
self._versions = {}
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
location = os.path.dirname(os.path.abspath(location))
|
|
|
|
|
|
|
|
if basename in ('ffmpeg', 'ffprobe'):
|
|
|
|
|
|
|
|
prefer_ffmpeg = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._paths = dict(
|
|
|
|
|
|
|
|
(p, os.path.join(location, p)) for p in programs)
|
|
|
|
|
|
|
|
self._versions = dict(
|
|
|
|
|
|
|
|
(p, get_exe_version(self._paths[p], args=['-version']))
|
|
|
|
|
|
|
|
for p in programs)
|
|
|
|
|
|
|
|
if self._versions is None:
|
|
|
|
|
|
|
|
self._versions = dict(
|
|
|
|
|
|
|
|
(p, get_exe_version(p, args=['-version'])) for p in programs)
|
|
|
|
|
|
|
|
self._paths = dict((p, p) for p in programs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if prefer_ffmpeg:
|
|
|
|
prefs = ('ffmpeg', 'avconv')
|
|
|
|
prefs = ('ffmpeg', 'avconv')
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
prefs = ('avconv', 'ffmpeg')
|
|
|
|
prefs = ('avconv', 'ffmpeg')
|
|
|
|
for p in prefs:
|
|
|
|
for p in prefs:
|
|
|
|
if self._versions[p]:
|
|
|
|
if self._versions[p]:
|
|
|
|
return p
|
|
|
|
self.basename = p
|
|
|
|
return None
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
if prefer_ffmpeg:
|
|
|
|
def _probe_executable(self):
|
|
|
|
|
|
|
|
if self._downloader.params.get('prefer_ffmpeg', False):
|
|
|
|
|
|
|
|
prefs = ('ffprobe', 'avprobe')
|
|
|
|
prefs = ('ffprobe', 'avprobe')
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
prefs = ('avprobe', 'ffprobe')
|
|
|
|
prefs = ('avprobe', 'ffprobe')
|
|
|
|
for p in prefs:
|
|
|
|
for p in prefs:
|
|
|
|
if self._versions[p]:
|
|
|
|
if self._versions[p]:
|
|
|
|
return p
|
|
|
|
self.probe_basename = p
|
|
|
|
return None
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def available(self):
|
|
|
|
|
|
|
|
return self.basename is not None
|
|
|
|
|
|
|
|
|
|
|
|
def _uses_avconv(self):
|
|
|
|
def _uses_avconv(self):
|
|
|
|
return self._executable == 'avconv'
|
|
|
|
return self.basename == 'avconv'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def executable(self):
|
|
|
|
|
|
|
|
return self._paths[self.basename]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def probe_executable(self):
|
|
|
|
|
|
|
|
return self._paths[self.probe_basename]
|
|
|
|
|
|
|
|
|
|
|
|
def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
|
|
|
|
def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
|
|
|
|
self.check_version()
|
|
|
|
self.check_version()
|
|
|
@ -88,7 +131,7 @@ class FFmpegPostProcessor(PostProcessor):
|
|
|
|
files_cmd = []
|
|
|
|
files_cmd = []
|
|
|
|
for path in input_paths:
|
|
|
|
for path in input_paths:
|
|
|
|
files_cmd.extend([encodeArgument('-i'), encodeFilename(path, True)])
|
|
|
|
files_cmd.extend([encodeArgument('-i'), encodeFilename(path, True)])
|
|
|
|
cmd = ([encodeFilename(self._executable, True), encodeArgument('-y')] +
|
|
|
|
cmd = ([encodeFilename(self.executable, True), encodeArgument('-y')] +
|
|
|
|
files_cmd +
|
|
|
|
files_cmd +
|
|
|
|
[encodeArgument(o) for o in opts] +
|
|
|
|
[encodeArgument(o) for o in opts] +
|
|
|
|
[encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
|
|
|
|
[encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
|
|
|
@ -127,13 +170,15 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
|
|
|
|
|
|
|
|
|
|
|
def get_audio_codec(self, path):
|
|
|
|
def get_audio_codec(self, path):
|
|
|
|
|
|
|
|
|
|
|
|
if not self._probe_executable:
|
|
|
|
if not self.probe_executable:
|
|
|
|
raise PostProcessingError('ffprobe or avprobe not found. Please install one.')
|
|
|
|
raise PostProcessingError('ffprobe or avprobe not found. Please install one.')
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
cmd = [
|
|
|
|
cmd = [
|
|
|
|
encodeFilename(self._probe_executable, True),
|
|
|
|
encodeFilename(self.probe_executable, True),
|
|
|
|
encodeArgument('-show_streams'),
|
|
|
|
encodeArgument('-show_streams'),
|
|
|
|
encodeFilename(self._ffmpeg_filename_argument(path), True)]
|
|
|
|
encodeFilename(self._ffmpeg_filename_argument(path), True)]
|
|
|
|
|
|
|
|
if self._downloader.params.get('verbose', False):
|
|
|
|
|
|
|
|
self._downloader.to_screen('[debug] ffprobe command line: %s' % shell_quote(cmd))
|
|
|
|
handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE)
|
|
|
|
handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE)
|
|
|
|
output = handle.communicate()[0]
|
|
|
|
output = handle.communicate()[0]
|
|
|
|
if handle.wait() != 0:
|
|
|
|
if handle.wait() != 0:
|
|
|
@ -223,14 +268,14 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
|
|
|
if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
|
|
|
|
if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
|
|
|
|
self._downloader.to_screen('[youtube] Post-process file %s exists, skipping' % new_path)
|
|
|
|
self._downloader.to_screen('[youtube] Post-process file %s exists, skipping' % new_path)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
self._downloader.to_screen('[' + self._executable + '] Destination: ' + new_path)
|
|
|
|
self._downloader.to_screen('[' + self.basename + '] Destination: ' + new_path)
|
|
|
|
self.run_ffmpeg(path, new_path, acodec, more_opts)
|
|
|
|
self.run_ffmpeg(path, new_path, acodec, more_opts)
|
|
|
|
except:
|
|
|
|
except:
|
|
|
|
etype, e, tb = sys.exc_info()
|
|
|
|
etype, e, tb = sys.exc_info()
|
|
|
|
if isinstance(e, AudioConversionError):
|
|
|
|
if isinstance(e, AudioConversionError):
|
|
|
|
msg = 'audio conversion failed: ' + e.msg
|
|
|
|
msg = 'audio conversion failed: ' + e.msg
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
msg = 'error running ' + self._executable
|
|
|
|
msg = 'error running ' + self.basename
|
|
|
|
raise PostProcessingError(msg)
|
|
|
|
raise PostProcessingError(msg)
|
|
|
|
|
|
|
|
|
|
|
|
# Try to update the date time for extracted audio file.
|
|
|
|
# Try to update the date time for extracted audio file.
|
|
|
|