201 lines
7.9 KiB
Python
201 lines
7.9 KiB
Python
# coding=utf-8
|
|
|
|
import logging
|
|
import re
|
|
from random import randint
|
|
from subliminal.providers.addic7ed import Addic7edProvider, Addic7edSubtitle, ParserBeautifulSoup, Language
|
|
from subliminal.cache import SHOW_EXPIRATION_TIME, region
|
|
from .mixins import PunctuationMixin
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
series_year_re = re.compile('^(?P<series>.+?)(?: \((?P<year>\d{4})\))?$')
|
|
remove_brackets_re = re.compile("^(.+?)( \([^\d]+\))$")
|
|
|
|
USE_BOOST = False
|
|
|
|
|
|
class PatchedAddic7edSubtitle(Addic7edSubtitle):
|
|
def __init__(self, *args, **kwargs):
|
|
super(PatchedAddic7edSubtitle, self).__init__(*args, **kwargs)
|
|
|
|
def get_matches(self, video, hearing_impaired=False):
|
|
matches = super(PatchedAddic7edSubtitle, self).get_matches(video, hearing_impaired=hearing_impaired)
|
|
if not USE_BOOST:
|
|
return matches
|
|
|
|
if {"series", "season", "episode", "year"}.issubset(matches) and "format" in matches:
|
|
matches.add("boost")
|
|
logger.info("Boosting Addic7ed subtitle")
|
|
return matches
|
|
|
|
|
|
class PatchedAddic7edProvider(PunctuationMixin, Addic7edProvider):
|
|
USE_ADDICTED_RANDOM_AGENTS = False
|
|
|
|
def __init__(self, username=None, password=None, use_random_agents=False):
|
|
super(PatchedAddic7edProvider, self).__init__(username=username, password=password)
|
|
self.USE_ADDICTED_RANDOM_AGENTS = use_random_agents
|
|
|
|
def initialize(self):
|
|
# patch: add optional user agent randomization
|
|
super(PatchedAddic7edProvider, self).initialize()
|
|
if self.USE_ADDICTED_RANDOM_AGENTS:
|
|
from .utils import FIRST_THOUSAND_OR_SO_USER_AGENTS as AGENT_LIST
|
|
logger.debug("addic7ed: using random user agents")
|
|
self.session.headers = {
|
|
'User-Agent': AGENT_LIST[randint(0, len(AGENT_LIST) - 1)],
|
|
'Referer': self.server_url,
|
|
}
|
|
|
|
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)
|
|
def _get_show_ids(self):
|
|
"""Get the ``dict`` of show ids per series by querying the `shows.php` page.
|
|
:return: show id per series, lower case and without quotes.
|
|
:rtype: dict
|
|
|
|
# patch: add punctuation cleaning
|
|
"""
|
|
# get the show page
|
|
logger.info('Getting show ids')
|
|
r = self.session.get(self.server_url + 'shows.php', timeout=10)
|
|
r.raise_for_status()
|
|
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
|
|
|
|
# populate the show ids
|
|
show_ids = {}
|
|
for show in soup.select('td.version > h3 > a[href^="/show/"]'):
|
|
show_clean = self.clean_punctuation(show.text.lower())
|
|
show_id = int(show['href'][6:])
|
|
show_ids[show_clean] = show_id
|
|
match = series_year_re.match(show_clean)
|
|
if match.group(2) and match.group(1) not in show_ids:
|
|
# year found, also add it without year
|
|
show_ids[match.group(1)] = show_id
|
|
|
|
logger.debug('Found %d show ids', len(show_ids))
|
|
|
|
return show_ids
|
|
|
|
def get_show_id(self, series, year=None, country_code=None):
|
|
"""Get the best matching show id for `series`, `year` and `country_code`.
|
|
First search in the result of :meth:`_get_show_ids` and fallback on a search with :meth:`_search_show_id`
|
|
:param str series: series of the episode.
|
|
:param year: year of the series, if any.
|
|
:type year: int or None
|
|
:param country_code: country code of the series, if any.
|
|
:type country_code: str or None
|
|
:return: the show id, if found.
|
|
:rtype: int or None
|
|
"""
|
|
series_clean = self.clean_punctuation(series.lower())
|
|
show_ids = self._get_show_ids()
|
|
show_id = None
|
|
|
|
# attempt with country
|
|
if not show_id and country_code:
|
|
logger.debug('Getting show id with country')
|
|
show_id = show_ids.get('%s (%s)' % (series_clean, country_code.lower()))
|
|
|
|
# attempt with year
|
|
if not show_id and year:
|
|
logger.debug('Getting show id with year')
|
|
show_id = show_ids.get('%s (%d)' % (series_clean, year))
|
|
|
|
# attempt clean
|
|
if not show_id:
|
|
logger.debug('Getting show id')
|
|
show_id = show_ids.get(series_clean)
|
|
|
|
if not show_id:
|
|
# show not found, try to match it without modifiers (mostly country codes such as US/UK)
|
|
match = remove_brackets_re.match(series_clean)
|
|
if match:
|
|
matched = match.group(1)
|
|
show_id = show_ids.get(matched)
|
|
if show_id:
|
|
logger.debug("show '%s' matched to '%s': %s", series, matched, show_id)
|
|
|
|
# search as last resort
|
|
if not show_id:
|
|
logger.warning('Series not found in show ids, attempting search')
|
|
show_id = self._search_show_id(series_clean)
|
|
|
|
return show_id
|
|
|
|
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)
|
|
def _search_show_id(self, series, year=None):
|
|
"""Search the show id from the `series` and `year`.
|
|
:param string series: series of the episode.
|
|
:param year: year of the series, if any.
|
|
:type year: int or None
|
|
:return: the show id, if found.
|
|
:rtype: int or None
|
|
|
|
# patch: add punctuation cleaning
|
|
"""
|
|
# build the params
|
|
series_year = '%s (%d)' % (series, year) if year is not None else series
|
|
params = {'search': series_year, 'Submit': 'Search'}
|
|
|
|
# make the search
|
|
logger.info('Searching show ids with %r', params)
|
|
r = self.session.get(self.server_url + 'search.php', params=params, timeout=10)
|
|
r.raise_for_status()
|
|
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
|
|
|
|
# get the suggestion
|
|
suggestion = soup.select('span.titulo > a[href^="/show/"]')
|
|
if not suggestion:
|
|
logger.warning('Show id not found: no suggestion')
|
|
return None
|
|
if not self.full_clean(suggestion[0].i.text.lower()) == self.full_clean(series_year.lower()):
|
|
logger.warning('Show id not found: suggestion does not match')
|
|
return None
|
|
show_id = int(suggestion[0]['href'][6:])
|
|
logger.debug('Found show id %d', show_id)
|
|
|
|
return show_id
|
|
|
|
def query(self, series, season, year=None, country=None):
|
|
# patch: fix logging
|
|
# get the show id
|
|
show_id = self.get_show_id(series, year, country)
|
|
if show_id is None:
|
|
logger.error('No show id found for %r (%r)', series, {'year': year, 'country': country})
|
|
return []
|
|
|
|
# get the page of the season of the show
|
|
logger.info('Getting the page of show id %d, season %d', show_id, season)
|
|
r = self.session.get(self.server_url + 'show/%d' % show_id, params={'season': season}, timeout=10)
|
|
r.raise_for_status()
|
|
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
|
|
|
|
# loop over subtitle rows
|
|
subtitles = []
|
|
for row in soup.select('tr.epeven'):
|
|
cells = row('td')
|
|
|
|
# ignore incomplete subtitles
|
|
status = cells[5].text
|
|
if status != 'Completed':
|
|
logger.debug('Ignoring subtitle with status %s', status)
|
|
continue
|
|
|
|
# read the item
|
|
language = Language.fromaddic7ed(cells[3].text)
|
|
hearing_impaired = bool(cells[6].text)
|
|
page_link = self.server_url + cells[2].a['href'][1:]
|
|
season = int(cells[0].text)
|
|
episode = int(cells[1].text)
|
|
title = cells[2].text
|
|
version = cells[4].text
|
|
download_link = cells[9].a['href'][1:]
|
|
|
|
subtitle = PatchedAddic7edSubtitle(language, hearing_impaired, page_link, series, season, episode, title, year,
|
|
version, download_link)
|
|
logger.debug('Found subtitle %r', subtitle)
|
|
subtitles.append(subtitle)
|
|
|
|
return subtitles
|