Skip to content
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

0.7.x #404

Merged
merged 28 commits into from
Mar 4, 2015
Merged

0.7.x #404

Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3b832a4
updated guessit and babelfish minimum requirements
caronc Sep 18, 2014
e6dc714
Eliminated Dict Comprehensions (PEP 274) references to allow sublimin…
caronc Sep 18, 2014
14c7443
applied guessit v0.7 support
caronc Sep 18, 2014
71d206a
allow searching for subtitles by best score; not exclusively hearing …
caronc Sep 18, 2014
73a4e5a
subliminal bugfix to prevent multiple matched subtitles from differen…
caronc Sep 18, 2014
3b52a93
added ability to prioritize multiple matched subtitles; Download Hear…
caronc Sep 18, 2014
88a2cb9
added graceful handling of subtitle providers that are simply offline…
caronc Sep 18, 2014
4702284
podnapisi website changes maded in Aug 2014 broke this provider in su…
caronc Sep 18, 2014
1e9588e
Added support for titles that contain quotes
caronc Oct 18, 2014
827c75f
better handling of duplicate download prevention
caronc Nov 11, 2014
6cdf189
Fixed TVSubtitles.net matching
caronc Nov 11, 2014
87aefa3
Massive overhaul on testing to make it Python v2.6 compatible
caronc Nov 11, 2014
90d06e2
logging output slighly adjusted (added some clarity and removed some …
caronc Nov 11, 2014
d635269
removed bierdopje provider since it's not referenced anymore anyway (…
caronc Nov 11, 2014
3c7634f
eliminated bierdopje entry point to fix testing issue
caronc Nov 11, 2014
bc575ea
comparison functions added to help with 3rd party filtering
caronc Nov 30, 2014
7fef3cf
scoring adjustments moved into compute_score() and some logging cleanup
caronc Nov 30, 2014
e6bd704
scan_video() now takes pre-guessed video as optional input. This grants
caronc Dec 7, 2014
d45e3b0
improved logging for provider debugging
caronc Jan 26, 2015
a25640b
fixes #425; I'm not proud of this fix; but it resolves the issue.
caronc Jan 26, 2015
3acbefb
fixes #428; I'm not proud of this fix; but it resolves the issue.
caronc Jan 26, 2015
6c28336
Merge branch '0.7.x' of github:caronc/subliminal into 0.7.x
caronc Jan 28, 2015
e45fe70
updated requirements
caronc Jan 29, 2015
a86694d
updated requirements so that Travis CI doesn't haul in guessit v0.10
caronc Jan 29, 2015
1b7291e
Merge branch '0.7.x' of github:caronc/subliminal into 0.7.x
caronc Jan 30, 2015
c74aa01
random user agent determined at runtime; default user-agent reference…
caronc Feb 1, 2015
16ddb8a
addic7ed timeout reverted back to 10 second timeout
caronc Feb 1, 2015
407cbe1
series/movie title sanitization refactored
caronc Feb 1, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
beautifulsoup4>=4.3.2
guessit>=0.6.2,<0.7
guessit>=0.7,<0.10
requests>=2.0.1
enzyme>=0.4.0
html5lib>=0.99
dogpile.cache>=0.5.2
babelfish>=0.4.0,<0.5
babelfish>=0.5.0
charade>=1.0.3
pysrt>=0.5.0
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
entry_points={
'console_scripts': ['subliminal = subliminal.cli:subliminal'],
'subliminal.providers': ['addic7ed = subliminal.providers.addic7ed:Addic7edProvider',
'bierdopje = subliminal.providers.bierdopje:BierDopjeProvider',
'opensubtitles = subliminal.providers.opensubtitles:OpenSubtitlesProvider',
'podnapisi = subliminal.providers.podnapisi:PodnapisiProvider',
'thesubdb = subliminal.providers.thesubdb:TheSubDBProvider',
Expand Down
5 changes: 4 additions & 1 deletion subliminal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
from .subtitle import Subtitle
from .video import VIDEO_EXTENSIONS, SUBTITLE_EXTENSIONS, Video, Episode, Movie, scan_videos, scan_video

class NullHandler(logging.Handler):
def emit(self, record):
pass

logging.getLogger(__name__).addHandler(logging.NullHandler())
logging.getLogger(__name__).addHandler(NullHandler())
133 changes: 106 additions & 27 deletions subliminal/api.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion subliminal/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


#: Subliminal's cache version
CACHE_VERSION = 1
CACHE_VERSION = 2


def subliminal_key_generator(namespace, fn, to_str=string_type):
Expand Down
16 changes: 3 additions & 13 deletions subliminal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ def subliminal():
filtering_group.add_argument('-f', '--force', action='store_true',
help='force subtitle download for videos with existing subtitles')

# addic7ed
addic7ed_group = parser.add_argument_group('addic7ed')
addic7ed_group.add_argument('--addic7ed-username', metavar='USERNAME', help='username for addic7ed provider')
addic7ed_group.add_argument('--addic7ed-password', metavar='PASSWORD', help='password for addic7ed provider')

# output
output_group = parser.add_argument_group('output')
output_exclusive_group = output_group.add_mutually_exclusive_group()
Expand All @@ -81,7 +76,7 @@ def subliminal():

# parse languages
try:
args.languages = {babelfish.Language.fromietf(l) for l in args.languages}
args.languages = set( babelfish.Language.fromietf(l) for l in args.languages )
except babelfish.Error:
parser.error('argument -l/--languages: codes are not IETF: %r' % args.languages)

Expand All @@ -90,7 +85,7 @@ def subliminal():
match = re.match(r'^(?:(?P<weeks>\d+?)w)?(?:(?P<days>\d+?)d)?(?:(?P<hours>\d+?)h)?$', args.age)
if not match:
parser.error('argument -a/--age: invalid age: %r' % args.age)
args.age = datetime.timedelta(**{k: int(v) for k, v in match.groupdict(0).items()})
args.age = datetime.timedelta(**dict([(k, int(v)) for k, v in match.groupdict(0).items()]))

# parse cache-file
args.cache_file = os.path.abspath(os.path.expanduser(args.cache_file))
Expand All @@ -100,11 +95,6 @@ def subliminal():

# parse provider configs
provider_configs = {}
if (args.addic7ed_username is not None and args.addic7ed_password is None
or args.addic7ed_username is None and args.addic7ed_password is not None):
parser.error('argument --addic7ed-username/--addic7ed-password: both arguments are required or none')
if args.addic7ed_username is not None and args.addic7ed_password is not None:
provider_configs['addic7ed'] = {'username': args.addic7ed_username, 'password': args.addic7ed_password}

# parse color
if args.color and colorlog is None:
Expand Down Expand Up @@ -146,7 +136,7 @@ def subliminal():
embedded_subtitles=not args.force, age=args.age)

# guess videos
videos.extend([Video.fromguess(os.path.split(p)[1], guessit.guess_file_info(p, 'autodetect')) for p in args.paths
videos.extend([Video.fromguess(os.path.split(p)[1], guessit.guess_file_info(p, info=['filename'])) for p in args.paths
if not os.path.exists(p)])

# download best subtitles
Expand Down
4 changes: 2 additions & 2 deletions subliminal/converters/addic7ed.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from babelfish import LanguageReverseConverter, get_language_converter
from babelfish import LanguageReverseConverter, language_converters


class Addic7edConverter(LanguageReverseConverter):
def __init__(self):
self.name_converter = get_language_converter('name')
self.name_converter = language_converters['name']
self.from_addic7ed = {'Català': ('cat',), 'Chinese (Simplified)': ('zho',), 'Chinese (Traditional)': ('zho',),
'Euskera': ('eus',), 'Galego': ('glg',), 'Greek': ('ell',), 'Malay': ('msa',),
'Portuguese (Brazilian)': ('por', 'BR'), 'Serbian (Cyrillic)': ('srp', None, 'Cyrl'),
Expand Down
2 changes: 1 addition & 1 deletion subliminal/converters/podnapisi.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def __init__(self):
11: ('jpn',), 4: ('kor',), 29: ('sqi',), 6: ('isl',), 19: ('lit',), 46: ('ukr',),
44: ('tha',), 53: ('cat',), 56: ('sin',), 21: ('lav',), 40: ('cmn',), 55: ('msa',),
42: ('hin',), 50: ('bel',)}
self.to_podnapisi = {v: k for k, v in self.from_podnapisi.items()}
self.to_podnapisi = dict([(v, k) for k, v in self.from_podnapisi.items()])
self.codes = set(self.from_podnapisi.keys())

def convert(self, alpha3, country=None, script=None):
Expand Down
6 changes: 3 additions & 3 deletions subliminal/converters/tvsubtitles.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from babelfish import LanguageReverseConverter, get_language_converter
from babelfish import LanguageReverseConverter, language_converters


class TVsubtitlesConverter(LanguageReverseConverter):
def __init__(self):
self.alpha2_converter = get_language_converter('alpha2')
self.alpha2_converter = language_converters['alpha2']
self.from_tvsubtitles = {'br': ('por', 'BR'), 'ua': ('ukr',), 'gr': ('ell',), 'cn': ('zho',), 'jp': ('jpn',),
'cz': ('ces',)}
self.to_tvsubtitles = {v: k for k, v in self.from_tvsubtitles}
self.to_tvsubtitles = set([(v, k) for k, v in self.from_tvsubtitles])
self.codes = self.alpha2_converter.codes | set(self.from_tvsubtitles.keys())

def convert(self, alpha3, country=None, script=None):
Expand Down
3 changes: 3 additions & 0 deletions subliminal/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import babelfish
from ..video import Episode, Movie

import re
#: The following characters are always stripped
IGNORED_CHARACTERS_RE = re.compile('[!@#$\'"]')

class Provider(object):
"""Base class for providers
Expand Down
59 changes: 18 additions & 41 deletions subliminal/providers/addic7ed.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import charade
import requests
from . import Provider
from . import IGNORED_CHARACTERS_RE
from .. import __version__
from ..cache import region
from ..api import RANDOM_USER_AGENT
from ..exceptions import ProviderConfigurationError, ProviderNotAvailable, InvalidSubtitle
from ..subtitle import Subtitle, is_valid_subtitle
from ..video import Episode
Expand Down Expand Up @@ -53,49 +55,21 @@ def compute_matches(self, video):


class Addic7edProvider(Provider):
languages = {babelfish.Language('por', 'BR')} | {babelfish.Language(l)
languages = set([babelfish.Language('por', 'BR')]) | set([babelfish.Language(l)
for l in ['ara', 'aze', 'ben', 'bos', 'bul', 'cat', 'ces', 'dan', 'deu', 'ell', 'eng', 'eus', 'fas',
'fin', 'fra', 'glg', 'heb', 'hrv', 'hun', 'hye', 'ind', 'ita', 'jpn', 'kor', 'mkd', 'msa',
'nld', 'nor', 'pol', 'por', 'ron', 'rus', 'slk', 'slv', 'spa', 'sqi', 'srp', 'swe', 'tha',
'tur', 'ukr', 'vie', 'zho']}
'tur', 'ukr', 'vie', 'zho']])
video_types = (Episode,)
server = 'http://www.addic7ed.com'

def __init__(self, username=None, password=None):
if username is not None and password is None or username is None and password is not None:
raise ProviderConfigurationError('Username and password must be specified')
self.username = username
self.password = password
self.logged_in = False

def initialize(self):
self.session = requests.Session()
self.session.headers = {'User-Agent': 'Subliminal/%s' % __version__}
# login
if self.username is not None and self.password is not None:
logger.debug('Logging in')
data = {'username': self.username, 'password': self.password, 'Submit': 'Log in'}
try:
r = self.session.post(self.server + '/dologin.php', data, timeout=10, allow_redirects=False)
except requests.Timeout:
raise ProviderNotAvailable('Timeout after 10 seconds')
if r.status_code == 302:
logger.info('Logged in')
self.logged_in = True
else:
logger.error('Failed to login')

def terminate(self):
# logout
if self.logged_in:
try:
r = self.session.get(self.server + '/logout.php', timeout=10)
logger.info('Logged out')
except requests.Timeout:
raise ProviderNotAvailable('Timeout after 10 seconds')
if r.status_code != 200:
raise ProviderNotAvailable('Request failed with status code %d' % r.status_code)
self.session.close()
#self.session.headers = {'User-Agent': 'Subliminal/%s' % __version__}
self.session.headers = {
'User-Agent': RANDOM_USER_AGENT,
'Referer': self.server,
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use a random user agent here? I'm generally against this because servers should be allowed to block subliminal activity. Although I don't want them too, it should be possible. We should contact the provider to know the reason why they would want to block us and have an agreement.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally agree with you but Addic7ted has recently blocked all access their site 'unless' we do this. I even forked my nzbget-subliminal code into using the name nokarma and explain here why. It is part of my later commits to this branch.

In short, I've attempted to make contact with them and after a week (now it's been about 3) without hearing back from them; i just did this dirty move so we could still use the provider. I can roll this back if you want; it will break this provider though (see #428).


def get(self, url, params=None):
"""Make a GET request on `url` with the given parameters
Expand All @@ -108,7 +82,7 @@ def get(self, url, params=None):

"""
try:
r = self.session.get(self.server + url, params=params, timeout=10)
r = self.session.get(self.server + url, params=params, timeout=30)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 10s timeout is plenty, if the provider doesn't respond by then, they are experiencing serious trouble.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i had a failure one day and got lazy and changed the value; i'll flip it back (in both cases you pointed out)

except requests.Timeout:
raise ProviderNotAvailable('Timeout after 10 seconds')
if r.status_code != 200:
Expand All @@ -126,7 +100,9 @@ def get_show_ids(self):
soup = self.get('/shows.php')
show_ids = {}
for html_show in soup.select('td.version > h3 > a[href^="/show/"]'):
show_ids[html_show.string.lower()] = int(html_show['href'][6:])
show_ids[
IGNORED_CHARACTERS_RE.sub('', html_show.string).lower()] = \
int(html_show['href'][6:])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should rename IGNORED_CHARACTERS_RE to something more cache-specific and create a sanitize_cache function (or similar) to put this logic.

return show_ids

@region.cache_on_arguments()
Expand All @@ -150,10 +126,11 @@ def find_show_id(self, series):

def query(self, series, season):
show_ids = self.get_show_ids()
if series.lower() in show_ids:
show_id = show_ids[series.lower()]
_series = IGNORED_CHARACTERS_RE.sub('', series).lower()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is confusing, please make this explicit such as sanitized_series

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense; i totally agree with the naming convention your suggestion. Will change (all references you pointed out)

if _series in show_ids:
show_id = show_ids[_series]
else:
show_id = self.find_show_id(series.lower())
show_id = self.find_show_id(_series)
if show_id is None:
return []
params = {'show_id': show_id, 'season': season}
Expand All @@ -180,7 +157,7 @@ def list_subtitles(self, video, languages):

def download_subtitle(self, subtitle):
try:
r = self.session.get(self.server + subtitle.download_link, timeout=10,
r = self.session.get(self.server + subtitle.download_link, timeout=30,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about the timeout here.

headers={'Referer': self.server + subtitle.referer})
except requests.Timeout:
raise ProviderNotAvailable('Timeout after 10 seconds')
Expand Down
135 changes: 0 additions & 135 deletions subliminal/providers/bierdopje.py

This file was deleted.

Loading