1# -*- coding: utf-8 -*-
2import pytest
3
4from flexget.components.parsing.parsers.parser_guessit import ParserGuessit
5from flexget.components.parsing.parsers.parser_internal import ParserInternal
6
7
8class TestSeriesParser:
9    @pytest.fixture(
10        scope='class', params=(ParserInternal, ParserGuessit), ids=['internal', 'guessit']
11    )
12    def parse(self, request):
13        p = request.param()
14
15        def parse(data, name=None, **kwargs):
16            return p.parse_series(data, name=name, **kwargs)
17
18        return parse
19
20    @pytest.fixture(scope='class')
21    def parse_invalid(self, parse):
22        def parse_invalid(name, data, **kwargs):
23            """Makes sure either ParseWarning is raised, or return is invalid."""
24            r = parse(name, data, **kwargs)
25            assert not r.valid, '{data} should not be valid'.format(data=data)
26
27        return parse_invalid
28
29    def test_proper(self, parse):
30        """SeriesParser: proper"""
31        s = parse(name='Something Interesting', data='Something.Interesting.S01E02.Proper-FlexGet')
32        assert s.season == 1
33        assert s.episode == 2
34        assert s.quality.name == 'unknown'
35        assert s.proper, 'did not detect proper from %s' % s.data
36        s = parse(name='foobar', data='foobar 720p proper s01e01')
37        assert s.proper, 'did not detect proper from %s' % s.data
38
39    def test_non_proper(self, parse):
40        """SeriesParser: non-proper"""
41        s = parse(name='Something Interesting', data='Something.Interesting.S01E02-FlexGet')
42        assert s.season == 1
43        assert s.episode == 2
44        assert s.quality.name == 'unknown'
45        assert not s.proper, 'detected proper'
46
47    def test_anime_proper(self, parse):
48        """SeriesParser: anime fansub style proper (13v2)"""
49        s = parse(name='Anime', data='[aoeu] Anime 19v2 [23BA98]')
50        assert s.identifier == 19
51        assert s.proper_count == 1
52        s = parse(name='Anime', data='Anime_-_19v3')
53        assert s.identifier == 19
54        assert s.proper_count == 2
55
56    def test_basic(self, parse, parse_invalid):
57        """SeriesParser: basic parsing"""
58        parse_invalid(
59            name='Something Interesting', data='The.Something.Interesting.S01E02-FlexGet'
60        )
61
62        s = parse(name='25', data='25.And.More.S01E02-FlexGet')
63        assert s.valid, 'Fix the implementation, should be valid'
64        assert s.identifier == 'S01E02', 'identifier broken'
65
66    def test_confusing_date(self, parse):
67        """SeriesParser: confusing (invalid) numbering scheme"""
68        s = parse(name='Something', data='Something.2008x12.13-FlexGet')
69        assert not s.episode, 'Should not have episode'
70        assert s.id_type == 'date'
71        assert s.identifier == '2008-12-13', 'invalid id'
72        assert s.valid, 'should be valid'
73
74    def test_unwanted_disc(self, parse_invalid):
75        """SeriesParser: unwanted disc releases"""
76        parse_invalid(name='Something', data='Something.S01D2.DVDR-FlexGet')
77
78    def test_season_x_ep(self, parse):
79        """SeriesParser: 01x02"""
80        s = parse(name='Something', data='Something.01x02-FlexGet')
81        assert s.season == 1 and s.episode == 2, 'failed to parse 01x02'
82
83        s = parse(name='Something', data='Something 1 x 2-FlexGet')
84        assert s.season == 1 and s.episode == 2, 'failed to parse 1 x 2'
85
86        # Ticket #732
87        s = parse(name='Something', data='Something - This is the Subtitle 14x9 [Group-Name]')
88        assert s.season == 14 and s.episode == 9, 'failed to parse %s' % s.data
89
90    @pytest.mark.skip(reason='FIX: #402 .. a bit hard to do')
91    def test_ep_in_square_brackets(self, parse):
92        """SeriesParser: [S01] [E02] NOT IMPLEMENTED"""
93        # FIX: #402 .. a bit hard to do
94        s = parse(name='Something', data='Something [S01] [E02]')
95        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
96
97    def test_ep_in_parenthesis(self, parse):
98        """SeriesParser: test ep in parenthesis"""
99        s = parse(name='Something', data='Something (S01E02)')
100        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
101
102    def test_season_episode(self, parse):
103        """SeriesParser: season X, episode Y"""
104        s = parse(name='Something', data='Something - Season 3, Episode 2')
105        assert s.season == 3 and s.episode == 2, 'failed to parse %s' % s
106
107        s = parse(name='Something', data='Something - Season2, Episode2')
108        assert s.season == 2 and s.episode == 2, 'failed to parse %s' % s
109
110        s = parse(name='Something', data='Something - Season2 Episode2')
111        assert s.season == 2 and s.episode == 2, 'failed to parse %s' % s
112
113    @pytest.mark.xfail(reason='Not supported in guessit, works for internal parser')
114    def test_series_episode(self, parse):
115        """SeriesParser: series X, episode Y"""
116        s = parse(name='Something', data='Something - Series 2, Episode 2')
117        assert s.season == 2 and s.episode == 2, 'failed to parse %s' % s
118
119        s = parse(name='Something', data='Something - Series3, Episode2')
120        assert s.season == 3 and s.episode == 2, 'failed to parse %s' % s
121
122        s = parse(name='Something', data='Something - Series4 Episode2')
123        assert s.season == 4 and s.episode == 2, 'failed to parse %s' % s
124
125    def test_episode(self, parse):
126        """SeriesParser: episode X (assume season 1)"""
127        s = parse(name='Something', data='Something - Episode2')
128        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
129
130        s = parse(name='Something', data='Something - Episode 2')
131        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
132
133        s = parse(name='Something', data='Something - Episode VIII')
134        assert s.season == 1 and s.episode == 8, 'failed to parse %s' % s
135
136    def test_ep(self, parse):
137        """SeriesParser: ep X (assume season 1)"""
138        s = parse(name='Something', data='Something - Ep2')
139        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
140
141        s = parse(name='Something', data='Something - Ep 2')
142        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
143
144        s = parse(name='Something', data='Something - Ep VIII')
145        assert s.season == 1 and s.episode == 8, 'failed to parse %s' % s
146
147        s = parse(name='Something', data='Something - E01')
148        assert s.season == 1 and s.episode == 1, 'failed to parse %s' % s
149
150    def test_season_episode_of_total(self, parse):
151        """SeriesParser: season X YofZ"""
152        s = parse(name='Something', data='Something Season 2 2of12')
153        assert s.season == 2 and s.episode == 2, 'failed to parse %s' % s
154
155        s = parse(name='Something', data='Something Season 2, 2 of 12')
156        assert s.season == 2 and s.episode == 2, 'failed to parse %s' % s
157
158    def test_episode_of_total(self, parse):
159        """SeriesParser: YofZ (assume season 1)"""
160        s = parse(name='Something', data='Something 2of12')
161        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
162
163        s = parse(name='Something', data='Something 2 of 12')
164        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
165
166    def test_part(self, parse):
167        """SeriesParser: test parsing part numeral (assume season 1)"""
168        s = parse(name='Test', data='Test.Pt.I.720p-FlexGet')
169        assert s.season == 1 and s.episode == 1, 'failed to parse %s' % s
170        s = parse(name='Test', data='Test.Pt.VI.720p-FlexGet')
171        assert s.season == 1 and s.episode == 6, 'failed to parse %s' % s
172        s = parse(name='Test', data='Test.Part.2.720p-FlexGet')
173        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s
174        assert s.identifier == 'S01E02'
175        s = parse(name='Test', data='Test.Part3.720p-FlexGet')
176        assert s.season == 1 and s.episode == 3, 'failed to parse %s' % s
177        s = parse(name='Test', data='Test.Season.3.Part.IV')
178        assert s.season == 3 and s.episode == 4, 'failed to parse %s' % s
179        s = parse(name='Test', data='Test.Part.One')
180        assert s.season == 1 and s.episode == 1, 'failed to parse %s' % s
181
182    def test_digits(self, parse):
183        """SeriesParser: digits (UID)"""
184        s = parse(name='Something', data='Something 01 FlexGet')
185        assert s.id == 1, 'failed to parse %s' % s.data
186        assert s.id_type == 'sequence'
187
188        s = parse(name='Something', data='Something-121.H264.FlexGet')
189        assert s.id == 121, 'failed to parse %s' % s.data
190        assert s.id_type == 'sequence'
191
192        s = parse(name='Something', data='Something 1 AC3')
193        assert s.id == 1, 'failed to parse %s' % s.data
194        assert s.id_type == 'sequence'
195
196        s = parse(name='Something', data='[TheGroup] Something - 12 1280x720 x264-Hi10P')
197        assert s.id == 12, 'failed to parse %s' % s.data
198        assert s.id_type == 'sequence'
199
200    def test_quality(self, parse):
201        """SeriesParser: quality"""
202        s = parse(name='Foo Bar', data='Foo.Bar.S01E01.720p.HDTV.x264-FlexGet')
203        assert s.season == 1 and s.episode == 1, 'failed to parse episodes from %s' % s.data
204        assert s.quality.name == '720p hdtv h264', 'failed to parse quality from %s' % s.data
205
206        s = parse(name='Test', data='Test.S01E01.720p-FlexGet')
207        assert s.quality.name == '720p', 'failed to parse quality from %s' % s.data
208
209        s = parse(name='30 Suck', data='30 Suck 4x4 [HDTV - FlexGet]')
210        assert s.quality.name == 'hdtv', 'failed to parse quality %s' % s.data
211
212        s = parse(name='ShowB', data='ShowB.S04E19.Name of Ep.720p.WEB-DL.DD5.1.H.264')
213        assert s.quality.name == '720p webdl h264 dd5.1', 'failed to parse quality %s' % s.data
214
215    def test_quality_parenthesis(self, parse):
216        """SeriesParser: quality in parenthesis"""
217        s = parse(name='Foo Bar', data='Foo.Bar.S01E01.[720p].HDTV.x264-FlexGet')
218        assert s.season == 1 and s.episode == 1, 'failed to parse episodes from %s' % s.data
219        assert s.quality.name == '720p hdtv h264', 'failed to parse quality from %s' % s.data
220
221        s = parse(name='Foo Bar', data='Foo.Bar.S01E01.(720p).HDTV.x264-FlexGet')
222        assert s.season == 1 and s.episode == 1, 'failed to parse episodes from %s' % s.data
223        assert s.quality.name == '720p hdtv h264', 'failed to parse quality from %s' % s.data
224
225        s = parse(name='Foo Bar', data='[720p]Foo.Bar.S01E01.HDTV.x264-FlexGet')
226        assert s.season == 1 and s.episode == 1, 'failed to parse episodes from %s' % s.data
227        assert s.quality.name == '720p hdtv h264', 'failed to parse quality from %s' % s.data
228
229    def test_numeric_names(self, parse):
230        """SeriesParser: numeric names (24)"""
231        s = parse(name='24', data='24.1x2-FlexGet')
232        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s.data
233
234        s = parse(name='90120', data='90120.1x2-FlexGet')
235        assert s.season == 1 and s.episode == 2, 'failed to parse %s' % s.data
236
237    def test_group_prefix(self, parse):
238        """SeriesParser: [group] before name"""
239        s = parse(name='Foo Bar', data='[l.u.l.z] Foo Bar - 11 (H.264) [5235532D].mkv')
240        assert s.id == 11, 'failed to parse %s' % s.data
241
242        s = parse(name='Foo Bar', data='[7.1.7.5] Foo Bar - 11 (H.264) [5235532D].mkv')
243        assert s.id == 11, 'failed to parse %s' % s.data
244
245    def test_hd_prefix(self, parse):
246        """SeriesParser: HD 720p before name"""
247        s = parse(name='Foo Bar', data='HD 720p: Foo Bar - 11 (H.264) [5235532D].mkv')
248        assert s.id == 11, 'failed to parse %s' % s.data
249        assert s.quality.name == '720p h264', 'failed to pick up quality'
250
251    def test_partially_numeric(self, parse):
252        """SeriesParser: partially numeric names"""
253        s = parse(name='Foo 2009', data='Foo.2009.S02E04.HDTV.XviD-2HD[FlexGet]')
254        assert s.season == 2 and s.episode == 4, 'failed to parse %s' % s.data
255        assert s.quality.name == 'hdtv xvid', 'failed to parse quality from %s' % s.data
256
257    def test_ignore_seasonpacks(self, parse, parse_invalid):
258        """SeriesParser: ignoring season packs"""
259        # parse_invalid(name='The Foo', data='The.Foo.S04.1080p.FlexGet.5.1')
260        parse_invalid(name='The Foo', data='The Foo S05 720p BluRay DTS x264-FlexGet')
261        parse_invalid(name='The Foo', data='The Foo S05 720p BluRay DTS x264-FlexGet')
262        parse_invalid(name='Something', data='Something S02 Pack 720p WEB-DL-FlexGet')
263        parse_invalid(name='Something', data='Something S06 AC3-CRAPL3SS')
264        parse_invalid(
265            name='Something',
266            data='Something SEASON 1 2010 540p BluRay QEBS AAC ANDROID IPAD MP4 FASM',
267        )
268        parse_invalid(name='Something', data='Something.1x0.Complete.Season-FlexGet')
269        parse_invalid(name='Something', data='Something.1xAll.Season.Complete-FlexGet')
270        parse_invalid(name='Something', data='Something Seasons 1 & 2 - Complete')
271        parse_invalid(name='Something', data='Something Seasons 4 Complete')
272        parse_invalid(name='Something', data='Something Seasons 1 2 3 4')
273        parse_invalid(name='Something', data='Something S6 E1-4')
274        parse_invalid(name='Something', data='Something_Season_1_Full_Season_2_EP_1-7_HD')
275        parse_invalid(name='Something', data='Something - Season 10 - FlexGet')
276        parse_invalid(name='Something', data='Something_ DISC_1_OF_2 MANofKENT INVICTA RG')
277        # Make sure no false positives
278        assert parse(name='Something', data='Something S01E03 Full Throttle').valid
279
280    def test_similar(self, parse):
281        s = parse(
282            name='Foo Bar', data='Foo.Bar:Doppelganger.S02E04.HDTV.FlexGet', strict_name=True
283        )
284        assert not s.valid, 'should not have parser Foo.Bar:Doppelganger'
285        s = parse(
286            name='Foo Bar', data='Foo.Bar.Doppelganger.S02E04.HDTV.FlexGet', strict_name=True
287        )
288        assert not s.valid, 'should not have parser Foo.Bar.Doppelganger'
289
290    def test_idiotic_numbering(self, parse):
291        """SeriesParser: idiotic 101, 102, 103, .. numbering"""
292        s = parse('Test.706.720p-FlexGet', name='test', identified_by='ep')
293        assert s.season == 7, 'didn\'t pick up season'
294        assert s.episode == 6, 'didn\'t pick up episode'
295
296    def test_idiotic_numbering_with_zero(self, parse):
297        """SeriesParser: idiotic 0101, 0102, 0103, .. numbering"""
298        s = parse('Test.0706.720p-FlexGet', name='test', identified_by='ep')
299        assert s.season == 7, 'season missing'
300        assert s.episode == 6, 'episode missing'
301        assert s.identifier == 'S07E06', 'identifier broken'
302
303    def test_idiotic_invalid(self, parse):
304        """SeriesParser: idiotic confused by invalid"""
305        s = parse(
306            'Test.Revealed.WS.PDTV.XviD-aAF.5190458.TPB.torrent', name='test', identified_by='ep'
307        )
308        # assert_raises(ParseWarning, s.parse)
309        assert not s.season == 5, 'confused, got season'
310        assert not s.season == 4, 'confused, got season'
311        assert not s.episode == 19, 'confused, got episode'
312        assert not s.episode == 58, 'confused, got episode'
313
314    def test_zeroes(self, parse):
315        """SeriesParser: test zeroes as a season, episode"""
316
317        for data in ['Test.S00E00-FlexGet', 'Test.S00E01-FlexGet', 'Test.S01E00-FlexGet']:
318            s = parse(name='Test', data=data)
319            id = s.identifier
320            assert s.valid, 'parser not a valid for %s' % data
321            assert isinstance(id, str), 'id is not a string for %s' % data
322            assert isinstance(s.season, int), 'season is not a int for %s' % data
323            assert isinstance(s.episode, int), 'season is not a int for %s' % data
324
325    def test_exact_name(self, parse):
326        """SeriesParser: test exact/strict name parsing"""
327
328        s = parse('Test.Foobar.S01E02.720p-FlexGet', name='test')
329        assert s.valid, 'normal failed'
330
331        s = parse('Test.A.S01E02.720p-FlexGet', name='test', strict_name=True)
332        assert not s.valid, 'strict A failed'
333
334        s = parse('Test.AB.S01E02.720p-FlexGet', name='Test AB', strict_name=True)
335        assert s.valid, 'strict AB failed'
336
337        s = parse('Red Tomato (US) S01E02 720p-FlexGet', name='Red Tomato', strict_name=True)
338        assert not s.valid, 'Red Tomato (US) should not match Red Tomato in exact mode'
339
340    def test_name_word_boundries(self, parse):
341        name = 'test'
342        s = parse('Test.S01E02.720p-FlexGet', name=name)
343        assert s.valid, 'normal failed'
344        # In non-exact mode these should match
345        s = parse('Test.crap.S01E02.720p-FlexGet', name=name)
346        assert s.valid, 'normal failed'
347        s = parse('Test_crap.S01E02.720p-FlexGet', name=name)
348        assert s.valid, 'underscore failed'
349        # However if the title ends mid-word, it should not match
350        s = parse('Testing.S01E02.720p-FlexGet', name=name)
351        assert not s.valid, 'word border failed'
352
353    def test_quality_as_ep(self, parse):
354        """SeriesParser: test that qualities are not picked as ep"""
355        from flexget.utils import qualities
356
357        for quality in qualities.all_components():
358            parse('FooBar %s XviD-FlexGet' % quality.name, name='FooBar')
359
360    def test_sound_as_ep(self, parse):
361        """SeriesParser: test that sound infos are not picked as ep"""
362        sounds = ['AC3', 'DD5.1', 'DTS']
363        for sound in sounds:
364            parse(data='FooBar %s XViD-FlexGet' % sound, name='FooBar')
365
366    @pytest.mark.xfail(reason='Bug in guessit, works for internal parser')
367    def test_ep_as_quality(self, parse):
368        """SeriesParser: test that eps are not picked as qualities"""
369        from flexget.utils import qualities
370
371        for quality1 in qualities.all_components():
372            # Attempt to create an episode number out of quality
373            mock_ep1 = ''.join(list(filter(str.isdigit, quality1.name)))
374            if not mock_ep1:
375                continue
376
377            for quality2 in qualities.all_components():
378                mock_ep2 = ''.join(list(filter(str.isdigit, quality2.name)))
379                if not mock_ep2:
380                    continue
381
382                # 720i, 1080i, etc. are failing because
383                # e.g the 720 in 720i can always be taken to mean 720p,
384                # which is a higher priority quality.
385                # Moreover, 1080 as an ep number is always failing because
386                # sequence regexps support at most 3 digits at the moment.
387                # Luckily, all of these cases are discarded by the following,
388                # which also discards the failing cases when episode number
389                # (e.g. 720) is greater or equal than quality number (e.g. 480p).
390                # There's nothing that can be done with those failing cases with the
391                # current
392                # "grab leftmost occurrence of highest quality-like thing" algorithm.
393                if int(mock_ep1) >= int(mock_ep2) or int(mock_ep2) > 999:
394                    continue
395
396                s = parse('FooBar - %s %s-FlexGet' % (mock_ep1, quality2.name), name='FooBar')
397                assert s.episode == int(mock_ep1), "confused episode %s with quality %s" % (
398                    mock_ep1,
399                    quality2.name,
400                )
401
402                # Also test with reversed relative order of episode and quality
403                s = parse('[%s] FooBar - %s [FlexGet]' % (quality2.name, mock_ep1), name='FooBar')
404                assert s.episode == int(mock_ep1), "confused episode %s with quality %s" % (
405                    mock_ep1,
406                    quality2.name,
407                )
408
409    def test_name_with_number(self, parse):
410        """SeriesParser: test number in a name"""
411        parse('Storage 13 no ep number', name='Storage 13')
412
413    def test_name_uncorrupted(self, parse):
414        """SeriesParser: test name doesn't get corrupted when cleaned"""
415        s = parse(
416            name='The New Adventures of Old Christine',
417            data='The.New.Adventures.of.Old.Christine.S05E16.HDTV.XviD-FlexGet',
418        )
419        assert s.name == 'The New Adventures of Old Christine'
420        assert s.season == 5
421        assert s.episode == 16
422        assert s.quality.name == 'hdtv xvid'
423
424    def test_from_groups(self, parse):
425        """SeriesParser: test from groups"""
426        s = parse('Test.S01E01-Group', name='Test', allow_groups=['xxxx', 'group'])
427        assert s.group.lower() == 'group', 'did not get group'
428
429    def test_group_dashes(self, parse):
430        """SeriesParser: group name around extra dashes"""
431        s = parse('Test.S01E01-FooBar-Group', name='Test', allow_groups=['xxxx', 'group'])
432        assert s.group.lower() == 'group', 'did not get group with extra dashes'
433
434    def test_id_and_hash(self, parse):
435        """SeriesParser: Series with confusing hash"""
436        s = parse(name='Something', data='Something 63 [560D3414]')
437        assert s.id == 63, 'failed to parse %s' % s.data
438
439        s = parse(name='Something', data='Something 62 [293A8395]')
440        assert s.id == 62, 'failed to parse %s' % s.data
441
442    def test_ticket_700(self, parse):
443        """SeriesParser: confusing name (#700)"""
444        s = parse(name='Something', data='Something 9x02 - Episode 2')
445        assert s.season == 9, 'failed to parse season'
446        assert s.episode == 2, 'failed to parse episode'
447
448    def test_date_id(self, parse):
449        """SeriesParser: Series with dates"""
450        s = parse(name='Something', data='Something.2010.10.25')
451        assert s.identifier == '2010-10-25', 'failed to parse %s' % s.data
452        assert s.id_type == 'date'
453
454        s = parse(name='Something', data='Something 2010-10-25')
455        assert s.identifier == '2010-10-25', 'failed to parse %s' % s.data
456        assert s.id_type == 'date'
457
458        s = parse(name='Something', data='Something 10/25/2010')
459        assert s.identifier == '2010-10-25', 'failed to parse %s' % s.data
460        assert s.id_type == 'date'
461
462        s = parse(name='Something', data='Something 25.10.2010')
463        assert s.identifier == '2010-10-25', 'failed to parse %s' % s.data
464        assert s.id_type == 'date'
465
466        # February 1 is picked rather than January 2 because it is closer to now
467        s = parse(name='Something', data='Something 1.2.11')
468        assert s.identifier == '2011-02-01', 'failed to parse %s' % s.data
469        assert s.id_type == 'date'
470
471        # Future dates should not be considered dates
472        s = parse(name='Something', data='Something 01.02.32')
473        assert s.id_type != 'date'
474
475        # Dates with parts used to be parsed as episodes.
476        s = parse(name='Something', data='Something.2010.10.25, Part 2')
477        assert s.identifier == '2010-10-25', 'failed to parse %s' % s.data
478        assert s.id_type == 'date'
479
480        # Text based dates
481        s = parse(name='Something', data='Something (18th july 2013)')
482        assert s.identifier == '2013-07-18', 'failed to parse %s' % s.data
483        assert s.id_type == 'date'
484
485        s = parse(name='Something', data='Something 2 mar 2013)')
486        assert s.identifier == '2013-03-02', 'failed to parse %s' % s.data
487        assert s.id_type == 'date'
488
489        s = parse(name='Something', data='Something 1st february 1993)')
490        assert s.identifier == '1993-02-01', 'failed to parse %s' % s.data
491        assert s.id_type == 'date'
492
493    def test_date_options(self, parse):
494        # By default we should pick the latest interpretation
495        s = parse(name='Something', data='Something 01-02-03')
496        assert s.identifier == '2003-02-01', 'failed to parse %s' % s.data
497
498        # Test it still works with both options specified
499        s = parse(
500            name='Something', data='Something 01-02-03', date_yearfirst=False, date_dayfirst=True
501        )
502        assert s.identifier == '2003-02-01', 'failed to parse %s' % s.data
503
504        # If we specify yearfirst yes it should force another interpretation
505        s = parse(name='Something', data='Something 01-02-03', date_yearfirst=True)
506        assert s.identifier == '2001-03-02', 'failed to parse %s' % s.data
507
508        # If we specify dayfirst no it should force the third interpretation
509        s = parse(name='Something', data='Something 01-02-03', date_dayfirst=False)
510        assert s.identifier == '2003-01-02', 'failed to parse %s' % s.data
511
512    def test_season_title_episode(self, parse):
513        """SeriesParser: Series with title between season and episode"""
514        s = parse(name='Something', data='Something.S5.Drunk.Santa.Part1')
515        assert s.season == 5, 'failed to parse season'
516        assert s.episode == 1, 'failed to parse episode'
517
518    def test_specials(self, parse):
519        """SeriesParser: Special episodes with no id"""
520        s = parse(
521            name='The Show', data='The Show 2005 A Christmas Carol 2010 Special 720p HDTV x264'
522        )
523        assert s.valid, 'Special episode should be valid'
524
525    def test_double_episodes(self, parse):
526        s = parse(name='Something', data='Something.S04E05-06')
527        assert s.season == 4, 'failed to parse season'
528        assert s.episode == 5, 'failed to parse episode'
529        assert s.episodes == 2, 'failed to parse episode range'
530        s = parse(name='Something', data='Something.S04E05-E06')
531        assert s.season == 4, 'failed to parse season'
532        assert s.episode == 5, 'failed to parse episode'
533        assert s.episodes == 2, 'failed to parse episode range'
534        s = parse(name='Something', data='Something.S04E05E06')
535        assert s.season == 4, 'failed to parse season'
536        assert s.episode == 5, 'failed to parse episode'
537        assert s.episodes == 2, 'failed to parse episode range'
538        s = parse(name='Something', data='Something.4x05-06')
539        assert s.season == 4, 'failed to parse season'
540        assert s.episode == 5, 'failed to parse episode'
541        assert s.episodes == 2, 'failed to parse episode range'
542        # Test that too large a range is not accepted
543        s = parse(name='Something', data='Something.S04E05-09')
544        assert not s.valid, 'large episode range should not be valid'
545        # Make sure regular identifier doesn't have end_episode
546        s = parse(name='Something', data='Something.S04E05')
547        assert s.episodes == 1, 'should not have detected end_episode'
548
549    def test_and_replacement(self, parse):
550        titles = [
551            'Alpha.&.Beta.S01E02.hdtv',
552            'alpha.and.beta.S01E02.hdtv',
553            'alpha&beta.S01E02.hdtv',
554        ]
555        for title in titles:
556            s = parse(name='Alpha & Beta', data=title)
557            assert s.valid
558            s = parse(name='Alpha and Beta', data=title)
559            assert s.valid
560        # Test 'and' isn't replaced within a word
561        s = parse(name='Sandy Dunes', data='S&y Dunes.S01E01.hdtv')
562        assert not s.valid
563
564    def test_unicode(self, parse):
565        s = parse(name='abc äää abc', data='abc.äää.abc.s01e02')
566        assert s.season == 1
567        assert s.episode == 2
568
569    def test_parentheticals(self, parse):
570        s = parse('The Show (US)', name="The Show (US)")
571        # Make sure US is ok outside of parentheses
572        s = parse('The.Show.US.S01E01', name="The Show (US)")
573        assert s.valid
574        # Make sure US is ok inside parentheses
575        s = parse('The Show (US) S01E01', name="The Show (US)")
576        assert s.valid
577        # Make sure it works without US
578        s = parse('The.Show.S01E01', name="The Show (US)")
579        assert s.valid
580        # Make sure it doesn't work with a different country
581        s = parse('The Show (UK) S01E01', name="The Show (US)")
582        assert not s.valid
583
584    def test_id_regexps(self, parse):
585        id_regexps = ['(dog)?e(cat)?']
586        s = parse('The Show dogecat', name='The Show', id_regexps=id_regexps)
587        assert s.valid
588        assert s.id == 'dog-cat'
589        s = parse('The Show doge', name='The Show', id_regexps=id_regexps)
590        assert s.valid
591        assert s.id == 'dog'
592        s = parse('The Show ecat', name='The Show', id_regexps=id_regexps)
593        assert s.valid
594        assert s.id == 'cat'
595        # assert_raises(ParseWarning, s.parse, 'The Show e')
596
597    def test_apostrophe(self, parse):
598        s = parse(name=u"FlexGet's show", data=u"FlexGet's show s01e01")
599        assert s.valid
600        s = parse(name=u"FlexGet's show", data=u"FlexGets show s01e01")
601        assert s.valid
602        s = parse(name=u"FlexGet's show", data=u"FlexGet s show s01e01")
603        assert s.valid
604        s = parse(name=u"FlexGet's show", data=u"FlexGet show s01e01")
605        assert not s.valid
606        # bad data with leftover escaping
607        s = parse(name=u"FlexGet's show", data=u"FlexGet\\'s show s01e01")
608        assert s.valid
609
610    def test_alternate_names(self, parse):
611        name = 'The Show'
612        alternate_names = ['Show', 'Completely Different']
613        s = parse('The Show S01E01', name=name, alternate_names=alternate_names)
614        assert s.valid
615        s = parse('Show S01E01', name=name, alternate_names=alternate_names)
616        assert s.valid
617        s = parse('Completely.Different.S01E01', name=name, alternate_names=alternate_names)
618        assert s.valid
619        s = parse('Not The Show S01E01', name=name, alternate_names=alternate_names)
620        assert not s.valid
621
622    def test_long_season(self, parse):
623        """SeriesParser: long season ID Ticket #2197"""
624        s = parse(
625            name='FlexGet', data='FlexGet.US.S2013E14.Title.Here.720p.HDTV.AAC5.1.x264-NOGRP'
626        )
627        assert s.season == 2013
628        assert s.episode == 14
629        assert s.quality.name == '720p hdtv h264 aac'
630        assert not s.proper, 'detected proper'
631
632        s = parse(
633            name='FlexGet',
634            data='FlexGet.Series.2013.14.of.21.Title.Here.720p.HDTV.AAC5.1.x264-NOGRP',
635        )
636        assert s.season == 2013
637        assert s.episode == 14
638        assert s.quality.name == '720p hdtv h264 aac'
639        assert not s.proper, 'detected proper'
640
641    def test_episode_with_season_pack_match_is_not_season_pack(self, parse):
642        r"""SeriesParser: Github issue #1986, s\d{1} parses as invalid season"""
643        s = parse(name='The Show', data='The.Show.S01E01.eps3.0.some.title.720p.x264-NOGRP')
644        assert s.valid
645        assert not s.season_pack
646        assert s.season == 1
647        assert s.episode == 1
648