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
,
22 class TestMetadataFromField(unittest
.TestCase
):
24 def test_format_to_regex(self
):
26 MetadataParserPP
.format_to_regex('%(title)s - %(artist)s'),
27 r
'(?P<title>.+)\ \-\ (?P<artist>.+)')
28 self
.assertEqual(MetadataParserPP
.format_to_regex(r
'(?P<x>.+)'), r
'(?P<x>.+)')
30 def test_field_to_template(self
):
31 self
.assertEqual(MetadataParserPP
.field_to_template('title'), '%(title)s')
32 self
.assertEqual(MetadataParserPP
.field_to_template('1'), '1')
33 self
.assertEqual(MetadataParserPP
.field_to_template('foo bar'), 'foo bar')
34 self
.assertEqual(MetadataParserPP
.field_to_template(' literal'), ' literal')
36 def test_metadatafromfield(self
):
38 MetadataFromFieldPP
.to_action('%(title)s \\: %(artist)s:%(title)s : %(artist)s'),
39 (MetadataParserPP
.Actions
.INTERPRET
, '%(title)s : %(artist)s', '%(title)s : %(artist)s'))
42 class TestConvertThumbnail(unittest
.TestCase
):
43 def test_escaping(self
):
44 pp
= FFmpegThumbnailsConvertorPP()
46 print('Skipping: ffmpeg not found')
49 file = 'test/testdata/thumbnails/foo %d bar/foo_%d.{}'
50 tests
= (('webp', 'png'), ('png', 'jpg'))
52 for inp
, out
in tests
:
53 out_file
= file.format(out
)
54 if os
.path
.exists(out_file
):
56 pp
.convert_thumbnail(file.format(inp
), out
)
57 assert os
.path
.exists(out_file
)
60 os
.remove(file.format(out
))
63 class TestExec(unittest
.TestCase
):
64 def test_parse_cmd(self
):
65 pp
= ExecPP(YoutubeDL(), '')
66 info
= {'filepath': 'file name'}
67 cmd
= 'echo %s' % compat_shlex_quote(info
['filepath'])
69 self
.assertEqual(pp
.parse_cmd('echo', info
), cmd
)
70 self
.assertEqual(pp
.parse_cmd('echo {}', info
), cmd
)
71 self
.assertEqual(pp
.parse_cmd('echo %(filepath)q', info
), cmd
)
74 class TestModifyChaptersPP(unittest
.TestCase
):
76 self
._pp
= ModifyChaptersPP(YoutubeDL())
79 def _sponsor_chapter(start
, end
, cat
, remove
=False):
80 c
= {'start_time': start, 'end_time': end, '_categories': [(cat, start, end)]}
86 def _chapter(start
, end
, title
=None, remove
=False):
87 c
= {'start_time': start, 'end_time': end}
94 def _chapters(self
, ends
, titles
):
95 self
.assertEqual(len(ends
), len(titles
))
98 for e
, t
in zip(ends
, titles
):
99 chapters
.append(self
._chapter
(start
, e
, t
))
103 def _remove_marked_arrange_sponsors_test_impl(
104 self
, chapters
, expected_chapters
, expected_removed
):
105 actual_chapters
, actual_removed
= (
106 self
._pp
._remove
_marked
_arrange
_sponsors
(chapters
))
107 for c
in actual_removed
:
109 c
.pop('_categories', None)
111 'start_time': c
['start_time'],
112 'end_time': c
['end_time'],
114 } for c
in actual_chapters
]
115 self
.assertSequenceEqual(expected_chapters
, actual_chapters
)
116 self
.assertSequenceEqual(expected_removed
, actual_removed
)
118 def test_remove_marked_arrange_sponsors_CanGetThroughUnaltered(self
):
119 chapters
= self
._chapters
([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
120 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, chapters
, [])
122 def test_remove_marked_arrange_sponsors_ChapterWithSponsors(self
):
123 chapters
= self
._chapters
([70], ['c']) + [
124 self
._sponsor
_chapter
(10, 20, 'sponsor'),
125 self
._sponsor
_chapter
(30, 40, 'preview'),
126 self
._sponsor
_chapter
(50, 60, 'filler')]
127 expected
= self
._chapters
(
128 [10, 20, 30, 40, 50, 60, 70],
129 ['c', '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Preview/Recap',
130 'c', '[SponsorBlock]: Filler Tangent', 'c'])
131 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
133 def test_remove_marked_arrange_sponsors_UniqueNamesForOverlappingSponsors(self
):
134 chapters
= self
._chapters
([120], ['c']) + [
135 self
._sponsor
_chapter
(10, 45, 'sponsor'), self
._sponsor
_chapter
(20, 40, 'selfpromo'),
136 self
._sponsor
_chapter
(50, 70, 'sponsor'), self
._sponsor
_chapter
(60, 85, 'selfpromo'),
137 self
._sponsor
_chapter
(90, 120, 'selfpromo'), self
._sponsor
_chapter
(100, 110, 'sponsor')]
138 expected
= self
._chapters
(
139 [10, 20, 40, 45, 50, 60, 70, 85, 90, 100, 110, 120],
140 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
141 '[SponsorBlock]: Sponsor',
142 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
143 '[SponsorBlock]: Unpaid/Self Promotion',
144 'c', '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Sponsor',
145 '[SponsorBlock]: Unpaid/Self Promotion'])
146 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
148 def test_remove_marked_arrange_sponsors_ChapterWithCuts(self
):
149 cuts
= [self
._chapter
(10, 20, remove
=True),
150 self
._sponsor
_chapter
(30, 40, 'sponsor', remove
=True),
151 self
._chapter
(50, 60, remove
=True)]
152 chapters
= self
._chapters
([70], ['c']) + cuts
153 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
154 chapters
, self
._chapters
([40], ['c']), cuts
)
156 def test_remove_marked_arrange_sponsors_ChapterWithSponsorsAndCuts(self
):
157 chapters
= self
._chapters
([70], ['c']) + [
158 self
._sponsor
_chapter
(10, 20, 'sponsor'),
159 self
._sponsor
_chapter
(30, 40, 'selfpromo', remove
=True),
160 self
._sponsor
_chapter
(50, 60, 'interaction')]
161 expected
= self
._chapters
([10, 20, 40, 50, 60],
162 ['c', '[SponsorBlock]: Sponsor', 'c',
163 '[SponsorBlock]: Interaction Reminder', 'c'])
164 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
165 chapters
, expected
, [self
._chapter
(30, 40, remove
=True)])
167 def test_remove_marked_arrange_sponsors_ChapterWithSponsorCutInTheMiddle(self
):
168 cuts
= [self
._sponsor
_chapter
(20, 30, 'selfpromo', remove
=True),
169 self
._chapter
(40, 50, remove
=True)]
170 chapters
= self
._chapters
([70], ['c']) + [self
._sponsor
_chapter
(10, 60, 'sponsor')] + cuts
171 expected
= self
._chapters
(
172 [10, 40, 50], ['c', '[SponsorBlock]: Sponsor', 'c'])
173 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
175 def test_remove_marked_arrange_sponsors_ChapterWithCutHidingSponsor(self
):
176 cuts
= [self
._sponsor
_chapter
(20, 50, 'selpromo', remove
=True)]
177 chapters
= self
._chapters
([60], ['c']) + [
178 self
._sponsor
_chapter
(10, 20, 'intro'),
179 self
._sponsor
_chapter
(30, 40, 'sponsor'),
180 self
._sponsor
_chapter
(50, 60, 'outro'),
182 expected
= self
._chapters
(
183 [10, 20, 30], ['c', '[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
184 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
186 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentSponsors(self
):
187 chapters
= self
._chapters
([70], ['c']) + [
188 self
._sponsor
_chapter
(10, 20, 'sponsor'),
189 self
._sponsor
_chapter
(20, 30, 'selfpromo'),
190 self
._sponsor
_chapter
(30, 40, 'interaction')]
191 expected
= self
._chapters
(
192 [10, 20, 30, 40, 70],
193 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
194 '[SponsorBlock]: Interaction Reminder', 'c'])
195 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
197 def test_remove_marked_arrange_sponsors_ChapterWithAdjacentCuts(self
):
198 chapters
= self
._chapters
([70], ['c']) + [
199 self
._sponsor
_chapter
(10, 20, 'sponsor'),
200 self
._sponsor
_chapter
(20, 30, 'interaction', remove
=True),
201 self
._chapter
(30, 40, remove
=True),
202 self
._sponsor
_chapter
(40, 50, 'selpromo', remove
=True),
203 self
._sponsor
_chapter
(50, 60, 'interaction')]
204 expected
= self
._chapters
([10, 20, 30, 40],
205 ['c', '[SponsorBlock]: Sponsor',
206 '[SponsorBlock]: Interaction Reminder', 'c'])
207 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
208 chapters
, expected
, [self
._chapter
(20, 50, remove
=True)])
210 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingSponsors(self
):
211 chapters
= self
._chapters
([70], ['c']) + [
212 self
._sponsor
_chapter
(10, 30, 'sponsor'),
213 self
._sponsor
_chapter
(20, 50, 'selfpromo'),
214 self
._sponsor
_chapter
(40, 60, 'interaction')]
215 expected
= self
._chapters
(
216 [10, 20, 30, 40, 50, 60, 70],
217 ['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
218 '[SponsorBlock]: Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
219 '[SponsorBlock]: Interaction Reminder', 'c'])
220 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
222 def test_remove_marked_arrange_sponsors_ChapterWithOverlappingCuts(self
):
223 chapters
= self
._chapters
([70], ['c']) + [
224 self
._sponsor
_chapter
(10, 30, 'sponsor', remove
=True),
225 self
._sponsor
_chapter
(20, 50, 'selfpromo', remove
=True),
226 self
._sponsor
_chapter
(40, 60, 'interaction', remove
=True)]
227 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
228 chapters
, self
._chapters
([20], ['c']), [self
._chapter
(10, 60, remove
=True)])
230 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsors(self
):
231 chapters
= self
._chapters
([170], ['c']) + [
232 self
._sponsor
_chapter
(0, 30, 'intro'),
233 self
._sponsor
_chapter
(20, 50, 'sponsor'),
234 self
._sponsor
_chapter
(40, 60, 'selfpromo'),
235 self
._sponsor
_chapter
(70, 90, 'sponsor'),
236 self
._sponsor
_chapter
(80, 100, 'sponsor'),
237 self
._sponsor
_chapter
(90, 110, 'sponsor'),
238 self
._sponsor
_chapter
(120, 140, 'selfpromo'),
239 self
._sponsor
_chapter
(130, 160, 'interaction'),
240 self
._sponsor
_chapter
(150, 170, 'outro')]
241 expected
= self
._chapters
(
242 [20, 30, 40, 50, 60, 70, 110, 120, 130, 140, 150, 160, 170],
243 ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Intermission/Intro Animation, Sponsor', '[SponsorBlock]: Sponsor',
244 '[SponsorBlock]: Sponsor, Unpaid/Self Promotion', '[SponsorBlock]: Unpaid/Self Promotion', 'c',
245 '[SponsorBlock]: Sponsor', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
246 '[SponsorBlock]: Unpaid/Self Promotion, Interaction Reminder',
247 '[SponsorBlock]: Interaction Reminder',
248 '[SponsorBlock]: Interaction Reminder, Endcards/Credits', '[SponsorBlock]: Endcards/Credits'])
249 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
251 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingCuts(self
):
252 chapters
= self
._chapters
([170], ['c']) + [
253 self
._chapter
(0, 30, remove
=True),
254 self
._sponsor
_chapter
(20, 50, 'sponsor', remove
=True),
255 self
._chapter
(40, 60, remove
=True),
256 self
._sponsor
_chapter
(70, 90, 'sponsor', remove
=True),
257 self
._chapter
(80, 100, remove
=True),
258 self
._chapter
(90, 110, remove
=True),
259 self
._sponsor
_chapter
(120, 140, 'sponsor', remove
=True),
260 self
._sponsor
_chapter
(130, 160, 'selfpromo', remove
=True),
261 self
._chapter
(150, 170, remove
=True)]
262 expected_cuts
= [self
._chapter
(0, 60, remove
=True),
263 self
._chapter
(70, 110, remove
=True),
264 self
._chapter
(120, 170, remove
=True)]
265 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
266 chapters
, self
._chapters
([20], ['c']), expected_cuts
)
268 def test_remove_marked_arrange_sponsors_OverlappingSponsorsDifferentTitlesAfterCut(self
):
269 chapters
= self
._chapters
([60], ['c']) + [
270 self
._sponsor
_chapter
(10, 60, 'sponsor'),
271 self
._sponsor
_chapter
(10, 40, 'intro'),
272 self
._sponsor
_chapter
(30, 50, 'interaction'),
273 self
._sponsor
_chapter
(30, 50, 'selfpromo', remove
=True),
274 self
._sponsor
_chapter
(40, 50, 'interaction'),
275 self
._sponsor
_chapter
(50, 60, 'outro')]
276 expected
= self
._chapters
(
277 [10, 30, 40], ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Endcards/Credits'])
278 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
279 chapters
, expected
, [self
._chapter
(30, 50, remove
=True)])
281 def test_remove_marked_arrange_sponsors_SponsorsNoLongerOverlapAfterCut(self
):
282 chapters
= self
._chapters
([70], ['c']) + [
283 self
._sponsor
_chapter
(10, 30, 'sponsor'),
284 self
._sponsor
_chapter
(20, 50, 'interaction'),
285 self
._sponsor
_chapter
(30, 50, 'selpromo', remove
=True),
286 self
._sponsor
_chapter
(40, 60, 'sponsor'),
287 self
._sponsor
_chapter
(50, 60, 'interaction')]
288 expected
= self
._chapters
(
289 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
290 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
291 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
292 chapters
, expected
, [self
._chapter
(30, 50, remove
=True)])
294 def test_remove_marked_arrange_sponsors_SponsorsStillOverlapAfterCut(self
):
295 chapters
= self
._chapters
([70], ['c']) + [
296 self
._sponsor
_chapter
(10, 60, 'sponsor'),
297 self
._sponsor
_chapter
(20, 60, 'interaction'),
298 self
._sponsor
_chapter
(30, 50, 'selfpromo', remove
=True)]
299 expected
= self
._chapters
(
300 [10, 20, 40, 50], ['c', '[SponsorBlock]: Sponsor',
301 '[SponsorBlock]: Sponsor, Interaction Reminder', 'c'])
302 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
303 chapters
, expected
, [self
._chapter
(30, 50, remove
=True)])
305 def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsorsAndCuts(self
):
306 chapters
= self
._chapters
([200], ['c']) + [
307 self
._sponsor
_chapter
(10, 40, 'sponsor'),
308 self
._sponsor
_chapter
(10, 30, 'intro'),
309 self
._chapter
(20, 30, remove
=True),
310 self
._sponsor
_chapter
(30, 40, 'selfpromo'),
311 self
._sponsor
_chapter
(50, 70, 'sponsor'),
312 self
._sponsor
_chapter
(60, 80, 'interaction'),
313 self
._chapter
(70, 80, remove
=True),
314 self
._sponsor
_chapter
(70, 90, 'sponsor'),
315 self
._sponsor
_chapter
(80, 100, 'interaction'),
316 self
._sponsor
_chapter
(120, 170, 'selfpromo'),
317 self
._sponsor
_chapter
(130, 180, 'outro'),
318 self
._chapter
(140, 150, remove
=True),
319 self
._chapter
(150, 160, remove
=True)]
320 expected
= self
._chapters
(
321 [10, 20, 30, 40, 50, 70, 80, 100, 110, 130, 140, 160],
322 ['c', '[SponsorBlock]: Sponsor, Intermission/Intro Animation', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
323 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Interaction Reminder',
324 '[SponsorBlock]: Interaction Reminder', 'c', '[SponsorBlock]: Unpaid/Self Promotion',
325 '[SponsorBlock]: Unpaid/Self Promotion, Endcards/Credits', '[SponsorBlock]: Endcards/Credits', 'c'])
326 expected_cuts
= [self
._chapter
(20, 30, remove
=True),
327 self
._chapter
(70, 80, remove
=True),
328 self
._chapter
(140, 160, remove
=True)]
329 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, expected_cuts
)
331 def test_remove_marked_arrange_sponsors_SponsorOverlapsMultipleChapters(self
):
332 chapters
= (self
._chapters
([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5'])
333 + [self
._sponsor
_chapter
(10, 90, 'sponsor')])
334 expected
= self
._chapters
([10, 90, 100], ['c1', '[SponsorBlock]: Sponsor', 'c5'])
335 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
337 def test_remove_marked_arrange_sponsors_CutOverlapsMultipleChapters(self
):
338 cuts
= [self
._chapter
(10, 90, remove
=True)]
339 chapters
= self
._chapters
([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5']) + cuts
340 expected
= self
._chapters
([10, 20], ['c1', 'c5'])
341 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
343 def test_remove_marked_arrange_sponsors_SponsorsWithinSomeChaptersAndOverlappingOthers(self
):
344 chapters
= (self
._chapters
([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4'])
345 + [self
._sponsor
_chapter
(20, 30, 'sponsor'),
346 self
._sponsor
_chapter
(50, 70, 'selfpromo')])
347 expected
= self
._chapters
([10, 20, 30, 40, 50, 70, 80],
348 ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c2', 'c3',
349 '[SponsorBlock]: Unpaid/Self Promotion', 'c4'])
350 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
352 def test_remove_marked_arrange_sponsors_CutsWithinSomeChaptersAndOverlappingOthers(self
):
353 cuts
= [self
._chapter
(20, 30, remove
=True), self
._chapter
(50, 70, remove
=True)]
354 chapters
= self
._chapters
([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4']) + cuts
355 expected
= self
._chapters
([10, 30, 40, 50], ['c1', 'c2', 'c3', 'c4'])
356 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
358 def test_remove_marked_arrange_sponsors_ChaptersAfterLastSponsor(self
):
359 chapters
= (self
._chapters
([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4'])
360 + [self
._sponsor
_chapter
(10, 30, 'music_offtopic')])
361 expected
= self
._chapters
(
362 [10, 30, 40, 50, 60],
363 ['c1', '[SponsorBlock]: Non-Music Section', 'c2', 'c3', 'c4'])
364 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
366 def test_remove_marked_arrange_sponsors_ChaptersAfterLastCut(self
):
367 cuts
= [self
._chapter
(10, 30, remove
=True)]
368 chapters
= self
._chapters
([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4']) + cuts
369 expected
= self
._chapters
([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
370 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
372 def test_remove_marked_arrange_sponsors_SponsorStartsAtChapterStart(self
):
373 chapters
= (self
._chapters
([10, 20, 40], ['c1', 'c2', 'c3'])
374 + [self
._sponsor
_chapter
(20, 30, 'sponsor')])
375 expected
= self
._chapters
([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
376 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
378 def test_remove_marked_arrange_sponsors_CutStartsAtChapterStart(self
):
379 cuts
= [self
._chapter
(20, 30, remove
=True)]
380 chapters
= self
._chapters
([10, 20, 40], ['c1', 'c2', 'c3']) + cuts
381 expected
= self
._chapters
([10, 20, 30], ['c1', 'c2', 'c3'])
382 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
384 def test_remove_marked_arrange_sponsors_SponsorEndsAtChapterEnd(self
):
385 chapters
= (self
._chapters
([10, 30, 40], ['c1', 'c2', 'c3'])
386 + [self
._sponsor
_chapter
(20, 30, 'sponsor')])
387 expected
= self
._chapters
([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
388 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
390 def test_remove_marked_arrange_sponsors_CutEndsAtChapterEnd(self
):
391 cuts
= [self
._chapter
(20, 30, remove
=True)]
392 chapters
= self
._chapters
([10, 30, 40], ['c1', 'c2', 'c3']) + cuts
393 expected
= self
._chapters
([10, 20, 30], ['c1', 'c2', 'c3'])
394 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
396 def test_remove_marked_arrange_sponsors_SponsorCoincidesWithChapters(self
):
397 chapters
= (self
._chapters
([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
398 + [self
._sponsor
_chapter
(10, 30, 'sponsor')])
399 expected
= self
._chapters
([10, 30, 40], ['c1', '[SponsorBlock]: Sponsor', 'c4'])
400 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
402 def test_remove_marked_arrange_sponsors_CutCoincidesWithChapters(self
):
403 cuts
= [self
._chapter
(10, 30, remove
=True)]
404 chapters
= self
._chapters
([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
405 expected
= self
._chapters
([10, 20], ['c1', 'c4'])
406 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
408 def test_remove_marked_arrange_sponsors_SponsorsAtVideoBoundaries(self
):
409 chapters
= (self
._chapters
([20, 40, 60], ['c1', 'c2', 'c3'])
410 + [self
._sponsor
_chapter
(0, 10, 'intro'), self
._sponsor
_chapter
(50, 60, 'outro')])
411 expected
= self
._chapters
(
412 [10, 20, 40, 50, 60], ['[SponsorBlock]: Intermission/Intro Animation', 'c1', 'c2', 'c3', '[SponsorBlock]: Endcards/Credits'])
413 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
415 def test_remove_marked_arrange_sponsors_CutsAtVideoBoundaries(self
):
416 cuts
= [self
._chapter
(0, 10, remove
=True), self
._chapter
(50, 60, remove
=True)]
417 chapters
= self
._chapters
([20, 40, 60], ['c1', 'c2', 'c3']) + cuts
418 expected
= self
._chapters
([10, 30, 40], ['c1', 'c2', 'c3'])
419 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
421 def test_remove_marked_arrange_sponsors_SponsorsOverlapChaptersAtVideoBoundaries(self
):
422 chapters
= (self
._chapters
([10, 40, 50], ['c1', 'c2', 'c3'])
423 + [self
._sponsor
_chapter
(0, 20, 'intro'), self
._sponsor
_chapter
(30, 50, 'outro')])
424 expected
= self
._chapters
(
425 [20, 30, 50], ['[SponsorBlock]: Intermission/Intro Animation', 'c2', '[SponsorBlock]: Endcards/Credits'])
426 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
428 def test_remove_marked_arrange_sponsors_CutsOverlapChaptersAtVideoBoundaries(self
):
429 cuts
= [self
._chapter
(0, 20, remove
=True), self
._chapter
(30, 50, remove
=True)]
430 chapters
= self
._chapters
([10, 40, 50], ['c1', 'c2', 'c3']) + cuts
431 expected
= self
._chapters
([10], ['c2'])
432 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, cuts
)
434 def test_remove_marked_arrange_sponsors_EverythingSponsored(self
):
435 chapters
= (self
._chapters
([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
436 + [self
._sponsor
_chapter
(0, 20, 'intro'), self
._sponsor
_chapter
(20, 40, 'outro')])
437 expected
= self
._chapters
([20, 40], ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
438 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, expected
, [])
440 def test_remove_marked_arrange_sponsors_EverythingCut(self
):
441 cuts
= [self
._chapter
(0, 20, remove
=True), self
._chapter
(20, 40, remove
=True)]
442 chapters
= self
._chapters
([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']) + cuts
443 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
444 chapters
, [], [self
._chapter
(0, 40, remove
=True)])
446 def test_remove_marked_arrange_sponsors_TinyChaptersInTheOriginalArePreserved(self
):
447 chapters
= self
._chapters
([0.1, 0.2, 0.3, 0.4], ['c1', 'c2', 'c3', 'c4'])
448 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(chapters
, chapters
, [])
450 def test_remove_marked_arrange_sponsors_TinySponsorsAreIgnored(self
):
451 chapters
= [self
._sponsor
_chapter
(0, 0.1, 'intro'), self
._chapter
(0.1, 0.2, 'c1'),
452 self
._sponsor
_chapter
(0.2, 0.3, 'sponsor'), self
._chapter
(0.3, 0.4, 'c2'),
453 self
._sponsor
_chapter
(0.4, 0.5, 'outro')]
454 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
455 chapters
, self
._chapters
([0.3, 0.5], ['c1', 'c2']), [])
457 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromCutsAreIgnored(self
):
458 cuts
= [self
._chapter
(1.5, 2.5, remove
=True)]
459 chapters
= self
._chapters
([2, 3, 3.5], ['c1', 'c2', 'c3']) + cuts
460 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
461 chapters
, self
._chapters
([2, 2.5], ['c1', 'c3']), cuts
)
463 def test_remove_marked_arrange_sponsors_SingleTinyChapterIsPreserved(self
):
464 cuts
= [self
._chapter
(0.5, 2, remove
=True)]
465 chapters
= self
._chapters
([2], ['c']) + cuts
466 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
467 chapters
, self
._chapters
([0.5], ['c']), cuts
)
469 def test_remove_marked_arrange_sponsors_TinyChapterAtTheStartPrependedToTheNext(self
):
470 cuts
= [self
._chapter
(0.5, 2, remove
=True)]
471 chapters
= self
._chapters
([2, 4], ['c1', 'c2']) + cuts
472 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
473 chapters
, self
._chapters
([2.5], ['c2']), cuts
)
475 def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromSponsorOverlapAreIgnored(self
):
476 chapters
= self
._chapters
([1, 3, 4], ['c1', 'c2', 'c3']) + [
477 self
._sponsor
_chapter
(1.5, 2.5, 'sponsor')]
478 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
479 chapters
, self
._chapters
([1.5, 2.5, 4], ['c1', '[SponsorBlock]: Sponsor', 'c3']), [])
481 def test_remove_marked_arrange_sponsors_TinySponsorsOverlapsAreIgnored(self
):
482 chapters
= self
._chapters
([2, 3, 5], ['c1', 'c2', 'c3']) + [
483 self
._sponsor
_chapter
(1, 3, 'sponsor'),
484 self
._sponsor
_chapter
(2.5, 4, 'selfpromo')
486 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
487 chapters
, self
._chapters
([1, 3, 4, 5], [
488 'c1', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion', 'c3']), [])
490 def test_remove_marked_arrange_sponsors_TinySponsorsPrependedToTheNextSponsor(self
):
491 chapters
= self
._chapters
([4], ['c']) + [
492 self
._sponsor
_chapter
(1.5, 2, 'sponsor'),
493 self
._sponsor
_chapter
(2, 4, 'selfpromo')
495 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
496 chapters
, self
._chapters
([1.5, 4], ['c', '[SponsorBlock]: Unpaid/Self Promotion']), [])
498 def test_remove_marked_arrange_sponsors_SmallestSponsorInTheOverlapGetsNamed(self
):
499 self
._pp
._sponsorblock
_chapter
_title
= '[SponsorBlock]: %(name)s'
500 chapters
= self
._chapters
([10], ['c']) + [
501 self
._sponsor
_chapter
(2, 8, 'sponsor'),
502 self
._sponsor
_chapter
(4, 6, 'selfpromo')
504 self
._remove
_marked
_arrange
_sponsors
_test
_impl
(
505 chapters
, self
._chapters
([2, 4, 6, 8, 10], [
506 'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
507 '[SponsorBlock]: Sponsor', 'c'
510 def test_make_concat_opts_CommonCase(self
):
511 sponsor_chapters
= [self
._chapter
(1, 2, 's1'), self
._chapter
(10, 20, 's2')]
512 expected
= '''ffconcat version 1.0
521 opts
= self
._pp
._make
_concat
_opts
(sponsor_chapters
, 30)
522 self
.assertEqual(expected
, ''.join(self
._pp
._concat
_spec
(['test'] * len(opts
), opts
)))
524 def test_make_concat_opts_NoZeroDurationChunkAtVideoStart(self
):
525 sponsor_chapters
= [self
._chapter
(0, 1, 's1'), self
._chapter
(10, 20, 's2')]
526 expected
= '''ffconcat version 1.0
533 opts
= self
._pp
._make
_concat
_opts
(sponsor_chapters
, 30)
534 self
.assertEqual(expected
, ''.join(self
._pp
._concat
_spec
(['test'] * len(opts
), opts
)))
536 def test_make_concat_opts_NoZeroDurationChunkAtVideoEnd(self
):
537 sponsor_chapters
= [self
._chapter
(1, 2, 's1'), self
._chapter
(10, 20, 's2')]
538 expected
= '''ffconcat version 1.0
545 opts
= self
._pp
._make
_concat
_opts
(sponsor_chapters
, 20)
546 self
.assertEqual(expected
, ''.join(self
._pp
._concat
_spec
(['test'] * len(opts
), opts
)))
548 def test_quote_for_concat_RunsOfQuotes(self
):
550 r
"'special '\'' '\'\''characters'\'\'\''galore'",
551 self
._pp
._quote
_for
_ffmpeg
("special ' ''characters'''galore"))
553 def test_quote_for_concat_QuotesAtStart(self
):
555 r
"\'\'\''special '\'' characters '\'' galore'",
556 self
._pp
._quote
_for
_ffmpeg
("'''special ' characters ' galore"))
558 def test_quote_for_concat_QuotesAtEnd(self
):
560 r
"'special '\'' characters '\'' galore'\'\'\'",
561 self
._pp
._quote
_for
_ffmpeg
("special ' characters ' galore'''"))