Adds GreekSubtitles, Subs4Free, Subs4Series, SubZ and XSubs providers (#310)

* Adds GreekSubtitles, Subs4Free, Subs4Series, SubZ and XSubs providers

* Various optimizations in greek providers
pull/489/head
Panagiotis Koutsias 5 years ago committed by morpheus65535
parent e129cafc7c
commit 176b2c818a

@ -83,6 +83,10 @@ defaults = {
'username': '',
'password': ''
},
'xsubs': {
'username': '',
'password': ''
},
'assrt': {
'token': ''
}}

@ -43,6 +43,9 @@ def get_providers_auth():
'legendastv': {'username': settings.legendastv.username,
'password': settings.legendastv.password,
},
'xsubs': {'username': settings.xsubs.username,
'password': settings.xsubs.password,
},
'assrt': {'token': settings.assrt.token, }
}

@ -368,6 +368,8 @@ def save_wizard():
settings.opensubtitles.vip = text_type(settings_opensubtitles_vip)
settings.opensubtitles.ssl = text_type(settings_opensubtitles_ssl)
settings.opensubtitles.skip_wrong_fps = text_type(settings_opensubtitles_skip_wrong_fps)
settings.xsubs.username = request.forms.get('settings_xsubs_username')
settings.xsubs.password = request.forms.get('settings_xsubs_password')
settings_subliminal_languages = request.forms.getall('settings_subliminal_languages')
c.execute("UPDATE table_settings_languages SET enabled = 0")
@ -1350,6 +1352,8 @@ def save_settings():
settings.opensubtitles.vip = text_type(settings_opensubtitles_vip)
settings.opensubtitles.ssl = text_type(settings_opensubtitles_ssl)
settings.opensubtitles.skip_wrong_fps = text_type(settings_opensubtitles_skip_wrong_fps)
settings.xsubs.username = request.forms.get('settings_xsubs_username')
settings.xsubs.password = request.forms.get('settings_xsubs_password')
settings_subliminal_languages = request.forms.getall('settings_subliminal_languages')
c.execute("UPDATE table_settings_languages SET enabled = 0")

@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
import io
import logging
import os
import zipfile
import rarfile
from subzero.language import Language
from guessit import guessit
from requests import Session
from six import text_type
from subliminal import __short_version__
from subliminal.providers import ParserBeautifulSoup, Provider
from subliminal.subtitle import SUBTITLE_EXTENSIONS, Subtitle, fix_line_ending, guess_matches
from subliminal.video import Episode, Movie
logger = logging.getLogger(__name__)
class GreekSubtitlesSubtitle(Subtitle):
"""GreekSubtitles Subtitle."""
provider_name = 'greeksubtitles'
def __init__(self, language, page_link, version, download_link):
super(GreekSubtitlesSubtitle, self).__init__(language, page_link=page_link)
self.version = version
self.download_link = download_link
self.hearing_impaired = None
self.encoding = 'windows-1253'
@property
def id(self):
return self.download_link
def get_matches(self, video):
matches = set()
# episode
if isinstance(video, Episode):
# other properties
matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True)
# movie
elif isinstance(video, Movie):
# other properties
matches |= guess_matches(video, guessit(self.version, {'type': 'movie'}), partial=True)
return matches
class GreekSubtitlesProvider(Provider):
"""GreekSubtitles Provider."""
languages = {Language(l) for l in ['ell', 'eng']}
server_url = 'http://gr.greek-subtitles.com/'
search_url = 'search.php?name={}'
download_url = 'http://www.greeksubtitles.info/getp.php?id={:d}'
subtitle_class = GreekSubtitlesSubtitle
def __init__(self):
self.session = None
def initialize(self):
self.session = Session()
self.session.headers['User-Agent'] = 'Subliminal/{}'.format(__short_version__)
def terminate(self):
self.session.close()
def query(self, keyword, season=None, episode=None, year=None):
params = keyword
if season and episode:
params += ' S{season:02d}E{episode:02d}'.format(season=season, episode=episode)
elif year:
params += ' {:4d}'.format(year)
logger.debug('Searching subtitles %r', params)
subtitles = []
search_link = self.server_url + text_type(self.search_url).format(params)
while True:
r = self.session.get(search_link, timeout=30)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return []
soup = ParserBeautifulSoup(r.content.decode('utf-8', 'ignore'), ['lxml', 'html.parser'])
# loop over subtitles cells
for cell in soup.select('td.latest_name > a:nth-of-type(1)'):
# read the item
subtitle_id = int(cell['href'].rsplit('/', 2)[1])
page_link = cell['href']
language = Language.fromalpha2(cell.parent.find('img')['src'].split('/')[-1].split('.')[0])
version = cell.text.strip() or None
if version is None:
version = ""
subtitle = self.subtitle_class(language, page_link, version, self.download_url.format(subtitle_id))
logger.debug('Found subtitle %r', subtitle)
subtitles.append(subtitle)
anchors = soup.select('td a')
next_page_available = False
for anchor in anchors:
if 'Next' in anchor.text and 'search.php' in anchor['href']:
search_link = self.server_url + anchor['href']
next_page_available = True
break
if not next_page_available:
break
return subtitles
def list_subtitles(self, video, languages):
if isinstance(video, Episode):
titles = [video.series] + video.alternative_series
elif isinstance(video, Movie):
titles = [video.title] + video.alternative_titles
else:
titles = []
subtitles = []
# query for subtitles with the show_id
for title in titles:
if isinstance(video, Episode):
subtitles += [s for s in self.query(title, season=video.season, episode=video.episode,
year=video.year)
if s.language in languages]
elif isinstance(video, Movie):
subtitles += [s for s in self.query(title, year=video.year)
if s.language in languages]
return subtitles
def download_subtitle(self, subtitle):
if isinstance(subtitle, GreekSubtitlesSubtitle):
# download the subtitle
logger.info('Downloading subtitle %r', subtitle)
r = self.session.get(subtitle.download_link, headers={'Referer': subtitle.page_link},
timeout=30)
r.raise_for_status()
if not r.content:
logger.debug('Unable to download subtitle. No data returned from provider')
return
archive = _get_archive(r.content)
subtitle_content = _get_subtitle_from_archive(archive)
if subtitle_content:
subtitle.content = fix_line_ending(subtitle_content)
else:
logger.debug('Could not extract subtitle from %r', archive)
def _get_archive(content):
# open the archive
archive_stream = io.BytesIO(content)
archive = None
if rarfile.is_rarfile(archive_stream):
logger.debug('Identified rar archive')
archive = rarfile.RarFile(archive_stream)
elif zipfile.is_zipfile(archive_stream):
logger.debug('Identified zip archive')
archive = zipfile.ZipFile(archive_stream)
return archive
def _get_subtitle_from_archive(archive):
for name in archive.namelist():
# discard hidden files
if os.path.split(name)[-1].startswith('.'):
continue
# discard non-subtitle files
if not name.lower().endswith(SUBTITLE_EXTENSIONS):
continue
return archive.read(name)
return None

