-
-
Notifications
You must be signed in to change notification settings - Fork 313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Cinemast provider #817
base: main
Are you sure you want to change the base?
Changes from all commits
26885b3
07fc287
d7d1769
5d74d55
2dfc825
31464b6
bc397d8
ddee12f
ecd885d
2ca23cf
7f55a9d
59a1312
f5d799d
f26b08f
b634a52
6eb24c1
1e75c43
be3cb67
11af13a
29d3358
e25589d
e140104
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: lint_python | ||
on: [pull_request, push] | ||
jobs: | ||
lint_python: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions/setup-python@v2 | ||
- run: pip install --upgrade pip wheel | ||
- run: pip install bandit black codespell flake8 flake8-2020 flake8-bugbear | ||
flake8-comprehensions isort mypy pytest pyupgrade safety | ||
- run: bandit --recursive --skip B101,B105,B106,B303,B314,B404,B405,B410,B603 . | ||
- run: black --check . || true | ||
- run: codespell --ignore-words-list=nd,reacher,serie --skip="*.yaml" | ||
- run: flake8 --ignore=B001,C401,C405,C408,C416,E122,E127,E128,E226,E265,E402,E722,E741,F401,R502,R503,R504,W504,W605 | ||
--count --max-complexity=36 --max-line-length=120 --show-source --statistics . | ||
- run: isort --check-only --profile black . || true | ||
- run: pip install -r dev-requirements.txt -r requirements.txt | ||
- run: mkdir --parents --verbose .mypy_cache | ||
- run: mypy --ignore-missing-imports --install-types --non-interactive . || true | ||
- run: pytest --doctest-modules . || true | ||
- run: shopt -s globstar && pyupgrade --py36-plus **/*.py || true | ||
- run: safety check |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,7 +53,7 @@ Changelog | |
^^^^^ | ||
**release date:** 2016-09-03 | ||
|
||
* Fix subscenter | ||
* Add Cinemast provider | ||
|
||
|
||
2.0.3 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
# -*- coding: utf-8 -*- | ||
import io | ||
import logging | ||
import zipfile | ||
|
||
from babelfish import Language | ||
from guessit import guessit | ||
from requests import Session | ||
|
||
from . import Provider | ||
from .. import __short_version__ | ||
from ..exceptions import AuthenticationError, ConfigurationError, ProviderError | ||
from ..matches import guess_matches | ||
from ..subtitle import Subtitle, fix_line_ending | ||
from ..utils import sanitize | ||
from ..video import Episode, Movie | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class CinemastSubtitle(Subtitle): | ||
"""Cinemast Subtitle.""" | ||
provider_name = 'cinemast' | ||
|
||
def __init__(self, language, page_link, series, season, episode, title, subtitle_id, subtitle_key, release): | ||
super(CinemastSubtitle, self).__init__(language, page_link=page_link) | ||
self.series = series | ||
self.season = season | ||
self.episode = episode | ||
self.title = title | ||
self.subtitle_id = subtitle_id | ||
self.subtitle_key = subtitle_key | ||
self.release = release | ||
|
||
@property | ||
def id(self): | ||
return str(self.subtitle_id) | ||
|
||
def get_matches(self, video): | ||
matches = set() | ||
|
||
# episode | ||
if isinstance(video, Episode): | ||
# series | ||
if video.series and (sanitize(self.title) 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') | ||
# guess | ||
matches |= guess_matches(video, guessit(self.release, {'type': 'episode'}), partial=True) | ||
# movie | ||
elif 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') | ||
|
||
# guess | ||
matches |= guess_matches(video, guessit(self.release, {'type': 'movie'}), partial=True) | ||
|
||
return matches | ||
|
||
|
||
class CinemastProvider(Provider): | ||
"""Cinemast Provider.""" | ||
languages = {Language.fromalpha2(l) for l in ['he']} | ||
server_url = 'http://www.cinemast.org/he/cinemast/api/' | ||
subtitle_class = CinemastSubtitle | ||
|
||
def __init__(self, username=None, password=None): | ||
if not (username and password): | ||
raise ConfigurationError('Username and password must be specified') | ||
|
||
self.session = None | ||
self.username = username | ||
self.password = password | ||
self.user_id = None | ||
self.token = None | ||
self.session = None | ||
|
||
def initialize(self): | ||
self.session = Session() | ||
self.session.headers['User-Agent'] = 'Subliminal/{}'.format(__short_version__) | ||
|
||
# login | ||
logger.debug('Logging in') | ||
url = self.server_url + 'login/' | ||
|
||
# actual login | ||
data = {'username': self.username, 'password': self.password} | ||
r = self.session.post(url, data=data, allow_redirects=False, timeout=10) | ||
|
||
if r.status_code != 200: | ||
raise AuthenticationError(self.username) | ||
|
||
try: | ||
result = r.json() | ||
if 'token' not in result: | ||
raise AuthenticationError(self.username) | ||
|
||
logger.info('Logged in') | ||
self.user_id = r.json().get('user') | ||
self.token = r.json().get('token') | ||
except ValueError: | ||
raise AuthenticationError(self.username) | ||
|
||
@staticmethod | ||
def _slugify_title(title): | ||
return title.lower().replace(' ', '-').replace('\'', '').replace('"', '').replace('.', '').replace(';', '') | ||
|
||
def terminate(self): | ||
# logout | ||
if self.token or self.user_id: | ||
logger.info('Logged out') | ||
self.token = None | ||
self.user_id = None | ||
|
||
self.session.close() | ||
|
||
def query(self, title, season=None, episode=None, year=None): | ||
query = { | ||
'q': title, | ||
'user': self.user_id, | ||
'token': self.token | ||
} | ||
|
||
# episode | ||
if season and episode: | ||
query['type'] = 'series' | ||
query['season'] = season | ||
query['episode'] = episode | ||
else: | ||
query['type'] = 'movies' | ||
if year: | ||
query['year_start'] = year - 1 | ||
query['year_end'] = year | ||
|
||
# get the list of subtitles | ||
logger.debug('Getting the list of subtitles') | ||
url = self.server_url + 'search/' | ||
r = self.session.post(url, data=query) | ||
r.raise_for_status() | ||
|
||
try: | ||
results = r.json() | ||
except ValueError: | ||
return {} | ||
|
||
# loop over results | ||
subtitles = {} | ||
for group_data in results.get('data', []): | ||
# create page link | ||
slug_name = self._slugify_title(group_data.get('name_en')) | ||
if query['type'] == 'series': | ||
page_link = self.server_url + 'subtitle/series/{}/{}/{}/'.format(slug_name, season, episode) | ||
else: | ||
page_link = self.server_url + 'subtitle/movie/{}/'.format(slug_name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
|
||
# go over each language | ||
for language_code, subtitles_data in group_data.get('subtitles', {}).items(): | ||
for subtitle_item in subtitles_data: | ||
# read the item | ||
language = Language.fromalpha2(language_code) | ||
subtitle_id = subtitle_item['id'] | ||
subtitle_key = subtitle_item['key'] | ||
release = subtitle_item['version'] | ||
|
||
# otherwise create it | ||
subtitle = self.subtitle_class(language, page_link, title, season, episode, title, subtitle_id, | ||
subtitle_key, release) | ||
logger.debug('Found subtitle %r', subtitle) | ||
subtitles[subtitle_id] = subtitle | ||
|
||
return subtitles.values() | ||
|
||
def list_subtitles(self, video, languages): | ||
season = episode = None | ||
|
||
if isinstance(video, Episode): | ||
titles = [video.series] + video.alternative_series | ||
season = video.season | ||
episode = video.episode | ||
else: | ||
titles = [video.title] + video.alternative_titles | ||
|
||
for title in titles: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I said before, this is going to flood the servers with many useless requests sometimes returning duplicated results. I'm really not a big fan of this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Diaoul It will stop in the first result found. And dogpile caches it so next time it won't hit server. |
||
subtitles = [s for s in self.query(title, season, episode) if s.language in languages] | ||
if subtitles: | ||
return subtitles | ||
|
||
return [] | ||
|
||
def download_subtitle(self, subtitle): | ||
# download | ||
url = self.server_url + 'subtitle/download/{}/'.format(subtitle.language.alpha2) | ||
params = { | ||
'v': subtitle.release, | ||
'key': subtitle.subtitle_key, | ||
'sub_id': subtitle.subtitle_id | ||
} | ||
data = { | ||
'user': self.user_id, | ||
'token': self.token | ||
} | ||
r = self.session.post(url, data=data, params=params, timeout=10) | ||
r.raise_for_status() | ||
|
||
# open the zip | ||
with zipfile.ZipFile(io.BytesIO(r.content)) as zf: | ||
# remove some filenames from the namelist | ||
namelist = [n for n in zf.namelist() if not n.endswith('.txt')] | ||
if len(namelist) > 1: | ||
raise ProviderError('More than one file to unzip') | ||
|
||
subtitle.content = fix_line_ending(zf.read(namelist[0])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
urlencode is required here due to above comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Diaoul urlencode is from urllib right? Requests already does the encoding and it's not needed. confirm?
no other provider uses urlencode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For
params
ordata
you are correct but even for the URL requests does that? Anyway, I'm not sure this makes any sense as with special characters the URL will probably be wrong no? If you can add tests that pass with accentuated characters and weird characters that'll be fine for me.