3 # Allow direct execution
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
.compat
import compat_shlex_quote
13 from yt_dlp
.postprocessor
import (
15 FFmpegThumbnailsConvertorPP
,
23 class TestMetadataFromField(unittest
.TestCase
):
25 def test_format_to_regex(self
):
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
):
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()
47 print('Skipping: ffmpeg not found')
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
):
57 pp
.convert_thumbnail(file.format(inp
), out
)
58 assert os
.path
.exists(out_file
)
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 %s' % compat_shlex_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
):
77 self
._pp
= ModifyChaptersPP(YoutubeDL())
80 def _sponsor_chapter(start
, end
, cat
, remove
=False, title
=None):
82 title
= SponsorBlockPP
.CATEGORIES
[cat
]
86 '_categories': [(cat
, start
, end
, title
)],
87 **({'remove': True} if remove
else {}),
91 def _chapter(start
, end
, title
=None, remove
=False):
92 c
= {'start_time': start
, 'end_time': end
}
99 def _chapters(self
, ends
, titles
):
100 self
.assertEqual(len(ends
), len(titles
))
103 for e
, t
in zip(ends
, titles
):
104 chapters
.append(self
._chapter
(start
, e
, t
))
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
:
114 c
.pop('_categories', None)
116 'start_time': c
['start_time'],
117 'end_time': c
['end_time'],
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
= 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
, [])
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
, [])
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
, [])
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
)
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)])
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
)
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'),
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
)
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
, [])
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)])
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
, [])
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)])
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
, [])
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
)
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)])
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)])
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)])
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
)
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
, [])
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
)
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
, [])
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
)
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
, [])
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
)
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
, [])
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
)
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
, [])
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
)
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
, [])
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
)
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
, [])
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
)
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
, [])
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
)
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
, [])
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)])
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
, [])
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']), [])
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
)
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
)
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
)
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']), [])
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')
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']), [])
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')
513 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
514 chapters
, self
._chapters
([1.5, 4], ['c', '[SponsorBlock]: Unpaid/Self Promotion']), [])
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')
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'
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
539 opts
= self
._pp
._make
_concat
_opts
(sponsor_chapters
, 30)
540 self
.assertEqual(expected
, ''.join(self
._pp
._concat
_spec
(['test'] * len(opts
), opts
)))
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
551 opts
= self
._pp
._make
_concat
_opts
(sponsor_chapters
, 30)
552 self
.assertEqual(expected
, ''.join(self
._pp
._concat
_spec
(['test'] * len(opts
), opts
)))
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
563 opts
= self
._pp
._make
_concat
_opts
(sponsor_chapters
, 20)
564 self
.assertEqual(expected
, ''.join(self
._pp
._concat
_spec
(['test'] * len(opts
), opts
)))
566 def test_quote_for_concat_RunsOfQuotes(self
):
568 r
"'special '\'' '\'\''characters'\'\'\''galore'",
569 self
._pp
._quote
_for
_ffmpeg
("special ' ''characters'''galore"))
571 def test_quote_for_concat_QuotesAtStart(self
):
573 r
"\'\'\''special '\'' characters '\'' galore'",
574 self
._pp
._quote
_for
_ffmpeg
("'''special ' characters ' galore"))
576 def test_quote_for_concat_QuotesAtEnd(self
):
578 r
"'special '\'' characters '\'' galore'\'\'\'",
579 self
._pp
._quote
_for
_ffmpeg
("special ' characters ' galore'''"))