@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
# encoding=utf8
import io
import logging
import os
import random
import rarfile
import re
import zipfile
from subzero.language import Language
from guessit import guessit
from requests import Session
from six import text_type
from subliminal.providers import ParserBeautifulSoup, Provider
from subliminal import __short_version__
from subliminal.cache import SHOW_EXPIRATION_TIME, region
from subliminal.score import get_equivalent_release_groups
from subliminal.subtitle import SUBTITLE_EXTENSIONS, Subtitle, fix_line_ending, guess_matches
from subliminal.utils import sanitize, sanitize_release_group
from subliminal.video import Movie
logger = logging.getLogger(__name__)
year_re = re.compile(r'^\((\d{4})\)$')
class Subs4FreeSubtitle(Subtitle):
"""Subs4Free Subtitle."""
provider_name = 'subs4free'
def __init__(self, language, page_link, title, year, version, download_link):
super(Subs4FreeSubtitle, self).__init__(language, page_link=page_link)
self.title = title
self.year = year
self.version = version
self.download_link = download_link
self.hearing_impaired = None
self.encoding = 'utf8'
@property
def id(self):
return self.download_link
def get_matches(self, video):
matches = set()
# movie
if isinstance(video, Movie):
# title
if video.title and (sanitize(self.title) in (
sanitize(name) for name in [video.title] + video.alternative_titles)):
matches.add('title')
# year
if video.year and self.year == video.year:
matches.add('year')
# release_group
if (video.release_group and self.version and
any(r in sanitize_release_group(self.version)
for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))):
matches.add('release_group')
# other properties
matches |= guess_matches(video, guessit(self.version, {'type': 'movie'}), partial=True)
return matches
class Subs4FreeProvider(Provider):
"""Subs4Free Provider."""
languages = {Language(l) for l in ['ell', 'eng']}
video_types = (Movie,)
server_url = 'https://www.sf4-industry.com'
download_url = '/getSub.html'
search_url = '/search_report.php?search={}&searchType=1'
subtitle_class = Subs4FreeSubtitle
def __init__(self):
self.session = None
def initialize(self):
self.session = Session()
self.session.headers['User-Agent'] = 'Subliminal/{}'.format(__short_version__)
def terminate(self):
self.session.close()
def get_show_ids(self, title, year=None):
"""Get the best matching show id for `series` and `year``.
First search in the result of :meth:`_get_show_suggestions`.
:param title: show title.
:param year: year of the show, if any.
:type year: int
:return: the show id, if found.
:rtype: str
"""
title_sanitized = sanitize(title).lower()
show_ids = self._get_suggestions(title)
matched_show_ids = []
for show in show_ids:
show_id = None
show_title = sanitize(show['title'])
# attempt with year
if not show_id and year:
logger.debug('Getting show id with year')
show_id = show['link'].split('?p=')[-1] if show_title == '{title} {year:d}'.format(
title=title_sanitized, year=year) else None
# attempt clean
if not show_id:
logger.debug('Getting show id')
show_id = show['link'].split('?p=')[-1] if show_title == title_sanitized else None
if show_id:
matched_show_ids.append(show_id)
return matched_show_ids
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME, to_str=text_type,
should_cache_fn=lambda value: value)
def _get_suggestions(self, title):
"""Search the show or movie id from the `title` and `year`.
:param str title: title of the show.
:return: the show suggestions found.
:rtype: dict
"""
# make the search
logger.info('Searching show ids with %r', title)
r = self.session.get(self.server_url + text_type(self.search_url).format(title),
headers={'Referer': self.server_url}, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return {}
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
suggestions = [{'link': l.attrs['value'], 'title': l.text}
for l in soup.select('select[name="Mov_sel"] > option[value]')]
logger.debug('Found suggestions: %r', suggestions)
return suggestions
def query(self, movie_id, title, year):
# get the season list of the show
logger.info('Getting the subtitle list of show id %s', movie_id)
if movie_id:
page_link = self.server_url + '/' + movie_id
else:
page_link = self.server_url + text_type(self.search_url).format(' '.join([title, str(year)]))
r = self.session.get(page_link, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return []
soup = ParserBeautifulSoup(r.content, ['html.parser'])
year_num = None
year_element = soup.select_one('td#dates_header > table div')
matches = False
if year_element:
matches = year_re.match(str(year_element.contents[2]).strip())
if matches:
year_num = int(matches.group(1))
title_element = soup.select_one('td#dates_header > table u')
show_title = str(title_element.contents[0]).strip() if title_element else None
subtitles = []
# loop over episode rows
for subtitle in soup.select('table.table_border div[align="center"] > div'):
# read common info
version = subtitle.find('b').text
download_link = self.server_url + subtitle.find('a')['href']
language = Language.fromalpha2(subtitle.find('img')['src'].split('/')[-1].split('.')[0])
subtitle = self.subtitle_class(language, page_link, show_title, year_num, version, download_link)
logger.debug('Found subtitle {!r}'.format(subtitle))
subtitles.append(subtitle)
return subtitles
def list_subtitles(self, video, languages):
# lookup show_id
titles = [video.title] + video.alternative_titles if isinstance(video, Movie) else []
show_ids = None
for title in titles:
show_ids = self.get_show_ids(title, video.year)
if show_ids and len(show_ids) > 0:
break
subtitles = []
# query for subtitles with the show_id
if show_ids and len(show_ids) > 0:
for show_id in show_ids:
subtitles += [s for s in self.query(show_id, video.title, video.year) if s.language in languages]
else:
subtitles += [s for s in self.query(None, video.title, video.year) if s.language in languages]
return subtitles
def download_subtitle(self, subtitle):
if isinstance(subtitle, Subs4FreeSubtitle):
# download the subtitle
logger.info('Downloading subtitle %r', subtitle)
r = self.session.get(subtitle.download_link, headers={'Referer': subtitle.page_link}, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('Unable to download subtitle. No data returned from provider')
return
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
download_element = soup.select_one('input[name="id"]')
image_element = soup.select_one('input[type="image"]')
subtitle_id = download_element['value'] if download_element else None
width = int(str(image_element['width']).strip('px')) if image_element else 0
height = int(str(image_element['height']).strip('px')) if image_element else 0
if not subtitle_id:
logger.debug('Unable to download subtitle. No download link found')
return
download_url = self.server_url + self.download_url
r = self.session.post(download_url, data={'utf8': 1, 'id': subtitle_id, 'x': random.randint(0, width),
'y': random.randint(0, height)},
headers={'Referer': subtitle.download_link}, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('Unable to download subtitle. No data returned from provider')
return
archive = _get_archive(r.content)
subtitle_content = _get_subtitle_from_archive(archive) if archive else r.content
if subtitle_content:
subtitle.content = fix_line_ending(subtitle_content)
else:
logger.debug('Could not extract subtitle from %r', archive)
def _get_archive(content):
# open the archive
archive_stream = io.BytesIO(content)
archive = None
if rarfile.is_rarfile(archive_stream):
logger.debug('Identified rar archive')
archive = rarfile.RarFile(archive_stream)
elif zipfile.is_zipfile(archive_stream):
logger.debug('Identified zip archive')
archive = zipfile.ZipFile(archive_stream)
return archive
def _get_subtitle_from_archive(archive):
for name in archive.namelist():
# discard hidden files
if os.path.split(name)[-1].startswith('.'):
continue
# discard non-subtitle files
if not name.lower().endswith(SUBTITLE_EXTENSIONS):
continue
return archive.read(name)
return None

@ -0,0 +1,272 @@
# -*- coding: utf-8 -*-
import io
import logging
import os
import rarfile
import re
import zipfile
from subzero.language import Language
from guessit import guessit
from requests import Session
from six import text_type
from subliminal.providers import ParserBeautifulSoup, Provider
from subliminal import __short_version__
from subliminal.cache import SHOW_EXPIRATION_TIME, region
from subliminal.score import get_equivalent_release_groups
from subliminal.subtitle import SUBTITLE_EXTENSIONS, Subtitle, fix_line_ending, guess_matches
from subliminal.utils import sanitize, sanitize_release_group
from subliminal.video import Episode
logger = logging.getLogger(__name__)
year_re = re.compile(r'^\((\d{4})\)$')
class Subs4SeriesSubtitle(Subtitle):
"""Subs4Series Subtitle."""
provider_name = 'subs4series'
def __init__(self, language, page_link, series, year, version, download_link):
super(Subs4SeriesSubtitle, self).__init__(language, page_link=page_link)
self.series = series
self.year = year
self.version = version
self.download_link = download_link
self.hearing_impaired = None
self.encoding = 'windows-1253'
@property
def id(self):
return self.download_link
def get_matches(self, video):
matches = set()
# episode
if isinstance(video, Episode):
# series name
if video.series and sanitize(self.series) in (
sanitize(name) for name in [video.series] + video.alternative_series):
matches.add('series')
# year
if video.original_series and self.year is None or video.year and video.year == self.year:
matches.add('year')
# release_group
if (video.release_group and self.version and
any(r in sanitize_release_group(self.version)
for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))):
matches.add('release_group')
# other properties
matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True)
return matches
class Subs4SeriesProvider(Provider):
"""Subs4Series Provider."""
languages = {Language(l) for l in ['ell', 'eng']}
video_types = (Episode,)
server_url = 'https://www.subs4series.com'
search_url = '/search_report.php?search={}&searchType=1'
episode_link = '/tv-series/{show_id}/season-{season:d}/episode-{episode:d}'
subtitle_class = Subs4SeriesSubtitle
def __init__(self):
self.session = None
def initialize(self):
self.session = Session()
self.session.headers['User-Agent'] = 'Subliminal/{}'.format(__short_version__)
def terminate(self):
self.session.close()
def get_show_ids(self, title, year=None):
"""Get the best matching show id for `series` and `year`.
First search in the result of :meth:`_get_show_suggestions`.
:param title: show title.
:param year: year of the show, if any.
:type year: int
:return: the show id, if found.
:rtype: str
"""
title_sanitized = sanitize(title).lower()
show_ids = self._get_suggestions(title)
matched_show_ids = []
for show in show_ids:
show_id = None
show_title = sanitize(show['title'])
# attempt with year
if not show_id and year:
logger.debug('Getting show id with year')
show_id = '/'.join(show['link'].rsplit('/', 2)[1:]) if show_title == '{title} {year:d}'.format(
title=title_sanitized, year=year) else None
# attempt clean
if not show_id:
logger.debug('Getting show id')
show_id = '/'.join(show['link'].rsplit('/', 2)[1:]) if show_title == title_sanitized else None
if show_id:
matched_show_ids.append(show_id)
return matched_show_ids
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME, to_str=text_type,
should_cache_fn=lambda value: value)
def _get_suggestions(self, title):
"""Search the show or movie id from the `title` and `year`.
:param str title: title of the show.
:return: the show suggestions found.
:rtype: dict
"""
# make the search
logger.info('Searching show ids with %r', title)
r = self.session.get(self.server_url + text_type(self.search_url).format(title),
headers={'Referer': self.server_url}, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return {}
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
series = [{'link': l.attrs['value'], 'title': l.text}
for l in soup.select('select[name="Mov_sel"] > option[value]')]
logger.debug('Found suggestions: %r', series)
return series
def query(self, show_id, series, season, episode, title):
# get the season list of the show
logger.info('Getting the subtitle list of show id %s', show_id)
if all((show_id, season, episode)):
page_link = self.server_url + self.episode_link.format(show_id=show_id, season=season, episode=episode)
else:
return []
r = self.session.get(page_link, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return []
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
year_num = None
matches = year_re.match(str(soup.select_one('#dates_header_br > table div').contents[2]).strip())
if matches:
year_num = int(matches.group(1))
show_title = str(soup.select_one('#dates_header_br > table u').contents[0]).strip()
subtitles = []
# loop over episode rows
for subtitle in soup.select('table.table_border div[align="center"] > div'):
# read common info
version = subtitle.find('b').text
download_link = self.server_url + subtitle.find('a')['href']
language = Language.fromalpha2(subtitle.find('img')['src'].split('/')[-1].split('.')[0])
subtitle = self.subtitle_class(language, page_link, show_title, year_num, version, download_link)
logger.debug('Found subtitle %r', subtitle)
subtitles.append(subtitle)
return subtitles
def list_subtitles(self, video, languages):
# lookup show_id
titles = [video.series] + video.alternative_series if isinstance(video, Episode) else []
show_ids = None
for title in titles:
show_ids = self.get_show_ids(title, video.year)
if show_ids and len(show_ids) > 0:
break
subtitles = []
# query for subtitles with the show_id
for show_id in show_ids:
subtitles += [s for s in self.query(show_id, video.series, video.season, video.episode, video.title)
if s.language in languages]
return subtitles
def download_subtitle(self, subtitle):
if isinstance(subtitle, Subs4SeriesSubtitle):
# download the subtitle
logger.info('Downloading subtitle %r', subtitle)
r = self.session.get(subtitle.download_link, headers={'Referer': subtitle.page_link}, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('Unable to download subtitle. No data returned from provider')
return
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
download_element = soup.select_one('a.style55ws')
if not download_element:
download_element = soup.select_one('form[method="post"]')
target = download_element['action'] if download_element else None
else:
target = download_element['href']
if not target:
logger.debug('Unable to download subtitle. No download link found')
return
download_url = self.server_url + target
r = self.session.get(download_url, headers={'Referer': subtitle.download_link}, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('Unable to download subtitle. No data returned from provider')
return
archive = _get_archive(r.content)
subtitle_content = _get_subtitle_from_archive(archive) if archive else r.content
if subtitle_content:
subtitle.content = fix_line_ending(subtitle_content)
else:
logger.debug('Could not extract subtitle from %r', archive)
def _get_archive(content):
# open the archive
archive_stream = io.BytesIO(content)
archive = None
if rarfile.is_rarfile(archive_stream):
logger.debug('Identified rar archive')
archive = rarfile.RarFile(archive_stream)
elif zipfile.is_zipfile(archive_stream):
logger.debug('Identified zip archive')
archive = zipfile.ZipFile(archive_stream)
return archive
def _get_subtitle_from_archive(archive):
for name in archive.namelist():
# discard hidden files
if os.path.split(name)[-1].startswith('.'):
continue
# discard non-subtitle files
if not name.lower().endswith(SUBTITLE_EXTENSIONS):
continue
return archive.read(name)
return None

@ -0,0 +1,318 @@
# -*- coding: utf-8 -*-
import io
import json
import logging
import os
import rarfile
import re
import zipfile
from subzero.language import Language
from guessit import guessit
from requests import Session
from six import text_type
from subliminal.providers import ParserBeautifulSoup, Provider
from subliminal import __short_version__
from subliminal.cache import SHOW_EXPIRATION_TIME, region
from subliminal.score import get_equivalent_release_groups
from subliminal.subtitle import SUBTITLE_EXTENSIONS, Subtitle, fix_line_ending, guess_matches
from subliminal.utils import sanitize, sanitize_release_group
from subliminal.video import Episode, Movie
logger = logging.getLogger(__name__)
episode_re = re.compile(r'^S(\d{2})E(\d{2})$')
class SubzSubtitle(Subtitle):
"""Subz Subtitle."""
provider_name = 'subz'
def __init__(self, language, page_link, series, season, episode, title, year, version, download_link):
super(SubzSubtitle, self).__init__(language, page_link=page_link)
self.series = series
self.season = season
self.episode = episode
self.title = title
self.year = year
self.version = version
self.download_link = download_link
self.hearing_impaired = None
self.encoding = 'windows-1253'
@property
def id(self):
return self.download_link
def get_matches(self, video):
matches = set()
video_type = None
# episode
if isinstance(video, Episode):
video_type = 'episode'
# series name
if video.series and sanitize(self.series) in (
sanitize(name) for name in [video.series] + video.alternative_series):
matches.add('series')
# season
if video.season and self.season == video.season:
matches.add('season')
# episode
if video.episode and self.episode == video.episode:
matches.add('episode')
# title of the episode
if video.title and sanitize(self.title) == sanitize(video.title):
matches.add('title')
# year
if video.original_series and self.year is None or video.year and video.year == self.year:
matches.add('year')
# movie
elif isinstance(video, Movie):
video_type = 'movie'
# title
if video.title and (sanitize(self.title) in (
sanitize(name) for name in [video.title] + video.alternative_titles)):
matches.add('title')
# year
if video.year and self.year == video.year:
matches.add('year')
# release_group
if (video.release_group and self.version and
any(r in sanitize_release_group(self.version)
for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))):
matches.add('release_group')
# other properties
matches |= guess_matches(video, guessit(self.version, {'type': video_type}), partial=True)
return matches
class SubzProvider(Provider):
"""Subz Provider."""
languages = {Language(l) for l in ['ell']}
server_url = 'https://subz.xyz'
sign_in_url = '/sessions'
sign_out_url = '/logout'
search_url = '/typeahead/{}'
episode_link = '/series/{show_id}/seasons/{season:d}/episodes/{episode:d}'
movie_link = '/movies/{}'
subtitle_class = SubzSubtitle
def __init__(self):
self.logged_in = False
self.session = None
def initialize(self):
self.session = Session()
self.session.headers['User-Agent'] = 'Subliminal/{}'.format(__short_version__)
def terminate(self):
self.session.close()
def get_show_ids(self, title, year=None, is_episode=True, country_code=None):
"""Get the best matching show id for `series`, `year` and `country_code`.
First search in the result of :meth:`_get_show_suggestions`.
:param title: show title.
:param year: year of the show, if any.
:type year: int
:param is_episode: if the search is for episode.
:type is_episode: bool
:param country_code: country code of the show, if any.
:type country_code: str
:return: the show id, if found.
:rtype: str
"""
title_sanitized = sanitize(title).lower()
show_ids = self._get_suggestions(title, is_episode)
matched_show_ids = []
for show in show_ids:
show_id = None
# attempt with country
if not show_id and country_code:
logger.debug('Getting show id with country')
if sanitize(show['title']) == text_type('{title} {country}').format(title=title_sanitized,
country=country_code.lower()):
show_id = show['link'].split('/')[-1]
# attempt with year
if not show_id and year:
logger.debug('Getting show id with year')
if sanitize(show['title']) == text_type('{title} {year}').format(title=title_sanitized, year=year):
show_id = show['link'].split('/')[-1]
# attempt clean
if not show_id:
logger.debug('Getting show id')
show_id = show['link'].split('/')[-1] if sanitize(show['title']) == title_sanitized else None
if show_id:
matched_show_ids.append(show_id)
return matched_show_ids
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME, to_str=text_type,
should_cache_fn=lambda value: value)
def _get_suggestions(self, title, is_episode=True):
"""Search the show or movie id from the `title` and `year`.
:param str title: title of the show.
:param is_episode: if the search is for episode.
:type is_episode: bool
:return: the show suggestions found.
:rtype: dict
"""
# make the search
logger.info('Searching show ids with %r', title)
r = self.session.get(self.server_url + text_type(self.search_url).format(title), timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return {}
show_type = 'series' if is_episode else 'movie'
parsed_suggestions = [s for s in json.loads(r.text) if 'type' in s and s['type'] == show_type]
logger.debug('Found suggestions: %r', parsed_suggestions)
return parsed_suggestions
def query(self, show_id, series, season, episode, title):
# get the season list of the show
logger.info('Getting the subtitle list of show id %s', show_id)
is_episode = False
if all((show_id, season, episode)):
is_episode = True
page_link = self.server_url + self.episode_link.format(show_id=show_id, season=season, episode=episode)
elif all((show_id, title)):
page_link = self.server_url + self.movie_link.format(show_id)
else:
return []
r = self.session.get(page_link, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return []
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
year_num = None
if not is_episode:
year_num = int(soup.select_one('span.year').text)
show_title = str(soup.select_one('#summary-wrapper > div.summary h1').contents[0]).strip()
subtitles = []
# loop over episode rows
for subtitle in soup.select('div[id="subtitles"] tr[data-id]'):
# read common info
version = subtitle.find('td', {'class': 'name'}).text
download_link = subtitle.find('a', {'class': 'btn-success'})['href'].strip('\'')
# read the episode info
if is_episode:
episode_numbers = soup.select_one('#summary-wrapper > div.container.summary span.main-title-sxe').text
season_num = None
episode_num = None
matches = episode_re.match(episode_numbers.strip())
if matches:
season_num = int(matches.group(1))
episode_num = int(matches.group(2))
episode_title = soup.select_one('#summary-wrapper > div.container.summary span.main-title').text
subtitle = self.subtitle_class(Language.fromalpha2('el'), page_link, show_title, season_num,
episode_num, episode_title, year_num, version, download_link)
# read the movie info
else:
subtitle = self.subtitle_class(Language.fromalpha2('el'), page_link, None, None, None, show_title,
year_num, version, download_link)
logger.debug('Found subtitle %r', subtitle)
subtitles.append(subtitle)
return subtitles
def list_subtitles(self, video, languages):
# lookup show_id
if isinstance(video, Episode):
titles = [video.series] + video.alternative_series
elif isinstance(video, Movie):
titles = [video.title] + video.alternative_titles
else:
titles = []
show_ids = None
for title in titles:
show_ids = self.get_show_ids(title, video.year, isinstance(video, Episode))
if show_ids is not None and len(show_ids) > 0:
break
subtitles = []
# query for subtitles with the show_id
for show_id in show_ids:
if isinstance(video, Episode):
subtitles += [s for s in self.query(show_id, video.series, video.season, video.episode, video.title)
if s.language in languages and s.season == video.season and s.episode == video.episode]
elif isinstance(video, Movie):
subtitles += [s for s in self.query(show_id, None, None, None, video.title)
if s.language in languages and s.year == video.year]
return subtitles
def download_subtitle(self, subtitle):
if isinstance(subtitle, SubzSubtitle):
# download the subtitle
logger.info('Downloading subtitle %r', subtitle)
r = self.session.get(subtitle.download_link, headers={'Referer': subtitle.page_link}, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('Unable to download subtitle. No data returned from provider')
return
archive = _get_archive(r.content)
subtitle_content = _get_subtitle_from_archive(archive)
if subtitle_content:
subtitle.content = fix_line_ending(subtitle_content)
else:
logger.debug('Could not extract subtitle from %r', archive)
def _get_archive(content):
# open the archive
archive_stream = io.BytesIO(content)
archive = None
if rarfile.is_rarfile(archive_stream):
logger.debug('Identified rar archive')
archive = rarfile.RarFile(archive_stream)
elif zipfile.is_zipfile(archive_stream):
logger.debug('Identified zip archive')
archive = zipfile.ZipFile(archive_stream)
return archive
def _get_subtitle_from_archive(archive):
for name in archive.namelist():
# discard hidden files
if os.path.split(name)[-1].startswith('.'):
continue
# discard non-subtitle files
if not name.lower().endswith(SUBTITLE_EXTENSIONS):
continue
return archive.read(name)
return None

@ -0,0 +1,302 @@
# -*- coding: utf-8 -*-
import logging
import re
from subzero.language import Language
from guessit import guessit
from requests import Session
from subliminal.providers import ParserBeautifulSoup, Provider
from subliminal import __short_version__
from subliminal.cache import SHOW_EXPIRATION_TIME, region
from subliminal.exceptions import AuthenticationError, ConfigurationError
from subliminal.score import get_equivalent_release_groups
from subliminal.subtitle import Subtitle, fix_line_ending, guess_matches
from subliminal.utils import sanitize, sanitize_release_group
from subliminal.video import Episode
logger = logging.getLogger(__name__)
article_re = re.compile(r'^([A-Za-z]{1,3}) (.*)$')
class XSubsSubtitle(Subtitle):
"""XSubs Subtitle."""
provider_name = 'xsubs'
def __init__(self, language, page_link, series, season, episode, year, title, version, download_link):
super(XSubsSubtitle, self).__init__(language, page_link=page_link)
self.series = series
self.season = season
self.episode = episode
self.year = year
self.title = title
self.version = version
self.download_link = download_link
self.hearing_impaired = None
self.encoding = 'windows-1253'
@property
def id(self):
return self.download_link
def get_matches(self, video):
matches = set()
if isinstance(video, Episode):
# series name
if video.series and sanitize(self.series) in (
sanitize(name) for name in [video.series] + video.alternative_series):
matches.add('series')
# season
if video.season and self.season == video.season:
matches.add('season')
# episode
if video.episode and self.episode == video.episode:
matches.add('episode')
# title of the episode
if video.title and sanitize(self.title) == sanitize(video.title):
matches.add('title')
# year
if video.original_series and self.year is None or video.year and video.year == self.year:
matches.add('year')
# release_group
if (video.release_group and self.version and
any(r in sanitize_release_group(self.version)
for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))):
matches.add('release_group')
# other properties
matches |= guess_matches(video, guessit(self.version, {'type': 'episode'}), partial=True)
return matches
class XSubsProvider(Provider):
"""XSubs Provider."""
languages = {Language(l) for l in ['ell']}
video_types = (Episode,)
server_url = 'http://xsubs.tv'
sign_in_url = '/xforum/account/signin/'
sign_out_url = '/xforum/account/signout/'
all_series_url = '/series/all.xml'
series_url = '/series/{:d}/main.xml'
season_url = '/series/{show_id:d}/{season:d}.xml'
page_link = '/ice/xsw.xml?srsid={show_id:d}#{season_id:d};{season:d}'
download_link = '/xthru/getsub/{:d}'
subtitle_class = XSubsSubtitle
def __init__(self, username=None, password=None):
if any((username, password)) and not all((username, password)):
raise ConfigurationError('Username and password must be specified')
self.username = username
self.password = password
self.logged_in = False
self.session = None
def initialize(self):
self.session = Session()
self.session.headers['User-Agent'] = 'Subliminal/{}'.format(__short_version__)
# login
if self.username and self.password:
logger.info('Logging in')
self.session.get(self.server_url + self.sign_in_url)
data = {'username': self.username,
'password': self.password,
'csrfmiddlewaretoken': self.session.cookies['csrftoken']}
r = self.session.post(self.server_url + self.sign_in_url, data, allow_redirects=False, timeout=10)
if r.status_code != 302:
raise AuthenticationError(self.username)
logger.debug('Logged in')
self.logged_in = True
def terminate(self):
# logout
if self.logged_in:
logger.info('Logging out')
r = self.session.get(self.server_url + self.sign_out_url, timeout=10)
r.raise_for_status()
logger.debug('Logged out')
self.logged_in = False
self.session.close()
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME, should_cache_fn=lambda value: value)
def _get_show_ids(self):
# get the shows page
logger.info('Getting show ids')
r = self.session.get(self.server_url + self.all_series_url, timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return []
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
# populate the show ids
show_ids = {}
for show_category in soup.findAll('seriesl'):
if show_category.attrs['category'] == u'Σειρές':
for show in show_category.findAll('series'):
show_ids[sanitize(show.text)] = int(show['srsid'])
break
logger.debug('Found %d show ids', len(show_ids))
return show_ids
def get_show_id(self, series_names, year=None, country_code=None):
series_sanitized_names = []
for name in series_names:
sanitized_name = sanitize(name)
series_sanitized_names.append(sanitized_name)
alternative_name = _get_alternative_name(sanitized_name)
if alternative_name:
series_sanitized_names.append(alternative_name)
show_ids = self._get_show_ids()
show_id = None
for series_sanitized in series_sanitized_names:
# attempt with country
if not show_id and country_code:
logger.debug('Getting show id with country')
show_id = show_ids.get('{series} {country}'.format(series=series_sanitized,
country=country_code.lower()))
# attempt with year
if not show_id and year:
logger.debug('Getting show id with year')
show_id = show_ids.get('{series} {year:d}'.format(series=series_sanitized, year=year))
# attempt with article at the end
if not show_id and year:
logger.debug('Getting show id with year in brackets')
show_id = show_ids.get('{series} [{year:d}]'.format(series=series_sanitized, year=year))
# attempt clean
if not show_id:
logger.debug('Getting show id')
show_id = show_ids.get(series_sanitized)
if show_id:
break
return int(show_id) if show_id else None
def query(self, show_id, series, season, year=None, country=None):
# get the season list of the show
logger.info('Getting the season list of show id %d', show_id)
r = self.session.get(self.server_url + self.series_url.format(show_id), timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return []
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
series_title = soup.find('name').text
# loop over season rows
seasons = soup.findAll('series_group')
season_id = None
for season_row in seasons:
try:
parsed_season = int(season_row['ssnnum'])
if parsed_season == season:
season_id = int(season_row['ssnid'])
break
except (ValueError, TypeError):
continue
if season_id is None:
logger.debug('Season not found in provider')
return []
# get the subtitle list of the season
logger.info('Getting the subtitle list of season %d', season)
r = self.session.get(self.server_url + self.season_url.format(show_id=show_id, season=season_id), timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('No data returned from provider')
return []
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
subtitles = []
# loop over episode rows
for episode in soup.findAll('subg'):
# read the episode info
etitle = episode.find('etitle')
if etitle is None:
continue
episode_num = int(etitle['number'].split('-')[0])
sgt = episode.find('sgt')
if sgt is None:
continue
season_num = int(sgt['ssnnum'])
# filter out unreleased subtitles
for subtitle in episode.findAll('sr'):
if subtitle['published_on'] == '':
continue
page_link = self.server_url + self.page_link.format(show_id=show_id, season_id=season_id,
season=season_num)
episode_title = etitle['title']
version = subtitle.fmt.text + ' ' + subtitle.team.text
download_link = self.server_url + self.download_link.format(int(subtitle['rlsid']))
subtitle = self.subtitle_class(Language.fromalpha2('el'), page_link, series_title, season_num,
episode_num, year, episode_title, version, download_link)
logger.debug('Found subtitle %r', subtitle)
subtitles.append(subtitle)
return subtitles
def list_subtitles(self, video, languages):
if isinstance(video, Episode):
# lookup show_id
titles = [video.series] + video.alternative_series
show_id = self.get_show_id(titles, video.year)
# query for subtitles with the show_id
if show_id:
subtitles = [s for s in self.query(show_id, video.series, video.season, video.year)
if s.language in languages and s.season == video.season and s.episode == video.episode]
if subtitles:
return subtitles
else:
logger.error('No show id found for %r (%r)', video.series, {'year': video.year})
return []
def download_subtitle(self, subtitle):
if isinstance(subtitle, XSubsSubtitle):
# download the subtitle
logger.info('Downloading subtitle %r', subtitle)
r = self.session.get(subtitle.download_link, headers={'Referer': subtitle.page_link},
timeout=10)
r.raise_for_status()
if not r.content:
logger.debug('Unable to download subtitle. No data returned from provider')
return
subtitle.content = fix_line_ending(r.content)
def _get_alternative_name(series):
article_match = article_re.match(series)
if article_match:
return '{series} {article}'.format(series=article_match.group(2), article=article_match.group(1))
return None

@ -1493,6 +1493,100 @@
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>GreekSubtitles</label>
</div>
<div class="one wide column">
<div id="greeksubtitles" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="greeksubtitles_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Subs4Free</label>
</div>
<div class="one wide column">
<div id="subs4free" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="subs4free_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Subs4Series</label>
</div>
<div class="one wide column">
<div id="subs4series" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="subs4series_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>SubZ</label>
</div>
<div class="one wide column">
<div id="subz" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="subz_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>XSubs</label>
</div>
<div class="one wide column">
<div id="xsubs" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="xsubs_option" class="ui grid container">
<div class="middle aligned row">
<div class="right aligned six wide column">
<label>Username</label>
</div>
<div class="six wide column">
<div class="ui fluid input">
<input name="settings_xsubs_username" type="text" value="{{settings.xsubs.username if settings.xsubs.username != None else ''}}">
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned six wide column">
<label>Password</label>
</div>
<div class="six wide column">
<div class="ui fluid input">
<input name="settings_xsubs_password" type="password" value="{{settings.xsubs.password if settings.xsubs.password != None else ''}}">
</div>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="eleven wide column">
<div class='field' hidden>

Loading…
Cancel
Save