We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Series not found in show ids (Medusa does not download subtiltes)
Medusa gives this warning for a few of my shows, for example How I Met Your Father:
2022-02-13 23:36:54 WARNING  Thread_0 :: [918cfe7] Series How I Met Your Father not found in show ids

It doesn't download subtitles for this show. I couldn't find anything related to this issue. Hope you can help.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `medusa/subtitle_providers/addic7ed.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 """Custom subliminal addic7ed.com subtitle provider module."""
3 
4 import hashlib
5 import logging
6 import re
7 
8 from babelfish import Language
9 
10 from guessit import guessit
11 
12 from requests import Session
13 
14 from subliminal.cache import SHOW_EXPIRATION_TIME, region
15 from subliminal.exceptions import ConfigurationError, DownloadLimitExceeded
16 from subliminal.matches import guess_matches
17 from subliminal.providers import ParserBeautifulSoup, Provider
18 from subliminal.subtitle import Subtitle, fix_line_ending
19 from subliminal.utils import sanitize
20 from subliminal.video import Episode
21 
22 logger = logging.getLogger(__name__)
23 
24 # language_converters.register('addic7ed = subliminal.converters.addic7ed:Addic7edConverter')
25 
26 # Series cell matching regex
27 show_cells_re = re.compile(b'<td class="vr">.*?</td>', re.DOTALL)
28 
29 #: Series header parsing regex
30 series_year_re = re.compile(r'^(?P<series>[ \w\'.:(),*&!?-]+?)(?: \((?P<year>\d{4})\))?$')
31 
32 
33 class Addic7edSubtitle(Subtitle):
34     """Addic7ed Subtitle."""
35 
36     provider_name = 'addic7ed'
37 
38     def __init__(self, language, hearing_impaired, page_link, series, season, episode, title, year, version,
39                  download_link):
40         super(Addic7edSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, page_link=page_link)
41         self.series = series
42         self.season = season
43         self.episode = episode
44         self.title = title
45         self.year = year
46         self.version = version
47         self.download_link = download_link
48 
49     @property
50     def id(self):
51         """Get id."""
52         return self.download_link
53 
54     @property
55     def info(self):
56         """Get info."""
57         return '{series}{yopen}{year}{yclose} s{season:02d}e{episode:02d}{topen}{title}{tclose}{version}'.format(
58             series=self.series, season=self.season, episode=self.episode, title=self.title, year=self.year or '',
59             version=self.version, yopen=' (' if self.year else '', yclose=')' if self.year else '',
60             topen=' - ' if self.title else '', tclose=' - ' if self.version else ''
61         )
62 
63     def get_matches(self, video):
64         """Get matches."""
65         # series name
66         matches = guess_matches(video, {
67             'title': self.series,
68             'season': self.season,
69             'episode': self.episode,
70             'episode_title': self.title,
71             'year': self.year,
72             'release_group': self.version,
73         })
74 
75         # resolution
76         if video.resolution and self.version and video.resolution in self.version.lower():
77             matches.add('resolution')
78         # other properties
79         if self.version:
80             matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True)
81 
82         return matches
83 
84 
85 class Addic7edProvider(Provider):
86     """Addic7ed Provider."""
87 
88     languages = {Language('por', 'BR')} | {Language(l) for l in [
89         'ara', 'aze', 'ben', 'bos', 'bul', 'cat', 'ces', 'dan', 'deu', 'ell', 'eng', 'eus', 'fas', 'fin', 'fra', 'glg',
90         'heb', 'hrv', 'hun', 'hye', 'ind', 'ita', 'jpn', 'kor', 'mkd', 'msa', 'nld', 'nor', 'pol', 'por', 'ron', 'rus',
91         'slk', 'slv', 'spa', 'sqi', 'srp', 'swe', 'tha', 'tur', 'ukr', 'vie', 'zho'
92     ]}
93     video_types = (Episode,)
94     server_url = 'http://www.addic7ed.com/'
95     subtitle_class = Addic7edSubtitle
96 
97     def __init__(self, username=None, password=None):
98         if any((username, password)) and not all((username, password)):
99             raise ConfigurationError('Username and password must be specified')
100 
101         self.username = username
102         self.password = hashlib.md5(password.encode('utf-8')).hexdigest()
103         self.logged_in = False
104         self.cookies = {'wikisubtitlesuser': self.username, 'wikisubtitlespass': self.password}
105 
106     def initialize(self):
107         """Initialize Addic7edProvider provider."""
108         self.session = Session()
109         self.session.headers['User-Agent'] = self.user_agent
110 
111         # login
112         if self.username and self.password:
113             logger.debug('Logged in')
114             self.logged_in = True
115 
116     def terminate(self):
117         """Terminate."""
118         # logout
119         logger.debug('Logged out')
120         self.logged_in = False
121 
122     @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)
123     def _get_show_ids(self):
124         """Get the ``dict`` of show ids per series by querying the `shows.php` page.
125 
126         :return: show id per series, lower case and without quotes.
127         :rtype: dict
128         """
129         # get the show page
130         logger.info('Getting show ids')
131         r = self.session.get(self.server_url + 'shows.php', timeout=20, cookies=self.cookies)
132         r.raise_for_status()
133 
134         # LXML parser seems to fail when parsing Addic7ed.com HTML markup.
135         # Last known version to work properly is 3.6.4 (next version, 3.7.0, fails)
136         # Assuming the site's markup is bad, and stripping it down to only contain what's needed.
137         show_cells = re.findall(show_cells_re, r.content)
138         if show_cells:
139             soup = ParserBeautifulSoup(b''.join(show_cells), ['lxml', 'html.parser'])
140         else:
141             # If RegEx fails, fall back to original r.content and use 'html.parser'
142             soup = ParserBeautifulSoup(r.content, ['html.parser'])
143 
144         # populate the show ids
145         show_ids = {}
146         for show in soup.select('td.vr > h3 > a[href^="/show/"]'):
147             show_ids[sanitize(show.text)] = int(show['href'][6:])
148         logger.debug('Found %d show ids', len(show_ids))
149 
150         return show_ids
151 
152     @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)
153     def _search_show_id(self, series, year=None):
154         """Search the show id from the `series` and `year`.
155 
156         :param str series: series of the episode.
157         :param year: year of the series, if any.
158         :type year: int
159         :return: the show id, if found.
160         :rtype: int
161         """
162         # addic7ed doesn't support search with quotes
163         series = series.replace("'", ' ')
164 
165         # build the params
166         series_year = '%s %d' % (series, year) if year is not None else series
167         params = {'search': series_year, 'Submit': 'Search'}
168 
169         r = self.session.get('http://www.addic7ed.com/srch.php', params=params, timeout=10, cookies=self.cookies)
170 
171         # make the search
172         logger.info('Searching show ids with %r', params)
173         r.raise_for_status()
174         soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
175 
176         # get the suggestion
177         suggestion = soup.select('span.titulo > a[href^="/show/"]')
178         if not suggestion:
179             logger.warning('Show id not found: no suggestion')
180             return None
181         if not sanitize(suggestion[0].i.text.replace("'", ' ')) == sanitize(series_year):
182             logger.warning('Show id not found: suggestion does not match')
183             return None
184         show_id = int(suggestion[0]['href'][6:])
185         logger.debug('Found show id %d', show_id)
186 
187         return show_id
188 
189     def get_show_id(self, series, year=None, country_code=None):
190         """Get the best matching show id for `series`, `year` and `country_code`.
191 
192         First search in the result of :meth:`_get_show_ids` and fallback on a search with :meth:`_search_show_id`.
193         :param str series: series of the episode.
194         :param year: year of the series, if any.
195         :type year: int
196         :param country_code: country code of the series, if any.
197         :type country_code: str
198         :return: the show id, if found.
199         :rtype: int
200         """
201         series_sanitized = sanitize(series).lower()
202         show_ids = self._get_show_ids()
203         show_id = None
204 
205         # attempt with country
206         if country_code:
207             logger.debug('Getting show id with country')
208             show_id = show_ids.get('%s %s' % (series_sanitized, country_code.lower()))
209 
210         # attempt with year
211         if not show_id and year:
212             logger.debug('Getting show id with year')
213             show_id = show_ids.get('%s %d' % (series_sanitized, year))
214 
215         # attempt clean
216         if not show_id:
217             logger.debug('Getting show id')
218             show_id = show_ids.get(series_sanitized)
219 
220         # search as last resort
221         if not show_id:
222             logger.warning('Series %s not found in show ids', series)
223             show_id = self._search_show_id(series)
224 
225         return show_id
226 
227     def query(self, show_id, series, season, year=None, country=None):
228         """Query provider to get all subitles for a specific show + season."""
229         # get the page of the season of the show
230         logger.info('Getting the page of show id %d, season %d', show_id, season)
231         r = self.session.get(self.server_url + 'show/%d' % show_id, params={'season': season}, timeout=60, cookies=self.cookies)
232         r.raise_for_status()
233 
234         if not r.content:
235             # Provider returns a status of 304 Not Modified with an empty content
236             # raise_for_status won't raise exception for that status code
237             logger.debug('No data returned from provider')
238             return []
239 
240         soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
241 
242         # loop over subtitle rows
243         match = series_year_re.match(soup.select('#header font')[0].text.strip()[:-10])
244         series = match.group('series')
245         year = int(match.group('year')) if match.group('year') else None
246         subtitles = []
247         for row in soup.select('tr.epeven'):
248             cells = row('td')
249 
250             # ignore incomplete subtitles
251             status = cells[5].text
252             if status != 'Completed':
253                 logger.debug('Ignoring subtitle with status %s', status)
254                 continue
255 
256             # read the item
257             language = Language.fromaddic7ed(cells[3].text)
258             hearing_impaired = bool(cells[6].text)
259             page_link = self.server_url + cells[2].a['href'][1:]
260             season = int(cells[0].text)
261             episode = int(cells[1].text)
262             title = cells[2].text
263             version = cells[4].text
264             download_link = cells[9].a['href'][1:]
265 
266             subtitle = self.subtitle_class(language, hearing_impaired, page_link, series, season, episode, title, year,
267                                            version, download_link)
268             logger.debug('Found subtitle %r', subtitle)
269             subtitles.append(subtitle)
270 
271         return subtitles
272 
273     def list_subtitles(self, video, languages):
274         """List Subitles."""
275         # lookup show_id
276         titles = [video.series] + video.alternative_series
277         show_id = None
278         for title in titles:
279             show_id = self.get_show_id(title, video.year)
280             if show_id is not None:
281                 break
282 
283         # query for subtitles with the show_id
284         if show_id is not None:
285             subtitles = [s for s in self.query(show_id, title, video.season, video.year)
286                          if s.language in languages and s.episode == video.episode]
287             if subtitles:
288                 return subtitles
289         else:
290             logger.error('No show id found for %r (%r)', video.series, {'year': video.year})
291 
292         return []
293 
294     def download_subtitle(self, subtitle):
295         """Download subtitles."""
296         # download the subtitle
297         logger.info('Downloading subtitle %r', subtitle)
298         r = self.session.get(self.server_url + subtitle.download_link, headers={'Referer': subtitle.page_link},
299                              timeout=20)
300         r.raise_for_status()
301 
302         if not r.content:
303             # Provider returns a status of 304 Not Modified with an empty content
304             # raise_for_status won't raise exception for that status code
305             logger.debug('Unable to download subtitle. No data returned from provider')
306             return
307 
308         # detect download limit exceeded
309         if r.headers['Content-Type'] == 'text/html':
310             raise DownloadLimitExceeded
311 
312         subtitle.content = fix_line_ending(r.content)
313
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/medusa/subtitle_providers/addic7ed.py b/medusa/subtitle_providers/addic7ed.py
--- a/medusa/subtitle_providers/addic7ed.py
+++ b/medusa/subtitle_providers/addic7ed.py
@@ -166,7 +166,7 @@
         series_year = '%s %d' % (series, year) if year is not None else series
         params = {'search': series_year, 'Submit': 'Search'}
 
-        r = self.session.get('http://www.addic7ed.com/srch.php', params=params, timeout=10, cookies=self.cookies)
+        r = self.session.get('http://www.addic7ed.com/srch.php', params=params, timeout=30, cookies=self.cookies)
 
         # make the search
         logger.info('Searching show ids with %r', params)
@@ -219,7 +219,7 @@
 
         # search as last resort
         if not show_id:
-            logger.warning('Series %s not found in show ids', series)
+            logger.info('Series %s not found in show ids', series)
             show_id = self._search_show_id(series)
 
         return show_id

{"golden_diff": "diff --git a/medusa/subtitle_providers/addic7ed.py b/medusa/subtitle_providers/addic7ed.py\n--- a/medusa/subtitle_providers/addic7ed.py\n+++ b/medusa/subtitle_providers/addic7ed.py\n@@ -166,7 +166,7 @@\n         series_year = '%s %d' % (series, year) if year is not None else series\n         params = {'search': series_year, 'Submit': 'Search'}\n \n-        r = self.session.get('http://www.addic7ed.com/srch.php', params=params, timeout=10, cookies=self.cookies)\n+        r = self.session.get('http://www.addic7ed.com/srch.php', params=params, timeout=30, cookies=self.cookies)\n \n         # make the search\n         logger.info('Searching show ids with %r', params)\n@@ -219,7 +219,7 @@\n \n         # search as last resort\n         if not show_id:\n-            logger.warning('Series %s not found in show ids', series)\n+            logger.info('Series %s not found in show ids', series)\n             show_id = self._search_show_id(series)\n \n         return show_id\n", "issue": "Series not found in show ids (Medusa does not download subtiltes)\nMedusa gives this warning for a few of my shows, for example How I Met Your Father:\r\n2022-02-13 23:36:54 WARNING  Thread_0 :: [918cfe7] Series How I Met Your Father not found in show ids\r\n\r\nIt doesn't download subtitles for this show. I couldn't find anything related to this issue. Hope you can help.\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"Custom subliminal addic7ed.com subtitle provider module.\"\"\"\n\nimport hashlib\nimport logging\nimport re\n\nfrom babelfish import Language\n\nfrom guessit import guessit\n\nfrom requests import Session\n\nfrom subliminal.cache import SHOW_EXPIRATION_TIME, region\nfrom subliminal.exceptions import ConfigurationError, DownloadLimitExceeded\nfrom subliminal.matches import guess_matches\nfrom subliminal.providers import ParserBeautifulSoup, Provider\nfrom subliminal.subtitle import Subtitle, fix_line_ending\nfrom subliminal.utils import sanitize\nfrom subliminal.video import Episode\n\nlogger = logging.getLogger(__name__)\n\n# language_converters.register('addic7ed = subliminal.converters.addic7ed:Addic7edConverter')\n\n# Series cell matching regex\nshow_cells_re = re.compile(b'<td class=\"vr\">.*?</td>', re.DOTALL)\n\n#: Series header parsing regex\nseries_year_re = re.compile(r'^(?P<series>[ \\w\\'.:(),*&!?-]+?)(?: \\((?P<year>\\d{4})\\))?$')\n\n\nclass Addic7edSubtitle(Subtitle):\n    \"\"\"Addic7ed Subtitle.\"\"\"\n\n    provider_name = 'addic7ed'\n\n    def __init__(self, language, hearing_impaired, page_link, series, season, episode, title, year, version,\n                 download_link):\n        super(Addic7edSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, page_link=page_link)\n        self.series = series\n        self.season = season\n        self.episode = episode\n        self.title = title\n        self.year = year\n        self.version = version\n        self.download_link = download_link\n\n    @property\n    def id(self):\n        \"\"\"Get id.\"\"\"\n        return self.download_link\n\n    @property\n    def info(self):\n        \"\"\"Get info.\"\"\"\n        return '{series}{yopen}{year}{yclose} s{season:02d}e{episode:02d}{topen}{title}{tclose}{version}'.format(\n            series=self.series, season=self.season, episode=self.episode, title=self.title, year=self.year or '',\n            version=self.version, yopen=' (' if self.year else '', yclose=')' if self.year else '',\n            topen=' - ' if self.title else '', tclose=' - ' if self.version else ''\n        )\n\n    def get_matches(self, video):\n        \"\"\"Get matches.\"\"\"\n        # series name\n        matches = guess_matches(video, {\n            'title': self.series,\n            'season': self.season,\n            'episode': self.episode,\n            'episode_title': self.title,\n            'year': self.year,\n            'release_group': self.version,\n        })\n\n        # resolution\n        if video.resolution and self.version and video.resolution in self.version.lower():\n            matches.add('resolution')\n        # other properties\n        if self.version:\n            matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True)\n\n        return matches\n\n\nclass Addic7edProvider(Provider):\n    \"\"\"Addic7ed Provider.\"\"\"\n\n    languages = {Language('por', 'BR')} | {Language(l) for l in [\n        'ara', 'aze', 'ben', 'bos', 'bul', 'cat', 'ces', 'dan', 'deu', 'ell', 'eng', 'eus', 'fas', 'fin', 'fra', 'glg',\n        'heb', 'hrv', 'hun', 'hye', 'ind', 'ita', 'jpn', 'kor', 'mkd', 'msa', 'nld', 'nor', 'pol', 'por', 'ron', 'rus',\n        'slk', 'slv', 'spa', 'sqi', 'srp', 'swe', 'tha', 'tur', 'ukr', 'vie', 'zho'\n    ]}\n    video_types = (Episode,)\n    server_url = 'http://www.addic7ed.com/'\n    subtitle_class = Addic7edSubtitle\n\n    def __init__(self, username=None, password=None):\n        if any((username, password)) and not all((username, password)):\n            raise ConfigurationError('Username and password must be specified')\n\n        self.username = username\n        self.password = hashlib.md5(password.encode('utf-8')).hexdigest()\n        self.logged_in = False\n        self.cookies = {'wikisubtitlesuser': self.username, 'wikisubtitlespass': self.password}\n\n    def initialize(self):\n        \"\"\"Initialize Addic7edProvider provider.\"\"\"\n        self.session = Session()\n        self.session.headers['User-Agent'] = self.user_agent\n\n        # login\n        if self.username and self.password:\n            logger.debug('Logged in')\n            self.logged_in = True\n\n    def terminate(self):\n        \"\"\"Terminate.\"\"\"\n        # logout\n        logger.debug('Logged out')\n        self.logged_in = False\n\n    @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)\n    def _get_show_ids(self):\n        \"\"\"Get the ``dict`` of show ids per series by querying the `shows.php` page.\n\n        :return: show id per series, lower case and without quotes.\n        :rtype: dict\n        \"\"\"\n        # get the show page\n        logger.info('Getting show ids')\n        r = self.session.get(self.server_url + 'shows.php', timeout=20, cookies=self.cookies)\n        r.raise_for_status()\n\n        # LXML parser seems to fail when parsing Addic7ed.com HTML markup.\n        # Last known version to work properly is 3.6.4 (next version, 3.7.0, fails)\n        # Assuming the site's markup is bad, and stripping it down to only contain what's needed.\n        show_cells = re.findall(show_cells_re, r.content)\n        if show_cells:\n            soup = ParserBeautifulSoup(b''.join(show_cells), ['lxml', 'html.parser'])\n        else:\n            # If RegEx fails, fall back to original r.content and use 'html.parser'\n            soup = ParserBeautifulSoup(r.content, ['html.parser'])\n\n        # populate the show ids\n        show_ids = {}\n        for show in soup.select('td.vr > h3 > a[href^=\"/show/\"]'):\n            show_ids[sanitize(show.text)] = int(show['href'][6:])\n        logger.debug('Found %d show ids', len(show_ids))\n\n        return show_ids\n\n    @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)\n    def _search_show_id(self, series, year=None):\n        \"\"\"Search the show id from the `series` and `year`.\n\n        :param str series: series of the episode.\n        :param year: year of the series, if any.\n        :type year: int\n        :return: the show id, if found.\n        :rtype: int\n        \"\"\"\n        # addic7ed doesn't support search with quotes\n        series = series.replace(\"'\", ' ')\n\n        # build the params\n        series_year = '%s %d' % (series, year) if year is not None else series\n        params = {'search': series_year, 'Submit': 'Search'}\n\n        r = self.session.get('http://www.addic7ed.com/srch.php', params=params, timeout=10, cookies=self.cookies)\n\n        # make the search\n        logger.info('Searching show ids with %r', params)\n        r.raise_for_status()\n        soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])\n\n        # get the suggestion\n        suggestion = soup.select('span.titulo > a[href^=\"/show/\"]')\n        if not suggestion:\n            logger.warning('Show id not found: no suggestion')\n            return None\n        if not sanitize(suggestion[0].i.text.replace(\"'\", ' ')) == sanitize(series_year):\n            logger.warning('Show id not found: suggestion does not match')\n            return None\n        show_id = int(suggestion[0]['href'][6:])\n        logger.debug('Found show id %d', show_id)\n\n        return show_id\n\n    def get_show_id(self, series, year=None, country_code=None):\n        \"\"\"Get the best matching show id for `series`, `year` and `country_code`.\n\n        First search in the result of :meth:`_get_show_ids` and fallback on a search with :meth:`_search_show_id`.\n        :param str series: series of the episode.\n        :param year: year of the series, if any.\n        :type year: int\n        :param country_code: country code of the series, if any.\n        :type country_code: str\n        :return: the show id, if found.\n        :rtype: int\n        \"\"\"\n        series_sanitized = sanitize(series).lower()\n        show_ids = self._get_show_ids()\n        show_id = None\n\n        # attempt with country\n        if country_code:\n            logger.debug('Getting show id with country')\n            show_id = show_ids.get('%s %s' % (series_sanitized, country_code.lower()))\n\n        # attempt with year\n        if not show_id and year:\n            logger.debug('Getting show id with year')\n            show_id = show_ids.get('%s %d' % (series_sanitized, year))\n\n        # attempt clean\n        if not show_id:\n            logger.debug('Getting show id')\n            show_id = show_ids.get(series_sanitized)\n\n        # search as last resort\n        if not show_id:\n            logger.warning('Series %s not found in show ids', series)\n            show_id = self._search_show_id(series)\n\n        return show_id\n\n    def query(self, show_id, series, season, year=None, country=None):\n        \"\"\"Query provider to get all subitles for a specific show + season.\"\"\"\n        # get the page of the season of the show\n        logger.info('Getting the page of show id %d, season %d', show_id, season)\n        r = self.session.get(self.server_url + 'show/%d' % show_id, params={'season': season}, timeout=60, cookies=self.cookies)\n        r.raise_for_status()\n\n        if not r.content:\n            # Provider returns a status of 304 Not Modified with an empty content\n            # raise_for_status won't raise exception for that status code\n            logger.debug('No data returned from provider')\n            return []\n\n        soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])\n\n        # loop over subtitle rows\n        match = series_year_re.match(soup.select('#header font')[0].text.strip()[:-10])\n        series = match.group('series')\n        year = int(match.group('year')) if match.group('year') else None\n        subtitles = []\n        for row in soup.select('tr.epeven'):\n            cells = row('td')\n\n            # ignore incomplete subtitles\n            status = cells[5].text\n            if status != 'Completed':\n                logger.debug('Ignoring subtitle with status %s', status)\n                continue\n\n            # read the item\n            language = Language.fromaddic7ed(cells[3].text)\n            hearing_impaired = bool(cells[6].text)\n            page_link = self.server_url + cells[2].a['href'][1:]\n            season = int(cells[0].text)\n            episode = int(cells[1].text)\n            title = cells[2].text\n            version = cells[4].text\n            download_link = cells[9].a['href'][1:]\n\n            subtitle = self.subtitle_class(language, hearing_impaired, page_link, series, season, episode, title, year,\n                                           version, download_link)\n            logger.debug('Found subtitle %r', subtitle)\n            subtitles.append(subtitle)\n\n        return subtitles\n\n    def list_subtitles(self, video, languages):\n        \"\"\"List Subitles.\"\"\"\n        # lookup show_id\n        titles = [video.series] + video.alternative_series\n        show_id = None\n        for title in titles:\n            show_id = self.get_show_id(title, video.year)\n            if show_id is not None:\n                break\n\n        # query for subtitles with the show_id\n        if show_id is not None:\n            subtitles = [s for s in self.query(show_id, title, video.season, video.year)\n                         if s.language in languages and s.episode == video.episode]\n            if subtitles:\n                return subtitles\n        else:\n            logger.error('No show id found for %r (%r)', video.series, {'year': video.year})\n\n        return []\n\n    def download_subtitle(self, subtitle):\n        \"\"\"Download subtitles.\"\"\"\n        # download the subtitle\n        logger.info('Downloading subtitle %r', subtitle)\n        r = self.session.get(self.server_url + subtitle.download_link, headers={'Referer': subtitle.page_link},\n                             timeout=20)\n        r.raise_for_status()\n\n        if not r.content:\n            # Provider returns a status of 304 Not Modified with an empty content\n            # raise_for_status won't raise exception for that status code\n            logger.debug('Unable to download subtitle. No data returned from provider')\n            return\n\n        # detect download limit exceeded\n        if r.headers['Content-Type'] == 'text/html':\n            raise DownloadLimitExceeded\n\n        subtitle.content = fix_line_ending(r.content)\n", "path": "medusa/subtitle_providers/addic7ed.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"Custom subliminal addic7ed.com subtitle provider module.\"\"\"\n\nimport hashlib\nimport logging\nimport re\n\nfrom babelfish import Language\n\nfrom guessit import guessit\n\nfrom requests import Session\n\nfrom subliminal.cache import SHOW_EXPIRATION_TIME, region\nfrom subliminal.exceptions import ConfigurationError, DownloadLimitExceeded\nfrom subliminal.matches import guess_matches\nfrom subliminal.providers import ParserBeautifulSoup, Provider\nfrom subliminal.subtitle import Subtitle, fix_line_ending\nfrom subliminal.utils import sanitize\nfrom subliminal.video import Episode\n\nlogger = logging.getLogger(__name__)\n\n# language_converters.register('addic7ed = subliminal.converters.addic7ed:Addic7edConverter')\n\n# Series cell matching regex\nshow_cells_re = re.compile(b'<td class=\"vr\">.*?</td>', re.DOTALL)\n\n#: Series header parsing regex\nseries_year_re = re.compile(r'^(?P<series>[ \\w\\'.:(),*&!?-]+?)(?: \\((?P<year>\\d{4})\\))?$')\n\n\nclass Addic7edSubtitle(Subtitle):\n    \"\"\"Addic7ed Subtitle.\"\"\"\n\n    provider_name = 'addic7ed'\n\n    def __init__(self, language, hearing_impaired, page_link, series, season, episode, title, year, version,\n                 download_link):\n        super(Addic7edSubtitle, self).__init__(language, hearing_impaired=hearing_impaired, page_link=page_link)\n        self.series = series\n        self.season = season\n        self.episode = episode\n        self.title = title\n        self.year = year\n        self.version = version\n        self.download_link = download_link\n\n    @property\n    def id(self):\n        \"\"\"Get id.\"\"\"\n        return self.download_link\n\n    @property\n    def info(self):\n        \"\"\"Get info.\"\"\"\n        return '{series}{yopen}{year}{yclose} s{season:02d}e{episode:02d}{topen}{title}{tclose}{version}'.format(\n            series=self.series, season=self.season, episode=self.episode, title=self.title, year=self.year or '',\n            version=self.version, yopen=' (' if self.year else '', yclose=')' if self.year else '',\n            topen=' - ' if self.title else '', tclose=' - ' if self.version else ''\n        )\n\n    def get_matches(self, video):\n        \"\"\"Get matches.\"\"\"\n        # series name\n        matches = guess_matches(video, {\n            'title': self.series,\n            'season': self.season,\n            'episode': self.episode,\n            'episode_title': self.title,\n            'year': self.year,\n            'release_group': self.version,\n        })\n\n        # resolution\n        if video.resolution and self.version and video.resolution in self.version.lower():\n            matches.add('resolution')\n        # other properties\n        if self.version:\n            matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True)\n\n        return matches\n\n\nclass Addic7edProvider(Provider):\n    \"\"\"Addic7ed Provider.\"\"\"\n\n    languages = {Language('por', 'BR')} | {Language(l) for l in [\n        'ara', 'aze', 'ben', 'bos', 'bul', 'cat', 'ces', 'dan', 'deu', 'ell', 'eng', 'eus', 'fas', 'fin', 'fra', 'glg',\n        'heb', 'hrv', 'hun', 'hye', 'ind', 'ita', 'jpn', 'kor', 'mkd', 'msa', 'nld', 'nor', 'pol', 'por', 'ron', 'rus',\n        'slk', 'slv', 'spa', 'sqi', 'srp', 'swe', 'tha', 'tur', 'ukr', 'vie', 'zho'\n    ]}\n    video_types = (Episode,)\n    server_url = 'http://www.addic7ed.com/'\n    subtitle_class = Addic7edSubtitle\n\n    def __init__(self, username=None, password=None):\n        if any((username, password)) and not all((username, password)):\n            raise ConfigurationError('Username and password must be specified')\n\n        self.username = username\n        self.password = hashlib.md5(password.encode('utf-8')).hexdigest()\n        self.logged_in = False\n        self.cookies = {'wikisubtitlesuser': self.username, 'wikisubtitlespass': self.password}\n\n    def initialize(self):\n        \"\"\"Initialize Addic7edProvider provider.\"\"\"\n        self.session = Session()\n        self.session.headers['User-Agent'] = self.user_agent\n\n        # login\n        if self.username and self.password:\n            logger.debug('Logged in')\n            self.logged_in = True\n\n    def terminate(self):\n        \"\"\"Terminate.\"\"\"\n        # logout\n        logger.debug('Logged out')\n        self.logged_in = False\n\n    @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)\n    def _get_show_ids(self):\n        \"\"\"Get the ``dict`` of show ids per series by querying the `shows.php` page.\n\n        :return: show id per series, lower case and without quotes.\n        :rtype: dict\n        \"\"\"\n        # get the show page\n        logger.info('Getting show ids')\n        r = self.session.get(self.server_url + 'shows.php', timeout=20, cookies=self.cookies)\n        r.raise_for_status()\n\n        # LXML parser seems to fail when parsing Addic7ed.com HTML markup.\n        # Last known version to work properly is 3.6.4 (next version, 3.7.0, fails)\n        # Assuming the site's markup is bad, and stripping it down to only contain what's needed.\n        show_cells = re.findall(show_cells_re, r.content)\n        if show_cells:\n            soup = ParserBeautifulSoup(b''.join(show_cells), ['lxml', 'html.parser'])\n        else:\n            # If RegEx fails, fall back to original r.content and use 'html.parser'\n            soup = ParserBeautifulSoup(r.content, ['html.parser'])\n\n        # populate the show ids\n        show_ids = {}\n        for show in soup.select('td.vr > h3 > a[href^=\"/show/\"]'):\n            show_ids[sanitize(show.text)] = int(show['href'][6:])\n        logger.debug('Found %d show ids', len(show_ids))\n\n        return show_ids\n\n    @region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)\n    def _search_show_id(self, series, year=None):\n        \"\"\"Search the show id from the `series` and `year`.\n\n        :param str series: series of the episode.\n        :param year: year of the series, if any.\n        :type year: int\n        :return: the show id, if found.\n        :rtype: int\n        \"\"\"\n        # addic7ed doesn't support search with quotes\n        series = series.replace(\"'\", ' ')\n\n        # build the params\n        series_year = '%s %d' % (series, year) if year is not None else series\n        params = {'search': series_year, 'Submit': 'Search'}\n\n        r = self.session.get('http://www.addic7ed.com/srch.php', params=params, timeout=30, cookies=self.cookies)\n\n        # make the search\n        logger.info('Searching show ids with %r', params)\n        r.raise_for_status()\n        soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])\n\n        # get the suggestion\n        suggestion = soup.select('span.titulo > a[href^=\"/show/\"]')\n        if not suggestion:\n            logger.warning('Show id not found: no suggestion')\n            return None\n        if not sanitize(suggestion[0].i.text.replace(\"'\", ' ')) == sanitize(series_year):\n            logger.warning('Show id not found: suggestion does not match')\n            return None\n        show_id = int(suggestion[0]['href'][6:])\n        logger.debug('Found show id %d', show_id)\n\n        return show_id\n\n    def get_show_id(self, series, year=None, country_code=None):\n        \"\"\"Get the best matching show id for `series`, `year` and `country_code`.\n\n        First search in the result of :meth:`_get_show_ids` and fallback on a search with :meth:`_search_show_id`.\n        :param str series: series of the episode.\n        :param year: year of the series, if any.\n        :type year: int\n        :param country_code: country code of the series, if any.\n        :type country_code: str\n        :return: the show id, if found.\n        :rtype: int\n        \"\"\"\n        series_sanitized = sanitize(series).lower()\n        show_ids = self._get_show_ids()\n        show_id = None\n\n        # attempt with country\n        if country_code:\n            logger.debug('Getting show id with country')\n            show_id = show_ids.get('%s %s' % (series_sanitized, country_code.lower()))\n\n        # attempt with year\n        if not show_id and year:\n            logger.debug('Getting show id with year')\n            show_id = show_ids.get('%s %d' % (series_sanitized, year))\n\n        # attempt clean\n        if not show_id:\n            logger.debug('Getting show id')\n            show_id = show_ids.get(series_sanitized)\n\n        # search as last resort\n        if not show_id:\n            logger.info('Series %s not found in show ids', series)\n            show_id = self._search_show_id(series)\n\n        return show_id\n\n    def query(self, show_id, series, season, year=None, country=None):\n        \"\"\"Query provider to get all subitles for a specific show + season.\"\"\"\n        # get the page of the season of the show\n        logger.info('Getting the page of show id %d, season %d', show_id, season)\n        r = self.session.get(self.server_url + 'show/%d' % show_id, params={'season': season}, timeout=60, cookies=self.cookies)\n        r.raise_for_status()\n\n        if not r.content:\n            # Provider returns a status of 304 Not Modified with an empty content\n            # raise_for_status won't raise exception for that status code\n            logger.debug('No data returned from provider')\n            return []\n\n        soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])\n\n        # loop over subtitle rows\n        match = series_year_re.match(soup.select('#header font')[0].text.strip()[:-10])\n        series = match.group('series')\n        year = int(match.group('year')) if match.group('year') else None\n        subtitles = []\n        for row in soup.select('tr.epeven'):\n            cells = row('td')\n\n            # ignore incomplete subtitles\n            status = cells[5].text\n            if status != 'Completed':\n                logger.debug('Ignoring subtitle with status %s', status)\n                continue\n\n            # read the item\n            language = Language.fromaddic7ed(cells[3].text)\n            hearing_impaired = bool(cells[6].text)\n            page_link = self.server_url + cells[2].a['href'][1:]\n            season = int(cells[0].text)\n            episode = int(cells[1].text)\n            title = cells[2].text\n            version = cells[4].text\n            download_link = cells[9].a['href'][1:]\n\n            subtitle = self.subtitle_class(language, hearing_impaired, page_link, series, season, episode, title, year,\n                                           version, download_link)\n            logger.debug('Found subtitle %r', subtitle)\n            subtitles.append(subtitle)\n\n        return subtitles\n\n    def list_subtitles(self, video, languages):\n        \"\"\"List Subitles.\"\"\"\n        # lookup show_id\n        titles = [video.series] + video.alternative_series\n        show_id = None\n        for title in titles:\n            show_id = self.get_show_id(title, video.year)\n            if show_id is not None:\n                break\n\n        # query for subtitles with the show_id\n        if show_id is not None:\n            subtitles = [s for s in self.query(show_id, title, video.season, video.year)\n                         if s.language in languages and s.episode == video.episode]\n            if subtitles:\n                return subtitles\n        else:\n            logger.error('No show id found for %r (%r)', video.series, {'year': video.year})\n\n        return []\n\n    def download_subtitle(self, subtitle):\n        \"\"\"Download subtitles.\"\"\"\n        # download the subtitle\n        logger.info('Downloading subtitle %r', subtitle)\n        r = self.session.get(self.server_url + subtitle.download_link, headers={'Referer': subtitle.page_link},\n                             timeout=20)\n        r.raise_for_status()\n\n        if not r.content:\n            # Provider returns a status of 304 Not Modified with an empty content\n            # raise_for_status won't raise exception for that status code\n            logger.debug('Unable to download subtitle. No data returned from provider')\n            return\n\n        # detect download limit exceeded\n        if r.headers['Content-Type'] == 'text/html':\n            raise DownloadLimitExceeded\n\n        subtitle.content = fix_line_ending(r.content)\n", "path": "medusa/subtitle_providers/addic7ed.py"}]}