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