]> jfr.im git - yt-dlp.git/blob - test/test_postprocessors.py
Native SponsorBlock implementation and related improvements (#360)
[yt-dlp.git] / test / test_postprocessors.py
1 #!/usr/bin/env python3
2
3 from __future__ import unicode_literals
4
5 # Allow direct execution
6 import os
7 import sys
8 import unittest
9
10 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11
12 from yt_dlp import YoutubeDL
13 from yt_dlp.compat import compat_shlex_quote
14 from yt_dlp.postprocessor import (
15 ExecPP,
16 FFmpegThumbnailsConvertorPP,
17 MetadataFromFieldPP,
18 MetadataParserPP,
19 ModifyChaptersPP
20 )
21
22
23 class TestMetadataFromField(unittest.TestCase):
24
25 def test_format_to_regex(self):
26 self.assertEqual(
27 MetadataParserPP.format_to_regex('%(title)s - %(artist)s'),
28 r'(?P<title>.+)\ \-\ (?P<artist>.+)')
29 self.assertEqual(MetadataParserPP.format_to_regex(r'(?P<x>.+)'), r'(?P<x>.+)')
30
31 def test_field_to_template(self):
32 self.assertEqual(MetadataParserPP.field_to_template('title'), '%(title)s')
33 self.assertEqual(MetadataParserPP.field_to_template('1'), '1')
34 self.assertEqual(MetadataParserPP.field_to_template('foo bar'), 'foo bar')
35 self.assertEqual(MetadataParserPP.field_to_template(' literal'), ' literal')
36
37 def test_metadatafromfield(self):
38 self.assertEqual(
39 MetadataFromFieldPP.to_action('%(title)s \\: %(artist)s:%(title)s : %(artist)s'),
40 (MetadataParserPP.Actions.INTERPRET, '%(title)s : %(artist)s', '%(title)s : %(artist)s'))
41
42
43 class TestConvertThumbnail(unittest.TestCase):
44 def test_escaping(self):
45 pp = FFmpegThumbnailsConvertorPP()
46 if not pp.available:
47 print('Skipping: ffmpeg not found')
48 return
49
50 file = 'test/testdata/thumbnails/foo %d bar/foo_%d.{}'
51 tests = (('webp', 'png'), ('png', 'jpg'))
52
53 for inp, out in tests:
54 out_file = file.format(out)
55 if os.path.exists(out_file):
56 os.remove(out_file)
57 pp.convert_thumbnail(file.format(inp), out)
58 assert os.path.exists(out_file)
59
60 for _, out in tests:
61 os.remove(file.format(out))
62
63
64 class TestExec(unittest.TestCase):
65 def test_parse_cmd(self):
66 pp = ExecPP(YoutubeDL(), '')
67 info = {'filepath': 'file name'}
68 cmd = 'echo %s' % compat_shlex_quote(info['filepath'])
69
70 self.assertEqual(pp.parse_cmd('echo', info), cmd)
71 self.assertEqual(pp.parse_cmd('echo {}', info), cmd)
72 self.assertEqual(pp.parse_cmd('echo %(filepath)q', info), cmd)
73
74
75 class TestModifyChaptersPP(unittest.TestCase):
76 def setUp(self):
77 self._pp = ModifyChaptersPP(YoutubeDL())
78
79 @staticmethod
80 def _sponsor_chapter(start, end, cat, remove=False):
81 c = {'start_time': start, 'end_time': end, '_categories': [(cat, start, end)]}
82 if remove:
83 c['remove'] = True
84 return c
85
86 @staticmethod
87 def _chapter(start, end, title=None, remove=False):
88 c = {'start_time': start, 'end_time': end}
89 if title is not None:
90 c['title'] = title
91 if remove:
92 c['remove'] = True
93 return c
94
95 def _chapters(self, ends, titles):
96 self.assertEqual(len(ends), len(titles))
97 start = 0
98 chapters = []
99 for e, t in zip(ends, titles):
100 chapters.append(self._chapter(start, e, t))
101 start = e
102 return chapters
103
104 def _remove_marked_arrange_sponsors_test_impl(
105 self, chapters, expected_chapters, expected_removed):
106 actual_chapters, actual_removed = (
107 self._pp._remove_marked_arrange_sponsors(chapters))
108 for c in actual_removed:
109 c.pop('title', None)
110 c.pop('_categories', None)
111 actual_chapters = [{
112 'start_time': c['start_time'],
113 'end_time': c['end_time'],
114 'title': c['title'],
115 } for c in actual_chapters]
116 self.assertSequenceEqual(expected_chapters, actual_chapters)
117 self.assertSequenceEqual(expected_removed, actual_removed)
118
119 def test_remove_marked_arrange_sponsors_CanGetThroughUnaltered(self):
120 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
121 self._remove_marked_arrange_sponsors_test_impl(chapters, chapters, [])
122
123 def test_remove_marked_arrange_sponsors_ChapterWithSponsors(self):
124 chapters = self._chapters([70], ['c']) + [
125 self._sponsor_chapter(10, 20, 'sponsor'),
126 self._sponsor_chapter(30, 40, 'preview'),
127 self._sponsor_chapter(50, 60, 'sponsor')]
128 expected = self._chapters(
129 [10, 20, 30, 40, 50, 60, 70],
130 ['c', '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Preview/Recap',
131 'c', '[SponsorBlock]: Sponsor', 'c'])
132 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
133
134 def test_remove_marked_arrange_sponsors_UniqueNamesForOverlappingSponsors(self):
135 chapters = self._chapters([120], ['c']) + [
136 self._sponsor_chapter(10, 45, 'sponsor'), self._sponsor_chapter(20, 40, 'selfpromo'),
137 self._sponsor_chapter(50, 70, 'sponsor'), self._sponsor_chapter(60, 85, 'selfpromo'),
138 self._sponsor_chapter(90, 120, 'selfpromo'), self._sponsor_chapter(100, 110, 'sponsor')]
139 expected = self._chapters(
140 [10, 20, 40, 45, 50, 60, 70, 85, 90, 100, 110, 120],
141 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
142 '[SponsorBlock]: Sponsor',
143 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
144 '[SponsorBlock]: Unpaid/Self Promotion',
145 'c', '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Sponsor',
146 '[SponsorBlock]: Unpaid/Self Promotion'])
147 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
148
149 def test_remove_marked_arrange_sponsors_ChapterWithCuts(self):
150 cuts = [self._chapter(10, 20, remove=True),
151 self._sponsor_chapter(30, 40, 'sponsor', remove=True),
152 self._chapter(50, 60, remove=True)]
153 chapters = self._chapters([70], ['c']) + cuts
154 self._remove_marked_arrange_sponsors_test_impl(
155 chapters, self._chapters([40], ['c']), cuts)
156
157 def test_remove_marked_arrange_sponsors_ChapterWithSponsorsAndCuts(self):
158 chapters = self._chapters([70], ['c']) + [
159 self._sponsor_chapter(10, 20, 'sponsor'),
160 self._sponsor_chapter(30, 40, 'selfpromo', remove=True),
161 self._sponsor_chapter(50, 60, 'interaction')]
162 expected = self._chapters([10, 20, 40, 50, 60],
163 ['c', '[SponsorBlock]: Sponsor', 'c',
164 '[SponsorBlock]: Interaction Reminder', 'c'])
165 self._remove_marked_arrange_sponsors_test_impl(
166 chapters, expected, [self._chapter(30, 40, remove=True)])
167
168 def test_remove_marked_arrange_sponsors_ChapterWithSponsorCutInTheMiddle(self):
169 cuts = [self._sponsor_chapter(20, 30, 'selfpromo', remove=True),
170 self._chapter(40, 50, remove=True)]
171 chapters = self._chapters([70], ['c']) + [self._sponsor_chapter(10, 60, 'sponsor')] + cuts
172 expected = self._chapters(
173 [10, 40, 50], ['c', '[SponsorBlock]: Sponsor', 'c'])
174 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
175
176 def test_remove_marked_arrange_sponsors_ChapterWithCutHidingSponsor(self):
177 cuts = [self._sponsor_chapter(20, 50, 'selpromo', remove=True)]
178 chapters = self._chapters([60], ['c']) + [
179 self._sponsor_chapter(10, 20, 'intro'),
180 self._sponsor_chapter(30, 40, 'sponsor'),
181 self._sponsor_chapter(50, 60, 'outro'),
182 ] + cuts
183 expected = self._chapters(
184 [10, 20, 30], ['c', '[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
185 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
186
187 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentSponsors(self):
188 chapters = self._chapters([70], ['c']) + [
189 self._sponsor_chapter(10, 20, 'sponsor'),
190 self._sponsor_chapter(20, 30, 'selfpromo'),
191 self._sponsor_chapter(30, 40, 'interaction')]
192 expected = self._chapters(
193 [10, 20, 30, 40, 70],
194 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
195 '[SponsorBlock]: Interaction Reminder', 'c'])
196 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
197
198 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentCuts(self):
199 chapters = self._chapters([70], ['c']) + [
200 self._sponsor_chapter(10, 20, 'sponsor'),
201 self._sponsor_chapter(20, 30, 'interaction', remove=True),
202 self._chapter(30, 40, remove=True),
203 self._sponsor_chapter(40, 50, 'selpromo', remove=True),
204 self._sponsor_chapter(50, 60, 'interaction')]
205 expected = self._chapters([10, 20, 30, 40],
206 ['c', '[SponsorBlock]: Sponsor',
207 '[SponsorBlock]: Interaction Reminder', 'c'])
208 self._remove_marked_arrange_sponsors_test_impl(
209 chapters, expected, [self._chapter(20, 50, remove=True)])
210
211 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingSponsors(self):
212 chapters = self._chapters([70], ['c']) + [
213 self._sponsor_chapter(10, 30, 'sponsor'),
214 self._sponsor_chapter(20, 50, 'selfpromo'),
215 self._sponsor_chapter(40, 60, 'interaction')]
216 expected = self._chapters(
217 [10, 20, 30, 40, 50, 60, 70],
218 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
219 '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
220 '[SponsorBlock]: Interaction Reminder', 'c'])
221 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
222
223 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingCuts(self):
224 chapters = self._chapters([70], ['c']) + [
225 self._sponsor_chapter(10, 30, 'sponsor', remove=True),
226 self._sponsor_chapter(20, 50, 'selfpromo', remove=True),
227 self._sponsor_chapter(40, 60, 'interaction', remove=True)]
228 self._remove_marked_arrange_sponsors_test_impl(
229 chapters, self._chapters([20], ['c']), [self._chapter(10, 60, remove=True)])
230
231 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsors(self):
232 chapters = self._chapters([170], ['c']) + [
233 self._sponsor_chapter(0, 30, 'intro'),
234 self._sponsor_chapter(20, 50, 'sponsor'),
235 self._sponsor_chapter(40, 60, 'selfpromo'),
236 self._sponsor_chapter(70, 90, 'sponsor'),
237 self._sponsor_chapter(80, 100, 'sponsor'),
238 self._sponsor_chapter(90, 110, 'sponsor'),
239 self._sponsor_chapter(120, 140, 'selfpromo'),
240 self._sponsor_chapter(130, 160, 'interaction'),
241 self._sponsor_chapter(150, 170, 'outro')]
242 expected = self._chapters(
243 [20, 30, 40, 50, 60, 70, 110, 120, 130, 140, 150, 160, 170],
244 ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Intermission/Intro Animation, Sponsor', '[SponsorBlock]: Sponsor',
245 '[SponsorBlock]: Sponsor, Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion', 'c',
246 '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
247 '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
248 '[SponsorBlock]: Interaction Reminder',
249 '[SponsorBlock]: Interaction Reminder, Endcards/Credits', '[SponsorBlock]: Endcards/Credits'])
250 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
251
252 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingCuts(self):
253 chapters = self._chapters([170], ['c']) + [
254 self._chapter(0, 30, remove=True),
255 self._sponsor_chapter(20, 50, 'sponsor', remove=True),
256 self._chapter(40, 60, remove=True),
257 self._sponsor_chapter(70, 90, 'sponsor', remove=True),
258 self._chapter(80, 100, remove=True),
259 self._chapter(90, 110, remove=True),
260 self._sponsor_chapter(120, 140, 'sponsor', remove=True),
261 self._sponsor_chapter(130, 160, 'selfpromo', remove=True),
262 self._chapter(150, 170, remove=True)]
263 expected_cuts = [self._chapter(0, 60, remove=True),
264 self._chapter(70, 110, remove=True),
265 self._chapter(120, 170, remove=True)]
266 self._remove_marked_arrange_sponsors_test_impl(
267 chapters, self._chapters([20], ['c']), expected_cuts)
268
269 def test_remove_marked_arrange_sponsors_OverlappingSponsorsDifferentTitlesAfterCut(self):
270 chapters = self._chapters([60], ['c']) + [
271 self._sponsor_chapter(10, 60, 'sponsor'),
272 self._sponsor_chapter(10, 40, 'intro'),
273 self._sponsor_chapter(30, 50, 'interaction'),
274 self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
275 self._sponsor_chapter(40, 50, 'interaction'),
276 self._sponsor_chapter(50, 60, 'outro')]
277 expected = self._chapters(
278 [10, 30, 40], ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Endcards/Credits'])
279 self._remove_marked_arrange_sponsors_test_impl(
280 chapters, expected, [self._chapter(30, 50, remove=True)])
281
282 def test_remove_marked_arrange_sponsors_SponsorsNoLongerOverlapAfterCut(self):
283 chapters = self._chapters([70], ['c']) + [
284 self._sponsor_chapter(10, 30, 'sponsor'),
285 self._sponsor_chapter(20, 50, 'interaction'),
286 self._sponsor_chapter(30, 50, 'selpromo', remove=True),
287 self._sponsor_chapter(40, 60, 'sponsor'),
288 self._sponsor_chapter(50, 60, 'interaction')]
289 expected = self._chapters(
290 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
291 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
292 self._remove_marked_arrange_sponsors_test_impl(
293 chapters, expected, [self._chapter(30, 50, remove=True)])
294
295 def test_remove_marked_arrange_sponsors_SponsorsStillOverlapAfterCut(self):
296 chapters = self._chapters([70], ['c']) + [
297 self._sponsor_chapter(10, 60, 'sponsor'),
298 self._sponsor_chapter(20, 60, 'interaction'),
299 self._sponsor_chapter(30, 50, 'selfpromo', remove=True)]
300 expected = self._chapters(
301 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
302 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
303 self._remove_marked_arrange_sponsors_test_impl(
304 chapters, expected, [self._chapter(30, 50, remove=True)])
305
306 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsorsAndCuts(self):
307 chapters = self._chapters([200], ['c']) + [
308 self._sponsor_chapter(10, 40, 'sponsor'),
309 self._sponsor_chapter(10, 30, 'intro'),
310 self._chapter(20, 30, remove=True),
311 self._sponsor_chapter(30, 40, 'selfpromo'),
312 self._sponsor_chapter(50, 70, 'sponsor'),
313 self._sponsor_chapter(60, 80, 'interaction'),
314 self._chapter(70, 80, remove=True),
315 self._sponsor_chapter(70, 90, 'sponsor'),
316 self._sponsor_chapter(80, 100, 'interaction'),
317 self._sponsor_chapter(120, 170, 'selfpromo'),
318 self._sponsor_chapter(130, 180, 'outro'),
319 self._chapter(140, 150, remove=True),
320 self._chapter(150, 160, remove=True)]
321 expected = self._chapters(
322 [10, 20, 30, 40, 50, 70, 80, 100, 110, 130, 140, 160],
323 ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
324 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Interaction Reminder',
325 '[SponsorBlock]: Interaction Reminder', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
326 '[SponsorBlock]: Unpaid/Self Promotion, Endcards/Credits', '[SponsorBlock]: Endcards/Credits', 'c'])
327 expected_cuts = [self._chapter(20, 30, remove=True),
328 self._chapter(70, 80, remove=True),
329 self._chapter(140, 160, remove=True)]
330 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, expected_cuts)
331
332 def test_remove_marked_arrange_sponsors_SponsorOverlapsMultipleChapters(self):
333 chapters = (self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5'])
334 + [self._sponsor_chapter(10, 90, 'sponsor')])
335 expected = self._chapters([10, 90, 100], ['c1', '[SponsorBlock]: Sponsor', 'c5'])
336 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
337
338 def test_remove_marked_arrange_sponsors_CutOverlapsMultipleChapters(self):
339 cuts = [self._chapter(10, 90, remove=True)]
340 chapters = self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5']) + cuts
341 expected = self._chapters([10, 20], ['c1', 'c5'])
342 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
343
344 def test_remove_marked_arrange_sponsors_SponsorsWithinSomeChaptersAndOverlappingOthers(self):
345 chapters = (self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4'])
346 + [self._sponsor_chapter(20, 30, 'sponsor'),
347 self._sponsor_chapter(50, 70, 'selfpromo')])
348 expected = self._chapters([10, 20, 30, 40, 50, 70, 80],
349 ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c2', 'c3',
350 '[SponsorBlock]: Unpaid/Self Promotion', 'c4'])
351 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
352
353 def test_remove_marked_arrange_sponsors_CutsWithinSomeChaptersAndOverlappingOthers(self):
354 cuts = [self._chapter(20, 30, remove=True), self._chapter(50, 70, remove=True)]
355 chapters = self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4']) + cuts
356 expected = self._chapters([10, 30, 40, 50], ['c1', 'c2', 'c3', 'c4'])
357 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
358
359 def test_remove_marked_arrange_sponsors_ChaptersAfterLastSponsor(self):
360 chapters = (self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4'])
361 + [self._sponsor_chapter(10, 30, 'music_offtopic')])
362 expected = self._chapters(
363 [10, 30, 40, 50, 60],
364 ['c1', '[SponsorBlock]: Non-Music Section', 'c2', 'c3', 'c4'])
365 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
366
367 def test_remove_marked_arrange_sponsors_ChaptersAfterLastCut(self):
368 cuts = [self._chapter(10, 30, remove=True)]
369 chapters = self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4']) + cuts
370 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
371 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
372
373 def test_remove_marked_arrange_sponsors_SponsorStartsAtChapterStart(self):
374 chapters = (self._chapters([10, 20, 40], ['c1', 'c2', 'c3'])
375 + [self._sponsor_chapter(20, 30, 'sponsor')])
376 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
377 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
378
379 def test_remove_marked_arrange_sponsors_CutStartsAtChapterStart(self):
380 cuts = [self._chapter(20, 30, remove=True)]
381 chapters = self._chapters([10, 20, 40], ['c1', 'c2', 'c3']) + cuts
382 expected = self._chapters([10, 20, 30], ['c1', 'c2', 'c3'])
383 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
384
385 def test_remove_marked_arrange_sponsors_SponsorEndsAtChapterEnd(self):
386 chapters = (self._chapters([10, 30, 40], ['c1', 'c2', 'c3'])
387 + [self._sponsor_chapter(20, 30, 'sponsor')])
388 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
389 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
390
391 def test_remove_marked_arrange_sponsors_CutEndsAtChapterEnd(self):
392 cuts = [self._chapter(20, 30, remove=True)]
393 chapters = self._chapters([10, 30, 40], ['c1', 'c2', 'c3']) + cuts
394 expected = self._chapters([10, 20, 30], ['c1', 'c2', 'c3'])
395 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
396
397 def test_remove_marked_arrange_sponsors_SponsorCoincidesWithChapters(self):
398 chapters = (self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
399 + [self._sponsor_chapter(10, 30, 'sponsor')])
400 expected = self._chapters([10, 30, 40], ['c1', '[SponsorBlock]: Sponsor', 'c4'])
401 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
402
403 def test_remove_marked_arrange_sponsors_CutCoincidesWithChapters(self):
404 cuts = [self._chapter(10, 30, remove=True)]
405 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
406 expected = self._chapters([10, 20], ['c1', 'c4'])
407 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
408
409 def test_remove_marked_arrange_sponsors_SponsorsAtVideoBoundaries(self):
410 chapters = (self._chapters([20, 40, 60], ['c1', 'c2', 'c3'])
411 + [self._sponsor_chapter(0, 10, 'intro'), self._sponsor_chapter(50, 60, 'outro')])
412 expected = self._chapters(
413 [10, 20, 40, 50, 60], ['[SponsorBlock]: Intermission/Intro Animation', 'c1', 'c2', 'c3', '[SponsorBlock]: Endcards/Credits'])
414 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
415
416 def test_remove_marked_arrange_sponsors_CutsAtVideoBoundaries(self):
417 cuts = [self._chapter(0, 10, remove=True), self._chapter(50, 60, remove=True)]
418 chapters = self._chapters([20, 40, 60], ['c1', 'c2', 'c3']) + cuts
419 expected = self._chapters([10, 30, 40], ['c1', 'c2', 'c3'])
420 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
421
422 def test_remove_marked_arrange_sponsors_SponsorsOverlapChaptersAtVideoBoundaries(self):
423 chapters = (self._chapters([10, 40, 50], ['c1', 'c2', 'c3'])
424 + [self._sponsor_chapter(0, 20, 'intro'), self._sponsor_chapter(30, 50, 'outro')])
425 expected = self._chapters(
426 [20, 30, 50], ['[SponsorBlock]: Intermission/Intro Animation', 'c2', '[SponsorBlock]: Endcards/Credits'])
427 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
428
429 def test_remove_marked_arrange_sponsors_CutsOverlapChaptersAtVideoBoundaries(self):
430 cuts = [self._chapter(0, 20, remove=True), self._chapter(30, 50, remove=True)]
431 chapters = self._chapters([10, 40, 50], ['c1', 'c2', 'c3']) + cuts
432 expected = self._chapters([10], ['c2'])
433 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
434
435 def test_remove_marked_arrange_sponsors_EverythingSponsored(self):
436 chapters = (self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
437 + [self._sponsor_chapter(0, 20, 'intro'), self._sponsor_chapter(20, 40, 'outro')])
438 expected = self._chapters([20, 40], ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
439 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
440
441 def test_remove_marked_arrange_sponsors_EverythingCut(self):
442 cuts = [self._chapter(0, 20, remove=True), self._chapter(20, 40, remove=True)]
443 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
444 self._remove_marked_arrange_sponsors_test_impl(
445 chapters, [], [self._chapter(0, 40, remove=True)])
446
447 def test_remove_marked_arrange_sponsors_TinyChaptersInTheOriginalArePreserved(self):
448 chapters = self._chapters([0.1, 0.2, 0.3, 0.4], ['c1', 'c2', 'c3', 'c4'])
449 self._remove_marked_arrange_sponsors_test_impl(chapters, chapters, [])
450
451 def test_remove_marked_arrange_sponsors_TinySponsorsAreIgnored(self):
452 chapters = [self._sponsor_chapter(0, 0.1, 'intro'), self._chapter(0.1, 0.2, 'c1'),
453 self._sponsor_chapter(0.2, 0.3, 'sponsor'), self._chapter(0.3, 0.4, 'c2'),
454 self._sponsor_chapter(0.4, 0.5, 'outro')]
455 self._remove_marked_arrange_sponsors_test_impl(
456 chapters, self._chapters([0.3, 0.5], ['c1', 'c2']), [])
457
458 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromCutsAreIgnored(self):
459 cuts = [self._chapter(1.5, 2.5, remove=True)]
460 chapters = self._chapters([2, 3, 3.5], ['c1', 'c2', 'c3']) + cuts
461 self._remove_marked_arrange_sponsors_test_impl(
462 chapters, self._chapters([2, 2.5], ['c1', 'c3']), cuts)
463
464 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromSponsorOverlapAreIgnored(self):
465 chapters = self._chapters([1, 3, 4], ['c1', 'c2', 'c3']) + [
466 self._sponsor_chapter(1.5, 2.5, 'sponsor')]
467 self._remove_marked_arrange_sponsors_test_impl(
468 chapters, self._chapters([1.5, 3, 4], ['c1', '[SponsorBlock]: Sponsor', 'c3']), [])
469
470 def test_remove_marked_arrange_sponsors_TinySponsorsOverlapsAreIgnored(self):
471 chapters = self._chapters([2, 3, 5], ['c1', 'c2', 'c3']) + [
472 self._sponsor_chapter(1, 3, 'sponsor'),
473 self._sponsor_chapter(2.5, 4, 'selfpromo')
474 ]
475 self._remove_marked_arrange_sponsors_test_impl(
476 chapters, self._chapters([1, 3, 4, 5], [
477 'c1', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion', 'c3']), [])
478
479 def test_make_concat_opts_CommonCase(self):
480 sponsor_chapters = [self._chapter(1, 2, 's1'), self._chapter(10, 20, 's2')]
481 expected = '''ffconcat version 1.0
482 file 'file:test'
483 outpoint 1.000000
484 file 'file:test'
485 inpoint 2.000000
486 outpoint 10.000000
487 file 'file:test'
488 inpoint 20.000000
489 '''
490 opts = self._pp._make_concat_opts(sponsor_chapters, 30)
491 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
492
493 def test_make_concat_opts_NoZeroDurationChunkAtVideoStart(self):
494 sponsor_chapters = [self._chapter(0, 1, 's1'), self._chapter(10, 20, 's2')]
495 expected = '''ffconcat version 1.0
496 file 'file:test'
497 inpoint 1.000000
498 outpoint 10.000000
499 file 'file:test'
500 inpoint 20.000000
501 '''
502 opts = self._pp._make_concat_opts(sponsor_chapters, 30)
503 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
504
505 def test_make_concat_opts_NoZeroDurationChunkAtVideoEnd(self):
506 sponsor_chapters = [self._chapter(1, 2, 's1'), self._chapter(10, 20, 's2')]
507 expected = '''ffconcat version 1.0
508 file 'file:test'
509 outpoint 1.000000
510 file 'file:test'
511 inpoint 2.000000
512 outpoint 10.000000
513 '''
514 opts = self._pp._make_concat_opts(sponsor_chapters, 20)
515 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
516
517 def test_quote_for_concat_RunsOfQuotes(self):
518 self.assertEqual(
519 r"'special '\'' '\'\''characters'\'\'\''galore'",
520 self._pp._quote_for_ffmpeg("special ' ''characters'''galore"))
521
522 def test_quote_for_concat_QuotesAtStart(self):
523 self.assertEqual(
524 r"\'\'\''special '\'' characters '\'' galore'",
525 self._pp._quote_for_ffmpeg("'''special ' characters ' galore"))
526
527 def test_quote_for_concat_QuotesAtEnd(self):
528 self.assertEqual(
529 r"'special '\'' characters '\'' galore'\'\'\'",
530 self._pp._quote_for_ffmpeg("special ' characters ' galore'''"))