[ie/twitter:spaces] Support video spaces (#10789)
[yt-dlp3.git] / test / test_postprocessors.py
blob603f85c654c6451c341c8cce6e745965d0610df7
1 #!/usr/bin/env python3
3 # Allow direct execution
4 import os
5 import sys
6 import unittest
8 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11 from yt_dlp import YoutubeDL
12 from yt_dlp.utils import shell_quote
13 from yt_dlp.postprocessor import (
14 ExecPP,
15 FFmpegThumbnailsConvertorPP,
16 MetadataFromFieldPP,
17 MetadataParserPP,
18 ModifyChaptersPP,
19 SponsorBlockPP,
23 class TestMetadataFromField(unittest.TestCase):
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>.+)')
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')
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'))
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
50 file = 'test/testdata/thumbnails/foo %d bar/foo_%d.{}'
51 tests = (('webp', 'png'), ('png', 'jpg'))
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)
60 for _, out in tests:
61 os.remove(file.format(out))
64 class TestExec(unittest.TestCase):
65 def test_parse_cmd(self):
66 pp = ExecPP(YoutubeDL(), '')
67 info = {'filepath': 'file name'}
68 cmd = 'echo {}'.format(shell_quote(info['filepath']))
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)
75 class TestModifyChaptersPP(unittest.TestCase):
76 def setUp(self):
77 self._pp = ModifyChaptersPP(YoutubeDL())
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 {}),
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
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
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)
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, [])
127 def test_remove_marked_arrange_sponsors_ChapterWithSponsors(self):
128 chapters = [
129 *self._chapters([70], ['c']),
130 self._sponsor_chapter(10, 20, 'sponsor'),
131 self._sponsor_chapter(30, 40, 'preview'),
132 self._sponsor_chapter(50, 60, 'filler')]
133 expected = self._chapters(
134 [10, 20, 30, 40, 50, 60, 70],
135 ['c', '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Preview/Recap',
136 'c', '[SponsorBlock]: Filler Tangent', 'c'])
137 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
139 def test_remove_marked_arrange_sponsors_SponsorBlockChapters(self):
140 chapters = [
141 *self._chapters([70], ['c']),
142 self._sponsor_chapter(10, 20, 'chapter', title='sb c1'),
143 self._sponsor_chapter(15, 16, 'chapter', title='sb c2'),
144 self._sponsor_chapter(30, 40, 'preview'),
145 self._sponsor_chapter(50, 60, 'filler')]
146 expected = self._chapters(
147 [10, 15, 16, 20, 30, 40, 50, 60, 70],
148 ['c', '[SponsorBlock]: sb c1', '[SponsorBlock]: sb c1, sb c2', '[SponsorBlock]: sb c1',
149 'c', '[SponsorBlock]: Preview/Recap',
150 'c', '[SponsorBlock]: Filler Tangent', 'c'])
151 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
153 def test_remove_marked_arrange_sponsors_UniqueNamesForOverlappingSponsors(self):
154 chapters = [
155 *self._chapters([120], ['c']),
156 self._sponsor_chapter(10, 45, 'sponsor'),
157 self._sponsor_chapter(20, 40, 'selfpromo'),
158 self._sponsor_chapter(50, 70, 'sponsor'),
159 self._sponsor_chapter(60, 85, 'selfpromo'),
160 self._sponsor_chapter(90, 120, 'selfpromo'),
161 self._sponsor_chapter(100, 110, 'sponsor')]
162 expected = self._chapters(
163 [10, 20, 40, 45, 50, 60, 70, 85, 90, 100, 110, 120],
164 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
165 '[SponsorBlock]: Sponsor',
166 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
167 '[SponsorBlock]: Unpaid/Self Promotion',
168 'c', '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Sponsor',
169 '[SponsorBlock]: Unpaid/Self Promotion'])
170 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
172 def test_remove_marked_arrange_sponsors_ChapterWithCuts(self):
173 cuts = [self._chapter(10, 20, remove=True),
174 self._sponsor_chapter(30, 40, 'sponsor', remove=True),
175 self._chapter(50, 60, remove=True)]
176 chapters = self._chapters([70], ['c']) + cuts
177 self._remove_marked_arrange_sponsors_test_impl(
178 chapters, self._chapters([40], ['c']), cuts)
180 def test_remove_marked_arrange_sponsors_ChapterWithSponsorsAndCuts(self):
181 chapters = [
182 *self._chapters([70], ['c']),
183 self._sponsor_chapter(10, 20, 'sponsor'),
184 self._sponsor_chapter(30, 40, 'selfpromo', remove=True),
185 self._sponsor_chapter(50, 60, 'interaction')]
186 expected = self._chapters([10, 20, 40, 50, 60],
187 ['c', '[SponsorBlock]: Sponsor', 'c',
188 '[SponsorBlock]: Interaction Reminder', 'c'])
189 self._remove_marked_arrange_sponsors_test_impl(
190 chapters, expected, [self._chapter(30, 40, remove=True)])
192 def test_remove_marked_arrange_sponsors_ChapterWithSponsorCutInTheMiddle(self):
193 cuts = [self._sponsor_chapter(20, 30, 'selfpromo', remove=True),
194 self._chapter(40, 50, remove=True)]
195 chapters = [
196 *self._chapters([70], ['c']),
197 self._sponsor_chapter(10, 60, 'sponsor'),
198 *cuts]
199 expected = self._chapters(
200 [10, 40, 50], ['c', '[SponsorBlock]: Sponsor', 'c'])
201 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
203 def test_remove_marked_arrange_sponsors_ChapterWithCutHidingSponsor(self):
204 cuts = [self._sponsor_chapter(20, 50, 'selfpromo', remove=True)]
205 chapters = [
206 *self._chapters([60], ['c']),
207 self._sponsor_chapter(10, 20, 'intro'),
208 self._sponsor_chapter(30, 40, 'sponsor'),
209 self._sponsor_chapter(50, 60, 'outro'),
210 *cuts]
211 expected = self._chapters(
212 [10, 20, 30], ['c', '[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
213 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
215 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentSponsors(self):
216 chapters = [
217 *self._chapters([70], ['c']),
218 self._sponsor_chapter(10, 20, 'sponsor'),
219 self._sponsor_chapter(20, 30, 'selfpromo'),
220 self._sponsor_chapter(30, 40, 'interaction')]
221 expected = self._chapters(
222 [10, 20, 30, 40, 70],
223 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
224 '[SponsorBlock]: Interaction Reminder', 'c'])
225 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
227 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentCuts(self):
228 chapters = [
229 *self._chapters([70], ['c']),
230 self._sponsor_chapter(10, 20, 'sponsor'),
231 self._sponsor_chapter(20, 30, 'interaction', remove=True),
232 self._chapter(30, 40, remove=True),
233 self._sponsor_chapter(40, 50, 'selfpromo', remove=True),
234 self._sponsor_chapter(50, 60, 'interaction')]
235 expected = self._chapters([10, 20, 30, 40],
236 ['c', '[SponsorBlock]: Sponsor',
237 '[SponsorBlock]: Interaction Reminder', 'c'])
238 self._remove_marked_arrange_sponsors_test_impl(
239 chapters, expected, [self._chapter(20, 50, remove=True)])
241 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingSponsors(self):
242 chapters = [
243 *self._chapters([70], ['c']),
244 self._sponsor_chapter(10, 30, 'sponsor'),
245 self._sponsor_chapter(20, 50, 'selfpromo'),
246 self._sponsor_chapter(40, 60, 'interaction')]
247 expected = self._chapters(
248 [10, 20, 30, 40, 50, 60, 70],
249 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
250 '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
251 '[SponsorBlock]: Interaction Reminder', 'c'])
252 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
254 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingCuts(self):
255 chapters = [
256 *self._chapters([70], ['c']),
257 self._sponsor_chapter(10, 30, 'sponsor', remove=True),
258 self._sponsor_chapter(20, 50, 'selfpromo', remove=True),
259 self._sponsor_chapter(40, 60, 'interaction', remove=True)]
260 self._remove_marked_arrange_sponsors_test_impl(
261 chapters, self._chapters([20], ['c']), [self._chapter(10, 60, remove=True)])
263 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsors(self):
264 chapters = [
265 *self._chapters([170], ['c']),
266 self._sponsor_chapter(0, 30, 'intro'),
267 self._sponsor_chapter(20, 50, 'sponsor'),
268 self._sponsor_chapter(40, 60, 'selfpromo'),
269 self._sponsor_chapter(70, 90, 'sponsor'),
270 self._sponsor_chapter(80, 100, 'sponsor'),
271 self._sponsor_chapter(90, 110, 'sponsor'),
272 self._sponsor_chapter(120, 140, 'selfpromo'),
273 self._sponsor_chapter(130, 160, 'interaction'),
274 self._sponsor_chapter(150, 170, 'outro')]
275 expected = self._chapters(
276 [20, 30, 40, 50, 60, 70, 110, 120, 130, 140, 150, 160, 170],
277 ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Intermission/Intro Animation, Sponsor', '[SponsorBlock]: Sponsor',
278 '[SponsorBlock]: Sponsor, Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion', 'c',
279 '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
280 '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
281 '[SponsorBlock]: Interaction Reminder',
282 '[SponsorBlock]: Interaction Reminder, Endcards/Credits', '[SponsorBlock]: Endcards/Credits'])
283 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
285 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingCuts(self):
286 chapters = [
287 *self._chapters([170], ['c']),
288 self._chapter(0, 30, remove=True),
289 self._sponsor_chapter(20, 50, 'sponsor', remove=True),
290 self._chapter(40, 60, remove=True),
291 self._sponsor_chapter(70, 90, 'sponsor', remove=True),
292 self._chapter(80, 100, remove=True),
293 self._chapter(90, 110, remove=True),
294 self._sponsor_chapter(120, 140, 'sponsor', remove=True),
295 self._sponsor_chapter(130, 160, 'selfpromo', remove=True),
296 self._chapter(150, 170, remove=True)]
297 expected_cuts = [self._chapter(0, 60, remove=True),
298 self._chapter(70, 110, remove=True),
299 self._chapter(120, 170, remove=True)]
300 self._remove_marked_arrange_sponsors_test_impl(
301 chapters, self._chapters([20], ['c']), expected_cuts)
303 def test_remove_marked_arrange_sponsors_OverlappingSponsorsDifferentTitlesAfterCut(self):
304 chapters = [
305 *self._chapters([60], ['c']),
306 self._sponsor_chapter(10, 60, 'sponsor'),
307 self._sponsor_chapter(10, 40, 'intro'),
308 self._sponsor_chapter(30, 50, 'interaction'),
309 self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
310 self._sponsor_chapter(40, 50, 'interaction'),
311 self._sponsor_chapter(50, 60, 'outro')]
312 expected = self._chapters(
313 [10, 30, 40], ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Endcards/Credits'])
314 self._remove_marked_arrange_sponsors_test_impl(
315 chapters, expected, [self._chapter(30, 50, remove=True)])
317 def test_remove_marked_arrange_sponsors_SponsorsNoLongerOverlapAfterCut(self):
318 chapters = [
319 *self._chapters([70], ['c']),
320 self._sponsor_chapter(10, 30, 'sponsor'),
321 self._sponsor_chapter(20, 50, 'interaction'),
322 self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
323 self._sponsor_chapter(40, 60, 'sponsor'),
324 self._sponsor_chapter(50, 60, 'interaction')]
325 expected = self._chapters(
326 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
327 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
328 self._remove_marked_arrange_sponsors_test_impl(
329 chapters, expected, [self._chapter(30, 50, remove=True)])
331 def test_remove_marked_arrange_sponsors_SponsorsStillOverlapAfterCut(self):
332 chapters = [
333 *self._chapters([70], ['c']),
334 self._sponsor_chapter(10, 60, 'sponsor'),
335 self._sponsor_chapter(20, 60, 'interaction'),
336 self._sponsor_chapter(30, 50, 'selfpromo', remove=True)]
337 expected = self._chapters(
338 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
339 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
340 self._remove_marked_arrange_sponsors_test_impl(
341 chapters, expected, [self._chapter(30, 50, remove=True)])
343 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsorsAndCuts(self):
344 chapters = [
345 *self._chapters([200], ['c']),
346 self._sponsor_chapter(10, 40, 'sponsor'),
347 self._sponsor_chapter(10, 30, 'intro'),
348 self._chapter(20, 30, remove=True),
349 self._sponsor_chapter(30, 40, 'selfpromo'),
350 self._sponsor_chapter(50, 70, 'sponsor'),
351 self._sponsor_chapter(60, 80, 'interaction'),
352 self._chapter(70, 80, remove=True),
353 self._sponsor_chapter(70, 90, 'sponsor'),
354 self._sponsor_chapter(80, 100, 'interaction'),
355 self._sponsor_chapter(120, 170, 'selfpromo'),
356 self._sponsor_chapter(130, 180, 'outro'),
357 self._chapter(140, 150, remove=True),
358 self._chapter(150, 160, remove=True)]
359 expected = self._chapters(
360 [10, 20, 30, 40, 50, 70, 80, 100, 110, 130, 140, 160],
361 ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
362 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Interaction Reminder',
363 '[SponsorBlock]: Interaction Reminder', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
364 '[SponsorBlock]: Unpaid/Self Promotion, Endcards/Credits', '[SponsorBlock]: Endcards/Credits', 'c'])
365 expected_cuts = [self._chapter(20, 30, remove=True),
366 self._chapter(70, 80, remove=True),
367 self._chapter(140, 160, remove=True)]
368 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, expected_cuts)
370 def test_remove_marked_arrange_sponsors_SponsorOverlapsMultipleChapters(self):
371 chapters = [
372 *self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5']),
373 self._sponsor_chapter(10, 90, 'sponsor')]
374 expected = self._chapters([10, 90, 100], ['c1', '[SponsorBlock]: Sponsor', 'c5'])
375 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
377 def test_remove_marked_arrange_sponsors_CutOverlapsMultipleChapters(self):
378 cuts = [self._chapter(10, 90, remove=True)]
379 chapters = self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5']) + cuts
380 expected = self._chapters([10, 20], ['c1', 'c5'])
381 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
383 def test_remove_marked_arrange_sponsors_SponsorsWithinSomeChaptersAndOverlappingOthers(self):
384 chapters = [
385 *self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4']),
386 self._sponsor_chapter(20, 30, 'sponsor'),
387 self._sponsor_chapter(50, 70, 'selfpromo')]
388 expected = self._chapters([10, 20, 30, 40, 50, 70, 80],
389 ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c2', 'c3',
390 '[SponsorBlock]: Unpaid/Self Promotion', 'c4'])
391 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
393 def test_remove_marked_arrange_sponsors_CutsWithinSomeChaptersAndOverlappingOthers(self):
394 cuts = [self._chapter(20, 30, remove=True), self._chapter(50, 70, remove=True)]
395 chapters = self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4']) + cuts
396 expected = self._chapters([10, 30, 40, 50], ['c1', 'c2', 'c3', 'c4'])
397 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
399 def test_remove_marked_arrange_sponsors_ChaptersAfterLastSponsor(self):
400 chapters = [
401 *self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4']),
402 self._sponsor_chapter(10, 30, 'music_offtopic')]
403 expected = self._chapters(
404 [10, 30, 40, 50, 60],
405 ['c1', '[SponsorBlock]: Non-Music Section', 'c2', 'c3', 'c4'])
406 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
408 def test_remove_marked_arrange_sponsors_ChaptersAfterLastCut(self):
409 cuts = [self._chapter(10, 30, remove=True)]
410 chapters = self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4']) + cuts
411 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
412 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
414 def test_remove_marked_arrange_sponsors_SponsorStartsAtChapterStart(self):
415 chapters = [
416 *self._chapters([10, 20, 40], ['c1', 'c2', 'c3']),
417 self._sponsor_chapter(20, 30, 'sponsor')]
418 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
419 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
421 def test_remove_marked_arrange_sponsors_CutStartsAtChapterStart(self):
422 cuts = [self._chapter(20, 30, remove=True)]
423 chapters = self._chapters([10, 20, 40], ['c1', 'c2', 'c3']) + cuts
424 expected = self._chapters([10, 20, 30], ['c1', 'c2', 'c3'])
425 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
427 def test_remove_marked_arrange_sponsors_SponsorEndsAtChapterEnd(self):
428 chapters = [
429 *self._chapters([10, 30, 40], ['c1', 'c2', 'c3']),
430 self._sponsor_chapter(20, 30, 'sponsor')]
431 expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
432 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
434 def test_remove_marked_arrange_sponsors_CutEndsAtChapterEnd(self):
435 cuts = [self._chapter(20, 30, remove=True)]
436 chapters = self._chapters([10, 30, 40], ['c1', 'c2', 'c3']) + cuts
437 expected = self._chapters([10, 20, 30], ['c1', 'c2', 'c3'])
438 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
440 def test_remove_marked_arrange_sponsors_SponsorCoincidesWithChapters(self):
441 chapters = [
442 *self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']),
443 self._sponsor_chapter(10, 30, 'sponsor')]
444 expected = self._chapters([10, 30, 40], ['c1', '[SponsorBlock]: Sponsor', 'c4'])
445 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
447 def test_remove_marked_arrange_sponsors_CutCoincidesWithChapters(self):
448 cuts = [self._chapter(10, 30, remove=True)]
449 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
450 expected = self._chapters([10, 20], ['c1', 'c4'])
451 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
453 def test_remove_marked_arrange_sponsors_SponsorsAtVideoBoundaries(self):
454 chapters = [
455 *self._chapters([20, 40, 60], ['c1', 'c2', 'c3']),
456 self._sponsor_chapter(0, 10, 'intro'), self._sponsor_chapter(50, 60, 'outro')]
457 expected = self._chapters(
458 [10, 20, 40, 50, 60], ['[SponsorBlock]: Intermission/Intro Animation', 'c1', 'c2', 'c3', '[SponsorBlock]: Endcards/Credits'])
459 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
461 def test_remove_marked_arrange_sponsors_CutsAtVideoBoundaries(self):
462 cuts = [self._chapter(0, 10, remove=True), self._chapter(50, 60, remove=True)]
463 chapters = self._chapters([20, 40, 60], ['c1', 'c2', 'c3']) + cuts
464 expected = self._chapters([10, 30, 40], ['c1', 'c2', 'c3'])
465 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
467 def test_remove_marked_arrange_sponsors_SponsorsOverlapChaptersAtVideoBoundaries(self):
468 chapters = [
469 *self._chapters([10, 40, 50], ['c1', 'c2', 'c3']),
470 self._sponsor_chapter(0, 20, 'intro'),
471 self._sponsor_chapter(30, 50, 'outro')]
472 expected = self._chapters(
473 [20, 30, 50], ['[SponsorBlock]: Intermission/Intro Animation', 'c2', '[SponsorBlock]: Endcards/Credits'])
474 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
476 def test_remove_marked_arrange_sponsors_CutsOverlapChaptersAtVideoBoundaries(self):
477 cuts = [self._chapter(0, 20, remove=True), self._chapter(30, 50, remove=True)]
478 chapters = self._chapters([10, 40, 50], ['c1', 'c2', 'c3']) + cuts
479 expected = self._chapters([10], ['c2'])
480 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
482 def test_remove_marked_arrange_sponsors_EverythingSponsored(self):
483 chapters = [
484 *self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']),
485 self._sponsor_chapter(0, 20, 'intro'),
486 self._sponsor_chapter(20, 40, 'outro')]
487 expected = self._chapters([20, 40], ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
488 self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
490 def test_remove_marked_arrange_sponsors_EverythingCut(self):
491 cuts = [self._chapter(0, 20, remove=True), self._chapter(20, 40, remove=True)]
492 chapters = self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
493 self._remove_marked_arrange_sponsors_test_impl(
494 chapters, [], [self._chapter(0, 40, remove=True)])
496 def test_remove_marked_arrange_sponsors_TinyChaptersInTheOriginalArePreserved(self):
497 chapters = self._chapters([0.1, 0.2, 0.3, 0.4], ['c1', 'c2', 'c3', 'c4'])
498 self._remove_marked_arrange_sponsors_test_impl(chapters, chapters, [])
500 def test_remove_marked_arrange_sponsors_TinySponsorsAreIgnored(self):
501 chapters = [self._sponsor_chapter(0, 0.1, 'intro'), self._chapter(0.1, 0.2, 'c1'),
502 self._sponsor_chapter(0.2, 0.3, 'sponsor'), self._chapter(0.3, 0.4, 'c2'),
503 self._sponsor_chapter(0.4, 0.5, 'outro')]
504 self._remove_marked_arrange_sponsors_test_impl(
505 chapters, self._chapters([0.3, 0.5], ['c1', 'c2']), [])
507 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromCutsAreIgnored(self):
508 cuts = [self._chapter(1.5, 2.5, remove=True)]
509 chapters = self._chapters([2, 3, 3.5], ['c1', 'c2', 'c3']) + cuts
510 self._remove_marked_arrange_sponsors_test_impl(
511 chapters, self._chapters([2, 2.5], ['c1', 'c3']), cuts)
513 def test_remove_marked_arrange_sponsors_SingleTinyChapterIsPreserved(self):
514 cuts = [self._chapter(0.5, 2, remove=True)]
515 chapters = self._chapters([2], ['c']) + cuts
516 self._remove_marked_arrange_sponsors_test_impl(
517 chapters, self._chapters([0.5], ['c']), cuts)
519 def test_remove_marked_arrange_sponsors_TinyChapterAtTheStartPrependedToTheNext(self):
520 cuts = [self._chapter(0.5, 2, remove=True)]
521 chapters = self._chapters([2, 4], ['c1', 'c2']) + cuts
522 self._remove_marked_arrange_sponsors_test_impl(
523 chapters, self._chapters([2.5], ['c2']), cuts)
525 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromSponsorOverlapAreIgnored(self):
526 chapters = [
527 *self._chapters([1, 3, 4], ['c1', 'c2', 'c3']),
528 self._sponsor_chapter(1.5, 2.5, 'sponsor')]
529 self._remove_marked_arrange_sponsors_test_impl(
530 chapters, self._chapters([1.5, 2.5, 4], ['c1', '[SponsorBlock]: Sponsor', 'c3']), [])
532 def test_remove_marked_arrange_sponsors_TinySponsorsOverlapsAreIgnored(self):
533 chapters = [
534 *self._chapters([2, 3, 5], ['c1', 'c2', 'c3']),
535 self._sponsor_chapter(1, 3, 'sponsor'),
536 self._sponsor_chapter(2.5, 4, 'selfpromo')]
537 self._remove_marked_arrange_sponsors_test_impl(
538 chapters, self._chapters([1, 3, 4, 5], [
539 'c1', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion', 'c3']), [])
541 def test_remove_marked_arrange_sponsors_TinySponsorsPrependedToTheNextSponsor(self):
542 chapters = [
543 *self._chapters([4], ['c']),
544 self._sponsor_chapter(1.5, 2, 'sponsor'),
545 self._sponsor_chapter(2, 4, 'selfpromo')]
546 self._remove_marked_arrange_sponsors_test_impl(
547 chapters, self._chapters([1.5, 4], ['c', '[SponsorBlock]: Unpaid/Self Promotion']), [])
549 def test_remove_marked_arrange_sponsors_SmallestSponsorInTheOverlapGetsNamed(self):
550 self._pp._sponsorblock_chapter_title = '[SponsorBlock]: %(name)s'
551 chapters = [
552 *self._chapters([10], ['c']),
553 self._sponsor_chapter(2, 8, 'sponsor'),
554 self._sponsor_chapter(4, 6, 'selfpromo')]
555 self._remove_marked_arrange_sponsors_test_impl(
556 chapters, self._chapters([2, 4, 6, 8, 10], [
557 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
558 '[SponsorBlock]: Sponsor', 'c',
559 ]), [])
561 def test_make_concat_opts_CommonCase(self):
562 sponsor_chapters = [self._chapter(1, 2, 's1'), self._chapter(10, 20, 's2')]
563 expected = '''ffconcat version 1.0
564 file 'file:test'
565 outpoint 1.000000
566 file 'file:test'
567 inpoint 2.000000
568 outpoint 10.000000
569 file 'file:test'
570 inpoint 20.000000
572 opts = self._pp._make_concat_opts(sponsor_chapters, 30)
573 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
575 def test_make_concat_opts_NoZeroDurationChunkAtVideoStart(self):
576 sponsor_chapters = [self._chapter(0, 1, 's1'), self._chapter(10, 20, 's2')]
577 expected = '''ffconcat version 1.0
578 file 'file:test'
579 inpoint 1.000000
580 outpoint 10.000000
581 file 'file:test'
582 inpoint 20.000000
584 opts = self._pp._make_concat_opts(sponsor_chapters, 30)
585 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
587 def test_make_concat_opts_NoZeroDurationChunkAtVideoEnd(self):
588 sponsor_chapters = [self._chapter(1, 2, 's1'), self._chapter(10, 20, 's2')]
589 expected = '''ffconcat version 1.0
590 file 'file:test'
591 outpoint 1.000000
592 file 'file:test'
593 inpoint 2.000000
594 outpoint 10.000000
596 opts = self._pp._make_concat_opts(sponsor_chapters, 20)
597 self.assertEqual(expected, ''.join(self._pp._concat_spec(['test'] * len(opts), opts)))
599 def test_quote_for_concat_RunsOfQuotes(self):
600 self.assertEqual(
601 r"'special '\'' '\'\''characters'\'\'\''galore'",
602 self._pp._quote_for_ffmpeg("special ' ''characters'''galore"))
604 def test_quote_for_concat_QuotesAtStart(self):
605 self.assertEqual(
606 r"\'\'\''special '\'' characters '\'' galore'",
607 self._pp._quote_for_ffmpeg("'''special ' characters ' galore"))
609 def test_quote_for_concat_QuotesAtEnd(self):
610 self.assertEqual(
611 r"'special '\'' characters '\'' galore'\'\'\'",
612 self._pp._quote_for_ffmpeg("special ' characters ' galore'''"))