diff --git a/gui/slick/interfaces/default/editShow.tmpl b/gui/slick/interfaces/default/editShow.tmpl
index 2ef5e94d..4dbae7e6 100644
--- a/gui/slick/interfaces/default/editShow.tmpl
+++ b/gui/slick/interfaces/default/editShow.tmpl
@@ -118,6 +118,17 @@ This DOES NOT allow Sick Beard to download non-english TV episodes!
(check this to have the episode archived after the first best match is found from your archive quality list)
#end if
+
+Ignored Words:
+Results with any of these words in the title will be filtered out
+Separate words with a comma, e.g. "word1,word2,word3"
+
+
+Required Words:
+Results without one of these words in the title will be filtered out
+Separate words with a comma, e.g. "word1,word2,word3"
+
+
diff --git a/lib/subliminal/__init__.py b/lib/subliminal/__init__.py
index 666440b6..77297f5a 100644
--- a/lib/subliminal/__init__.py
+++ b/lib/subliminal/__init__.py
@@ -31,4 +31,4 @@ except ImportError:
__all__ = ['SERVICES', 'LANGUAGE_INDEX', 'SERVICE_INDEX', 'SERVICE_CONFIDENCE',
'MATCHING_CONFIDENCE', 'list_subtitles', 'download_subtitles', 'Pool']
-logging.getLogger(__name__).addHandler(NullHandler())
+logging.getLogger("subliminal").addHandler(NullHandler())
diff --git a/lib/subliminal/api.py b/lib/subliminal/api.py
index 51416f8c..f95fda3b 100644
--- a/lib/subliminal/api.py
+++ b/lib/subliminal/api.py
@@ -23,7 +23,7 @@ import logging
__all__ = ['list_subtitles', 'download_subtitles']
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
def list_subtitles(paths, languages=None, services=None, force=True, multi=False, cache_dir=None, max_depth=3, scan_filter=None):
@@ -94,7 +94,10 @@ def download_subtitles(paths, languages=None, services=None, force=True, multi=F
order = order or [LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE]
subtitles_by_video = list_subtitles(paths, languages, services, force, multi, cache_dir, max_depth, scan_filter)
for video, subtitles in subtitles_by_video.iteritems():
- subtitles.sort(key=lambda s: key_subtitles(s, video, languages, services, order), reverse=True)
+ try:
+ subtitles.sort(key=lambda s: key_subtitles(s, video, languages, services, order), reverse=True)
+ except StopIteration:
+ break
results = []
service_instances = {}
tasks = create_download_tasks(subtitles_by_video, languages, multi)
diff --git a/lib/subliminal/async.py b/lib/subliminal/async.py
index 6a69b766..ff42764b 100644
--- a/lib/subliminal/async.py
+++ b/lib/subliminal/async.py
@@ -26,7 +26,7 @@ import threading
__all__ = ['Worker', 'Pool']
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class Worker(threading.Thread):
diff --git a/lib/subliminal/cache.py b/lib/subliminal/cache.py
index 9add007a..31275e00 100644
--- a/lib/subliminal/cache.py
+++ b/lib/subliminal/cache.py
@@ -27,7 +27,7 @@ except ImportError:
__all__ = ['Cache', 'cachedmethod']
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class Cache(object):
diff --git a/lib/subliminal/core.py b/lib/subliminal/core.py
index 537fa655..9c3fa3dc 100644
--- a/lib/subliminal/core.py
+++ b/lib/subliminal/core.py
@@ -31,8 +31,8 @@ import logging
__all__ = ['SERVICES', 'LANGUAGE_INDEX', 'SERVICE_INDEX', 'SERVICE_CONFIDENCE', 'MATCHING_CONFIDENCE',
'create_list_tasks', 'create_download_tasks', 'consume_task', 'matching_confidence',
'key_subtitles', 'group_by_video']
-logger = logging.getLogger(__name__)
-SERVICES = ['opensubtitles', 'bierdopje', 'subswiki', 'subtitulos', 'thesubdb', 'addic7ed', 'tvsubtitles']
+logger = logging.getLogger("subliminal")
+SERVICES = ['opensubtitles', 'subswiki', 'subtitulos', 'thesubdb', 'addic7ed', 'tvsubtitles', 'itasa', 'usub']
LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE = range(4)
diff --git a/lib/subliminal/infos.py b/lib/subliminal/infos.py
index 220ea859..5ab2084a 100644
--- a/lib/subliminal/infos.py
+++ b/lib/subliminal/infos.py
@@ -15,4 +15,4 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see .
-__version__ = '0.6.2'
+__version__ = '0.6.3'
diff --git a/lib/subliminal/language.py b/lib/subliminal/language.py
index efc75bb4..6403bcc0 100644
--- a/lib/subliminal/language.py
+++ b/lib/subliminal/language.py
@@ -20,7 +20,7 @@ import re
import logging
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
COUNTRIES = [('AF', 'AFG', '004', u'Afghanistan'),
@@ -619,6 +619,7 @@ LANGUAGES = [('aar', '', 'aa', u'Afar', u'afar'),
('pli', '', 'pi', u'Pali', u'pali'),
('pol', '', 'pl', u'Polish', u'polonais'),
('pon', '', '', u'Pohnpeian', u'pohnpei'),
+ ('pob', '', 'pb', u'Brazilian Portuguese', u'brazilian portuguese'),
('por', '', 'pt', u'Portuguese', u'portugais'),
('pra', '', '', u'Prakrit languages', u'prâkrit, langues'),
('pro', '', '', u'Provençal, Old (to 1500)', u'provençal ancien (jusqu\'à 1500)'),
diff --git a/lib/subliminal/services/__init__.py b/lib/subliminal/services/__init__.py
index b82b309c..c52dd5dc 100644
--- a/lib/subliminal/services/__init__.py
+++ b/lib/subliminal/services/__init__.py
@@ -27,7 +27,7 @@ import zipfile
__all__ = ['ServiceBase', 'ServiceConfig']
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class ServiceBase(object):
@@ -82,7 +82,7 @@ class ServiceBase(object):
"""Initialize connection"""
logger.debug(u'Initializing %s' % self.__class__.__name__)
self.session = requests.session()
- self.session.headers.update({'User-Agent': self.user_agent})
+ self.session.headers.update({'User-Agent': self.user_agent})
def init_cache(self):
"""Initialize cache, make sure it is loaded from disk"""
@@ -220,14 +220,16 @@ class ServiceBase(object):
# TODO: could check if maybe we already have a text file and
# download it directly
raise DownloadFailedError('Downloaded file is not a zip file')
- with zipfile.ZipFile(zippath) as zipsub:
- for subfile in zipsub.namelist():
- if os.path.splitext(subfile)[1] in EXTENSIONS:
- with open(filepath, 'w') as f:
- f.write(zipsub.open(subfile).read())
- break
- else:
- raise DownloadFailedError('No subtitles found in zip file')
+ zipsub = zipfile.ZipFile(zippath)
+ for subfile in zipsub.namelist():
+ if os.path.splitext(subfile)[1] in EXTENSIONS:
+ with open(filepath, 'wb') as f:
+ f.write(zipsub.open(subfile).read())
+ break
+ else:
+ zipsub.close()
+ raise DownloadFailedError('No subtitles found in zip file')
+ zipsub.close()
os.remove(zippath)
except Exception as e:
logger.error(u'Download %s failed: %s' % (url, e))
diff --git a/lib/subliminal/services/addic7ed.py b/lib/subliminal/services/addic7ed.py
index 492fd744..665c5708 100644
--- a/lib/subliminal/services/addic7ed.py
+++ b/lib/subliminal/services/addic7ed.py
@@ -29,16 +29,17 @@ import os
import re
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class Addic7ed(ServiceBase):
server_url = 'http://www.addic7ed.com'
+ site_url = 'http://www.addic7ed.com'
api_based = False
#TODO: Complete this
languages = language_set(['ar', 'ca', 'de', 'el', 'en', 'es', 'eu', 'fr', 'ga', 'gl', 'he', 'hr', 'hu',
- 'it', 'pl', 'pt', 'ro', 'ru', 'se', 'pt-br'])
- language_map = {'Portuguese (Brazilian)': Language('por-BR'), 'Greek': Language('gre'),
+ 'it', 'pl', 'pt', 'ro', 'ru', 'se', 'pb'])
+ language_map = {'Portuguese (Brazilian)': Language('pob'), 'Greek': Language('gre'),
'Spanish (Latin America)': Language('spa'), 'Galego': Language('glg'),
u'Català': Language('cat')}
videos = [Episode]
@@ -63,6 +64,7 @@ class Addic7ed(ServiceBase):
return self.query(video.path or video.release, languages, get_keywords(video.guess), video.series, video.season, video.episode)
def query(self, filepath, languages, keywords, series, season, episode):
+
logger.debug(u'Getting subtitles for %s season %d episode %d with languages %r' % (series, season, episode, languages))
self.init_cache()
try:
@@ -90,7 +92,7 @@ class Addic7ed(ServiceBase):
continue
sub_keywords = split_keyword(cells[4].text.strip().lower())
#TODO: Maybe allow empty keywords here? (same in Subtitulos)
- if not keywords & sub_keywords:
+ if keywords and not keywords & sub_keywords:
logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords))
continue
sub_link = '%s/%s' % (self.server_url, cells[9].a['href'])
diff --git a/lib/subliminal/services/bierdopje.py b/lib/subliminal/services/bierdopje.py
index 03577cd8..8642afb8 100644
--- a/lib/subliminal/services/bierdopje.py
+++ b/lib/subliminal/services/bierdopje.py
@@ -31,11 +31,12 @@ except ImportError:
import pickle
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class BierDopje(ServiceBase):
server_url = 'http://api.bierdopje.com/A2B638AC5D804C2E/'
+ site_url = 'http://www.bierdopje.com'
user_agent = 'Subliminal/0.6'
api_based = True
languages = language_set(['eng', 'dut'])
diff --git a/lib/subliminal/services/itasa.py b/lib/subliminal/services/itasa.py
new file mode 100644
index 00000000..f726a156
--- /dev/null
+++ b/lib/subliminal/services/itasa.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mr_Orange
+#
+# This file is part of subliminal.
+#
+# subliminal is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# subliminal is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with subliminal. If not, see .
+from . import ServiceBase
+from ..exceptions import DownloadFailedError, ServiceError
+from ..cache import cachedmethod
+from ..language import language_set, Language
+from ..subtitles import get_subtitle_path, ResultSubtitle, EXTENSIONS
+from ..utils import get_keywords
+from ..videos import Episode
+from bs4 import BeautifulSoup
+import logging
+import re
+import os
+import requests
+import zipfile
+import StringIO
+import guessit
+
+from sickbeard.common import Quality
+
+logger = logging.getLogger("subliminal")
+
+
+class Itasa(ServiceBase):
+ server_url = 'http://www.italiansubs.net/'
+ site_url = 'http://www.italiansubs.net/'
+ api_based = False
+ languages = language_set(['it'])
+ videos = [Episode]
+ require_video = False
+ required_features = ['permissive']
+ quality_dict = {Quality.SDTV : '',
+ Quality.SDDVD : 'dvdrip',
+ Quality.RAWHDTV : '1080i',
+ Quality.HDTV : '720p',
+ Quality.FULLHDTV : ('1080p','720p'),
+ Quality.HDWEBDL : 'web-dl',
+ Quality.FULLHDWEBDL : 'web-dl',
+ Quality.HDBLURAY : ('bdrip', 'bluray'),
+ Quality.FULLHDBLURAY : ('bdrip', 'bluray'),
+ Quality.UNKNOWN : 'unknown' #Any subtitle will be downloaded
+ }
+
+ def init(self):
+
+ super(Itasa, self).init()
+ login_pattern = ''
+
+ response = requests.get(self.server_url + 'index.php')
+ if response.status_code != 200:
+ raise ServiceError('Initiate failed')
+
+ match = re.search(login_pattern, response.content, re.IGNORECASE | re.DOTALL)
+ if not match:
+ raise ServiceError('Can not find unique id parameter on page')
+
+ login_parameter = {'username': 'sickbeard',
+ 'passwd': 'subliminal',
+ 'remember': 'yes',
+ 'Submit': 'Login',
+ 'remember': 'yes',
+ 'option': 'com_user',
+ 'task': 'login',
+ 'silent': 'true',
+ 'return': match.group(1),
+ match.group(2): match.group(3)
+ }
+
+ self.session = requests.session()
+ r = self.session.post(self.server_url + 'index.php', data=login_parameter)
+ if not re.search('logouticon.png', r.content, re.IGNORECASE | re.DOTALL):
+ raise ServiceError('Itasa Login Failed')
+
+ @cachedmethod
+ def get_series_id(self, name):
+ """Get the show page and cache every show found in it"""
+ r = self.session.get(self.server_url + 'index.php?option=com_remository&Itemid=9')
+ soup = BeautifulSoup(r.content, self.required_features)
+ all_series = soup.find('div', attrs = {'id' : 'remositorycontainerlist'})
+ for tv_series in all_series.find_all(href=re.compile('func=select')):
+ series_name = tv_series.text.lower().strip().replace(':','')
+ match = re.search('&id=([0-9]+)', tv_series['href'])
+ if match is None:
+ continue
+ series_id = int(match.group(1))
+ self.cache_for(self.get_series_id, args=(series_name,), result=series_id)
+ return self.cached_value(self.get_series_id, args=(name,))
+
+ def get_episode_id(self, series, series_id, season, episode, quality):
+ """Get the id subtitle for episode with the given quality"""
+
+ season_link = None
+ quality_link = None
+ episode_id = None
+
+ r = self.session.get(self.server_url + 'index.php?option=com_remository&Itemid=6&func=select&id=' + str(series_id))
+ soup = BeautifulSoup(r.content, self.required_features)
+ all_seasons = soup.find('div', attrs = {'id' : 'remositorycontainerlist'})
+ for seasons in all_seasons.find_all(href=re.compile('func=select')):
+ if seasons.text.lower().strip() == 'stagione %s' % str(season):
+ season_link = seasons['href']
+ break
+
+ if not season_link:
+ logger.debug(u'Could not find season %s for series %s' % (series, str(season)))
+ return None
+
+ r = self.session.get(season_link)
+ soup = BeautifulSoup(r.content, self.required_features)
+
+ all_qualities = soup.find('div', attrs = {'id' : 'remositorycontainerlist'})
+ for qualities in all_qualities.find_all(href=re.compile('func=select')):
+ if qualities.text.lower().strip() in self.quality_dict[quality]:
+ quality_link = qualities['href']
+ r = self.session.get(qualities['href'])
+ soup = BeautifulSoup(r.content, self.required_features)
+ break
+
+ #If we want SDTV we are just on the right page so quality link will be None
+ if not quality == Quality.SDTV and not quality_link:
+ logger.debug(u'Could not find a subtitle with required quality for series %s season %s' % (series, str(season)))
+ return None
+
+ all_episodes = soup.find('div', attrs = {'id' : 'remositoryfilelisting'})
+ for episodes in all_episodes.find_all(href=re.compile('func=fileinfo')):
+ ep_string = "%(seasonnumber)dx%(episodenumber)02d" % {'seasonnumber': season, 'episodenumber': episode}
+ if re.search(ep_string, episodes.text, re.I) or re.search('completa$', episodes.text, re.I):
+ match = re.search('&id=([0-9]+)', episodes['href'])
+ if match:
+ episode_id = match.group(1)
+ return episode_id
+
+ return episode_id
+
+ def list_checked(self, video, languages):
+ return self.query(video.path or video.release, languages, get_keywords(video.guess), video.series, video.season, video.episode)
+
+ def query(self, filepath, languages, keywords, series, season, episode):
+
+ logger.debug(u'Getting subtitles for %s season %d episode %d with languages %r' % (series, season, episode, languages))
+ self.init_cache()
+ try:
+ series = series.lower().replace('(','').replace(')','')
+ series_id = self.get_series_id(series)
+ except KeyError:
+ logger.debug(u'Could not find series id for %s' % series)
+ return []
+
+ episode_id = self.get_episode_id(series, series_id, season, episode, Quality.nameQuality(filepath))
+ if not episode_id:
+ logger.debug(u'Could not find subtitle for series %s' % series)
+ return []
+
+ r = self.session.get(self.server_url + 'index.php?option=com_remository&Itemid=6&func=fileinfo&id=' + episode_id)
+ soup = BeautifulSoup(r.content)
+
+ sub_link = soup.find('div', attrs = {'id' : 'remositoryfileinfo'}).find(href=re.compile('func=download'))['href']
+ sub_language = self.get_language('it')
+ path = get_subtitle_path(filepath, sub_language, self.config.multi)
+ subtitle = ResultSubtitle(path, sub_language, self.__class__.__name__.lower(), sub_link)
+
+ return [subtitle]
+
+ def download(self, subtitle):
+
+ logger.info(u'Downloading %s in %s' % (subtitle.link, subtitle.path))
+ try:
+ r = self.session.get(subtitle.link, headers={'Referer': self.server_url, 'User-Agent': self.user_agent})
+ zipcontent = StringIO.StringIO(r.content)
+ zipsub = zipfile.ZipFile(zipcontent)
+
+# if not zipsub.is_zipfile(zipcontent):
+# raise DownloadFailedError('Downloaded file is not a zip file')
+
+ subfile = ''
+ if len(zipsub.namelist()) == 1:
+ subfile = zipsub.namelist()[0]
+ else:
+ #Season Zip Retrive Season and episode Numbers from path
+ guess = guessit.guess_file_info(subtitle.path, 'episode')
+ ep_string = "s%(seasonnumber)02de%(episodenumber)02d" % {'seasonnumber': guess['season'], 'episodenumber': guess['episodeNumber']}
+ for file in zipsub.namelist():
+ if re.search(ep_string, file, re.I):
+ subfile = file
+ break
+ if os.path.splitext(subfile)[1] in EXTENSIONS:
+ with open(subtitle.path, 'wb') as f:
+ f.write(zipsub.open(subfile).read())
+ else:
+ zipsub.close()
+ raise DownloadFailedError('No subtitles found in zip file')
+
+ zipsub.close()
+ except Exception as e:
+ if os.path.exists(subtitle.path):
+ os.remove(subtitle.path)
+ raise DownloadFailedError(str(e))
+
+ logger.debug(u'Download finished')
+
+Service = Itasa
\ No newline at end of file
diff --git a/lib/subliminal/services/opensubtitles.py b/lib/subliminal/services/opensubtitles.py
index 1cee25ba..65599d24 100644
--- a/lib/subliminal/services/opensubtitles.py
+++ b/lib/subliminal/services/opensubtitles.py
@@ -27,11 +27,12 @@ import os.path
import xmlrpclib
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class OpenSubtitles(ServiceBase):
server_url = 'http://api.opensubtitles.org/xml-rpc'
+ site_url = 'http://www.opensubtitles.org'
api_based = True
# Source: http://www.opensubtitles.org/addons/export_languages.php
languages = language_set(['aar', 'abk', 'ace', 'ach', 'ada', 'ady', 'afa', 'afh', 'afr', 'ain', 'aka', 'akk',
@@ -73,9 +74,9 @@ class OpenSubtitles(ServiceBase):
'twi', 'tyv', 'udm', 'uga', 'uig', 'ukr', 'umb', 'urd', 'uzb', 'vai', 'ven', 'vie',
'vol', 'vot', 'wak', 'wal', 'war', 'was', 'wel', 'wen', 'wln', 'wol', 'xal', 'xho',
'yao', 'yap', 'yid', 'yor', 'ypk', 'zap', 'zen', 'zha', 'znd', 'zul', 'zun',
- 'por-BR', 'rum-MD'])
- language_map = {'mol': Language('rum-MD'), 'scc': Language('srp'), 'pob': Language('por-BR'),
- Language('rum-MD'): 'mol', Language('srp'): 'scc', Language('por-BR'): 'pob'}
+ 'pob', 'rum-MD'])
+ language_map = {'mol': Language('rum-MD'), 'scc': Language('srp'),
+ Language('rum-MD'): 'mol', Language('srp'): 'scc'}
language_code = 'alpha3'
videos = [Episode, Movie]
require_video = False
diff --git a/lib/subliminal/services/podnapisi.py b/lib/subliminal/services/podnapisi.py
index 618c0e77..be02dd51 100644
--- a/lib/subliminal/services/podnapisi.py
+++ b/lib/subliminal/services/podnapisi.py
@@ -26,20 +26,21 @@ import logging
import xmlrpclib
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class Podnapisi(ServiceBase):
server_url = 'http://ssp.podnapisi.net:8000'
+ site_url = 'http://www.podnapisi.net'
api_based = True
languages = language_set(['ar', 'be', 'bg', 'bs', 'ca', 'ca', 'cs', 'da', 'de', 'el', 'en',
'es', 'et', 'fa', 'fi', 'fr', 'ga', 'he', 'hi', 'hr', 'hu', 'id',
'is', 'it', 'ja', 'ko', 'lt', 'lv', 'mk', 'ms', 'nl', 'nn', 'pl',
'pt', 'ro', 'ru', 'sk', 'sl', 'sq', 'sr', 'sv', 'th', 'tr', 'uk',
- 'vi', 'zh', 'es-ar', 'pt-br'])
+ 'vi', 'zh', 'es-ar', 'pb'])
language_map = {'jp': Language('jpn'), Language('jpn'): 'jp',
'gr': Language('gre'), Language('gre'): 'gr',
- 'pb': Language('por-BR'), Language('por-BR'): 'pb',
+# 'pb': Language('por-BR'), Language('por-BR'): 'pb',
'ag': Language('spa-AR'), Language('spa-AR'): 'ag',
'cyr': Language('srp')}
videos = [Episode, Movie]
diff --git a/lib/subliminal/services/podnapisiweb.py b/lib/subliminal/services/podnapisiweb.py
new file mode 100644
index 00000000..57397b75
--- /dev/null
+++ b/lib/subliminal/services/podnapisiweb.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+# Copyright 2011-2012 Antoine Bertin
+#
+# This file is part of subliminal.
+#
+# subliminal is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# subliminal is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with subliminal. If not, see .
+from . import ServiceBase
+from ..exceptions import DownloadFailedError
+from ..language import Language, language_set
+from ..subtitles import ResultSubtitle
+from ..utils import get_keywords
+from ..videos import Episode, Movie
+from bs4 import BeautifulSoup
+import guessit
+import logging
+import re
+from subliminal.subtitles import get_subtitle_path
+
+
+logger = logging.getLogger("subliminal")
+
+
+class PodnapisiWeb(ServiceBase):
+ server_url = 'http://simple.podnapisi.net'
+ site_url = 'http://www.podnapisi.net'
+ api_based = True
+ user_agent = 'Subliminal/0.6'
+ videos = [Episode, Movie]
+ require_video = False
+ required_features = ['xml']
+ languages = language_set(['Albanian', 'Arabic', 'Spanish (Argentina)', 'Belarusian', 'Bosnian', 'Portuguese (Brazil)', 'Bulgarian', 'Catalan',
+ 'Chinese', 'Croatian', 'Czech', 'Danish', 'Dutch', 'English', 'Estonian', 'Persian',
+ 'Finnish', 'French', 'German', 'gre', 'Kalaallisut', 'Hebrew', 'Hindi', 'Hungarian',
+ 'Icelandic', 'Indonesian', 'Irish', 'Italian', 'Japanese', 'Kazakh', 'Korean', 'Latvian',
+ 'Lithuanian', 'Macedonian', 'Malay', 'Norwegian', 'Polish', 'Portuguese', 'Romanian',
+ 'Russian', 'Serbian', 'Sinhala', 'Slovak', 'Slovenian', 'Spanish', 'Swedish', 'Thai',
+ 'Turkish', 'Ukrainian', 'Vietnamese'])
+ language_map = {Language('Albanian'): 29, Language('Arabic'): 12, Language('Spanish (Argentina)'): 14, Language('Belarusian'): 50,
+ Language('Bosnian'): 10, Language('Portuguese (Brazil)'): 48, Language('Bulgarian'): 33, Language('Catalan'): 53,
+ Language('Chinese'): 17, Language('Croatian'): 38, Language('Czech'): 7, Language('Danish'): 24,
+ Language('Dutch'): 23, Language('English'): 2, Language('Estonian'): 20, Language('Persian'): 52,
+ Language('Finnish'): 31, Language('French'): 8, Language('German'): 5, Language('gre'): 16,
+ Language('Kalaallisut'): 57, Language('Hebrew'): 22, Language('Hindi'): 42, Language('Hungarian'): 15,
+ Language('Icelandic'): 6, Language('Indonesian'): 54, Language('Irish'): 49, Language('Italian'): 9,
+ Language('Japanese'): 11, Language('Kazakh'): 58, Language('Korean'): 4, Language('Latvian'): 21,
+ Language('Lithuanian'): 19, Language('Macedonian'): 35, Language('Malay'): 55,
+ Language('Norwegian'): 3, Language('Polish'): 26, Language('Portuguese'): 32, Language('Romanian'): 13,
+ Language('Russian'): 27, Language('Serbian'): 36, Language('Sinhala'): 56, Language('Slovak'): 37,
+ Language('Slovenian'): 1, Language('Spanish'): 28, Language('Swedish'): 25, Language('Thai'): 44,
+ Language('Turkish'): 30, Language('Ukrainian'): 46, Language('Vietnamese'): 51,
+ 29: Language('Albanian'), 12: Language('Arabic'), 14: Language('Spanish (Argentina)'), 50: Language('Belarusian'),
+ 10: Language('Bosnian'), 48: Language('Portuguese (Brazil)'), 33: Language('Bulgarian'), 53: Language('Catalan'),
+ 17: Language('Chinese'), 38: Language('Croatian'), 7: Language('Czech'), 24: Language('Danish'),
+ 23: Language('Dutch'), 2: Language('English'), 20: Language('Estonian'), 52: Language('Persian'),
+ 31: Language('Finnish'), 8: Language('French'), 5: Language('German'), 16: Language('gre'),
+ 57: Language('Kalaallisut'), 22: Language('Hebrew'), 42: Language('Hindi'), 15: Language('Hungarian'),
+ 6: Language('Icelandic'), 54: Language('Indonesian'), 49: Language('Irish'), 9: Language('Italian'),
+ 11: Language('Japanese'), 58: Language('Kazakh'), 4: Language('Korean'), 21: Language('Latvian'),
+ 19: Language('Lithuanian'), 35: Language('Macedonian'), 55: Language('Malay'), 40: Language('Chinese'),
+ 3: Language('Norwegian'), 26: Language('Polish'), 32: Language('Portuguese'), 13: Language('Romanian'),
+ 27: Language('Russian'), 36: Language('Serbian'), 47: Language('Serbian'), 56: Language('Sinhala'),
+ 37: Language('Slovak'), 1: Language('Slovenian'), 28: Language('Spanish'), 25: Language('Swedish'),
+ 44: Language('Thai'), 30: Language('Turkish'), 46: Language('Ukrainian'), Language('Vietnamese'): 51}
+
+ def list_checked(self, video, languages):
+ if isinstance(video, Movie):
+ return self.query(video.path or video.release, languages, video.title, year=video.year,
+ keywords=get_keywords(video.guess))
+ if isinstance(video, Episode):
+ return self.query(video.path or video.release, languages, video.series, season=video.season,
+ episode=video.episode, keywords=get_keywords(video.guess))
+
+ def query(self, filepath, languages, title, season=None, episode=None, year=None, keywords=None):
+ params = {'sXML': 1, 'sK': title, 'sJ': ','.join([str(self.get_code(l)) for l in languages])}
+ if season is not None:
+ params['sTS'] = season
+ if episode is not None:
+ params['sTE'] = episode
+ if year is not None:
+ params['sY'] = year
+ if keywords is not None:
+ params['sR'] = keywords
+ r = self.session.get(self.server_url + '/ppodnapisi/search', params=params)
+ if r.status_code != 200:
+ logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
+ return []
+ subtitles = []
+ soup = BeautifulSoup(r.content, self.required_features)
+ for sub in soup('subtitle'):
+ if 'n' in sub.flags:
+ logger.debug(u'Skipping hearing impaired')
+ continue
+ language = self.get_language(sub.languageId.text)
+ confidence = float(sub.rating.text) / 5.0
+ sub_keywords = set()
+ for release in sub.release.text.split():
+ sub_keywords |= get_keywords(guessit.guess_file_info(release + '.srt', 'autodetect'))
+ sub_path = get_subtitle_path(filepath, language, self.config.multi)
+ subtitle = ResultSubtitle(sub_path, language, self.__class__.__name__.lower(),
+ sub.url.text, confidence=confidence, keywords=sub_keywords)
+ subtitles.append(subtitle)
+ return subtitles
+
+ def download(self, subtitle):
+ r = self.session.get(subtitle.link)
+ if r.status_code != 200:
+ raise DownloadFailedError()
+ soup = BeautifulSoup(r.content)
+ self.download_zip_file(self.server_url + soup.find('a', href=re.compile('download'))['href'], subtitle.path)
+ return subtitle
+
+
+Service = PodnapisiWeb
diff --git a/lib/subliminal/services/subswiki.py b/lib/subliminal/services/subswiki.py
index add79a5f..9f9a3414 100644
--- a/lib/subliminal/services/subswiki.py
+++ b/lib/subliminal/services/subswiki.py
@@ -26,15 +26,16 @@ import logging
import urllib
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class SubsWiki(ServiceBase):
server_url = 'http://www.subswiki.com'
+ site_url = 'http://www.subswiki.com'
api_based = False
- languages = language_set(['eng-US', 'eng-GB', 'eng', 'fre', 'por-BR', 'por', 'spa-ES', u'spa', u'ita', u'cat'])
+ languages = language_set(['eng-US', 'eng-GB', 'eng', 'fre', 'pob', 'por', 'spa-ES', u'spa', u'ita', u'cat'])
language_map = {u'Español': Language('spa'), u'Español (España)': Language('spa'), u'Español (Latinoamérica)': Language('spa'),
- u'Català': Language('cat'), u'Brazilian': Language('por-BR'), u'English (US)': Language('eng-US'),
+ u'Català': Language('cat'), u'Brazilian': Language('pob'), u'English (US)': Language('eng-US'),
u'English (UK)': Language('eng-GB')}
language_code = 'name'
videos = [Episode, Movie]
@@ -77,7 +78,7 @@ class SubsWiki(ServiceBase):
subtitles = []
for sub in soup('td', {'class': 'NewsTitle'}):
sub_keywords = split_keyword(sub.b.string.lower())
- if not keywords & sub_keywords:
+ if keywords and not keywords & sub_keywords:
logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords))
continue
for html_language in sub.parent.parent.find_all('td', {'class': 'language'}):
diff --git a/lib/subliminal/services/subtitulos.py b/lib/subliminal/services/subtitulos.py
index ee225b8b..6dd085a3 100644
--- a/lib/subliminal/services/subtitulos.py
+++ b/lib/subliminal/services/subtitulos.py
@@ -27,15 +27,16 @@ import unicodedata
import urllib
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class Subtitulos(ServiceBase):
server_url = 'http://www.subtitulos.es'
+ site_url = 'http://www.subtitulos.es'
api_based = False
- languages = language_set(['eng-US', 'eng-GB', 'eng', 'fre', 'por-BR', 'por', 'spa-ES', u'spa', u'ita', u'cat'])
- language_map = {u'Español': Language('spa'), u'Español (España)': Language('spa'), u'Español (Latinoamérica)': Language('spa'),
- u'Català': Language('cat'), u'Brazilian': Language('por-BR'), u'English (US)': Language('eng-US'),
+ languages = language_set(['eng-US', 'eng-GB', 'eng', 'fre', 'pob', 'por', 'spa-ES', u'spa', u'ita', u'cat'])
+ language_map = {u'Español': Language('spa'), u'Español (España)': Language('spa'), #u'Español (Latinoamérica)': Language('spa'),
+ u'Català': Language('cat'), u'Brazilian': Language('pob'), u'English (US)': Language('eng-US'),
u'English (UK)': Language('eng-GB'), 'Galego': Language('glg')}
language_code = 'name'
videos = [Episode]
@@ -45,12 +46,13 @@ class Subtitulos(ServiceBase):
# and the 'ó' char directly. This is because now BS4 converts the html
# code chars into their equivalent unicode char
release_pattern = re.compile('Versi.+n (.+) ([0-9]+).([0-9])+ megabytes')
-
+ extra_keywords_pattern = re.compile("(?:con|para)\s(?:720p)?(?:\-|\s)?([A-Za-z]+)(?:\-|\s)?(?:720p)?(?:\s|\.)(?:y\s)?(?:720p)?(?:\-\s)?([A-Za-z]+)?(?:\-\s)?(?:720p)?(?:\.)?");
+
def list_checked(self, video, languages):
return self.query(video.path or video.release, languages, get_keywords(video.guess), video.series, video.season, video.episode)
def query(self, filepath, languages, keywords, series, season, episode):
- request_series = series.lower().replace(' ', '_')
+ request_series = series.lower().replace(' ', '-').replace('&', '@').replace('(','').replace(')','')
if isinstance(request_series, unicode):
request_series = unicodedata.normalize('NFKD', request_series).encode('ascii', 'ignore')
logger.debug(u'Getting subtitles for %s season %d episode %d with languages %r' % (series, season, episode, languages))
@@ -65,7 +67,7 @@ class Subtitulos(ServiceBase):
subtitles = []
for sub in soup('div', {'id': 'version'}):
sub_keywords = split_keyword(self.release_pattern.search(sub.find('p', {'class': 'title-sub'}).contents[1]).group(1).lower())
- if not keywords & sub_keywords:
+ if keywords and not keywords & sub_keywords:
logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords))
continue
for html_language in sub.findAllNext('ul', {'class': 'sslist'}):
diff --git a/lib/subliminal/services/thesubdb.py b/lib/subliminal/services/thesubdb.py
index 274c775f..93787ad6 100644
--- a/lib/subliminal/services/thesubdb.py
+++ b/lib/subliminal/services/thesubdb.py
@@ -16,22 +16,23 @@
# You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see .
from . import ServiceBase
-from ..language import language_set
+from ..language import language_set, Language
from ..subtitles import get_subtitle_path, ResultSubtitle
from ..videos import Episode, Movie, UnknownVideo
import logging
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
class TheSubDB(ServiceBase):
server_url = 'http://api.thesubdb.com'
+ site_url = 'http://www.thesubdb.com/'
user_agent = 'SubDB/1.0 (subliminal/0.6; https://github.com/Diaoul/subliminal)'
api_based = True
# Source: http://api.thesubdb.com/?action=languages
languages = language_set(['af', 'cs', 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'id', 'it',
- 'la', 'nl', 'no', 'oc', 'pl', 'pt', 'ro', 'ru', 'sl', 'sr', 'sv',
+ 'la', 'nl', 'no', 'oc', 'pl', 'pb', 'ro', 'ru', 'sl', 'sr', 'sv',
'tr'])
videos = [Movie, Episode, UnknownVideo]
require_video = True
@@ -48,6 +49,10 @@ class TheSubDB(ServiceBase):
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
return []
available_languages = language_set(r.content.split(','))
+ #this is needed becase for theSubDB pt languages is Portoguese Brazil and not Portoguese#
+ #So we are deleting pt language and adding pb language
+ if Language('pt') in available_languages:
+ available_languages = available_languages - language_set(['pt']) | language_set(['pb'])
languages &= available_languages
if not languages:
logger.debug(u'Could not find subtitles for hash %s with languages %r (only %r available)' % (moviehash, languages, available_languages))
diff --git a/lib/subliminal/services/tvsubtitles.py b/lib/subliminal/services/tvsubtitles.py
index a260f169..f6b2fd52 100644
--- a/lib/subliminal/services/tvsubtitles.py
+++ b/lib/subliminal/services/tvsubtitles.py
@@ -26,7 +26,7 @@ import logging
import re
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
def match(pattern, string):
@@ -39,13 +39,14 @@ def match(pattern, string):
class TvSubtitles(ServiceBase):
server_url = 'http://www.tvsubtitles.net'
+ site_url = 'http://www.tvsubtitles.net'
api_based = False
languages = language_set(['ar', 'bg', 'cs', 'da', 'de', 'el', 'en', 'es', 'fi', 'fr', 'hu',
'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ro', 'ru', 'sv', 'tr', 'uk',
- 'zh', 'pt-br'])
+ 'zh', 'pb'])
#TODO: Find more exceptions
language_map = {'gr': Language('gre'), 'cz': Language('cze'), 'ua': Language('ukr'),
- 'cn': Language('chi')}
+ 'cn': Language('chi'), 'br': Language('pob')}
videos = [Episode]
require_video = False
required_features = ['permissive']
diff --git a/lib/subliminal/services/usub.py b/lib/subliminal/services/usub.py
new file mode 100644
index 00000000..d7d07677
--- /dev/null
+++ b/lib/subliminal/services/usub.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+# Copyright 2013 Julien Goret
+#
+# This file is part of subliminal.
+#
+# subliminal is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# subliminal is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with subliminal. If not, see .
+from . import ServiceBase
+from ..exceptions import ServiceError
+from ..language import language_set, Language
+from ..subtitles import get_subtitle_path, ResultSubtitle
+from ..utils import get_keywords, split_keyword
+from ..videos import Episode
+from bs4 import BeautifulSoup
+import logging
+import urllib
+
+logger = logging.getLogger("subliminal")
+
+class Usub(ServiceBase):
+ server_url = 'http://www.u-sub.net/sous-titres'
+ site_url = 'http://www.u-sub.net/'
+ api_based = False
+ languages = language_set(['fr'])
+ videos = [Episode]
+ require_video = False
+ #required_features = ['permissive']
+
+ def list_checked(self, video, languages):
+ return self.query(video.path or video.release, languages, get_keywords(video.guess), series=video.series, season=video.season, episode=video.episode)
+
+ def query(self, filepath, languages, keywords=None, series=None, season=None, episode=None):
+
+ ## Check if we really got informations about our episode
+ if series and season and episode:
+ request_series = series.lower().replace(' ', '-')
+ if isinstance(request_series, unicode):
+ request_series = request_series.encode('utf-8')
+ logger.debug(u'Getting subtitles for %s season %d episode %d with language %r' % (series, season, episode, languages))
+ r = self.session.get('%s/%s/saison_%s' % (self.server_url, urllib.quote(request_series),season))
+ if r.status_code == 404:
+ print "Error 404"
+ logger.debug(u'Could not find subtitles for %s' % (series))
+ return []
+ else:
+ print "One or more parameter missing"
+ raise ServiceError('One or more parameter missing')
+
+ ## Check if we didn't got an big and nasty http error
+ if r.status_code != 200:
+ print u'Request %s returned status code %d' % (r.url, r.status_code)
+ logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
+ return []
+
+ ## Editing episode informations to be able to use it with our search
+ if episode < 10 :
+ episode_num='0'+str(episode)
+ else :
+ episode_num=str(episode)
+ season_num = str(season)
+ series_name = series.lower().replace(' ', '.')
+ possible_episode_naming = [season_num+'x'+episode_num,season_num+episode_num]
+
+
+ ## Actually parsing the page for the good subtitles
+ soup = BeautifulSoup(r.content, self.required_features)
+ subtitles = []
+ subtitles_list = soup.find('table', {'id' : 'subtitles_list'})
+ link_list = subtitles_list.findAll('a', {'class' : 'dl_link'})
+
+ for link in link_list :
+ link_url = link.get('href')
+ splited_link = link_url.split('/')
+ filename = splited_link[len(splited_link)-1]
+ for episode_naming in possible_episode_naming :
+ if episode_naming in filename :
+ for language in languages:
+ path = get_subtitle_path(filepath, language, self.config.multi)
+ subtitle = ResultSubtitle(path, language, self.__class__.__name__.lower(), '%s' % (link_url))
+ subtitles.append(subtitle)
+ return subtitles
+
+ def download(self, subtitle):
+ ## All downloaded files are zip files
+ self.download_zip_file(subtitle.link, subtitle.path)
+ return subtitle
+
+
+Service = Usub
diff --git a/lib/subliminal/videos.py b/lib/subliminal/videos.py
index 8bbb0a39..9d533b2b 100644
--- a/lib/subliminal/videos.py
+++ b/lib/subliminal/videos.py
@@ -26,10 +26,13 @@ import mimetypes
import os
import struct
+from sickbeard import encodingKludge as ek
+import sickbeard
+
__all__ = ['EXTENSIONS', 'MIMETYPES', 'Video', 'Episode', 'Movie', 'UnknownVideo',
'scan', 'hash_opensubtitles', 'hash_thesubdb']
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("subliminal")
#: Video extensions
EXTENSIONS = ['.avi', '.mkv', '.mpg', '.mp4', '.m4v', '.mov', '.ogm', '.ogv', '.wmv',
@@ -55,6 +58,10 @@ class Video(object):
self.imdbid = imdbid
self._path = None
self.hashes = {}
+
+ if isinstance(path, unicode):
+ path = path.encode('utf-8')
+
if os.path.exists(path):
self._path = path
self.size = os.path.getsize(self._path)
@@ -138,6 +145,10 @@ class Video(object):
if folder == '':
folder = '.'
existing = [f for f in os.listdir(folder) if f.startswith(basename)]
+ if sickbeard.SUBTITLES_DIR:
+ subsDir = ek.ek(os.path.join, folder, sickbeard.SUBTITLES_DIR)
+ if ek.ek(os.path.isdir, subsDir):
+ existing.extend([f for f in os.listdir(subsDir) if f.startswith(basename)])
for path in existing:
for ext in subtitles.EXTENSIONS:
if path.endswith(ext):
@@ -214,6 +225,9 @@ def scan(entry, max_depth=3, scan_filter=None, depth=0):
:rtype: list of (:class:`Video`, [:class:`~subliminal.subtitles.Subtitle`])
"""
+ if isinstance(entry, unicode):
+ entry = entry.encode('utf-8')
+
if depth > max_depth and max_depth != 0: # we do not want to search the whole file system except if max_depth = 0
return []
if os.path.isdir(entry): # a dir? recurse
diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py
index 3ba3d4f5..23f4ad2e 100644
--- a/sickbeard/databases/mainDB.py
+++ b/sickbeard/databases/mainDB.py
@@ -27,7 +27,7 @@ from sickbeard import encodingKludge as ek
from sickbeard.name_parser.parser import NameParser, InvalidNameException
MIN_DB_VERSION = 9 # oldest db version we support migrating from
-MAX_DB_VERSION = 28
+MAX_DB_VERSION = 29
class MainSanityCheck(db.DBSanityCheck):
@@ -538,10 +538,14 @@ class AddProperSearch(AddUpdateTVDB):
class AddDvdOrderOption(AddProperSearch):
def test(self):
- return self.hasColumn("tv_shows", "dvdorder")
+ return self.checkDBVersion() >= 20
def execute(self):
- self.connection.action("ALTER TABLE tv_shows ADD COLUMN dvdorder NUMERIC DEFAULT 0")
+ backupDatabase(20)
+
+ logger.log(u"Adding column dvdorder to tvshows")
+ if not self.hasColumn("tv_shows", "dvdorder"):
+ self.addColumn("tv_shows", "dvdorder", "NUMERIC", "0")
self.incDBVersion()
@@ -552,6 +556,10 @@ class ConvertTVShowsToIndexerScheme(AddDvdOrderOption):
def execute(self):
backupDatabase(22)
+ logger.log(u"Adding column dvdorder to tvshows")
+ if not self.hasColumn("tv_shows", "dvdorder"):
+ self.addColumn("tv_shows", "dvdorder", "NUMERIC", "0")
+
logger.log(u"Converting TV Shows table to Indexer Scheme...")
if self.hasTable("tmp_tv_shows"):
@@ -657,7 +665,9 @@ class AddArchiveFirstMatchOption(ConvertInfoToIndexerScheme):
def execute(self):
backupDatabase(26)
- self.connection.action("ALTER TABLE tv_shows ADD COLUMN archive_firstmatch NUMERIC DEFAULT 0")
+ logger.log(u"Adding column archive_firstmatch to tvshows")
+ if not self.hasColumn("tv_shows", "archive_firstmatch"):
+ self.addColumn("tv_shows", "archive_firstmatch", "NUMERIC", "0")
self.incDBVersion()
@@ -696,4 +706,23 @@ class ConvertIndexerToInteger(AddSceneNumbering):
self.connection.mass_action(ql)
- self.incDBVersion()
\ No newline at end of file
+ self.incDBVersion()
+
+class AddRequireAndIgnoreWords(ConvertIndexerToInteger):
+ """ Adding column rls_require_words and rls_ignore_words to tv_shows """
+
+ def test(self):
+ return self.checkDBVersion() >= 29
+
+ def execute(self):
+ backupDatabase(29)
+
+ logger.log(u"Adding column rls_require_words to tvshows")
+ if not self.hasColumn("tv_shows", "rls_require_words"):
+ self.addColumn("tv_shows", "rls_require_words", "TEXT", "")
+
+ logger.log(u"Adding column rls_ignore_words to tvshows")
+ if not self.hasColumn("tv_shows", "rls_ignore_words"):
+ self.addColumn("tv_shows", "rls_ignore_words", "TEXT", "")
+
+ self.incDBVersion()
diff --git a/sickbeard/properFinder.py b/sickbeard/properFinder.py
index 26a112ba..a7105722 100644
--- a/sickbeard/properFinder.py
+++ b/sickbeard/properFinder.py
@@ -161,13 +161,23 @@ class ProperFinder():
logger.DEBUG)
continue
+ showObj = helpers.findCertainShow(sickbeard.showList, curProper.indexerid)
+ if not showObj:
+ logger.log(u"Unable to find the show with indexerID " + str(curProper.indexerid), logger.ERROR)
+ continue
+
+ if showObj.rls_ignore_words and search.filter_release_name(curProper.name, showObj.rls_ignore_words):
+ logger.log(u"Ignoring " + curProper.name + " based on ignored words filter: " + showObj.rls_ignore_words,
+ logger.MESSAGE)
+ continue
+
+ if showObj.rls_require_words and not search.filter_release_name(curProper.name, showObj.rls_require_words):
+ logger.log(u"Ignoring " + curProper.name + " based on required words filter: " + showObj.rls_require_words,
+ logger.MESSAGE)
+ continue
+
# if we have an air-by-date show then get the real season/episode numbers
if curProper.season == -1 and curProper.indexerid:
- showObj = helpers.findCertainShow(sickbeard.showList, curProper.indexerid)
- if not showObj:
- logger.log(u"This should never have happened, post a bug about this!", logger.ERROR)
- raise Exception("BAD STUFF HAPPENED")
-
indexer_lang = showObj.lang
lINDEXER_API_PARMS = sickbeard.indexerApi(showObj.indexer).api_params.copy()
if indexer_lang and not indexer_lang == 'en':
diff --git a/sickbeard/search.py b/sickbeard/search.py
index 0fd473f9..5b1ea86f 100644
--- a/sickbeard/search.py
+++ b/sickbeard/search.py
@@ -19,6 +19,7 @@
from __future__ import with_statement
import os
+import re
import traceback
import datetime
@@ -212,7 +213,7 @@ def searchForNeededEpisodes():
if not bestResult or bestResult.quality < curResult.quality:
bestResult = curResult
- bestResult = pickBestResult(curFoundResults[curEp])
+ bestResult = pickBestResult(curFoundResults[curEp], curEp.show)
# if all results were rejected move on to the next episode
if not bestResult:
@@ -231,8 +232,27 @@ def searchForNeededEpisodes():
return foundResults.values()
+def filter_release_name(name, filter_words):
+ """
+ Filters out results based on filter_words
-def pickBestResult(results, quality_list=None):
+ name: name to check
+ filter_words : Words to filter on, separated by comma
+
+ Returns: False if the release name is OK, True if it contains one of the filter_words
+ """
+ if filter_words:
+ for test_word in filter_words.split(','):
+ test_word = test_word.strip()
+
+ if test_word:
+ if re.search('(^|[\W_]|[\s_])' + test_word + '($|[\W_]|[\s_])', name, re.I):
+ logger.log(u"" + name + " contains word: " + test_word, logger.DEBUG)
+ return True
+
+ return False
+
+def pickBestResult(results, show, quality_list=None):
logger.log(u"Picking the best result out of " + str([x.name for x in results]), logger.DEBUG)
# find the best result for the current episode
@@ -244,6 +264,16 @@ def pickBestResult(results, quality_list=None):
logger.log(cur_result.name + " is a quality we know we don't want, rejecting it", logger.DEBUG)
continue
+ if show.rls_ignore_words and filter_release_name(cur_result.name, show.rls_ignore_words):
+ logger.log(u"Ignoring " + cur_result.name + " based on ignored words filter: " + show.rls_ignore_words,
+ logger.MESSAGE)
+ continue
+
+ if show.rls_require_words and not filter_release_name(cur_result.name, show.rls_require_words):
+ logger.log(u"Ignoring " + cur_result.name + " based on required words filter: " + show.rls_require_words,
+ logger.MESSAGE)
+ continue
+
if sickbeard.USE_FAILED_DOWNLOADS and failed_history.hasFailed(cur_result.name, cur_result.size,
cur_result.provider.name):
logger.log(cur_result.name + u" has previously failed, rejecting it")
@@ -371,7 +401,7 @@ def findEpisode(episode, manualSearch=False):
logger.log(u"No NZB/Torrent providers found or enabled in the sickbeard config. Please check your settings.",
logger.ERROR)
- bestResult = pickBestResult(foundResults)
+ bestResult = pickBestResult(foundResults, episode.show)
return bestResult
@@ -426,7 +456,7 @@ def findSeason(show, season):
# pick the best season NZB
bestSeasonNZB = None
if SEASON_RESULT in foundResults:
- bestSeasonNZB = pickBestResult(foundResults[SEASON_RESULT], anyQualities + bestQualities)
+ bestSeasonNZB = pickBestResult(foundResults[SEASON_RESULT], show, anyQualities + bestQualities)
highest_quality_overall = 0
for cur_season in foundResults:
@@ -595,6 +625,6 @@ def findSeason(show, season):
if len(foundResults[curEp]) == 0:
continue
- finalResults.append(pickBestResult(foundResults[curEp]))
+ finalResults.append(pickBestResult(foundResults[curEp], show))
return finalResults
diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py
index 8fe8272f..4cd49f24 100644
--- a/sickbeard/show_name_helpers.py
+++ b/sickbeard/show_name_helpers.py
@@ -69,9 +69,10 @@ def filterBadReleases(name):
# return True
# if any of the bad strings are in the name then say no
- for x in resultFilters + sickbeard.IGNORE_WORDS.split(','):
- if re.search('(^|[\W_]|[\s_])' + x.strip() + '($|[\W_]|[\s_])', name, re.I):
- logger.log(u"Invalid scene release: " + name + " contains " + x + ", ignoring it", logger.DEBUG)
+ for ignore_word in resultFilters + sickbeard.IGNORE_WORDS.split(','):
+ ignore_word = ignore_word.strip()
+ if re.search('(^|[\W_]|[\s_])' + ignore_word + '($|[\W_]|[\s_])', name, re.I):
+ logger.log(u"Invalid scene release: " + name + " contains " + ignore_word + ", ignoring it", logger.DEBUG)
return False
return True
diff --git a/sickbeard/subtitles.py b/sickbeard/subtitles.py
index 510f6897..fcdd3d4c 100644
--- a/sickbeard/subtitles.py
+++ b/sickbeard/subtitles.py
@@ -28,10 +28,8 @@ from sickbeard import history
from lib import subliminal
SINGLE = 'und'
-
-
def sortedServiceList():
- servicesMapping = dict([(x.lower(), x) for x in subliminal.Subtitle.core.Providers])
+ servicesMapping = dict([(x.lower(), x) for x in subliminal.core.SERVICES])
newList = []
@@ -39,50 +37,33 @@ def sortedServiceList():
curIndex = 0
for curService in sickbeard.SUBTITLES_SERVICES_LIST:
if curService in servicesMapping:
- curServiceDict = {'id': curService, 'image': curService + '.png', 'name': servicesMapping[curService],
- 'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[curIndex] == 1,
- 'api_based': __import__('lib.subliminal.services.' + curService, globals=globals(),
- locals=locals(), fromlist=['Service'],
- level=-1).Service.api_based,
- 'url': __import__('lib.subliminal.services.' + curService, globals=globals(),
- locals=locals(), fromlist=['Service'], level=-1).Service.site_url}
+ curServiceDict = {'id': curService, 'image': curService+'.png', 'name': servicesMapping[curService], 'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[curIndex] == 1, 'api_based': __import__('lib.subliminal.services.' + curService, globals=globals(), locals=locals(), fromlist=['Service'], level=-1).Service.api_based, 'url': __import__('lib.subliminal.services.' + curService, globals=globals(), locals=locals(), fromlist=['Service'], level=-1).Service.site_url}
newList.append(curServiceDict)
curIndex += 1
# add any services that are missing from that list
for curService in servicesMapping.keys():
if curService not in [x['id'] for x in newList]:
- curServiceDict = {'id': curService, 'image': curService + '.png', 'name': servicesMapping[curService],
- 'enabled': False,
- 'api_based': __import__('lib.subliminal.services.' + curService, globals=globals(),
- locals=locals(), fromlist=['Service'],
- level=-1).Service.api_based,
- 'url': __import__('lib.subliminal.services.' + curService, globals=globals(),
- locals=locals(), fromlist=['Service'], level=-1).Service.site_url}
+ curServiceDict = {'id': curService, 'image': curService+'.png', 'name': servicesMapping[curService], 'enabled': False, 'api_based': __import__('lib.subliminal.services.' + curService, globals=globals(), locals=locals(), fromlist=['Service'], level=-1).Service.api_based, 'url': __import__('lib.subliminal.services.' + curService, globals=globals(), locals=locals(), fromlist=['Service'], level=-1).Service.site_url}
newList.append(curServiceDict)
return newList
-
-
+
def getEnabledServiceList():
return [x['name'] for x in sortedServiceList() if x['enabled']]
-
-
+
def isValidLanguage(language):
return subliminal.language.language_list(language)
-
def getLanguageName(selectLang):
return subliminal.language.Language(selectLang).name
-
-def wantedLanguages(sqlLike=False):
+def wantedLanguages(sqlLike = False):
wantedLanguages = sorted(sickbeard.SUBTITLES_LANGUAGES)
if sqlLike:
return '%' + ','.join(wantedLanguages) + '%'
return wantedLanguages
-
def subtitlesLanguages(video_path):
"""Return a list detected subtitles for the given video file"""
video = subliminal.videos.Video.from_path(video_path)
@@ -95,27 +76,22 @@ def subtitlesLanguages(video_path):
languages.add(SINGLE)
return list(languages)
-
# Return a list with languages that have alpha2 code
def subtitleLanguageFilter():
return [language for language in subliminal.language.LANGUAGES if language[2] != ""]
-
class SubtitlesFinder():
"""
The SubtitlesFinder will be executed every hour but will not necessarly search
and download subtitles. Only if the defined rule is true
"""
-
def run(self):
# TODO: Put that in the __init__ before starting the thread?
if not sickbeard.USE_SUBTITLES:
logger.log(u'Subtitles support disabled', logger.DEBUG)
return
if len(sickbeard.subtitles.getEnabledServiceList()) < 1:
- logger.log(
- u'Not enough services selected. At least 1 service is required to search subtitles in the background',
- logger.ERROR)
+ logger.log(u'Not enough services selected. At least 1 service is required to search subtitles in the background', logger.ERROR)
return
logger.log(u'Checking for subtitles', logger.MESSAGE)
@@ -126,51 +102,40 @@ class SubtitlesFinder():
# - episode subtitles != config wanted languages or SINGLE (depends on config multi)
# - search count < 2 and diff(airdate, now) > 1 week : now -> 1d
# - search count < 7 and diff(airdate, now) <= 1 week : now -> 4h -> 8h -> 16h -> 1d -> 1d -> 1d
-
+
myDB = db.DBConnection()
today = datetime.date.today().toordinal()
# you have 5 minutes to understand that one. Good luck
- sqlResults = myDB.select(
- 'SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) AS airdate_daydiff FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?) AND ((e.subtitles_searchcount <= 2 AND (? - e.airdate) > 7) OR (e.subtitles_searchcount <= 7 AND (? - e.airdate) <= 7)) AND (e.status IN (' + ','.join(
- [str(x) for x in Quality.DOWNLOADED]) + ') OR (e.status IN (' + ','.join(
- [str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER]) + ') AND e.location != ""))',
- [today, wantedLanguages(True), today, today])
+ sqlResults = myDB.select('SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) AS airdate_daydiff FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?) AND ((e.subtitles_searchcount <= 2 AND (? - e.airdate) > 7) OR (e.subtitles_searchcount <= 7 AND (? - e.airdate) <= 7)) AND (e.status IN ('+','.join([str(x) for x in Quality.DOWNLOADED])+') OR (e.status IN ('+','.join([str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER])+') AND e.location != ""))', [today, wantedLanguages(True), today, today])
if len(sqlResults) == 0:
logger.log('No subtitles to download', logger.MESSAGE)
return
-
+
rules = self._getRules()
now = datetime.datetime.now()
for epToSub in sqlResults:
if not ek.ek(os.path.isfile, epToSub['location']):
- logger.log('Episode file does not exist, cannot download subtitles for episode %dx%d of show %s' % (
- epToSub['season'], epToSub['episode'], epToSub['show_name']), logger.DEBUG)
+ logger.log('Episode file does not exist, cannot download subtitles for episode %dx%d of show %s' % (epToSub['season'], epToSub['episode'], epToSub['show_name']), logger.DEBUG)
continue
-
+
# Old shows rule
- if ((epToSub['airdate_daydiff'] > 7 and epToSub['searchcount'] < 2 and now - datetime.datetime.strptime(
- epToSub['lastsearch'], '%Y-%m-%d %H:%M:%S') > datetime.timedelta(
- hours=rules['old'][epToSub['searchcount']])) or
+ if ((epToSub['airdate_daydiff'] > 7 and epToSub['searchcount'] < 2 and now - datetime.datetime.strptime(epToSub['lastsearch'], '%Y-%m-%d %H:%M:%S') > datetime.timedelta(hours=rules['old'][epToSub['searchcount']])) or
# Recent shows rule
- (epToSub['airdate_daydiff'] <= 7 and epToSub[
- 'searchcount'] < 7 and now - datetime.datetime.strptime(epToSub['lastsearch'],
- '%Y-%m-%d %H:%M:%S') > datetime.timedelta(
- hours=rules['new'][epToSub['searchcount']]))):
- logger.log('Downloading subtitles for episode %dx%d of show %s' % (
- epToSub['season'], epToSub['episode'], epToSub['show_name']), logger.DEBUG)
-
+ (epToSub['airdate_daydiff'] <= 7 and epToSub['searchcount'] < 7 and now - datetime.datetime.strptime(epToSub['lastsearch'], '%Y-%m-%d %H:%M:%S') > datetime.timedelta(hours=rules['new'][epToSub['searchcount']]))):
+ logger.log('Downloading subtitles for episode %dx%d of show %s' % (epToSub['season'], epToSub['episode'], epToSub['show_name']), logger.DEBUG)
+
showObj = helpers.findCertainShow(sickbeard.showList, int(epToSub['showid']))
if not showObj:
logger.log(u'Show not found', logger.DEBUG)
return
-
+
epObj = showObj.getEpisode(int(epToSub["season"]), int(epToSub["episode"]))
if isinstance(epObj, str):
logger.log(u'Episode not found', logger.DEBUG)
return
-
+
previous_subtitles = epObj.subtitles
-
+
try:
subtitles = epObj.downloadSubtitles()
diff --git a/sickbeard/tv.py b/sickbeard/tv.py
index 3ea8b5aa..19d2e698 100644
--- a/sickbeard/tv.py
+++ b/sickbeard/tv.py
@@ -80,6 +80,9 @@ class TVShow(object):
self.lang = lang
self.last_update_indexer = 1
+ self.rls_ignore_words = ""
+ self.rls_require_words = ""
+
self.lock = threading.Lock()
self._isDirGood = False
@@ -710,10 +713,13 @@ class TVShow(object):
self.last_update_indexer = sqlResults[0]["last_update_indexer"]
+ self.rls_ignore_words = sqlResults[0]["rls_ignore_words"]
+ self.rls_require_words = sqlResults[0]["rls_require_words"]
+
if not self.imdbid:
self.imdbid = sqlResults[0]["imdb_id"]
- #Get IMDb_info from database
+ #Get IMDb_info from database
sqlResults = myDB.select("SELECT * FROM imdb_info WHERE indexer_id = ?", [self.indexerid])
if len(sqlResults) == 0:
@@ -976,7 +982,9 @@ class TVShow(object):
"startyear": self.startyear,
"lang": self.lang,
"imdb_id": self.imdbid,
- "last_update_indexer": self.last_update_indexer
+ "last_update_indexer": self.last_update_indexer,
+ "rls_ignore_words": self.rls_ignore_words,
+ "rls_require_words": self.rls_require_words
}
myDB.upsert("tv_shows", newValueDict, controlValueDict)
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index c1ba96f9..5cdd5468 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -2850,7 +2850,7 @@ class Home:
@cherrypy.expose
def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[],
flatten_folders=None, paused=None, directCall=False, air_by_date=None, dvdorder=None, indexerLang=None,
- subtitles=None, archive_firstmatch=None):
+ subtitles=None, archive_firstmatch=None, rls_ignore_words=None, rls_require_words=None):
if show is None:
errString = "Invalid show ID: " + str(show)
@@ -2935,6 +2935,9 @@ class Home:
showObj.dvdorder = dvdorder
showObj.archive_firstmatch = archive_firstmatch
+ showObj.rls_ignore_words = rls_ignore_words
+ showObj.rls_require_words = rls_require_words
+
# if we change location clear the db of episodes, change it, write to db, and rescan
if os.path.normpath(showObj._location) != os.path.normpath(location):
logger.log(os.path.normpath(showObj._location) + " != " + os.path.normpath(location), logger.DEBUG)