mirror of https://github.com/yt-dlp/yt-dlp.git
Sponskrub integration
This commit is contained in:
parent
76d321f68f
commit
a9e7f54670
13
README.md
13
README.md
|
@ -523,6 +523,19 @@ I will add some memorable short links to the binaries so you can download them e
|
||||||
--convert-subs FORMAT Convert the subtitles to other format
|
--convert-subs FORMAT Convert the subtitles to other format
|
||||||
(currently supported: srt|ass|vtt|lrc)
|
(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:
|
## Extractor Options:
|
||||||
--ignore-dynamic-mpd Do not process dynamic DASH manifests
|
--ignore-dynamic-mpd Do not process dynamic DASH manifests
|
||||||
|
|
||||||
|
|
|
@ -2110,13 +2110,16 @@ class YoutubeDL(object):
|
||||||
if not ensure_dir_exists(fname):
|
if not ensure_dir_exists(fname):
|
||||||
return
|
return
|
||||||
downloaded.append(fname)
|
downloaded.append(fname)
|
||||||
partial_success = dl(fname, new_info)
|
partial_success, real_download = dl(fname, new_info)
|
||||||
success = success and partial_success
|
success = success and partial_success
|
||||||
info_dict['__postprocessors'] = postprocessors
|
info_dict['__postprocessors'] = postprocessors
|
||||||
info_dict['__files_to_merge'] = downloaded
|
info_dict['__files_to_merge'] = downloaded
|
||||||
|
# Even if there were no downloads, it is being merged only now
|
||||||
|
info_dict['__real_download'] = True
|
||||||
else:
|
else:
|
||||||
# Just a single file
|
# 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:
|
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))
|
self.report_error('unable to download video data: %s' % error_to_compat_str(err))
|
||||||
return
|
return
|
||||||
|
|
|
@ -310,6 +310,17 @@ def _real_main(argv=None):
|
||||||
# contents
|
# contents
|
||||||
if opts.xattrs:
|
if opts.xattrs:
|
||||||
postprocessors.append({'key': 'XAttrMetadata'})
|
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.
|
# 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.
|
# So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
|
||||||
if opts.exec_cmd:
|
if opts.exec_cmd:
|
||||||
|
|
|
@ -351,7 +351,7 @@ class FileDownloader(object):
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'total_bytes': os.path.getsize(encodeFilename(filename)),
|
'total_bytes': os.path.getsize(encodeFilename(filename)),
|
||||||
})
|
})
|
||||||
return True
|
return True, False
|
||||||
|
|
||||||
if subtitle is False:
|
if subtitle is False:
|
||||||
min_sleep_interval = self.params.get('sleep_interval')
|
min_sleep_interval = self.params.get('sleep_interval')
|
||||||
|
@ -372,7 +372,7 @@ class FileDownloader(object):
|
||||||
'[download] Sleeping %s seconds...' % (
|
'[download] Sleeping %s seconds...' % (
|
||||||
sleep_interval_sub))
|
sleep_interval_sub))
|
||||||
time.sleep(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):
|
def real_download(self, filename, info_dict):
|
||||||
"""Real download process. Redefine in subclasses."""
|
"""Real download process. Redefine in subclasses."""
|
||||||
|
|
|
@ -946,6 +946,31 @@ def parseOpts(overrideArguments=None):
|
||||||
metavar='FORMAT', dest='convertsubtitles', default=None,
|
metavar='FORMAT', dest='convertsubtitles', default=None,
|
||||||
help='Convert the subtitles to other format (currently supported: srt|ass|vtt|lrc)')
|
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 = optparse.OptionGroup(parser, 'Extractor Options')
|
||||||
extractor.add_option(
|
extractor.add_option(
|
||||||
'--allow-dynamic-mpd',
|
'--allow-dynamic-mpd',
|
||||||
|
|
|
@ -17,6 +17,7 @@ from .ffmpeg import (
|
||||||
from .xattrpp import XAttrMetadataPP
|
from .xattrpp import XAttrMetadataPP
|
||||||
from .execafterdownload import ExecAfterDownloadPP
|
from .execafterdownload import ExecAfterDownloadPP
|
||||||
from .metadatafromtitle import MetadataFromTitlePP
|
from .metadatafromtitle import MetadataFromTitlePP
|
||||||
|
from .sponskrub import SponSkrubPP
|
||||||
|
|
||||||
|
|
||||||
def get_postprocessor(key):
|
def get_postprocessor(key):
|
||||||
|
@ -38,5 +39,6 @@ __all__ = [
|
||||||
'FFmpegVideoConvertorPP',
|
'FFmpegVideoConvertorPP',
|
||||||
'FFmpegVideoRemuxerPP',
|
'FFmpegVideoRemuxerPP',
|
||||||
'MetadataFromTitlePP',
|
'MetadataFromTitlePP',
|
||||||
|
'SponSkrubPP',
|
||||||
'XAttrMetadataPP',
|
'XAttrMetadataPP',
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
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
|
Loading…
Reference in New Issue