]> jfr.im git - yt-dlp.git/blob - test/test_postprocessors.py
[misc] Cleanup (#9765)
[yt-dlp.git] / test / test_postprocessors.py
1 #!/usr/bin/env python3
2
3 # Allow direct execution
4 import os
5 import sys
6 import unittest
7
8 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
10
11 from yt_dlp import YoutubeDL
12 from yt_dlp.compat import compat_shlex_quote
13 from yt_dlp.postprocessor import (
14 ExecPP,
15 FFmpegThumbnailsConvertorPP,
16 MetadataFromFieldPP,
17 MetadataParserPP,
18 ModifyChaptersPP,
19 SponsorBlockPP,
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, title=None):
81 if title is None:
82 title = SponsorBlockPP.CATEGORIES[cat]
83 return {
84 'start_time': start,
85 'end_time': end,
86 '_categories': [(cat, start, end, title)],
87 **({'remove': True} if remove else {}),
88 }
89
90 @staticmethod
91 def _chapter(start, end, title=None, remove=False):
92 c = {'start_time': start, 'end_time': end}
93 if title is not None:
94 c['title'] = title
95 if remove:
96 c['remove'] = True
97 return c
98
99 def _chapters(self, ends, titles):
100 self.assertEqual(len(ends), len(titles))
101 start = 0
102 chapters = []
103 for e, t in zip(ends, titles):
104 chapters.append(self._chapter(start, e, t))
105 start = e
106 return chapters
107
108 def _remove_marked_arrange_sponsors_test_impl(
109 self, chapters, expected_chapters, expected_removed):
110 actual_chapters, actual_removed = (
111 self._pp._remove_marked_arrange_sponsors(chapters))
112 for c in actual_removed:
113 c.pop('title', None)
114 c.pop('_categories', None)
115 actual_chapters = [{
116 'start_time': c['start_time'],
117 'end_time': c['end_time'],
118 'title': c['title'],
119 } for c in actual_chapters]
120 self.assertSequenceEqual(expected_chapters, actual_chapters)
121 self.assertSequenceEqual(expected_removed, actual_removed)
122
123 def test_remove_marked_arrange_sponsors_CanGetThroughUnaltered(self):
124 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
125 self._remove_marked_arrange_sponsors_test_impl(chapters, chapters, [])
126
127 def test_remove_marked_arrange_sponsors_ChapterWithSponsors(self):
128 chapters = self._chapters([70], ['c']) + [
129 self._sponsor_chapter(10, 20, 'sponsor'),
130 self._sponsor_chapter(30, 40, 'preview'),
131 self._sponsor_chapter(50, 60, 'filler')]
132 expected = self._chapters(
133 [10, 20, 30, 40, 50, 60, 70],
134 ['c', '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Preview/Recap',
135 'c', '[SponsorBlock]: Filler Tangent', 'c'])
136 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
137
138 def test_remove_marked_arrange_sponsors_SponsorBlockChapters(self):
139 chapters = self._chapters([70], ['c']) + [
140 self._sponsor_chapter(10, 20, 'chapter', title='sb c1'),
141 self._sponsor_chapter(15, 16, 'chapter', title='sb c2'),
142 self._sponsor_chapter(30, 40, 'preview'),
143 self._sponsor_chapter(50, 60, 'filler')]
144 expected = self._chapters(
145 [10, 15, 16, 20, 30, 40, 50, 60, 70],
146 ['c', '[SponsorBlock]: sb c1', '[SponsorBlock]: sb c1, sb c2', '[SponsorBlock]: sb c1',
147 'c', '[SponsorBlock]: Preview/Recap',
148 'c', '[SponsorBlock]: Filler Tangent', 'c'])
149 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
150
151 def test_remove_marked_arrange_sponsors_UniqueNamesForOverlappingSponsors(self):
152 chapters = self._chapters([120], ['c']) + [
153 self._sponsor_chapter(10, 45, 'sponsor'), self._sponsor_chapter(20, 40, 'selfpromo'),
154 self._sponsor_chapter(50, 70, 'sponsor'), self._sponsor_chapter(60, 85, 'selfpromo'),
155 self._sponsor_chapter(90, 120, 'selfpromo'), self._sponsor_chapter(100, 110, 'sponsor')]
156 expected = self._chapters(
157 [10, 20, 40, 45, 50, 60, 70, 85, 90, 100, 110, 120],
158 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
159 '[SponsorBlock]: Sponsor',
160 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
161 '[SponsorBlock]: Unpaid/Self Promotion',
162 'c', '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Sponsor',
163 '[SponsorBlock]: Unpaid/Self Promotion'])
164 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
165
166 def test_remove_marked_arrange_sponsors_ChapterWithCuts(self):
167 cuts = [self._chapter(10, 20, remove=True),
168 self._sponsor_chapter(30, 40, 'sponsor', remove=True),
169 self._chapter(50, 60, remove=True)]
170 chapters = self._chapters([70], ['c']) + cuts
171 self._remove_marked_arrange_sponsors_test_impl(
172 chapters, self._chapters([40], ['c']), cuts)
173
174 def test_remove_marked_arrange_sponsors_ChapterWithSponsorsAndCuts(self):
175 chapters = self._chapters([70], ['c']) + [
176 self._sponsor_chapter(10, 20, 'sponsor'),
177 self._sponsor_chapter(30, 40, 'selfpromo', remove=True),
178 self._sponsor_chapter(50, 60, 'interaction')]
179 expected = self._chapters([10, 20, 40, 50, 60],
180 ['c', '[SponsorBlock]: Sponsor', 'c',
181 '[SponsorBlock]: Interaction Reminder', 'c'])
182 self._remove_marked_arrange_sponsors_test_impl(
183 chapters, expected, [self._chapter(30, 40, remove=True)])
184
185 def test_remove_marked_arrange_sponsors_ChapterWithSponsorCutInTheMiddle(self):
186 cuts = [self._sponsor_chapter(20, 30, 'selfpromo', remove=True),
187 self._chapter(40, 50, remove=True)]
188 chapters = self._chapters([70], ['c']) + [self._sponsor_chapter(10, 60, 'sponsor')] + cuts
189 expected = self._chapters(
190 [10, 40, 50], ['c', '[SponsorBlock]: Sponsor', 'c'])
191 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
192
193 def test_remove_marked_arrange_sponsors_ChapterWithCutHidingSponsor(self):
194 cuts = [self._sponsor_chapter(20, 50, 'selfpromo', remove=True)]
195 chapters = self._chapters([60], ['c']) + [
196 self._sponsor_chapter(10, 20, 'intro'),
197 self._sponsor_chapter(30, 40, 'sponsor'),
198 self._sponsor_chapter(50, 60, 'outro'),
199 ] + cuts
200 expected = self._chapters(
201 [10, 20, 30], ['c', '[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
202 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
203
204 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentSponsors(self):
205 chapters = self._chapters([70], ['c']) + [
206 self._sponsor_chapter(10, 20, 'sponsor'),
207 self._sponsor_chapter(20, 30, 'selfpromo'),
208 self._sponsor_chapter(30, 40, 'interaction')]
209 expected = self._chapters(
210 [10, 20, 30, 40, 70],
211 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
212 '[SponsorBlock]: Interaction Reminder', 'c'])
213 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
214
215 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentCuts(self):
216 chapters = self._chapters([70], ['c']) + [
217 self._sponsor_chapter(10, 20, 'sponsor'),
218 self._sponsor_chapter(20, 30, 'interaction', remove=True),
219 self._chapter(30, 40, remove=True),
220 self._sponsor_chapter(40, 50, 'selfpromo', remove=True),
221 self._sponsor_chapter(50, 60, 'interaction')]
222 expected = self._chapters([10, 20, 30, 40],
223 ['c', '[SponsorBlock]: Sponsor',
224 '[SponsorBlock]: Interaction Reminder', 'c'])
225 self._remove_marked_arrange_sponsors_test_impl(
226 chapters, expected, [self._chapter(20, 50, remove=True)])
227
228 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingSponsors(self):
229 chapters = self._chapters([70], ['c']) + [
230 self._sponsor_chapter(10, 30, 'sponsor'),
231 self._sponsor_chapter(20, 50, 'selfpromo'),
232 self._sponsor_chapter(40, 60, 'interaction')]
233 expected = self._chapters(
234 [10, 20, 30, 40, 50, 60, 70],
235 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
236 '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
237 '[SponsorBlock]: Interaction Reminder', 'c'])
238 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
239
240 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingCuts(self):
241 chapters = self._chapters([70], ['c']) + [
242 self._sponsor_chapter(10, 30, 'sponsor', remove=True),
243 self._sponsor_chapter(20, 50, 'selfpromo', remove=True),
244 self._sponsor_chapter(40, 60, 'interaction', remove=True)]
245 self._remove_marked_arrange_sponsors_test_impl(
246 chapters, self._chapters([20], ['c']), [self._chapter(10, 60, remove=True)])
247
248 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsors(self):
249 chapters = self._chapters([170], ['c']) + [
250 self._sponsor_chapter(0, 30, 'intro'),
251 self._sponsor_chapter(20, 50, 'sponsor'),
252 self._sponsor_chapter(40, 60, 'selfpromo'),
253 self._sponsor_chapter(70, 90, 'sponsor'),
254 self._sponsor_chapter(80, 100, 'sponsor'),
255 self._sponsor_chapter(90, 110, 'sponsor'),
256 self._sponsor_chapter(120, 140, 'selfpromo'),
257 self._sponsor_chapter(130, 160, 'interaction'),
258 self._sponsor_chapter(150, 170, 'outro')]
259 expected = self._chapters(
260 [20, 30, 40, 50, 60, 70, 110, 120, 130, 140, 150, 160, 170],
261 ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Intermission/Intro Animation, Sponsor', '[SponsorBlock]: Sponsor',
262 '[SponsorBlock]: Sponsor, Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion', 'c',
263 '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
264 '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
265 '[SponsorBlock]: Interaction Reminder',
266 '[SponsorBlock]: Interaction Reminder, Endcards/Credits', '[SponsorBlock]: Endcards/Credits'])
267 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
268
269 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingCuts(self):
270 chapters = self._chapters([170], ['c']) + [
271 self._chapter(0, 30, remove=True),
272 self._sponsor_chapter(20, 50, 'sponsor', remove=True),
273 self._chapter(40, 60, remove=True),
274 self._sponsor_chapter(70, 90, 'sponsor', remove=True),
275 self._chapter(80, 100, remove=True),
276 self._chapter(90, 110, remove=True),
277 self._sponsor_chapter(120, 140, 'sponsor', remove=True),
278 self._sponsor_chapter(130, 160, 'selfpromo', remove=True),
279 self._chapter(150, 170, remove=True)]
280 expected_cuts = [self._chapter(0, 60, remove=True),
281 self._chapter(70, 110, remove=True),
282 self._chapter(120, 170, remove=True)]
283 self._remove_marked_arrange_sponsors_test_impl(
284 chapters, self._chapters([20], ['c']), expected_cuts)
285
286 def test_remove_marked_arrange_sponsors_OverlappingSponsorsDifferentTitlesAfterCut(self):
287 chapters = self._chapters([60], ['c']) + [
288 self._sponsor_chapter(10, 60, 'sponsor'),
289 self._sponsor_chapter(10, 40, 'intro'),
290 self._sponsor_chapter(30, 50, 'interaction'),
291 self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
292 self._sponsor_chapter(40, 50, 'interaction'),
293 self._sponsor_chapter(50, 60, 'outro')]
294 expected = self._chapters(
295 [10, 30, 40], ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Endcards/Credits'])
296 self._remove_marked_arrange_sponsors_test_impl(
297 chapters, expected, [self._chapter(30, 50, remove=True)])
298
299 def test_remove_marked_arrange_sponsors_SponsorsNoLongerOverlapAfterCut(self):
300 chapters = self._chapters([70], ['c']) + [
301 self._sponsor_chapter(10, 30, 'sponsor'),
302 self._sponsor_chapter(20, 50, 'interaction'),
303 self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
304 self._sponsor_chapter(40, 60, 'sponsor'),
305 self._sponsor_chapter(50, 60, 'interaction')]
306 expected = self._chapters(
307 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
308 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
309 self._remove_marked_arrange_sponsors_test_impl(
310 chapters, expected, [self._chapter(30, 50, remove=True)])
311
312 def test_remove_marked_arrange_sponsors_SponsorsStillOverlapAfterCut(self):
313 chapters = self._chapters([70], ['c']) + [
314 self._sponsor_chapter(10, 60, 'sponsor'),
315 self._sponsor_chapter(20, 60, 'interaction'),
316 self._sponsor_chapter(30, 50, 'selfpromo', remove=True)]
317 expected = self._chapters(
318 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
319 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
320 self._remove_marked_arrange_sponsors_test_impl(
321 chapters, expected, [self._chapter(30, 50, remove=True)])
322
323 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsorsAndCuts(self):
324 chapters = self._chapters([200], ['c']) + [
325 self._sponsor_chapter(10, 40, 'sponsor'),
326 self._sponsor_chapter(10, 30, 'intro'),
327 self._chapter(20, 30, remove=True),
328 self._sponsor_chapter(30, 40, 'selfpromo'),
329 self._sponsor_chapter(50, 70, 'sponsor'),
330 self._sponsor_chapter(60, 80, 'interaction'),
331 self._chapter(70, 80, remove=True),
332 self._sponsor_chapter(70, 90, 'sponsor'),
333 self._sponsor_chapter(80, 100, 'interaction'),
334 self._sponsor_chapter(120, 170, 'selfpromo'),
335 self._sponsor_chapter(130, 180, 'outro'),
336 self._chapter(140, 150, remove=True),
337 self._chapter(150, 160, remove=True)]
338 expected = self._chapters(
339 [10, 20, 30, 40, 50, 70, 80, 100, 110, 130, 140, 160],
340 ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
341 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Interaction Reminder',
342 '[SponsorBlock]: Interaction Reminder', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
343 '[SponsorBlock]: Unpaid/Self Promotion, Endcards/Credits', '[SponsorBlock]: Endcards/Credits', 'c'])
344 expected_cuts = [self._chapter(20, 30, remove=True),
345 self._chapter(70, 80, remove=True),
346 self._chapter(140, 160, remove=True)]
347 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, expected_cuts)
348
349 def test_remove_marked_arrange_sponsors_SponsorOverlapsMultipleChapters(self):
350 chapters = (self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5'])
351 + [self._sponsor_chapter(10, 90, 'sponsor')])
352 expected = self._chapters([10, 90, 100], ['c1', '[SponsorBlock]: Sponsor', 'c5'])
353 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
354
355 def test_remove_marked_arrange_sponsors_CutOverlapsMultipleChapters(self):
356 cuts = [self._chapter(10, 90, remove=True)]
357 chapters = self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5']) + cuts
358 expected = self._chapters([10, 20], ['c1', 'c5'])
359 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
360
361 def test_remove_marked_arrange_sponsors_SponsorsWithinSomeChaptersAndOverlappingOthers(self):
362 chapters = (self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4'])
363 + [self._sponsor_chapter(20, 30, 'sponsor'),
364 self._sponsor_chapter(50, 70, 'selfpromo')])
365 expected = self._chapters([10, 20, 30, 40, 50, 70, 80],
366 ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c2', 'c3',
367 '[SponsorBlock]: Unpaid/Self Promotion', 'c4'])
368 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
369
370 def test_remove_marked_arrange_sponsors_CutsWithinSomeChaptersAndOverlappingOthers(self):
371 cuts = [self._chapter(20, 30, remove=True), self._chapter(50, 70, remove=True)]
372 chapters = self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4']) + cuts
373 expected = self._chapters([10, 30, 40, 50], ['c1', 'c2', 'c3', 'c4'])
374 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
375
376 def test_remove_marked_arrange_sponsors_ChaptersAfterLastSponsor(self):
377 chapters = (self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4'])
378 + [self._sponsor_chapter(10, 30, 'music_offtopic')])
379 expected = self._chapters(
380 [10, 30, 40, 50, 60],
381 ['c1', '[SponsorBlock]: Non-Music Section', 'c2', 'c3', 'c4'])
382 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
383
384 def test_remove_marked_arrange_sponsors_ChaptersAfterLastCut(self):
385 cuts = [self._chapter(10, 30, remove=True)]
386 chapters = self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4']) + cuts
387 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
388 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
389
390 def test_remove_marked_arrange_sponsors_SponsorStartsAtChapterStart(self):
391 chapters = (self._chapters([10, 20, 40], ['c1', 'c2', 'c3'])
392 + [self._sponsor_chapter(20, 30, 'sponsor')])
393 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
394 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
395
396 def test_remove_marked_arrange_sponsors_CutStartsAtChapterStart(self):
397 cuts = [self._chapter(20, 30, remove=True)]
398 chapters = self._chapters([10, 20, 40], ['c1', 'c2', 'c3']) + cuts
399 expected = self._chapters([10, 20, 30], ['c1', 'c2', 'c3'])
400 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
401
402 def test_remove_marked_arrange_sponsors_SponsorEndsAtChapterEnd(self):
403 chapters = (self._chapters([10, 30, 40], ['c1', 'c2', 'c3'])
404 + [self._sponsor_chapter(20, 30, 'sponsor')])
405 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
406 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
407
408 def test_remove_marked_arrange_sponsors_CutEndsAtChapterEnd(self):
409 cuts = [self._chapter(20, 30, remove=True)]
410 chapters = self._chapters([10, 30, 40], ['c1', 'c2', 'c3']) + cuts
411 expected = self._chapters([10, 20, 30], ['c1', 'c2', 'c3'])
412 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
413
414 def test_remove_marked_arrange_sponsors_SponsorCoincidesWithChapters(self):
415 chapters = (self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
416 + [self._sponsor_chapter(10, 30, 'sponsor')])
417 expected = self._chapters([10, 30, 40], ['c1', '[SponsorBlock]: Sponsor', 'c4'])
418 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
419
420 def test_remove_marked_arrange_sponsors_CutCoincidesWithChapters(self):
421 cuts = [self._chapter(10, 30, remove=True)]
422 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
423 expected = self._chapters([10, 20], ['c1', 'c4'])
424 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
425
426 def test_remove_marked_arrange_sponsors_SponsorsAtVideoBoundaries(self):
427 chapters = (self._chapters([20, 40, 60], ['c1', 'c2', 'c3'])
428 + [self._sponsor_chapter(0, 10, 'intro'), self._sponsor_chapter(50, 60, 'outro')])
429 expected = self._chapters(
430 [10, 20, 40, 50, 60], ['[SponsorBlock]: Intermission/Intro Animation', 'c1', 'c2', 'c3', '[SponsorBlock]: Endcards/Credits'])
431 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
432
433 def test_remove_marked_arrange_sponsors_CutsAtVideoBoundaries(self):
434 cuts = [self._chapter(0, 10, remove=True), self._chapter(50, 60, remove=True)]
435 chapters = self._chapters([20, 40, 60], ['c1', 'c2', 'c3']) + cuts
436 expected = self._chapters([10, 30, 40], ['c1', 'c2', 'c3'])
437 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
438
439 def test_remove_marked_arrange_sponsors_SponsorsOverlapChaptersAtVideoBoundaries(self):
440 chapters = (self._chapters([10, 40, 50], ['c1', 'c2', 'c3'])
441 + [self._sponsor_chapter(0, 20, 'intro'), self._sponsor_chapter(30, 50, 'outro')])
442 expected = self._chapters(
443 [20, 30, 50], ['[SponsorBlock]: Intermission/Intro Animation', 'c2', '[SponsorBlock]: Endcards/Credits'])
444 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
445
446 def test_remove_marked_arrange_sponsors_CutsOverlapChaptersAtVideoBoundaries(self):
447 cuts = [self._chapter(0, 20, remove=True), self._chapter(30, 50, remove=True)]
448 chapters = self._chapters([10, 40, 50], ['c1', 'c2', 'c3']) + cuts
449 expected = self._chapters([10], ['c2'])
450 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
451
452 def test_remove_marked_arrange_sponsors_EverythingSponsored(self):
453 chapters = (self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
454 + [self._sponsor_chapter(0, 20, 'intro'), self._sponsor_chapter(20, 40, 'outro')])
455 expected = self._chapters([20, 40], ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
456 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
457
458 def test_remove_marked_arrange_sponsors_EverythingCut(self):
459 cuts = [self._chapter(0, 20, remove=True), self._chapter(20, 40, remove=True)]
460 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
461 self._remove_marked_arrange_sponsors_test_impl(
462 chapters, [], [self._chapter(0, 40, remove=True)])
463
464 def test_remove_marked_arrange_sponsors_TinyChaptersInTheOriginalArePreserved(self):
465 chapters = self._chapters([0.1, 0.2, 0.3, 0.4], ['c1', 'c2', 'c3', 'c4'])
466 self._remove_marked_arrange_sponsors_test_impl(chapters, chapters, [])
467
468 def test_remove_marked_arrange_sponsors_TinySponsorsAreIgnored(self):
469 chapters = [self._sponsor_chapter(0, 0.1, 'intro'), self._chapter(0.1, 0.2, 'c1'),
470 self._sponsor_chapter(0.2, 0.3, 'sponsor'), self._chapter(0.3, 0.4, 'c2'),
471 self._sponsor_chapter(0.4, 0.5, 'outro')]
472 self._remove_marked_arrange_sponsors_test_impl(
473 chapters, self._chapters([0.3, 0.5], ['c1', 'c2']), [])
474
475 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromCutsAreIgnored(self):
476 cuts = [self._chapter(1.5, 2.5, remove=True)]
477 chapters = self._chapters([2, 3, 3.5], ['c1', 'c2', 'c3']) + cuts
478 self._remove_marked_arrange_sponsors_test_impl(
479 chapters, self._chapters([2, 2.5], ['c1', 'c3']), cuts)
480
481 def test_remove_marked_arrange_sponsors_SingleTinyChapterIsPreserved(self):
482 cuts = [self._chapter(0.5, 2, remove=True)]
483 chapters = self._chapters([2], ['c']) + cuts
484 self._remove_marked_arrange_sponsors_test_impl(
485 chapters, self._chapters([0.5], ['c']), cuts)
486
487 def test_remove_marked_arrange_sponsors_TinyChapterAtTheStartPrependedToTheNext(self):
488 cuts = [self._chapter(0.5, 2, remove=True)]
489 chapters = self._chapters([2, 4], ['c1', 'c2']) + cuts
490 self._remove_marked_arrange_sponsors_test_impl(
491 chapters, self._chapters([2.5], ['c2']), cuts)
492
493 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromSponsorOverlapAreIgnored(self):
494 chapters = self._chapters([1, 3, 4], ['c1', 'c2', 'c3']) + [
495 self._sponsor_chapter(1.5, 2.5, 'sponsor')]
496 self._remove_marked_arrange_sponsors_test_impl(
497 chapters, self._chapters([1.5, 2.5, 4], ['c1', '[SponsorBlock]: Sponsor', 'c3']), [])
498
499 def test_remove_marked_arrange_sponsors_TinySponsorsOverlapsAreIgnored(self):
500 chapters = self._chapters([2, 3, 5], ['c1', 'c2', 'c3']) + [
501 self._sponsor_chapter(1, 3, 'sponsor'),
502 self._sponsor_chapter(2.5, 4, 'selfpromo')
503 ]
504 self._remove_marked_arrange_sponsors_test_impl(
505 chapters, self._chapters([1, 3, 4, 5], [
506 'c1', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion', 'c3']), [])
507
508 def test_remove_marked_arrange_sponsors_TinySponsorsPrependedToTheNextSponsor(self):
509 chapters = self._chapters([4], ['c']) + [
510 self._sponsor_chapter(1.5, 2, 'sponsor'),
511 self._sponsor_chapter(2, 4, 'selfpromo')
512 ]
513 self._remove_marked_arrange_sponsors_test_impl(
514 chapters, self._chapters([1.5, 4], ['c', '[SponsorBlock]: Unpaid/Self Promotion']), [])
515
516 def test_remove_marked_arrange_sponsors_SmallestSponsorInTheOverlapGetsNamed(self):
517 self._pp._sponsorblock_chapter_title = '[SponsorBlock]: %(name)s'
518 chapters = self._chapters([10], ['c']) + [
519 self._sponsor_chapter(2, 8, 'sponsor'),
520 self._sponsor_chapter(4, 6, 'selfpromo')
521 ]
522 self._remove_marked_arrange_sponsors_test_impl(
523 chapters, self._chapters([2, 4, 6, 8, 10], [
524 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
525 '[SponsorBlock]: Sponsor', 'c'
526 ]), [])
527
528 def test_make_concat_opts_CommonCase(self):
529 sponsor_chapters = [self._chapter(1, 2, 's1'), self._chapter(10, 20, 's2')]
530 expected = '''ffconcat version 1.0
531 file 'file:test'
532 outpoint 1.000000
533 file 'file:test'
534 inpoint 2.000000
535 outpoint 10.000000
536 file 'file:test'
537 inpoint 20.000000
538 '''
539 opts = self._pp._make_concat_opts(sponsor_chapters, 30)
540 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
541
542 def test_make_concat_opts_NoZeroDurationChunkAtVideoStart(self):
543 sponsor_chapters = [self._chapter(0, 1, 's1'), self._chapter(10, 20, 's2')]
544 expected = '''ffconcat version 1.0
545 file 'file:test'
546 inpoint 1.000000
547 outpoint 10.000000
548 file 'file:test'
549 inpoint 20.000000
550 '''
551 opts = self._pp._make_concat_opts(sponsor_chapters, 30)
552 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
553
554 def test_make_concat_opts_NoZeroDurationChunkAtVideoEnd(self):
555 sponsor_chapters = [self._chapter(1, 2, 's1'), self._chapter(10, 20, 's2')]
556 expected = '''ffconcat version 1.0
557 file 'file:test'
558 outpoint 1.000000
559 file 'file:test'
560 inpoint 2.000000
561 outpoint 10.000000
562 '''
563 opts = self._pp._make_concat_opts(sponsor_chapters, 20)
564 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
565
566 def test_quote_for_concat_RunsOfQuotes(self):
567 self.assertEqual(
568 r"'special '\'' '\'\''characters'\'\'\''galore'",
569 self._pp._quote_for_ffmpeg("special ' ''characters'''galore"))
570
571 def test_quote_for_concat_QuotesAtStart(self):
572 self.assertEqual(
573 r"\'\'\''special '\'' characters '\'' galore'",
574 self._pp._quote_for_ffmpeg("'''special ' characters ' galore"))
575
576 def test_quote_for_concat_QuotesAtEnd(self):
577 self.assertEqual(
578 r"'special '\'' characters '\'' galore'\'\'\'",
579 self._pp._quote_for_ffmpeg("special ' characters ' galore'''"))