--convert-subs FORMAT Convert the subtitles to other format
(currently supported: srt|ass|vtt|lrc)
+## SponSkrub Options (SponsorBlock)
+ --sponskrub Use sponskrub to mark sponsored sections
+ with the data available in SponsorBlock API
+ (Youtube only)
+ --sponskrub-cut Cut out the sponsor sections instead of
+ simply marking them
+ --sponskrub-force Run sponskrub even if the video was
+ already downloaded. Use with caution
+ --sponskrub-location Location of the sponskrub binary;
+ either the path to the binary or its
+ containing directory
+ --sponskrub-args Give these arguments to sponskrub
+
## Extractor Options:
--ignore-dynamic-mpd Do not process dynamic DASH manifests
if not ensure_dir_exists(fname):
return
downloaded.append(fname)
- partial_success = dl(fname, new_info)
+ partial_success, real_download = dl(fname, new_info)
success = success and partial_success
info_dict['__postprocessors'] = postprocessors
info_dict['__files_to_merge'] = downloaded
+ # Even if there were no downloads, it is being merged only now
+ info_dict['__real_download'] = True
else:
# Just a single file
- success = dl(filename, info_dict)
+ success, real_download = dl(filename, info_dict)
+ info_dict['__real_download'] = real_download
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
self.report_error('unable to download video data: %s' % error_to_compat_str(err))
return
# contents
if opts.xattrs:
postprocessors.append({'key': 'XAttrMetadata'})
+ # This should be below all ffmpeg PP because it may cut parts out from the video
+ # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
+ if opts.sponskrub is not False:
+ postprocessors.append({
+ 'key': 'SponSkrub',
+ 'path': opts.sponskrub_path,
+ 'args': opts.sponskrub_args,
+ 'cut': opts.sponskrub_cut,
+ 'force': opts.sponskrub_force,
+ 'ignoreerror': opts.sponskrub is None,
+ })
# Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
# So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
if opts.exec_cmd:
'status': 'finished',
'total_bytes': os.path.getsize(encodeFilename(filename)),
})
- return True
+ return True, False
if subtitle is False:
min_sleep_interval = self.params.get('sleep_interval')
'[download] Sleeping %s seconds...' % (
sleep_interval_sub))
time.sleep(sleep_interval_sub)
- return self.real_download(filename, info_dict)
+ return self.real_download(filename, info_dict), True
def real_download(self, filename, info_dict):
"""Real download process. Redefine in subclasses."""
metavar='FORMAT', dest='convertsubtitles', default=None,
help='Convert the subtitles to other format (currently supported: srt|ass|vtt|lrc)')
+ extractor = optparse.OptionGroup(parser, 'SponSkrub Options (SponsorBlock)')
+ extractor.add_option(
+ '--sponskrub',
+ action='store_true', dest='sponskrub', default=None,
+ help='Use sponskrub to mark sponsored sections with the data available in SponsorBlock API (Youtube only)')
+ extractor.add_option(
+ '--no-sponskrub',
+ action='store_false', dest='sponskrub',
+ help=optparse.SUPPRESS_HELP)
+ extractor.add_option(
+ '--sponskrub-cut', default=False,
+ action='store_true', dest='sponskrub_cut',
+ help='Cut out the sponsor sections instead of simply marking them')
+ extractor.add_option(
+ '--sponskrub-force', default=False,
+ action='store_true', dest='sponskrub_force',
+ help='Run sponskrub even if the video was already downloaded')
+ extractor.add_option(
+ '--sponskrub-location', metavar='PATH',
+ dest='sponskrub_path', default='',
+ help='Location of the sponskrub binary; either the path to the binary or its containing directory.')
+ extractor.add_option(
+ '--sponskrub-args', dest='sponskrub_args',
+ help='Give these arguments to sponskrub')
+
extractor = optparse.OptionGroup(parser, 'Extractor Options')
extractor.add_option(
'--allow-dynamic-mpd',
from .xattrpp import XAttrMetadataPP
from .execafterdownload import ExecAfterDownloadPP
from .metadatafromtitle import MetadataFromTitlePP
+from .sponskrub import SponSkrubPP
def get_postprocessor(key):
'FFmpegVideoConvertorPP',
'FFmpegVideoRemuxerPP',
'MetadataFromTitlePP',
+ 'SponSkrubPP',
'XAttrMetadataPP',
]
--- /dev/null
+from __future__ import unicode_literals
+import os
+import subprocess
+
+from .common import PostProcessor
+from ..compat import compat_shlex_split
+from ..utils import (
+ check_executable,
+ encodeArgument,
+ shell_quote,
+ PostProcessingError,
+)
+
+
+class SponSkrubPP(PostProcessor):
+ _temp_ext = 'spons'
+ _def_args = []
+ _exe_name = 'sponskrub'
+
+ def __init__(self, downloader, path='', args=None, ignoreerror=False, cut=False, force=False):
+ PostProcessor.__init__(self, downloader)
+ self.force = force
+ self.cutout = cut
+ self.args = ['-chapter'] if not cut else []
+ self.args += self._def_args if args is None else compat_shlex_split(args)
+ self.path = self.get_exe(path)
+
+ if not ignoreerror and self.path is None:
+ if path:
+ raise PostProcessingError('sponskrub not found in "%s"' % path)
+ else:
+ raise PostProcessingError('sponskrub not found. Please install or provide the path using --sponskrub-path.')
+
+ def get_exe(self, path=''):
+ if not path or not check_executable(path, ['-h']):
+ path = os.path.join(path, self._exe_name)
+ if not check_executable(path, ['-h']):
+ return None
+ return path
+
+ def run(self, information):
+ if self.path is None:
+ return [], information
+
+ if information['extractor_key'].lower() != 'youtube':
+ self._downloader.to_screen('[sponskrub] Skipping sponskrub since it is not a YouTube video')
+ return [], information
+ if self.cutout and not self.force and not information.get('__real_download', False):
+ self._downloader.to_screen(
+ '[sponskrub] Skipping sponskrub since the video was already downloaded. '
+ 'Use --sponskrub-force to run sponskrub anyway')
+ return [], information
+
+ self._downloader.to_screen('[sponskrub] Trying to %s sponsor sections' % ('remove' if self.cutout else 'mark'))
+ if self.cutout:
+ self._downloader.to_screen('WARNING: Cutting out sponsor segments will cause the subtitles to go out of sync.')
+ if not information.get('__real_download', False):
+ self._downloader.to_screen('WARNING: If sponskrub is run multiple times, unintended parts of the video could be cut out.')
+
+ filename = information['filepath']
+ temp_filename = filename + '.' + self._temp_ext + os.path.splitext(filename)[1]
+ if os.path.exists(temp_filename):
+ os.remove(temp_filename)
+
+ cmd = [self.path]
+ if self.args:
+ cmd += self.args
+ cmd += ['--', information['id'], filename, temp_filename]
+ cmd = [encodeArgument(i) for i in cmd]
+
+ if self._downloader.params.get('verbose', False):
+ self._downloader.to_screen('[debug] sponskrub command line: %s' % shell_quote(cmd))
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+
+ if p.returncode == 0:
+ os.remove(filename)
+ os.rename(temp_filename, filename)
+ self._downloader.to_screen('[sponskrub] Sponsor sections have been %s' % ('removed' if self.cutout else 'marked'))
+ elif p.returncode != 3: # error code 3 means there was no info about the video
+ stderr = stderr.decode('utf-8', 'replace')
+ msg = stderr.strip().split('\n')[-1]
+ raise PostProcessingError(msg if msg else 'sponskrub failed with error code %s!' % p.returncode)
+ else:
+ self._downloader.to_screen('[sponskrub] No segments in the SponsorBlock database')
+ return [], information