[ModifyChapters] Allow removing sections by timestamp

Eg: --remove-chapters "*10:15-15:00".
The `*` prefix is used so as to avoid any conflicts with other valid regex
This commit is contained in:
pukkandan 2021-10-18 16:03:05 +05:30
parent e820fbaa6f
commit 2d9ec70423
No known key found for this signature in database
GPG key ID: 0F00D95A001F4698
4 changed files with 30 additions and 5 deletions

View file

@ -847,7 +847,11 @@ ## Post-Processing Options:
--no-split-chapters Do not split video based on chapters --no-split-chapters Do not split video based on chapters
(default) (default)
--remove-chapters REGEX Remove chapters whose title matches the --remove-chapters REGEX Remove chapters whose title matches the
given regular expression. This option can given regular expression. Time ranges
prefixed by a "*" can also be used in place
of chapters to remove the specified range.
Eg: --remove-chapters "*10:15-15:00"
--remove-chapters "intro". This option can
be used multiple times be used multiple times
--no-remove-chapters Do not remove any chapters from the file --no-remove-chapters Do not remove any chapters from the file
(default) (default)

View file

@ -31,6 +31,7 @@
expand_path, expand_path,
match_filter_func, match_filter_func,
MaxDownloadsReached, MaxDownloadsReached,
parse_duration,
preferredencoding, preferredencoding,
read_batch_urls, read_batch_urls,
RejectedVideoReached, RejectedVideoReached,
@ -490,8 +491,14 @@ def report_conflict(arg1, arg2):
if opts.allsubtitles and not opts.writeautomaticsub: if opts.allsubtitles and not opts.writeautomaticsub:
opts.writesubtitles = True opts.writesubtitles = True
# ModifyChapters must run before FFmpegMetadataPP # ModifyChapters must run before FFmpegMetadataPP
remove_chapters_patterns = [] remove_chapters_patterns, remove_ranges = [], []
for regex in opts.remove_chapters: for regex in opts.remove_chapters:
if regex.startswith('*'):
dur = list(map(parse_duration, regex[1:].split('-')))
if len(dur) == 2 and all(t is not None for t in dur):
remove_ranges.append(tuple(dur))
continue
parser.error(f'invalid --remove-chapters time range {regex!r}. Must be of the form ?start-end')
try: try:
remove_chapters_patterns.append(re.compile(regex)) remove_chapters_patterns.append(re.compile(regex))
except re.error as err: except re.error as err:
@ -501,6 +508,7 @@ def report_conflict(arg1, arg2):
'key': 'ModifyChapters', 'key': 'ModifyChapters',
'remove_chapters_patterns': remove_chapters_patterns, 'remove_chapters_patterns': remove_chapters_patterns,
'remove_sponsor_segments': opts.sponsorblock_remove, 'remove_sponsor_segments': opts.sponsorblock_remove,
'remove_ranges': remove_ranges,
'sponsorblock_chapter_title': opts.sponsorblock_chapter_title, 'sponsorblock_chapter_title': opts.sponsorblock_chapter_title,
'force_keyframes': opts.force_keyframes_at_cuts 'force_keyframes': opts.force_keyframes_at_cuts
}) })

View file

@ -1378,7 +1378,11 @@ def _dict_from_options_callback(
postproc.add_option( postproc.add_option(
'--remove-chapters', '--remove-chapters',
metavar='REGEX', dest='remove_chapters', action='append', metavar='REGEX', dest='remove_chapters', action='append',
help='Remove chapters whose title matches the given regular expression. This option can be used multiple times') help=(
'Remove chapters whose title matches the given regular expression. '
'Time ranges prefixed by a "*" can also be used in place of chapters to remove the specified range. '
'Eg: --remove-chapters "*10:15-15:00" --remove-chapters "intro". '
'This option can be used multiple times'))
postproc.add_option( postproc.add_option(
'--no-remove-chapters', dest='remove_chapters', action='store_const', const=None, '--no-remove-chapters', dest='remove_chapters', action='store_const', const=None,
help='Do not remove any chapters from the file (default)') help='Do not remove any chapters from the file (default)')

View file

@ -20,11 +20,12 @@
class ModifyChaptersPP(FFmpegPostProcessor): class ModifyChaptersPP(FFmpegPostProcessor):
def __init__(self, downloader, remove_chapters_patterns=None, remove_sponsor_segments=None, def __init__(self, downloader, remove_chapters_patterns=None, remove_sponsor_segments=None, remove_ranges=None,
sponsorblock_chapter_title=DEFAULT_SPONSORBLOCK_CHAPTER_TITLE, force_keyframes=False): *, sponsorblock_chapter_title=DEFAULT_SPONSORBLOCK_CHAPTER_TITLE, force_keyframes=False):
FFmpegPostProcessor.__init__(self, downloader) FFmpegPostProcessor.__init__(self, downloader)
self._remove_chapters_patterns = set(remove_chapters_patterns or []) self._remove_chapters_patterns = set(remove_chapters_patterns or [])
self._remove_sponsor_segments = set(remove_sponsor_segments or []) self._remove_sponsor_segments = set(remove_sponsor_segments or [])
self._ranges_to_remove = set(remove_ranges or [])
self._sponsorblock_chapter_title = sponsorblock_chapter_title self._sponsorblock_chapter_title = sponsorblock_chapter_title
self._force_keyframes = force_keyframes self._force_keyframes = force_keyframes
@ -97,6 +98,14 @@ def _mark_chapters_to_remove(self, chapters, sponsor_chapters):
if warn_no_chapter_to_remove: if warn_no_chapter_to_remove:
self.to_screen('There are no matching SponsorBlock chapters') self.to_screen('There are no matching SponsorBlock chapters')
sponsor_chapters.extend({
'start_time': start,
'end_time': end,
'category': 'manually_removed',
'_categories': [('manually_removed', start, end)],
'remove': True,
} for start, end in self._ranges_to_remove)
return chapters, sponsor_chapters return chapters, sponsor_chapters
def _get_supported_subs(self, info): def _get_supported_subs(self, info):