+ cmd = 'echo %s' % compat_shlex_quote(info['filepath'])
+
+ self.assertEqual(pp.parse_cmd('echo', info), cmd)
+ self.assertEqual(pp.parse_cmd('echo {}', info), cmd)
+ self.assertEqual(pp.parse_cmd('echo %(filepath)q', info), cmd)
+
+
+class TestModifyChaptersPP(unittest.TestCase):
+ def setUp(self):
+ self._pp = ModifyChaptersPP(YoutubeDL())
+
+ @staticmethod
+ def _sponsor_chapter(start, end, cat, remove=False, title=None):
+ if title is None:
+ title = SponsorBlockPP.CATEGORIES[cat]
+ return {
+ 'start_time': start,
+ 'end_time': end,
+ '_categories': [(cat, start, end, title)],
+ **({'remove': True} if remove else {}),
+ }
+
+ @staticmethod
+ def _chapter(start, end, title=None, remove=False):
+ c = {'start_time': start, 'end_time': end}
+ if title is not None:
+ c['title'] = title
+ if remove:
+ c['remove'] = True
+ return c
+
+ def _chapters(self, ends, titles):
+ self.assertEqual(len(ends), len(titles))
+ start = 0
+ chapters = []
+ for e, t in zip(ends, titles):
+ chapters.append(self._chapter(start, e, t))
+ start = e
+ return chapters
+
+ def _remove_marked_arrange_sponsors_test_impl(
+ self, chapters, expected_chapters, expected_removed):
+ actual_chapters, actual_removed = (
+ self._pp._remove_marked_arrange_sponsors(chapters))
+ for c in actual_removed:
+ c.pop('title', None)
+ c.pop('_categories', None)
+ actual_chapters = [{
+ 'start_time': c['start_time'],
+ 'end_time': c['end_time'],
+ 'title': c['title'],
+ } for c in actual_chapters]
+ self.assertSequenceEqual(expected_chapters, actual_chapters)
+ self.assertSequenceEqual(expected_removed, actual_removed)
+
+ def test_remove_marked_arrange_sponsors_CanGetThroughUnaltered(self):
+ chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, chapters, [])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithSponsors(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 20, 'sponsor'),
+ self._sponsor_chapter(30, 40, 'preview'),
+ self._sponsor_chapter(50, 60, 'filler')]
+ expected = self._chapters(
+ [10, 20, 30, 40, 50, 60, 70],
+ ['c', '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Preview/Recap',
+ 'c', '[SponsorBlock]: Filler Tangent', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_SponsorBlockChapters(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 20, 'chapter', title='sb c1'),
+ self._sponsor_chapter(15, 16, 'chapter', title='sb c2'),
+ self._sponsor_chapter(30, 40, 'preview'),
+ self._sponsor_chapter(50, 60, 'filler')]
+ expected = self._chapters(
+ [10, 15, 16, 20, 30, 40, 50, 60, 70],
+ ['c', '[SponsorBlock]: sb c1', '[SponsorBlock]: sb c1, sb c2', '[SponsorBlock]: sb c1',
+ 'c', '[SponsorBlock]: Preview/Recap',
+ 'c', '[SponsorBlock]: Filler Tangent', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_UniqueNamesForOverlappingSponsors(self):
+ chapters = self._chapters([120], ['c']) + [
+ self._sponsor_chapter(10, 45, 'sponsor'), self._sponsor_chapter(20, 40, 'selfpromo'),
+ self._sponsor_chapter(50, 70, 'sponsor'), self._sponsor_chapter(60, 85, 'selfpromo'),
+ self._sponsor_chapter(90, 120, 'selfpromo'), self._sponsor_chapter(100, 110, 'sponsor')]
+ expected = self._chapters(
+ [10, 20, 40, 45, 50, 60, 70, 85, 90, 100, 110, 120],
+ ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
+ '[SponsorBlock]: Sponsor',
+ 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
+ '[SponsorBlock]: Unpaid/Self Promotion',
+ 'c', '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Sponsor',
+ '[SponsorBlock]: Unpaid/Self Promotion'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithCuts(self):
+ cuts = [self._chapter(10, 20, remove=True),
+ self._sponsor_chapter(30, 40, 'sponsor', remove=True),
+ self._chapter(50, 60, remove=True)]
+ chapters = self._chapters([70], ['c']) + cuts
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, self._chapters([40], ['c']), cuts)
+
+ def test_remove_marked_arrange_sponsors_ChapterWithSponsorsAndCuts(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 20, 'sponsor'),
+ self._sponsor_chapter(30, 40, 'selfpromo', remove=True),
+ self._sponsor_chapter(50, 60, 'interaction')]
+ expected = self._chapters([10, 20, 40, 50, 60],
+ ['c', '[SponsorBlock]: Sponsor', 'c',
+ '[SponsorBlock]: Interaction Reminder', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, expected, [self._chapter(30, 40, remove=True)])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithSponsorCutInTheMiddle(self):
+ cuts = [self._sponsor_chapter(20, 30, 'selfpromo', remove=True),
+ self._chapter(40, 50, remove=True)]
+ chapters = self._chapters([70], ['c']) + [self._sponsor_chapter(10, 60, 'sponsor')] + cuts
+ expected = self._chapters(
+ [10, 40, 50], ['c', '[SponsorBlock]: Sponsor', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
+
+ def test_remove_marked_arrange_sponsors_ChapterWithCutHidingSponsor(self):
+ cuts = [self._sponsor_chapter(20, 50, 'selfpromo', remove=True)]
+ chapters = self._chapters([60], ['c']) + [
+ self._sponsor_chapter(10, 20, 'intro'),
+ self._sponsor_chapter(30, 40, 'sponsor'),
+ self._sponsor_chapter(50, 60, 'outro'),
+ ] + cuts
+ expected = self._chapters(
+ [10, 20, 30], ['c', '[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
+
+ def test_remove_marked_arrange_sponsors_ChapterWithAdjacentSponsors(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 20, 'sponsor'),
+ self._sponsor_chapter(20, 30, 'selfpromo'),
+ self._sponsor_chapter(30, 40, 'interaction')]
+ expected = self._chapters(
+ [10, 20, 30, 40, 70],
+ ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
+ '[SponsorBlock]: Interaction Reminder', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithAdjacentCuts(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 20, 'sponsor'),
+ self._sponsor_chapter(20, 30, 'interaction', remove=True),
+ self._chapter(30, 40, remove=True),
+ self._sponsor_chapter(40, 50, 'selfpromo', remove=True),
+ self._sponsor_chapter(50, 60, 'interaction')]
+ expected = self._chapters([10, 20, 30, 40],
+ ['c', '[SponsorBlock]: Sponsor',
+ '[SponsorBlock]: Interaction Reminder', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, expected, [self._chapter(20, 50, remove=True)])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithOverlappingSponsors(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 30, 'sponsor'),
+ self._sponsor_chapter(20, 50, 'selfpromo'),
+ self._sponsor_chapter(40, 60, 'interaction')]
+ expected = self._chapters(
+ [10, 20, 30, 40, 50, 60, 70],
+ ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
+ '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
+ '[SponsorBlock]: Interaction Reminder', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithOverlappingCuts(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 30, 'sponsor', remove=True),
+ self._sponsor_chapter(20, 50, 'selfpromo', remove=True),
+ self._sponsor_chapter(40, 60, 'interaction', remove=True)]
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, self._chapters([20], ['c']), [self._chapter(10, 60, remove=True)])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsors(self):
+ chapters = self._chapters([170], ['c']) + [
+ self._sponsor_chapter(0, 30, 'intro'),
+ self._sponsor_chapter(20, 50, 'sponsor'),
+ self._sponsor_chapter(40, 60, 'selfpromo'),
+ self._sponsor_chapter(70, 90, 'sponsor'),
+ self._sponsor_chapter(80, 100, 'sponsor'),
+ self._sponsor_chapter(90, 110, 'sponsor'),
+ self._sponsor_chapter(120, 140, 'selfpromo'),
+ self._sponsor_chapter(130, 160, 'interaction'),
+ self._sponsor_chapter(150, 170, 'outro')]
+ expected = self._chapters(
+ [20, 30, 40, 50, 60, 70, 110, 120, 130, 140, 150, 160, 170],
+ ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Intermission/Intro Animation, Sponsor', '[SponsorBlock]: Sponsor',
+ '[SponsorBlock]: Sponsor, Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion', 'c',
+ '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
+ '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
+ '[SponsorBlock]: Interaction Reminder',
+ '[SponsorBlock]: Interaction Reminder, Endcards/Credits', '[SponsorBlock]: Endcards/Credits'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingCuts(self):
+ chapters = self._chapters([170], ['c']) + [
+ self._chapter(0, 30, remove=True),
+ self._sponsor_chapter(20, 50, 'sponsor', remove=True),
+ self._chapter(40, 60, remove=True),
+ self._sponsor_chapter(70, 90, 'sponsor', remove=True),
+ self._chapter(80, 100, remove=True),
+ self._chapter(90, 110, remove=True),
+ self._sponsor_chapter(120, 140, 'sponsor', remove=True),
+ self._sponsor_chapter(130, 160, 'selfpromo', remove=True),
+ self._chapter(150, 170, remove=True)]
+ expected_cuts = [self._chapter(0, 60, remove=True),
+ self._chapter(70, 110, remove=True),
+ self._chapter(120, 170, remove=True)]
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, self._chapters([20], ['c']), expected_cuts)
+
+ def test_remove_marked_arrange_sponsors_OverlappingSponsorsDifferentTitlesAfterCut(self):
+ chapters = self._chapters([60], ['c']) + [
+ self._sponsor_chapter(10, 60, 'sponsor'),
+ self._sponsor_chapter(10, 40, 'intro'),
+ self._sponsor_chapter(30, 50, 'interaction'),
+ self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
+ self._sponsor_chapter(40, 50, 'interaction'),
+ self._sponsor_chapter(50, 60, 'outro')]
+ expected = self._chapters(
+ [10, 30, 40], ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Endcards/Credits'])
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, expected, [self._chapter(30, 50, remove=True)])
+
+ def test_remove_marked_arrange_sponsors_SponsorsNoLongerOverlapAfterCut(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 30, 'sponsor'),
+ self._sponsor_chapter(20, 50, 'interaction'),
+ self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
+ self._sponsor_chapter(40, 60, 'sponsor'),
+ self._sponsor_chapter(50, 60, 'interaction')]
+ expected = self._chapters(
+ [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
+ '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, expected, [self._chapter(30, 50, remove=True)])
+
+ def test_remove_marked_arrange_sponsors_SponsorsStillOverlapAfterCut(self):
+ chapters = self._chapters([70], ['c']) + [
+ self._sponsor_chapter(10, 60, 'sponsor'),
+ self._sponsor_chapter(20, 60, 'interaction'),
+ self._sponsor_chapter(30, 50, 'selfpromo', remove=True)]
+ expected = self._chapters(
+ [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
+ '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
+ self._remove_marked_arrange_sponsors_test_impl(
+ chapters, expected, [self._chapter(30, 50, remove=True)])
+
+ def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsorsAndCuts(self):
+ chapters = self._chapters([200], ['c']) + [
+ self._sponsor_chapter(10, 40, 'sponsor'),
+ self._sponsor_chapter(10, 30, 'intro'),
+ self._chapter(20, 30, remove=True),
+ self._sponsor_chapter(30, 40, 'selfpromo'),
+ self._sponsor_chapter(50, 70, 'sponsor'),
+ self._sponsor_chapter(60, 80, 'interaction'),
+ self._chapter(70, 80, remove=True),
+ self._sponsor_chapter(70, 90, 'sponsor'),
+ self._sponsor_chapter(80, 100, 'interaction'),
+ self._sponsor_chapter(120, 170, 'selfpromo'),
+ self._sponsor_chapter(130, 180, 'outro'),
+ self._chapter(140, 150, remove=True),
+ self._chapter(150, 160, remove=True)]
+ expected = self._chapters(
+ [10, 20, 30, 40, 50, 70, 80, 100, 110, 130, 140, 160],
+ ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
+ 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Interaction Reminder',
+ '[SponsorBlock]: Interaction Reminder', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
+ '[SponsorBlock]: Unpaid/Self Promotion, Endcards/Credits', '[SponsorBlock]: Endcards/Credits', 'c'])
+ expected_cuts = [self._chapter(20, 30, remove=True),
+ self._chapter(70, 80, remove=True),
+ self._chapter(140, 160, remove=True)]
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, expected_cuts)
+
+ def test_remove_marked_arrange_sponsors_SponsorOverlapsMultipleChapters(self):
+ chapters = (self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5'])
+ + [self._sponsor_chapter(10, 90, 'sponsor')])
+ expected = self._chapters([10, 90, 100], ['c1', '[SponsorBlock]: Sponsor', 'c5'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_CutOverlapsMultipleChapters(self):
+ cuts = [self._chapter(10, 90, remove=True)]
+ chapters = self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5']) + cuts
+ expected = self._chapters([10, 20], ['c1', 'c5'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
+
+ def test_remove_marked_arrange_sponsors_SponsorsWithinSomeChaptersAndOverlappingOthers(self):
+ chapters = (self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4'])
+ + [self._sponsor_chapter(20, 30, 'sponsor'),
+ self._sponsor_chapter(50, 70, 'selfpromo')])
+ expected = self._chapters([10, 20, 30, 40, 50, 70, 80],
+ ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c2', 'c3',
+ '[SponsorBlock]: Unpaid/Self Promotion', 'c4'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
+
+ def test_remove_marked_arrange_sponsors_CutsWithinSomeChaptersAndOverlappingOthers(self):
+ cuts = [self._chapter(20, 30, remove=True), self._chapter(50, 70, remove=True)]
+ chapters = self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4']) + cuts
+ expected = self._chapters([10, 30, 40, 50], ['c1', 'c2', 'c3', 'c4'])
+ self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)