]> jfr.im git - yt-dlp.git/commitdiff
Sponskrub integration
authorpukkandan <redacted>
Sun, 15 Nov 2020 00:28:41 +0000 (05:58 +0530)
committerpukkandan <redacted>
Mon, 4 Jan 2021 17:43:27 +0000 (23:13 +0530)
README.md
youtube_dlc/YoutubeDL.py
youtube_dlc/__init__.py
youtube_dlc/downloader/common.py
youtube_dlc/options.py
youtube_dlc/postprocessor/__init__.py
youtube_dlc/postprocessor/sponskrub.py [new file with mode: 0644]

index 0681869c7293468a72c5ca5153bcfc1a9c6d450d..20d801555529da2b9c810a3c3a50d4aa7c6b6e9c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -523,6 +523,19 @@ ## Post-processing Options:
     --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
 
index cbfb03c7bc978e0e1d0041a758e53971c5922c7e..2cc02e46fad4e8f28447e789a6f3dc491e8c7d0a 100644 (file)
@@ -2110,13 +2110,16 @@ def compatible_formats(formats):
                             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
index 72dd40a56fd7dada59dd72381e5182c3f5907a29..dd8925d68da8711d78038f458a60694edde623ff 100644 (file)
@@ -310,6 +310,17 @@ def parse_retries(retries):
     # 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:
index 7d303be1cf28b2124dc7f5c043295f41850e227b..a0acb655622acdbc1d16531cbcc926b2040353c6 100644 (file)
@@ -351,7 +351,7 @@ def download(self, filename, info_dict, subtitle=False):
                     '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')
@@ -372,7 +372,7 @@ def download(self, filename, info_dict, subtitle=False):
                     '[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."""
index f2878e4685c6eea515554b765b1da3c1ed347119..093b71a21165e0eddfcaef0c88ca86a57e7a2a81 100644 (file)
@@ -946,6 +946,31 @@ def _comma_separated_values_options_callback(option, opt_str, value, parser):
         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',
index 2c470282378cbca61b29ef6e0921cde1d7d92688..e160909a70f3f81dba29ee88482d7b93f9a8ef95 100644 (file)
@@ -17,6 +17,7 @@
 from .xattrpp import XAttrMetadataPP
 from .execafterdownload import ExecAfterDownloadPP
 from .metadatafromtitle import MetadataFromTitlePP
+from .sponskrub import SponSkrubPP
 
 
 def get_postprocessor(key):
@@ -38,5 +39,6 @@ def get_postprocessor(key):
     'FFmpegVideoConvertorPP',
     'FFmpegVideoRemuxerPP',
     'MetadataFromTitlePP',
+    'SponSkrubPP',
     'XAttrMetadataPP',
 ]
diff --git a/youtube_dlc/postprocessor/sponskrub.py b/youtube_dlc/postprocessor/sponskrub.py
new file mode 100644 (file)
index 0000000..8ef6120
--- /dev/null
@@ -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