Browse Source

Change add core dedicated base class tvinfo_base to unify future info sources.

Change improve scene_exceptions interfacing to indexer API.
Change start implementing new interface.
Add tvdb api lib indexerapiinterface.
Change rebase traktinterface on tvinfo_base.
Switch back exceptions handling for indexers.
Change optimize loading of episodes during update to prevent slow loading with new tvinfo_base.
Change add scene_numbering loading.
Change search show webserve.
Change tv.py to new interface.
Change to more complex locking.
Change switch network_timezones from os.walk to scandir.
Change persons data.
Change add actor parsing to new castlist property.
pull/1289/head
Prinz23 5 years ago
committed by JackDandy
parent
commit
db2e0de282
  1. 1
      CHANGES.md
  2. 72
      lib/libtrakt/indexerapiinterface.py
  3. 402
      lib/tvdb_api/tvdb_api.py
  4. 18
      lib/tvdb_api/tvdb_exceptions.py
  5. 1
      lib/tvinfo_base/__init__.py
  6. 750
      lib/tvinfo_base/base.py
  7. 52
      lib/tvinfo_base/exceptions.py
  8. 2
      sickbeard/__init__.py
  9. 9
      sickbeard/helpers.py
  10. 61
      sickbeard/indexers/indexer_api.py
  11. 77
      sickbeard/indexers/indexer_exceptions.py
  12. 2
      sickbeard/logger.py
  13. 50
      sickbeard/metadata/generic.py
  14. 39
      sickbeard/metadata/kodi.py
  15. 44
      sickbeard/metadata/mede8er.py
  16. 37
      sickbeard/metadata/mediabrowser.py
  17. 17
      sickbeard/metadata/tivo.py
  18. 17
      sickbeard/metadata/wdtv.py
  19. 53
      sickbeard/metadata/xbmc_12plus.py
  20. 21
      sickbeard/name_parser/parser.py
  21. 19
      sickbeard/network_timezones.py
  22. 137
      sickbeard/scene_numbering.py
  23. 77
      sickbeard/show_queue.py
  24. 162
      sickbeard/tv.py
  25. 29
      sickbeard/webapi.py
  26. 12
      sickbeard/webserve.py

1
CHANGES.md

@ -19,6 +19,7 @@
* Add Telegram notifier
* Change enable image caching on browse pages
* Change update sceneNameCache after scene names are updated
* Change add core dedicated base class tvinfo_base to unify future info sources
[develop changelog]

72
lib/libtrakt/indexerapiinterface.py

@ -1,41 +1,21 @@
import logging
import re
import time
from .exceptions import TraktShowNotFound, TraktException
from .exceptions import TraktException
from exceptions_helper import ex
from six import iteritems
from .trakt import TraktAPI
from tvinfo_base.exceptions import BaseTVinfoShownotfound
from tvinfo_base import TVInfoBase
# noinspection PyUnreachableCode
if False:
from typing import Any, AnyStr, List, Optional
from tvinfo_base import TVInfoShow
log = logging.getLogger('trakt_api')
log.addHandler(logging.NullHandler())
class ShowContainer(dict):
"""Simple dict that holds a series of Show instances
"""
def __init__(self, **kwargs):
super(ShowContainer, self).__init__(**kwargs)
self._stack = []
self._lastgc = time.time()
def __setitem__(self, key, value):
self._stack.append(key)
# keep only the 100th latest results
if time.time() - self._lastgc > 20:
for o in self._stack[:-100]:
del self[o]
self._stack = self._stack[-100:]
self._lastgc = time.time()
super(ShowContainer, self).__setitem__(key, value)
class TraktSearchTypes(object):
text = 1
trakt_id = 'trakt'
@ -61,13 +41,13 @@ class TraktResultTypes(object):
pass
class TraktIndexer(object):
class TraktIndexer(TVInfoBase):
# noinspection PyUnusedLocal
# noinspection PyDefaultArgument
def __init__(self, custom_ui=None, sleep_retry=None, search_type=TraktSearchTypes.text,
result_types=[TraktResultTypes.show], *args, **kwargs):
self.config = {
super(TraktIndexer, self).__init__(*args, **kwargs)
self.config.update({
'apikey': '',
'debug_enabled': False,
'custom_ui': custom_ui,
@ -82,23 +62,21 @@ class TraktIndexer(object):
'sleep_retry': sleep_retry,
'result_types': result_types if isinstance(result_types, list) and all(
[x in TraktResultTypes.all for x in result_types]) else [TraktResultTypes.show],
}
})
self.corrections = {}
self.shows = ShowContainer()
def _get_series(self, series):
def _search_show(self, name, **kwargs):
# type: (AnyStr, Optional[Any]) -> List[TVInfoShow]
"""This searches Trakt for the series name,
If a custom_ui UI is configured, it uses this to select the correct
series.
"""
all_series = self.search(series)
all_series = self.search(name)
if not isinstance(all_series, list):
all_series = [all_series]
if 0 == len(all_series):
log.debug('Series result returned zero')
raise TraktShowNotFound('Show-name search returned zero results (cannot find show on TVDB)')
raise BaseTVinfoShownotfound('Show-name search returned zero results (cannot find show on TVDB)')
if None is not self.config['custom_ui']:
log.debug('Using custom UI %s' % (repr(self.config['custom_ui'])))
@ -109,23 +87,6 @@ class TraktIndexer(object):
return all_series
def __getitem__(self, key):
"""Handles trakt_instance['seriesname'] calls.
The dict index should be the show id
"""
if isinstance(key, tuple) and 2 == len(key):
key = key[0]
self.config['searchterm'] = key
selected_series = self._get_series(key)
if isinstance(selected_series, dict):
selected_series = [selected_series]
return selected_series
def __repr__(self):
return str(self.shows)
@staticmethod
def _dict_prevent_none(d, key, default):
v = None
@ -134,6 +95,7 @@ class TraktIndexer(object):
return (v, default)[None is v]
def search(self, series):
# type: (AnyStr) -> List
if TraktSearchTypes.text != self.config['search_type']:
url = '/search/%s/%s?type=%s&extended=full&limit=100' % (self.config['search_type'], series,
','.join(self.config['result_types']))

402
lib/tvdb_api/tvdb_api.py

@ -22,23 +22,23 @@ import requests.exceptions
import datetime
import re
from six import integer_types, string_types, text_type, iteritems, PY2
from _23 import list_values
from six import integer_types, string_types, iteritems, PY2
from _23 import list_values, map_list
from sg_helpers import clean_data, try_int, get_url
from collections import OrderedDict
from tvinfo_base import TVInfoBase, CastList, Character, CrewList, Person, RoleTypes
from lib.dateutil.parser import parse
from lib.cachecontrol import CacheControl, caches
from .tvdb_ui import BaseUI, ConsoleUI
from .tvdb_exceptions import (
TvdbError, TvdbShownotfound, TvdbSeasonnotfound, TvdbEpisodenotfound,
TvdbAttributenotfound, TvdbTokenexpired)
from .tvdb_exceptions import TvdbError, TvdbShownotfound, TvdbTokenexpired
# noinspection PyUnreachableCode
if False:
# noinspection PyUnresolvedReferences
from typing import AnyStr, Dict, Optional
from typing import Any, AnyStr, Dict, List, Optional
from tvinfo_base import TVInfoShow
THETVDB_V2_API_TOKEN = {'token': None, 'datetime': datetime.datetime.fromordinal(1)}
@ -103,241 +103,6 @@ def retry(exception_to_check, tries=4, delay=3, backoff=2):
return deco_retry
class ShowContainer(dict):
"""Simple dict that holds a series of Show instances
"""
def __init__(self, **kwargs):
super(ShowContainer, self).__init__(**kwargs)
self._stack = []
self._lastgc = time.time()
def __setitem__(self, key, value):
self._stack.append(key)
# keep only the 100th latest results
if time.time() - self._lastgc > 20:
for o in self._stack[:-100]:
del self[o]
self._stack = self._stack[-100:]
self._lastgc = time.time()
super(ShowContainer, self).__setitem__(key, value)
class Show(dict):
"""Holds a dict of seasons, and show data.
"""
def __init__(self):
dict.__init__(self)
self.data = {}
self.ep_loaded = False
def __repr__(self):
return '<Show %r (containing %s seasons)>' % (self.data.get(u'seriesname', 'instance'), len(self))
def __getattr__(self, key):
if key in self:
# Key is an episode, return it
return self[key]
if key in self.data:
# Non-numeric request is for show-data
return self.data[key]
raise AttributeError
def __getitem__(self, key):
if key in self:
# Key is an episode, return it
return dict.__getitem__(self, key)
if key in self.data:
# Non-numeric request is for show-data
return dict.__getitem__(self.data, key)
# Data wasn't found, raise appropriate error
if isinstance(key, integer_types) or isinstance(key, string_types) and key.isdigit():
# Episode number x was not found
raise TvdbSeasonnotfound('Could not find season %s' % (repr(key)))
else:
# If it's not numeric, it must be an attribute name, which
# doesn't exist, so attribute error.
raise TvdbAttributenotfound('Cannot find attribute %s' % (repr(key)))
def __nonzero__(self):
return any(self.data.keys())
def aired_on(self, date):
ret = self.search(str(date), 'firstaired')
if 0 == len(ret):
raise TvdbEpisodenotfound('Could not find any episodes that aired on %s' % date)
return ret
def search(self, term=None, key=None):
"""
Search all episodes in show. Can search all data, or a specific key (for
example, episodename)
Always returns an array (can be empty). First index contains the first
match, and so on.
Each array index is an Episode() instance, so doing
search_results[0]['episodename'] will retrieve the episode name of the
first match.
Search terms are converted to lower case (unicode) strings.
# Examples
These examples assume t is an instance of Tvdb():
>> t = Tvdb()
>>
To search for all episodes of Scrubs with a bit of data
containing "my first day":
>> t['Scrubs'].search("my first day")
[<Episode 01x01 - My First Day>]
>>
Search for "My Name Is Earl" episode named "Faked His Own Death":
>> t['My Name Is Earl'].search('Faked His Own Death', key = 'episodename')
[<Episode 01x04 - Faked His Own Death>]
>>
To search Scrubs for all episodes with "mentor" in the episode name:
>> t['scrubs'].search('mentor', key = 'episodename')
[<Episode 01x02 - My Mentor>, <Episode 03x15 - My Tormented Mentor>]
>>
# Using search results
>> results = t['Scrubs'].search("my first")
>> print results[0]['episodename']
My First Day
>> for x in results: print x['episodename']
My First Day
My First Step
My First Kill
>>
"""
results = []
for cur_season in list_values(self):
searchresult = cur_season.search(term=term, key=key)
if 0 != len(searchresult):
results.extend(searchresult)
return results
class Season(dict):
def __init__(self, show=None, **kwargs):
"""The show attribute points to the parent show
"""
super(Season, self).__init__(**kwargs)
self.show = show
def __repr__(self):
return '<Season instance (containing %s episodes)>' % (len(self.keys()))
def __getattr__(self, episode_number):
if episode_number in self:
return self[episode_number]
raise AttributeError
def __getitem__(self, episode_number):
if episode_number not in self:
raise TvdbEpisodenotfound('Could not find episode %s' % (repr(episode_number)))
else:
return dict.__getitem__(self, episode_number)
def search(self, term=None, key=None):
"""Search all episodes in season, returns a list of matching Episode
instances.
>> t = Tvdb()
>> t['scrubs'][1].search('first day')
[<Episode 01x01 - My First Day>]
>>
See Show.search documentation for further information on search
"""
results = []
for ep in list_values(self):
searchresult = ep.search(term=term, key=key)
if None is not searchresult:
results.append(searchresult)
return results
class Episode(dict):
def __init__(self, season=None, **kwargs):
"""The season attribute points to the parent season
"""
super(Episode, self).__init__(**kwargs)
self.season = season
def __repr__(self):
seasno, epno = int(self.get(u'seasonnumber', 0)), int(self.get(u'episodenumber', 0))
epname = self.get(u'episodename')
if None is not epname:
return '<Episode %02dx%02d - %r>' % (seasno, epno, epname)
else:
return '<Episode %02dx%02d>' % (seasno, epno)
def __getattr__(self, key):
if key in self:
return self[key]
raise AttributeError
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
raise TvdbAttributenotfound('Cannot find attribute %s' % (repr(key)))
def search(self, term=None, key=None):
"""Search episode data for term, if it matches, return the Episode (self).
The key parameter can be used to limit the search to a specific element,
for example, episodename.
This primarily for use use by Show.search and Season.search. See
Show.search for further information on search
Simple example:
>> e = Episode()
>> e['episodename'] = "An Example"
>> e.search("examp")
<Episode 00x00 - An Example>
>>
Limiting by key:
>> e.search("examp", key = "episodename")
<Episode 00x00 - An Example>
>>
"""
if None is term:
raise TypeError('must supply string to search for (contents)')
term = text_type(term).lower()
for cur_key, cur_value in iteritems(self):
cur_key, cur_value = text_type(cur_key).lower(), text_type(cur_value).lower()
if None is not key and cur_key != key:
# Do not search this key
continue
if cur_value.find(text_type(term).lower()) > -1:
return self
class Actors(list):
"""Holds all Actor instances for a show
"""
@ -358,7 +123,7 @@ class Actor(dict):
return '<Actor "%r">' % self.get('name')
class Tvdb(object):
class Tvdb(TVInfoBase):
"""Create easy-to-use interface to name of season/episode name
>> t = Tvdb()
>> t['Scrubs'][1][24]['episodename']
@ -448,11 +213,7 @@ class Tvdb(object):
"""
self.shows = ShowContainer() # Holds all Show classes
self.corrections = {} # Holds show-name to show_id mapping
self.show_not_found = False
self.not_found = False
super(Tvdb, self).__init__(*args, **kwargs)
self.config = {}
if None is not apikey:
@ -539,6 +300,14 @@ class Tvdb(object):
self.config['url_series_images'] = '%(base_url)sseries/%%s/images/query?keyType=%%s' % self.config
self.config['url_artworks'] = 'https://artworks.thetvdb.com/banners/%s'
def _search_show(self, name, **kwargs):
# type: (AnyStr, Optional[Any]) -> List[TVInfoShow]
def map_data(data):
data['poster'] = data.get('image')
return data
return map_list(map_data, self.get_series(name))
def get_new_token(self):
global THETVDB_V2_API_TOKEN
token = THETVDB_V2_API_TOKEN.get('token', None)
@ -737,40 +506,8 @@ class Tvdb(object):
except (KeyError, IndexError, Exception):
pass
def _set_item(self, sid, seas, ep, attrib, value):
"""Creates a new episode, creating Show(), Season() and
Episode()s as required. Called by _get_show_data to populate show
Since the nice-to-use tvdb[1][24]['name] interface
makes it impossible to do tvdb[1][24]['name] = "name"
and still be capable of checking if an episode exists
so we can raise tvdb_shownotfound, we have a slightly
less pretty method of setting items.. but since the API
is supposed to be read-only, this is the best way to
do it!
The problem is that calling tvdb[1][24]['episodename'] = "name"
calls __getitem__ on tvdb[1], there is no way to check if
tvdb.__dict__ should have a key "1" before we auto-create it
"""
if sid not in self.shows:
self.shows[sid] = Show()
if seas not in self.shows[sid]:
self.shows[sid][seas] = Season(show=self.shows[sid])
if ep not in self.shows[sid][seas]:
self.shows[sid][seas][ep] = Episode(season=self.shows[sid][seas])
self.shows[sid][seas][ep][attrib] = value
def _set_show_data(self, sid, key, value, add=False):
"""Sets self.shows[sid] to a new Show instance, or sets the data
"""
if sid not in self.shows:
self.shows[sid] = Show()
if add and isinstance(self.shows[sid].data, dict) and key in self.shows[sid].data:
self.shows[sid].data[key].update(value)
else:
self.shows[sid].data[key] = value
def search(self, series):
# type: (AnyStr) -> List
"""This searches TheTVDB.com for the series name
and returns the result list
"""
@ -789,7 +526,7 @@ class Tvdb(object):
return []
def _get_series(self, series):
def get_series(self, series):
"""This searches TheTVDB.com for the series name,
If a custom_ui UI is configured, it uses this to select the correct
series. If not, and interactive == True, ConsoleUI is used, if not
@ -853,16 +590,20 @@ class Tvdb(object):
def _parse_actors(self, sid, actor_list):
a = []
cast = CastList()
try:
for n in sorted(actor_list, key=lambda x: x['sortorder']):
a.append({'character': {'id': None,
'name': n.get('role', '').strip(),
role_image = (None, self.config['url_artworks'] % n.get('image'))[any([n.get('image')])]
character_name = n.get('role', '').strip()
person_name = n.get('name', '').strip()
character_id = n.get('id', None)
a.append({'character': {'id': character_id,
'name': character_name,
'url': None, # not supported by tvdb
'image': (None, self.config['url_artworks'] %
n.get('image'))[any([n.get('image')])],
'image': role_image,
},
'person': {'id': None, # not supported by tvdb
'name': n.get('name', '').strip(),
'person': {'id': None,
'name': person_name,
'url': None, # not supported by tvdb
'image': None, # not supported by tvdb
'birthday': None, # not supported by tvdb
@ -871,9 +612,14 @@ class Tvdb(object):
'country': None, # not supported by tvdb
},
})
cast[RoleTypes.ActorMain].append(
Character(p_id=character_id, name=character_name,
person=Person(name=person_name), image=role_image))
except (BaseException, Exception):
pass
self._set_show_data(sid, 'actors', a)
self._set_show_data(sid, 'cast', cast)
self.shows[sid].actors_loaded = True
def get_episode_data(self, epid):
# Parse episode information
@ -900,7 +646,8 @@ class Tvdb(object):
def _parse_images(self, sid, language, show_data, image_type, enabled_type):
mapped_img_types = {'banner': 'series'}
excluded_main_data = enabled_type in ['seasons_enabled', 'seasonwides_enabled']
if self.config[enabled_type]:
loaded_name = '%s_loaded' % image_type
if self.config[enabled_type] and not getattr(self.shows.get(sid), loaded_name, False):
image_data = self._getetsrc(self.config['url_series_images'] %
(sid, mapped_img_types.get(image_type, image_type)), language=language)
if image_data and 0 < len(image_data.get('data', '') or ''):
@ -913,44 +660,46 @@ class Tvdb(object):
self._set_show_data(sid, u'%s_thumb' % image_type, url_thumb)
excluded_main_data = True # artwork found so prevent fallback
self._parse_banners(sid, image_data['data'])
self.shows[sid].__dict__[loaded_name] = True
# fallback image thumbnail for none excluded_main_data if artwork is not found
if not excluded_main_data and show_data['data'].get(image_type):
self._set_show_data(sid, u'%s_thumb' % image_type,
re.sub(r'\.jpg$', '_t.jpg', show_data['data'][image_type], flags=re.I))
def _get_show_data(self, sid, language, get_ep_info=False):
def _get_show_data(self, sid, language, get_ep_info=False, **kwargs):
# type: (integer_types, AnyStr, bool, Optional[Any]) -> bool
"""Takes a series ID, gets the epInfo URL and parses the TVDB
XML file into the shows dict in layout:
shows[series_id][season_number][episode_number]
"""
# Parse show information
log.debug('Getting all series data for %s' % sid)
url = self.config['url_series_info'] % sid
show_data = self._getetsrc(url, language=language)
if sid not in self.shows or None is self.shows[sid].id:
log.debug('Getting all series data for %s' % sid)
show_data = self._getetsrc(url, language=language)
# check and make sure we have data to process and that it contains a series name
if not (show_data and 'seriesname' in show_data.get('data', {}) or {}):
return False
# check and make sure we have data to process and that it contains a series name
if not (show_data and 'seriesname' in show_data.get('data', {}) or {}):
return False
for k, v in iteritems(show_data['data']):
self._set_show_data(sid, k, v)
if sid in self.shows:
self.shows[sid].ep_loaded = get_ep_info
for k, v in iteritems(show_data['data']):
self._set_show_data(sid, k, v)
else:
show_data = {'data': {}}
for img_type, en_type in [(u'poster', 'posters_enabled'), (u'banner', 'banners_enabled'),
(u'fanart', 'fanart_enabled'), (u'season', 'seasons_enabled'),
(u'seasonwide', 'seasonwides_enabled')]:
self._parse_images(sid, language, show_data, img_type, en_type)
if self.config['actors_enabled']:
if self.config['actors_enabled'] and not getattr(self.shows.get(sid), 'actors_loaded', False):
actor_data = self._getetsrc(self.config['url_actors_info'] % sid, language=language)
if actor_data and 0 < len(actor_data.get('data', '') or ''):
self._parse_actors(sid, actor_data['data'])
if get_ep_info:
if get_ep_info and not getattr(self.shows.get(sid), 'ep_loaded', False):
# Parse episode data
log.debug('Getting all episodes of %s' % sid)
@ -1016,6 +765,28 @@ class Tvdb(object):
k = ep_map_keys[k]
self._set_item(sid, seas_no, ep_no, k, v)
crew = CrewList()
cast = CastList()
try:
for director in cur_ep.get('directors', []):
crew[RoleTypes.CrewDirector].append(Person(name=director))
except (BaseException, Exception):
pass
try:
for guest in cur_ep.get('gueststars_list', []):
cast[RoleTypes.ActorGuest].append(Character(person=Person(name=guest)))
except (BaseException, Exception):
pass
try:
for writers in cur_ep.get('writers', []):
crew[RoleTypes.CrewWriter].append(Person(name=writers))
except (BaseException, Exception):
pass
self._set_item(sid, seas_no, ep_no, 'crew', crew)
self._set_item(sid, seas_no, ep_no, 'cast', cast)
self.shows[sid].ep_loaded = True
return True
def _name_to_sid(self, name):
@ -1028,7 +799,7 @@ class Tvdb(object):
return self.corrections[name]
else:
log.debug('Getting show %s' % name)
selected_series = self._get_series(name)
selected_series = self.get_series(name)
if isinstance(selected_series, dict):
selected_series = [selected_series]
sids = [int(x['id']) for x in selected_series if
@ -1036,33 +807,6 @@ class Tvdb(object):
self.corrections.update(dict([(x['seriesname'], int(x['id'])) for x in selected_series]))
return sids
def __getitem__(self, key):
"""Handles tvdb_instance['seriesname'] calls.
The dict index should be the show id
"""
arg = None
if isinstance(key, tuple) and 2 == len(key):
key, arg = key
if not isinstance(arg, bool):
arg = None
if isinstance(key, integer_types):
# Item is integer, treat as show id
if key not in self.shows or (not self.shows[key].ep_loaded and arg in (None, True)):
self._get_show_data(key, self.config['language'], (True, arg)[None is not arg])
return None if key not in self.shows else self.shows[key]
key = str(key).lower()
self.config['searchterm'] = key
selected_series = self._get_series(key)
if isinstance(selected_series, dict):
selected_series = [selected_series]
[[self._set_show_data(show['id'], k, v) for k, v in iteritems(show)] for show in selected_series]
return selected_series
def __repr__(self):
return str(self.shows)
def main():
"""Simple example of using tvdb_api - it just

18
lib/tvdb_api/tvdb_exceptions.py

@ -13,52 +13,54 @@ __version__ = '1.9'
__all__ = ['TvdbException', 'TvdbError', 'TvdbUserabort', 'TvdbShownotfound',
'TvdbSeasonnotfound', 'TvdbEpisodenotfound', 'TvdbAttributenotfound', 'TvdbTokenexpired']
from tvinfo_base.exceptions import *
class TvdbException(Exception):
class TvdbException(BaseTVinfoException):
"""Any exception generated by tvdb_api
"""
pass
class TvdbError(TvdbException):
class TvdbError(BaseTVinfoError, TvdbException):
"""An error with thetvdb.com (Cannot connect, for example)
"""
pass
class TvdbUserabort(TvdbException):
class TvdbUserabort(BaseTVinfoUserabort, TvdbError):
"""User aborted the interactive selection (via
the q command, ^c etc)
"""
pass
class TvdbShownotfound(TvdbException):
class TvdbShownotfound(BaseTVinfoShownotfound, TvdbError):
"""Show cannot be found on thetvdb.com (non-existant show)
"""
pass
class TvdbSeasonnotfound(TvdbException):
class TvdbSeasonnotfound(BaseTVinfoSeasonnotfound, TvdbError):
"""Season cannot be found on thetvdb.com
"""
pass
class TvdbEpisodenotfound(TvdbException):
class TvdbEpisodenotfound(BaseTVinfoEpisodenotfound, TvdbError):
"""Episode cannot be found on thetvdb.com
"""
pass
class TvdbAttributenotfound(TvdbException):
class TvdbAttributenotfound(BaseTVinfoAttributenotfound, TvdbError):
"""Raised if an episode does not have the requested
attribute (such as a episode name)
"""
pass
class TvdbTokenexpired(TvdbException):
class TvdbTokenexpired(BaseTVinfoAuthenticationerror, TvdbError):
"""token expired or missing thetvdb.com
"""
pass

1
lib/tvinfo_base/__init__.py

@ -0,0 +1 @@
from .base import *

750
lib/tvinfo_base/base.py

@ -0,0 +1,750 @@
import copy
import datetime
import logging
import threading
import time
from six import integer_types, iteritems, iterkeys, string_types, text_type
from _23 import list_items, list_values
from .exceptions import *
# noinspection PyUnreachableCode
if False:
from typing import Any, AnyStr, Dict, List, Optional, Tuple, Union
log = logging.getLogger('TVInfo')
log.addHandler(logging.NullHandler())
TVInfoShowContainer = {} # type: Dict[ShowContainer]
class ShowContainer(dict):
"""Simple dict that holds a series of Show instances
"""
def __init__(self, **kwargs):
super(ShowContainer, self).__init__(**kwargs)
# limit caching of TVInfoShow objects to 15 minutes
self.max_age = 900 # type: integer_types
self.lock = threading.RLock()
def __setitem__(self, k, v):
super(ShowContainer, self).__setitem__(k, (v, time.time()))
def __getitem__(self, k):
return super(ShowContainer, self).__getitem__(k)[0]
def cleanup_old(self):
"""
remove entries that are older then max_age
"""
acquired_lock = self.lock.acquire(False)
if acquired_lock:
try:
current_time = time.time()
for k, v in list_items(self):
if self.max_age < current_time - v[1]:
lock_acquired = self[k].lock.acquire(False)
if lock_acquired:
try:
del self[k]
except (BaseException, Exception):
try:
self[k].lock.release()
except RuntimeError:
pass
finally:
self.lock.release()
def __str__(self):
nr_shows = len(self)
return '<ShowContainer (containing %s Show%s)>' % (nr_shows, ('s', '')[1 == nr_shows])
def __repr__(self):
return self.__str__()
class TVInfoShow(dict):
"""Holds a dict of seasons, and show data.
"""
def __init__(self):
dict.__init__(self)
self.lock = threading.RLock()
self.data = {} # type: Dict
self.ep_loaded = False # type: bool
self.poster_loaded = False # type: bool
self.banner_loaded = False # type: bool
self.fanart_loaded = False # type: bool
self.season_images_loaded = False # type: bool
self.seasonwide_images_loaded = False # type: bool
self.actors_loaded = False # type: bool
self.show_not_found = False # type: bool
self.id = None # type: integer_types
self.ids = {} # type: Dict[AnyStr, Optional[integer_types, AnyStr]]
self.slug = None # type: Optional[AnyStr]
self.seriesid = None # type: integer_types
self.seriesname = None # type: Optional[AnyStr]
self.aliases = [] # type: List[AnyStr]
self.season = None # type: integer_types
self.classification = None # type: Optional[AnyStr]
self.genre = None # type: Optional[AnyStr]
self.genre_list = [] # type: List[AnyStr]
self.actors = [] # type: List[Dict]
self.cast = CastList() # type: Dict[integer_types, Character]
self.show_type = [] # type: List[AnyStr]
self.network = None # type: Optional[AnyStr]
self.network_id = None # type: integer_types
self.network_timezone = None # type: Optional[AnyStr]
self.network_country = None # type: Optional[AnyStr]
self.network_country_code = None # type: Optional[AnyStr]
self.network_is_stream = None # type: Optional[bool]
self.runtime = None # type: integer_types
self.language = None # type: Optional[AnyStr]
self.official_site = None # type: Optional[AnyStr]
self.imdb_id = None # type: Optional[AnyStr]
self.zap2itid = None # type: Optional[AnyStr]
self.airs_dayofweek = None # type: Optional[AnyStr]
self.airs_time = None # type: Optional[AnyStr]
self.firstaired = None # type: Optional[AnyStr]
self.added = None # type: Optional[AnyStr]
self.addedby = None # type: Union[integer_types, AnyStr]
self.siteratingcount = None # type: integer_types
self.slug = None # type: Optional[AnyStr]
self.lastupdated = None # type: integer_types
self.contentrating = None # type: Optional[AnyStr]
self.rating = None # type: integer_types
self.status = None # type: Optional[AnyStr]
self.overview = None # type: Optional[AnyStr]
self.poster = None # type: Optional[AnyStr]
self.poster_thumb = None # type: Optional[AnyStr]
self.banner = None # type: Optional[AnyStr]
self.banner_thumb = None # type: Optional[AnyStr]
self.fanart = None # type: Optional[AnyStr]
self.banners = [] # type: Union[List, Dict]
def __str__(self):
nr_seasons = len(self)
return '<Show %r (containing %s season%s)>' % (self.seriesname, nr_seasons, ('s', '')[1 == nr_seasons])
def __repr__(self):
return self.__str__()
def __getattr__(self, key):
if key in self:
# Key is an episode, return it
return self[key]
if key in self.data:
# Non-numeric request is for show-data
return self.data[key]
raise AttributeError
def __getitem__(self, key):
if key in self:
# Key is an episode, return it
return dict.__getitem__(self, key)
if key in self.data:
# Non-numeric request is for show-data
return dict.__getitem__(self.data, key)
# Data wasn't found, raise appropriate error
if isinstance(key, integer_types) or isinstance(key, string_types) and key.isdigit():
# Episode number x was not found
raise BaseTVinfoSeasonnotfound('Could not find season %s' % (repr(key)))
else:
# If it's not numeric, it must be an attribute name, which
# doesn't exist, so attribute error.
raise BaseTVinfoAttributenotfound('Cannot find attribute %s' % (repr(key)))
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if 'lock' != k:
setattr(result, k, copy.deepcopy(v, memo))
for k, v in self.items():
result[k] = copy.deepcopy(v)
if isinstance(k, integer_types):
setattr(result[k], 'show', result)
return result
def __nonzero__(self):
return any(self.data.keys())
def aired_on(self, date):
ret = self.search(str(date), 'firstaired')
if 0 == len(ret):
raise BaseTVinfoEpisodenotfound('Could not find any episodes that aired on %s' % date)
return ret
def search(self, term=None, key=None):
"""
Search all episodes in show. Can search all data, or a specific key (for
example, episodename)
Always returns an array (can be empty). First index contains the first
match, and so on.
"""
results = []
for cur_season in list_values(self):
searchresult = cur_season.search(term=term, key=key)
if 0 != len(searchresult):
results.extend(searchresult)
return results
class TVInfoSeason(dict):
def __init__(self, show=None, **kwargs):
"""The show attribute points to the parent show
"""
super(TVInfoSeason, self).__init__(**kwargs)
self.show = show # type: TVInfoShow
self.id = None # type: integer_types
self.number = None # type: integer_types
self.name = None # type: Optional[AnyStr]
self.actors = [] # type: List[Dict]
self.cast = CastList() # type: Dict[integer_types, Character]
self.network = None # type: Optional[AnyStr]
self.network_id = None # type: integer_types
self.network_timezone = None # type: Optional[AnyStr]
self.network_country = None # type: Optional[AnyStr]
self.network_country_code = None # type: Optional[AnyStr]
self.network_is_stream = None # type: Optional[bool]
self.ordered = None # type: Optional[integer_types]
self.start_date = None # type: Optional[AnyStr]
self.end_date = None # type: Optional[AnyStr]
self.poster = None # type: Optional[AnyStr]
self.summery = None # type: Optional[AnyStr]
def __str__(self):
nr_episodes = len(self)
return '<Season %s instance (containing %s episode%s)>' % \
(self.number, nr_episodes, ('s', '')[1 == nr_episodes])
def __repr__(self):
return self.__str__()
def __getattr__(self, episode_number):
if episode_number in self:
return self[episode_number]
raise AttributeError
def __getitem__(self, episode_number):
if episode_number not in self:
raise BaseTVinfoEpisodenotfound('Could not find episode %s' % (repr(episode_number)))
else:
return dict.__getitem__(self, episode_number)
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if 'show' != k:
setattr(result, k, copy.deepcopy(v, memo))
for k, v in self.items():
result[k] = copy.deepcopy(v)
if isinstance(k, integer_types):
setattr(result[k], 'season', result)
return result
def search(self, term=None, key=None):
"""Search all episodes in season, returns a list of matching Episode
instances.
"""
results = []
for ep in list_values(self):
searchresult = ep.search(term=term, key=key)
if None is not searchresult:
results.append(searchresult)
return results
class TVInfoEpisode(dict):
def __init__(self, season=None, **kwargs):
"""The season attribute points to the parent season
"""
super(TVInfoEpisode, self).__init__(**kwargs)
self.id = None # type: integer_types
self.seriesid = None # type: integer_types
self.season = season # type: TVInfoSeason
self.seasonnumber = None # type: integer_types
self.episodenumber = None # type: integer_types
self.absolute_number = None # type: integer_types
self.is_special = None # type: Optional[bool]
self.actors = [] # type: List[Dict]
self.gueststars = None # type: Optional[AnyStr]
self.gueststars_list = [] # type: List[AnyStr]
self.cast = CastList() # type: Dict[integer_types, Character]
self.directors = [] # type: List[AnyStr]
self.writer = None # type: Optional[AnyStr]
self.writers = [] # type: List[AnyStr]
self.crew = CrewList() # type: Dict[integer_types, Person]
self.episodename = None # type: Optional[AnyStr]
self.overview = None # type: Optional[AnyStr]
self.language = {'episodeName': None, 'overview': None} # type: Dict[AnyStr, Optional[AnyStr]]
self.productioncode = None # type: Optional[AnyStr]
self.showurl = None # type: Optional[AnyStr]
self.lastupdated = None # type: integer_types
self.dvddiscid = None # type: Optional[AnyStr]
self.dvd_season = None # type: integer_types
self.dvd_episodenumber = None # type: integer_types
self.dvdchapter = None # type: integer_types
self.firstaired = None # type: Optional[AnyStr]
self.airtime = None # type: Optional[AnyStr]
self.network = None # type: Optional[AnyStr]
self.network_id = None # type: integer_types
self.network_timezone = None # type: Optional[AnyStr]
self.network_country = None # type: Optional[AnyStr]
self.network_country_code = None # type: Optional[AnyStr]
self.network_is_stream = None # type: Optional[bool]
self.filename = None # type: Optional[AnyStr]
self.lastupdatedby = None # type: Union[integer_types, AnyStr]
self.airsafterseason = None # type: integer_types
self.airsbeforeseason = None # type: integer_types
self.airsbeforeepisode = None # type: integer_types
self.imdb_id = None # type: Optional[AnyStr]
self.contentrating = None # type: Optional[AnyStr]
self.thumbadded = None # type: Optional[AnyStr]
self.rating = None # type: Union[integer_types, float]
self.siteratingcount = None # type: integer_types
def __str__(self):
seasno, epno = int(getattr(self, 'seasonnumber', 0)), int(getattr(self, 'episodenumber', 0))
epname = getattr(self, 'episodename', '')
if None is not epname:
return '<Episode %02dx%02d - %r>' % (seasno, epno, epname)
else:
return '<Episode %02dx%02d>' % (seasno, epno)
def __repr__(self):
return self.__str__()
def __getattr__(self, key):
if key in self:
return self[key]
raise AttributeError
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
raise BaseTVinfoAttributenotfound('Cannot find attribute %s' % (repr(key)))
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if 'season' != k:
setattr(result, k, copy.deepcopy(v, memo))
for k, v in self.items():
result[k] = copy.deepcopy(v)
return result
def search(self, term=None, key=None):
"""Search episode data for term, if it matches, return the Episode (self).
The key parameter can be used to limit the search to a specific element,
for example, episodename.
"""
if None is term:
raise TypeError('must supply string to search for (contents)')
term = text_type(term).lower()
for cur_key, cur_value in iteritems(self):
cur_key, cur_value = text_type(cur_key).lower(), text_type(cur_value).lower()
if None is not key and cur_key != key:
# Do not search this key
continue
if cur_value.find(text_type(term).lower()) > -1:
return self
class Persons(dict):
"""Holds all Persons instances for a show
"""
def __str__(self):
persons_count = len(self)
return '<Persons (containing %s Person%s)>' % (persons_count, ('', 's')[1 != persons_count])
def __repr__(self):
return self.__str__()
class CastList(Persons):
def __init__(self, **kwargs):
super(CastList, self).__init__(**kwargs)
for t in iterkeys(RoleTypes.reverse):
if t < RoleTypes.crew_limit:
self[t] = [] # type: List[Character]
def __str__(self):
persons_count = []
for t in iterkeys(RoleTypes.reverse):
if t < RoleTypes.crew_limit:
if len(self.get(t, [])):
persons_count.append('%s: %s' % (RoleTypes.reverse[t], len(self.get(t, []))))
persons_text = ', '.join(persons_count)
persons_text = ('0', '(%s)' % persons_text)['' != persons_text]
return '<Cast (containing %s Person%s)>' % (persons_text, ('', 's')['' != persons_text])
def __repr__(self):
return self.__str__()
class CrewList(Persons):
def __init__(self, **kwargs):
super(CrewList, self).__init__(**kwargs)
for t in iterkeys(RoleTypes.reverse):
if t >= RoleTypes.crew_limit:
self[t] = [] # type: List[Person]
def __str__(self):
persons_count = []
for t in iterkeys(RoleTypes.reverse):
if t >= RoleTypes.crew_limit:
if len(self.get(t, [])):
persons_count.append('%s: %s' % (RoleTypes.reverse[t], len(self.get(t, []))))
persons_text = ', '.join(persons_count)
persons_text = ('0', '(%s)' % persons_text)['' != persons_text]
return '<Crew (containing %s Person%s)>' % (persons_text, ('', 's')['' != persons_text])
def __repr__(self):
return self.__str__()
class PersonBase(dict):
"""Represents a single person. Should contain..
id,
image,
name,
role,
sortorder
"""
def __init__(self, p_id=None, name=None, image=None, gender=None, bio=None, birthdate=None, deathdate=None,
country=None, country_code=None, country_timezone=None, **kwargs):
super(PersonBase, self).__init__(**kwargs)
self.id = p_id # type: Optional[integer_types]
self.name = name # type: Optional[AnyStr]
self.image = image # type: Optional[AnyStr]
self.gender = gender # type: Optional[int]
self.bio = bio # type: Optional[AnyStr]
self.birthdate = birthdate # type: Optional[datetime.date]
self.deathdate = deathdate # type: Optional[datetime.date]
self.country = country # type: Optional[AnyStr]
self.country_code = country_code # type: Optional[AnyStr]
self.country_timezone = country_timezone # type: Optional[AnyStr]
def calc_age(self, date=None):
# type: (Optional[datetime.date]) -> Optional[int]
if isinstance(self.birthdate, datetime.date):
today = (datetime.date.today(), date)[isinstance(date, datetime.date)]
today = (today, self.deathdate)[isinstance(self.deathdate, datetime.date) and today > self.deathdate]
try:
birthday = self.birthdate.replace(year=today.year)
# raised when birth date is February 29
# and the current year is not a leap year
except ValueError:
birthday = self.birthdate.replace(year=today.year,
month=self.birthdate.month + 1, day=1)
if birthday > today:
return today.year - birthday.year - 1
else:
return today.year - birthday.year
@property
def age(self):
# type: (...) -> Optional[int]
"""
:return: age of person if birthdate is known, in case of deathdate is known return age of death
"""
return self.calc_age()
def __str__(self):
return '<Person "%s">' % self.name
def __repr__(self):
return self.__str__()
class PersonGenders(object):
male = 1
female = 2
reverse = {1: 'Male', 2: 'Female'}
class Person(PersonBase):
def __init__(self, p_id=None, name=None, image=None, gender=None, bio=None, birthdate=None, deathdate=None,
country=None, country_code=None, country_timezone=None, **kwargs):
super(Person, self).__init__(p_id=p_id, name=name, image=image, gender=gender, bio=bio, birthdate=birthdate,
deathdate=deathdate, country=country, country_code=country_code,
country_timezone=country_timezone, **kwargs)
self.credits = [] # type: List
def __str__(self):
return '<Person "%s">' % self.name
def __repr__(self):
return self.__str__()
class Character(PersonBase):
def __init__(self, person=None, voice=None, plays_self=None, **kwargs):
super(Character, self).__init__(**kwargs)
self.person = person # type: Optional[Person]
self.voice = voice # type: Optional[bool]
self.plays_self = plays_self # type: Optional[bool]
def __str__(self):
pn = ''
if None is not self.person and getattr(self.person, 'name', None):
pn = ' - (%s)' % getattr(self.person, 'name', '')
return '<Character "%s%s">' % (self.name, pn)
def __repr__(self):
return self.__str__()
class RoleTypes(object):
# Actor types
ActorMain = 1
ActorRecurring = 2
ActorGuest = 3
ActorSpecialGuest = 4
# Crew types (int's >= crew_limit)
CrewDirector = 50
CrewWriter = 51
CrewProducer = 52
reverse = {1: 'Main', 2: 'Recurring', 3: 'Guest', 4: 'Special Guest', 50: 'Director', 51: 'Writer', 52: 'Producer'}
crew_limit = 50
class TVInfoBase(object):
def __init__(self, *args, **kwargs):
global TVInfoShowContainer
if self.__class__.__name__ not in TVInfoShowContainer:
TVInfoShowContainer[self.__class__.__name__] = ShowContainer()
self.shows = TVInfoShowContainer[self.__class__.__name__] # type: ShowContainer
self.shows.cleanup_old()
self.lang = None # type: Optional[AnyStr]
self.corrections = {} # type: Dict
self.show_not_found = False # type: bool
self.not_found = False # type: bool
self.config = {
'apikey': '',
'debug_enabled': False,
'custom_ui': None,
'proxy': None,
'cache_enabled': False,
'cache_location': '',
'valid_languages': [],
'langabbv_to_id': {},
'language': 'en',
'base_url': '',
'banners_enabled': False,
'posters_enabled': False,
'seasons_enabled': False,
'seasonwides_enabled': False,
'fanart_enabled': False,
'actors_enabled': False,
} # type: Dict[AnyStr, Any]
def _must_load_data(self, sid, load_episodes):
# type: (integer_types, bool) -> bool
"""
returns if show data has to be fetched for (extra) data (episodes, images, ...)
or can taken from self.shows cache
:param sid: show id
:param load_episodes: should episodes be loaded
"""
if sid not in self.shows or None is self.shows[sid].id or \
(load_episodes and not getattr(self.shows[sid], 'ep_loaded', False)):
return True
for data_type, en_type in [(u'poster', 'posters_enabled'), (u'banner', 'banners_enabled'),
(u'fanart', 'fanart_enabled'), (u'season', 'seasons_enabled'),
(u'seasonwide', 'seasonwides_enabled'), (u'actors', 'actors_enabled')]:
if self.config.get(en_type, False) and not getattr(self.shows[sid], '%s_loaded' % data_type, False):
return True
return False
def get_person(self, p_id, **kwargs):
# type: (integer_types, Optional[Any]) -> Optional[Person]
"""
get person's data
:param p_id: persons id
:return: person object
"""
pass
def search_person(self, name):
# type: (AnyStr) -> List[Person]
"""
search for person by name
:param name: name to search for
:return: list of found person's
"""
pass
def _get_show_data(self, sid, language, get_ep_info=False, **kwargs):
# type: (integer_types, AnyStr, bool, Optional[Any]) -> bool
"""
internal function that should be overwritten in class to get data for given show id
:param sid: show id
:param language: language
:param get_ep_info: get episodes
"""
pass
def get_show(self, show_id, load_episodes=True, **kwargs):
# type: (integer_types, bool, Optional[Any]) -> Optional[TVInfoShow]
"""
get data for show id
:param show_id: id of show
:param load_episodes: load episodes
:return: show object
"""
self.shows.lock.acquire()
try:
if show_id not in self.shows:
self.shows[show_id] = TVInfoShow() # type: TVInfoShow
with self.shows[show_id].lock:
self.shows.lock.release()
try:
if self._must_load_data(show_id, load_episodes):
self._get_show_data(show_id, self.config['language'], load_episodes)
if None is self.shows[show_id].id:
with self.shows.lock:
del self.shows[show_id]
return None if show_id not in self.shows else copy.deepcopy(self.shows[show_id])
finally:
try:
if None is self.shows[show_id].id:
with self.shows.lock:
del self.shows[show_id]
except (BaseException, Exception):
pass
finally:
try:
self.shows.lock.release()
except RuntimeError:
pass
# noinspection PyMethodMayBeStatic
def _search_show(self, name, **kwargs):
# type: (AnyStr, Optional[Any]) -> List[Dict]
"""
internal search function to find shows, should be overwritten in class
:param name: name to search for
"""
return []
def search_show(self, name, **kwargs):
# type: (AnyStr, Optional[Any]) -> List[Dict]
"""
search for series with name
:param name: series name to search for
:return: list of series
"""
if not isinstance(name, string_types):
name = text_type(name)
name = name.lower()
selected_series = self._search_show(name)
if isinstance(selected_series, dict):
selected_series = [selected_series]
if not isinstance(selected_series, list) or 0 == len(selected_series):
log.debug('Series result returned zero')
raise BaseTVinfoShownotfound('Show-name search returned zero results (cannot find show on TVDB)')
return selected_series
def _set_item(self, sid, seas, ep, attrib, value):
# type: (integer_types, integer_types, integer_types, integer_types, Any, Any) -> None
"""Creates a new episode, creating Show(), Season() and
Episode()s as required. Called by _get_show_data to populate show
Since the nice-to-use tvinfo[1][24]['name] interface
makes it impossible to do tvinfo[1][24]['name] = "name"
and still be capable of checking if an episode exists
so we can raise tvinfo_shownotfound, we have a slightly
less pretty method of setting items.. but since the API
is supposed to be read-only, this is the best way to
do it!
The problem is that calling tvinfo[1][24]['episodename'] = "name"
calls __getitem__ on tvinfo[1], there is no way to check if
tvinfo.__dict__ should have a key "1" before we auto-create it
"""
# if sid not in self.shows:
# self.shows[sid] = TVInfoShow()
if seas not in self.shows[sid]:
self.shows[sid][seas] = TVInfoSeason(show=self.shows[sid])
self.shows[sid][seas].number = seas
if ep not in self.shows[sid][seas]:
self.shows[sid][seas][ep] = TVInfoEpisode(season=self.shows[sid][seas])
if attrib not in ('cast', 'crew'):
self.shows[sid][seas][ep][attrib] = value
self.shows[sid][seas][ep].__dict__[attrib] = value
def _set_show_data(self, sid, key, value, add=False):
# type: (integer_types, Any, Any, bool) -> None
"""Sets self.shows[sid] to a new Show instance, or sets the data
"""
# if sid not in self.shows:
# self.shows[sid] = TVInfoShow()
if key not in ('cast', 'crew'):
if add and isinstance(self.shows[sid].data, dict) and key in self.shows[sid].data:
self.shows[sid].data[key].update(value)
else:
self.shows[sid].data[key] = value
if '_banners' == key:
p_key = 'banners'
else:
p_key = key
if add and key in self.shows[sid].__dict__ and isinstance(self.shows[sid].__dict__[p_key], dict):
self.shows[sid].__dict__[p_key].update(self.shows[sid].data[key])
else:
self.shows[sid].__dict__[p_key] = self.shows[sid].data[key]
else:
if add and key in self.shows[sid].__dict__ and isinstance(self.shows[sid].__dict__[key], dict):
self.shows[sid].__dict__[key].update(value)
else:
self.shows[sid].__dict__[key] = value
def __getitem__(self, item):
# type: (Union[AnyStr, integer_types, Tuple[integer_types, bool]]) -> Union[TVInfoShow, List[Dict], None]
"""Legacy handler (use get_show or search_show instead)
Handles class_instance['seriesname'] calls.
The dict index should be the show id
"""
arg = None
if isinstance(item, tuple) and 2 == len(item):
item, arg = item
if not isinstance(arg, bool):
arg = None
if isinstance(item, integer_types):
# Item is integer, treat as show id
return self.get_show(item, (True, arg)[None is not arg])
return self.search_show(item)
# noinspection PyMethodMayBeStatic
def search(self, series):
# type: (AnyStr) -> List
"""This searches for the series name
and returns the result list
"""
return []
def __str__(self):
return '<TVInfo(%s) (containing: %s)>' % (self.__class__.__name__, text_type(self.shows))
def __repr__(self):
return self.__str__()

52
lib/tvinfo_base/exceptions.py

@ -0,0 +1,52 @@
class BaseTVinfoException(Exception):
"""Base Exception
"""
pass
class BaseTVinfoError(BaseTVinfoException):
"""Base Error
"""
pass
class BaseTVinfoUserabort(BaseTVinfoError):
"""User aborted the interactive selection (via
the q command, ^c etc)
"""
pass
class BaseTVinfoShownotfound(BaseTVinfoError):
"""Show cannot be found
"""
pass
class BaseTVinfoSeasonnotfound(BaseTVinfoError):
"""Season cannot be found
"""
pass
class BaseTVinfoEpisodenotfound(BaseTVinfoError):
"""Episode cannot be found
"""
pass
class BaseTVinfoAttributenotfound(BaseTVinfoError):
"""Raised if an episode does not have the requested
attribute (such as a episode name)
"""
pass
class BaseTVinfoAuthenticationerror(BaseTVinfoError):
"""auth expired or missing
"""
pass
class BaseTVinfoIndexerInitError(BaseTVinfoError):
pass

2
sickbeard/__init__.py

@ -47,7 +47,7 @@ from .config import check_section, check_setting_int, check_setting_str, ConfigM
from .databases import cache_db, failed_db, mainDB
from .indexers.indexer_api import TVInfoAPI
from .indexers.indexer_config import TVINFO_IMDB, TVINFO_TVDB
from .indexers.indexer_exceptions import *
from tvinfo_base.exceptions import *
from .providers.generic import GenericProvider
from .providers.newznab import NewznabConstants
from .tv import TVidProdid

9
sickbeard/helpers.py

@ -43,6 +43,7 @@ import sickbeard
from . import db, logger, notifiers
from .common import cpu_presets, mediaExtensions, Overview, Quality, statusStrings, subtitleExtensions, \
ARCHIVED, DOWNLOADED, FAILED, IGNORED, SKIPPED, SNATCHED_ANY, SUBTITLED, UNAIRED, UNKNOWN, WANTED
from tvinfo_base.exceptions import *
# noinspection PyPep8Naming
import encodingKludge as ek
from exceptions_helper import ex, MultipleShowObjectsException
@ -1196,12 +1197,8 @@ def validate_show(show_obj, season=None, episode=None):
return t
return t[show_obj.prodid][season][episode]
except Exception as e:
if sickbeard.check_exception_type(e, sickbeard.ExceptionTuples.tvinfo_episodenotfound,
sickbeard.ExceptionTuples.tvinfo_seasonnotfound, TypeError):
pass
else:
raise e
except (BaseTVinfoEpisodenotfound, BaseTVinfoSeasonnotfound, TypeError):
pass
def touch_file(fname, atime=None):

61
sickbeard/indexers/indexer_api.py

@ -16,11 +16,11 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import os
import time
from .indexer_config import init_config, tvinfo_config
from ..helpers import proxy_setting
import sickbeard
from tvinfo_base import TVInfoBase
from _23 import list_values
@ -29,62 +29,6 @@ if False:
from typing import AnyStr, Dict
class ShowContainer(dict):
"""
Simple dict that holds a series of Show instances
"""
# noinspection PyMissingConstructor
def __init__(self):
self._stack = []
self._lastgc = time.time()
def __setitem__(self, key, value):
self._stack.append(key)
# keep only the 100th latest results
if time.time() - self._lastgc > 20:
for o in self._stack[:-100]:
del self[o]
self._stack = self._stack[-100:]
self._lastgc = time.time()
super(ShowContainer, self).__setitem__(key, value)
class DummyIndexer(object):
# noinspection PyUnusedLocal
def __init__(self, *args, **kwargs):
self.config = {
'apikey': '',
'debug_enabled': False,
'custom_ui': None,
'proxy': None,
'cache_enabled': False,
'cache_location': '',
'valid_languages': [],
'langabbv_to_id': {},
'language': 'en',
'base_url': '',
}
self.corrections = {}
self.shows = ShowContainer()
def __getitem__(self, key):
return None
def __repr__(self):
return str(self.shows)
# noinspection PyUnusedLocal
@staticmethod
def search(series):
return []
class TVInfoAPI(object):
def __init__(self, tvid=None):
self.tvid = int(tvid) if tvid else None
@ -93,13 +37,14 @@ class TVInfoAPI(object):
pass
def setup(self, *args, **kwargs):
# type: (...) -> TVInfoBase
if self.tvid:
if tvinfo_config[self.tvid]['active'] or ('no_dummy' in kwargs and True is kwargs['no_dummy']):
if 'no_dummy' in kwargs:
kwargs.pop('no_dummy')
return tvinfo_config[self.tvid]['module'](*args, **kwargs)
else:
return DummyIndexer(*args, **kwargs)
return TVInfoBase(*args, **kwargs)
@property
def config(self):

77
sickbeard/indexers/indexer_exceptions.py

@ -3,78 +3,5 @@
# project:indexer_api
# license:unlicense (http://unlicense.org/)
__all__ = ['check_exception_type', 'ExceptionTuples',
'BaseTVinfoException', 'BaseTVinfoError', 'BaseTVinfoAuthenticationerror',
'BaseTVinfoUserabort', 'BaseTVinfoAttributenotfound', 'BaseTVinfoEpisodenotfound',
'BaseTVinfoSeasonnotfound', 'BaseTVinfoShownotfound']
"""Custom exceptions used or raised by indexer_api"""
from lib.tvdb_api.tvdb_exceptions import \
TvdbException, TvdbAttributenotfound, TvdbEpisodenotfound, TvdbError, \
TvdbSeasonnotfound, TvdbShownotfound, TvdbUserabort, TvdbTokenexpired
indexer_excepts = [
'tvinfo_exception', 'tvinfo_error', 'tvinfo_userabort',
'tvinfo_shownotfound', 'tvinfo_seasonnotfound', 'tvinfo_episodenotfound',
'tvinfo_attributenotfound', 'tvinfo_authenticationerror']
tvdb_excepts = [
'tvdb_exception', 'tvdb_error', 'tvdb_userabort', 'tvdb_shownotfound',
'tvdb_seasonnotfound', 'tvdb_episodenotfound', 'tvdb_attributenotfound',
'tvdb_tokenexpired']
tvdbv1_excepts = [
'tvdb_exception_v1', 'tvdb_error_v1', 'tvdb_userabort_v1', 'tvdb_shownotfound_v1',
'tvdb_seasonnotfound_v1', 'tvdb_episodenotfound_v1', 'tvdb_attributenotfound_v1']
class BaseTVinfoException(Exception):
pass
class BaseTVinfoError(Exception):
pass
class BaseTVinfoAuthenticationerror(Exception):
pass
class BaseTVinfoUserabort(Exception):
pass
class BaseTVinfoAttributenotfound(Exception):
pass
class BaseTVinfoEpisodenotfound(Exception):
pass
class BaseTVinfoSeasonnotfound(Exception):
pass
class BaseTVinfoShownotfound(Exception):
pass
# link API exceptions to our exception handler
class ExceptionTuples:
tvinfo_exception = TvdbException, BaseTVinfoException
tvinfo_error = TvdbError, BaseTVinfoError
tvinfo_authenticationerror = TvdbTokenexpired, BaseTVinfoAuthenticationerror
tvinfo_userabort = TvdbUserabort, BaseTVinfoUserabort
tvinfo_attributenotfound = TvdbAttributenotfound, BaseTVinfoAttributenotfound
tvinfo_episodenotfound = TvdbEpisodenotfound, BaseTVinfoEpisodenotfound
tvinfo_seasonnotfound = TvdbSeasonnotfound, BaseTVinfoSeasonnotfound
tvinfo_shownotfound = TvdbShownotfound, BaseTVinfoShownotfound
def check_exception_type(ex, ex_class, *args):
# type: (...) -> bool
if issubclass(ex.__class__, ex_class + args):
return True
return False
# noinspection PyUnresolvedReferences
from tvinfo_base.exceptions import *

2
sickbeard/logger.py

@ -73,7 +73,7 @@ class SBRotatingLogHandler(object):
self.console_logging = False # type: bool
self.log_lock = threading.Lock()
self.log_types = ['sickbeard', 'tornado.application', 'tornado.general', 'subliminal', 'adba', 'encodingKludge',
'tvdb_api']
'tvdb_api', 'TVInfo']
self.external_loggers = ['sg_helper', 'libtrakt', 'trakt_api']
self.log_types_null = ['tornado.access']

50
sickbeard/metadata/generic.py

@ -28,7 +28,7 @@ from . import helpers as metadata_helpers
from .. import helpers, logger
from ..indexers import indexer_config
from ..indexers.indexer_config import TVINFO_TVDB
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
import sickbeard
# noinspection PyPep8Naming
import encodingKludge as ek
@ -343,12 +343,9 @@ class GenericMetadata(object):
logger.log('Metadata provider %s creating show metadata for %s' % (self.name, show_obj.name), logger.DEBUG)
try:
result = self.write_show_file(show_obj)
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('Unable to find useful show metadata for %s on %s: %s' % (
self.name, sickbeard.TVInfoAPI(show_obj.tvid).name, ex(e)), logger.WARNING)
else:
raise e
except BaseTVinfoError as e:
logger.log('Unable to find useful show metadata for %s on %s: %s' % (
self.name, sickbeard.TVInfoAPI(show_obj.tvid).name, ex(e)), logger.WARNING)
return result
@ -360,12 +357,9 @@ class GenericMetadata(object):
logger.DEBUG)
try:
result = self.write_ep_file(ep_obj)
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('Unable to find useful episode metadata for %s on %s: %s' % (
self.name, sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.WARNING)
else:
raise e
except BaseTVinfoError as e:
logger.log('Unable to find useful episode metadata for %s on %s: %s' % (
self.name, sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.WARNING)
return result
@ -504,12 +498,8 @@ class GenericMetadata(object):
t = sickbeard.TVInfoAPI(TVINFO_TVDB).setup(**tvinfo_config)
ep_info = t[cur_ep_obj.show_obj.prodid][cur_ep_obj.season][cur_ep_obj.episode]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_episodenotfound,
ExceptionTuples.tvinfo_seasonnotfound, TypeError):
ep_info = None
else:
raise e
except (BaseTVinfoEpisodenotfound, BaseTVinfoSeasonnotfound, TypeError):
ep_info = None
else:
ep_info = helpers.validate_show(cur_ep_obj.show_obj, cur_ep_obj.season, cur_ep_obj.episode)
@ -861,13 +851,10 @@ class GenericMetadata(object):
t = sickbeard.TVInfoAPI(show_obj.tvid).setup(**tvinfo_config)
show_info = t[show_obj.prodid, False]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error, IOError):
logger.log(u"Unable to look up show on " + sickbeard.TVInfoAPI(
show_obj.tvid).name + ", not downloading images: " + ex(e), logger.WARNING)
return None
else:
raise e
except (BaseTVinfoError, IOError) as e:
logger.log(u"Unable to look up show on " + sickbeard.TVInfoAPI(
show_obj.tvid).name + ", not downloading images: " + ex(e), logger.WARNING)
return None
if not self._valid_show(show_info, show_obj):
return None
@ -999,13 +986,10 @@ class GenericMetadata(object):
t = sickbeard.TVInfoAPI(show_obj.tvid).setup(**tvinfo_config)
tvinfo_obj_show = t[show_obj.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error, IOError):
logger.log(u'Unable to look up show on ' + sickbeard.TVInfoAPI(
show_obj.tvid).name + ', not downloading images: ' + ex(e), logger.WARNING)
return result
else:
raise e
except (BaseTVinfoError, IOError) as e:
logger.log(u'Unable to look up show on ' + sickbeard.TVInfoAPI(
show_obj.tvid).name + ', not downloading images: ' + ex(e), logger.WARNING)
return result
if not self._valid_show(tvinfo_obj_show, show_obj):
return result

39
sickbeard/metadata/kodi.py

@ -22,7 +22,7 @@ import os
from . import generic
from .. import helpers, logger
from ..indexers.indexer_config import TVINFO_IMDB, TVINFO_TVDB
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
import sickbeard
# noinspection PyPep8Naming
import encodingKludge as ek
@ -127,19 +127,15 @@ class KODIMetadata(generic.GenericMetadata):
try:
show_info = t[int(show_ID)]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
logger.log('Unable to find show with id %s on %s, skipping it' % (show_ID, sickbeard.TVInfoAPI(
show_obj.tvid).name), logger.ERROR)
raise
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log(
'%s is down, can\'t use its data to add this show' % sickbeard.TVInfoAPI(show_obj.tvid).name,
logger.ERROR)
raise
else:
raise e
except BaseTVinfoShownotfound as e:
logger.log('Unable to find show with id %s on %s, skipping it' % (show_ID, sickbeard.TVInfoAPI(
show_obj.tvid).name), logger.ERROR)
raise e
except BaseTVinfoError as e:
logger.log(
'%s is down, can\'t use its data to add this show' % sickbeard.TVInfoAPI(show_obj.tvid).name,
logger.ERROR)
raise e
if not self._valid_show(show_info, show_obj):
return
@ -267,15 +263,12 @@ class KODIMetadata(generic.GenericMetadata):
try:
t = sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).setup(**tvinfo_config)
show_info = t[ep_obj.show_obj.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
raise exceptions_helper.ShowNotFoundException(ex(e))
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('Unable to connect to %s while creating meta files - skipping - %s' % (sickbeard.TVInfoAPI(
ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return
else:
raise e
except BaseTVinfoShownotfound as e:
raise exceptions_helper.ShowNotFoundException(ex(e))
except BaseTVinfoError as e:
logger.log('Unable to connect to %s while creating meta files - skipping - %s' % (sickbeard.TVInfoAPI(
ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return
if not self._valid_show(show_info, ep_obj.show_obj):
return

44
sickbeard/metadata/mede8er.py

@ -20,7 +20,7 @@ import datetime
from . import mediabrowser
from .. import helpers, logger
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
import sickbeard
import exceptions_helper
from exceptions_helper import ex
@ -126,16 +126,12 @@ class Mede8erMetadata(mediabrowser.MediaBrowserMetadata):
try:
show_info = t[int(show_obj.prodid)]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
logger.log(u'Unable to find show with id ' + str(show_obj.prodid) + ' on tvdb, skipping it', logger.ERROR)
raise
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log(u'TVDB is down, can\'t use its data to make the NFO', logger.ERROR)
raise
else:
raise e
except BaseTVinfoShownotfound as e:
logger.log(u'Unable to find show with id ' + str(show_obj.prodid) + ' on tvdb, skipping it', logger.ERROR)
raise e
except BaseTVinfoError as e:
logger.log(u'TVDB is down, can\'t use its data to make the NFO', logger.ERROR)
raise e
if not self._valid_show(show_info, show_obj):
return
@ -149,13 +145,10 @@ class Mede8erMetadata(mediabrowser.MediaBrowserMetadata):
logger.log('Incomplete info for show with id %s on %s, skipping it' %
(show_obj.prodid, sickbeard.TVInfoAPI(show_obj.tvid).name), logger.ERROR)
return False
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_attributenotfound):
logger.log('Incomplete info for show with id %s on %s, skipping it' %
(show_obj.prodid, sickbeard.TVInfoAPI(show_obj.tvid).name), logger.ERROR)
return False
else:
raise e
except BaseTVinfoAttributenotfound:
logger.log('Incomplete info for show with id %s on %s, skipping it' %
(show_obj.prodid, sickbeard.TVInfoAPI(show_obj.tvid).name), logger.ERROR)
return False
SeriesName = etree.SubElement(tv_node, 'title')
if None is not show_info['seriesname']:
@ -245,15 +238,12 @@ class Mede8erMetadata(mediabrowser.MediaBrowserMetadata):
t = sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).setup(**tvinfo_config)
show_info = t[ep_obj.show_obj.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
raise exceptions_helper.ShowNotFoundException(ex(e))
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('Unable to connect to %s while creating meta files - skipping - %s' %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
else:
raise e
except BaseTVinfoShownotfound as e:
raise exceptions_helper.ShowNotFoundException(ex(e))
except BaseTVinfoError as e:
logger.log('Unable to connect to %s while creating meta files - skipping - %s' %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
if not self._valid_show(show_info, ep_obj.show_obj):
return

37
sickbeard/metadata/mediabrowser.py

@ -22,7 +22,7 @@ import re
from . import generic
from .. import helpers, logger
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
import sickbeard
# noinspection PyPep8Naming
import encodingKludge as ek
@ -255,18 +255,14 @@ class MediaBrowserMetadata(generic.GenericMetadata):
try:
show_info = t[int(show_obj.prodid)]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
logger.log("Unable to find show with id %s on %s, skipping it" %
(show_obj.prodid, sickbeard.TVInfoAPI(show_obj.tvid).name), logger.ERROR)
raise
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log("%s is down, can't use its data to make the NFO" % sickbeard.TVInfoAPI(show_obj.tvid).name,
logger.ERROR)
raise
else:
raise e
except BaseTVinfoShownotfound as e:
logger.log("Unable to find show with id %s on %s, skipping it" %
(show_obj.prodid, sickbeard.TVInfoAPI(show_obj.tvid).name), logger.ERROR)
raise e
except BaseTVinfoError as e:
logger.log("%s is down, can't use its data to make the NFO" % sickbeard.TVInfoAPI(show_obj.tvid).name,
logger.ERROR)
raise e
if not self._valid_show(show_info, show_obj):
return
@ -420,15 +416,12 @@ class MediaBrowserMetadata(generic.GenericMetadata):
t = sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).setup(**tvinfo_config)
show_info = t[ep_obj.show_obj.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
raise exceptions_helper.ShowNotFoundException(ex(e))
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log("Unable to connect to %s while creating meta files - skipping - %s" %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
else:
raise e
except BaseTVinfoShownotfound as e:
raise exceptions_helper.ShowNotFoundException(ex(e))
except BaseTVinfoError as e:
logger.log("Unable to connect to %s while creating meta files - skipping - %s" %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
if not self._valid_show(show_info, ep_obj.show_obj):
return

17
sickbeard/metadata/tivo.py

@ -24,7 +24,7 @@ import os
from . import generic
from .. import helpers, logger
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
import sickbeard
# noinspection PyPep8Naming
import encodingKludge as ek
@ -203,15 +203,12 @@ class TIVOMetadata(generic.GenericMetadata):
t = sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).setup(**tvinfo_config)
show_info = t[ep_obj.show_obj.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
raise exceptions_helper.ShowNotFoundException(ex(e))
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log("Unable to connect to %s while creating meta files - skipping - %s" %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
else:
raise e
except BaseTVinfoShownotfound as e:
raise exceptions_helper.ShowNotFoundException(ex(e))
except BaseTVinfoError as e:
logger.log("Unable to connect to %s while creating meta files - skipping - %s" %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
if not self._valid_show(show_info, ep_obj.show_obj):
return

17
sickbeard/metadata/wdtv.py

@ -22,7 +22,7 @@ import re
from . import generic
from .. import helpers, logger
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
import sickbeard
# noinspection PyPep8Naming
import encodingKludge as ek
@ -204,15 +204,12 @@ class WDTVMetadata(generic.GenericMetadata):
t = sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).setup(**tvinfo_config)
show_info = t[ep_obj.show_obj.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
raise exceptions_helper.ShowNotFoundException(ex(e))
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log("Unable to connect to %s while creating meta files - skipping - %s" %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
else:
raise e
except BaseTVinfoShownotfound as e:
raise exceptions_helper.ShowNotFoundException(ex(e))
except BaseTVinfoError as e:
logger.log("Unable to connect to %s while creating meta files - skipping - %s" %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return False
if not self._valid_show(show_info, ep_obj.show_obj):
return

53
sickbeard/metadata/xbmc_12plus.py

@ -19,7 +19,7 @@ import datetime
from . import generic
from .. import helpers, logger
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
import sickbeard
import exceptions_helper
from exceptions_helper import ex
@ -121,18 +121,14 @@ class XBMC12PlusMetadata(generic.GenericMetadata):
try:
show_info = t[int(show_id)]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
logger.log('Unable to find show with id %s on %s, skipping it' %
(show_id, sickbeard.TVInfoAPI(show_obj.tvid).name), logger.ERROR)
raise
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('%s is down, can\'t use its data to add this show' % sickbeard.TVInfoAPI(show_obj.tvid).name,
logger.ERROR)
raise
else:
raise e
except BaseTVinfoShownotfound as e:
logger.log('Unable to find show with id %s on %s, skipping it' %
(show_id, sickbeard.TVInfoAPI(show_obj.tvid).name), logger.ERROR)
raise e
except BaseTVinfoError as e:
logger.log('%s is down, can\'t use its data to add this show' % sickbeard.TVInfoAPI(show_obj.tvid).name,
logger.ERROR)
raise e
if not self._valid_show(show_info, show_obj):
return
@ -227,15 +223,12 @@ class XBMC12PlusMetadata(generic.GenericMetadata):
try:
t = sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).setup(**tvinfo_config)
show_info = t[ep_obj.show_obj.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound):
raise exceptions_helper.ShowNotFoundException(ex(e))
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('Unable to connect to %s while creating meta files - skipping - %s' %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return
else:
raise e
except BaseTVinfoShownotfound as e:
raise exceptions_helper.ShowNotFoundException(ex(e))
except BaseTVinfoError as e:
logger.log('Unable to connect to %s while creating meta files - skipping - %s' %
(sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name, ex(e)), logger.ERROR)
return
if not self._valid_show(show_info, ep_obj.show_obj):
return
@ -250,15 +243,13 @@ class XBMC12PlusMetadata(generic.GenericMetadata):
try:
ep_info = show_info[cur_ep_obj.season][cur_ep_obj.episode]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_episodenotfound,
ExceptionTuples.tvinfo_seasonnotfound):
logger.log('Unable to find episode %sx%s on %s.. has it been removed? Should I delete from db?' %
(cur_ep_obj.season, cur_ep_obj.episode, sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name))
return None
else:
logger.log(u'Not generating nfo because failed to fetched tv info data at this time', logger.DEBUG)
return None
except (BaseTVinfoEpisodenotfound, BaseTVinfoSeasonnotfound) as e:
logger.log('Unable to find episode %sx%s on %s.. has it been removed? Should I delete from db?' %
(cur_ep_obj.season, cur_ep_obj.episode, sickbeard.TVInfoAPI(ep_obj.show_obj.tvid).name))
return None
except (BaseException, Exception):
logger.log(u'Not generating nfo because failed to fetched tv info data at this time', logger.DEBUG)
return None
if None is getattr(ep_info, 'firstaired', None):
ep_info['firstaired'] = str(datetime.date.fromordinal(1))

21
sickbeard/name_parser/parser.py

@ -39,7 +39,7 @@ import encodingKludge as ek
from exceptions_helper import ex
import sickbeard
from .. import common, db, helpers, logger, scene_exceptions, scene_numbering
from ..indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
from ..classes import OrderedDefaultdict
from .._legacy_classes import LegacyParseResult
@ -382,17 +382,14 @@ class NameParser(object):
season_number = int(ep_obj['seasonnumber'])
episode_numbers = [int(ep_obj['episodenumber'])]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_episodenotfound):
logger.log(u'Unable to find episode with date ' + str(best_result.air_date)
+ ' for show ' + show_obj.name + ', skipping', logger.WARNING)
episode_numbers = []
elif check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log(u'Unable to contact ' + sickbeard.TVInfoAPI(show_obj.tvid).name
+ ': ' + ex(e), logger.WARNING)
episode_numbers = []
else:
raise e
except BaseTVinfoEpisodenotfound as e:
logger.log(u'Unable to find episode with date ' + str(best_result.air_date)
+ ' for show ' + show_obj.name + ', skipping', logger.WARNING)
episode_numbers = []
except BaseTVinfoError as e:
logger.log(u'Unable to contact ' + sickbeard.TVInfoAPI(show_obj.tvid).name
+ ': ' + ex(e), logger.WARNING)
episode_numbers = []
for epNo in episode_numbers:
s = season_number

19
sickbeard/network_timezones.py

@ -30,10 +30,12 @@ import encodingKludge as ek
from lib.dateutil import tz, zoneinfo
from lib.tzlocal import get_localzone
from _23 import scandir
from six import iteritems
# noinspection PyUnreachableCode
if False:
from _23 import DirEntry
from typing import AnyStr, Optional, Tuple, Union
# regex to parse time (12/24 hour format)
@ -123,18 +125,17 @@ def _remove_old_zoneinfo():
cur_file = helpers.real_path(ek.ek(join, sickbeard.ZONEINFO_DIR, cur_zoneinfo))
for (path, dirs, files) in chain.from_iterable(
[ek.ek(os.walk, helpers.real_path(_dir))
for entry in chain.from_iterable(
[ek.ek(scandir, helpers.real_path(_dir))
for _dir in (sickbeard.ZONEINFO_DIR, ek.ek(os.path.dirname, zoneinfo.__file__))]):
for filename in files:
if filename.endswith('.tar.gz'):
file_w_path = ek.ek(join, path, filename)
if file_w_path != cur_file and ek.ek(isfile, file_w_path):
if entry.is_file(follow_symlinks=False):
if entry.name.endswith('.tar.gz'):
if entry.path != cur_file:
try:
ek.ek(os.remove, file_w_path)
logger.log(u'Delete unneeded old zoneinfo File: %s' % file_w_path)
ek.ek(os.remove, entry.path)
logger.log(u'Delete unneeded old zoneinfo File: %s' % entry.path)
except (BaseException, Exception):
logger.log(u'Unable to delete: %s' % file_w_path, logger.ERROR)
logger.log(u'Unable to delete: %s' % entry.path, logger.ERROR)
def _update_zoneinfo():

137
sickbeard/scene_numbering.py

@ -39,7 +39,8 @@ if False:
from typing import Dict, Tuple
def get_scene_numbering(tvid, prodid, season, episode, fallback_to_xem=True):
def get_scene_numbering(tvid, prodid, season, episode, fallback_to_xem=True, show_sql=None, scene_sql=None,
show_obj=None):
"""
Returns a tuple, (season, episode), with the scene numbering (if there is one),
otherwise returns the xem numbering (if fallback_to_xem is set), otherwise
@ -63,22 +64,23 @@ def get_scene_numbering(tvid, prodid, season, episode, fallback_to_xem=True):
return season, episode
tvid, prodid = int(tvid), int(prodid)
show_obj = sickbeard.helpers.find_show_by_id({tvid: prodid})
if None is show_obj:
show_obj = sickbeard.helpers.find_show_by_id({tvid: prodid})
if show_obj and not show_obj.is_scene:
return season, episode
result = find_scene_numbering(tvid, prodid, season, episode)
result = find_scene_numbering(tvid, prodid, season, episode, scene_sql=scene_sql)
if result:
return result
else:
if fallback_to_xem:
xem_result = find_xem_numbering(tvid, prodid, season, episode)
xem_result = find_xem_numbering(tvid, prodid, season, episode, show_sql=show_sql)
if xem_result:
return xem_result
return season, episode
def find_scene_numbering(tvid, prodid, season, episode):
def find_scene_numbering(tvid, prodid, season, episode, scene_sql=None):
"""
Same as get_scene_numbering(), but returns None if scene numbering is not set
:param tvid: tvid
@ -97,19 +99,30 @@ def find_scene_numbering(tvid, prodid, season, episode):
tvid, prodid = int(tvid), int(prodid)
my_db = db.DBConnection()
rows = my_db.select(
'SELECT scene_season, scene_episode'
' FROM scene_numbering'
' WHERE indexer = ? AND indexer_id = ?'
' AND season = ? AND episode = ? AND (scene_season OR scene_episode) != 0',
[tvid, prodid, season, episode])
rows = None
if None is not scene_sql:
for e in scene_sql:
if e['season'] == season and e['episode'] == episode:
if e['scene_season'] or e['scene_episode']:
rows = [e]
break
else:
my_db = db.DBConnection()
rows = my_db.select(
'SELECT scene_season, scene_episode'
' FROM scene_numbering'
' WHERE indexer = ? AND indexer_id = ?'
' AND season = ? AND episode = ? AND (scene_season OR scene_episode) != 0',
[tvid, prodid, season, episode])
if rows:
return int(rows[0]['scene_season']), int(rows[0]['scene_episode'])
s_s, s_e = int(rows[0]['scene_season']), int(rows[0]['scene_episode'])
if None is not s_s and None is not s_e:
return s_s, s_e
def get_scene_absolute_numbering(tvid, prodid, absolute_number, season, episode, fallback_to_xem=True):
def get_scene_absolute_numbering(tvid, prodid, absolute_number, season, episode, fallback_to_xem=True, show_sql=None,
scene_sql=None, show_obj=None):
"""
Returns a tuple, (season, episode), with the scene numbering (if there is one),
otherwise returns the xem numbering (if fallback_to_xem is set), otherwise
@ -138,22 +151,23 @@ def get_scene_absolute_numbering(tvid, prodid, absolute_number, season, episode,
tvid, prodid = int(tvid), int(prodid)
show_obj = sickbeard.helpers.find_show_by_id({tvid: prodid})
if None is show_obj:
show_obj = sickbeard.helpers.find_show_by_id({tvid: prodid})
if show_obj and not show_obj.is_scene and not has_sxe:
return absolute_number
result = find_scene_absolute_numbering(tvid, prodid, absolute_number, season, episode)
result = find_scene_absolute_numbering(tvid, prodid, absolute_number, season, episode, scene_sql=scene_sql)
if result:
return result
else:
if fallback_to_xem:
xem_result = find_xem_absolute_numbering(tvid, prodid, absolute_number, season, episode)
xem_result = find_xem_absolute_numbering(tvid, prodid, absolute_number, season, episode, show_sql=show_sql)
if xem_result:
return xem_result
return absolute_number
def find_scene_absolute_numbering(tvid, prodid, absolute_number, season=None, episode=None):
def find_scene_absolute_numbering(tvid, prodid, absolute_number, season=None, episode=None, scene_sql=None):
"""
Same as get_scene_numbering(), but returns None if scene numbering is not set
@ -176,18 +190,26 @@ def find_scene_absolute_numbering(tvid, prodid, absolute_number, season=None, ep
tvid, prodid = int(tvid), int(prodid)
my_db = db.DBConnection()
sql_vars, cond = (([absolute_number], 'AND absolute_number = ?'),
([season, episode], 'AND season = ? AND episode = ?'))[has_sxe]
rows = my_db.select(
'SELECT scene_absolute_number'
' FROM scene_numbering'
' WHERE indexer = ? AND indexer_id = ?'
' %s AND scene_absolute_number != 0' % cond,
[tvid, prodid] + sql_vars)
rows = None
if None is not scene_sql:
for e in scene_sql:
if e['season'] == season and e['episode'] == episode:
if e['scene_absolute_number']:
rows = [e]
break
else:
my_db = db.DBConnection()
sql_vars, cond = (([absolute_number], 'AND absolute_number = ?'),
([season, episode], 'AND season = ? AND episode = ?'))[has_sxe]
rows = my_db.select(
'SELECT scene_absolute_number'
' FROM scene_numbering'
' WHERE indexer = ? AND indexer_id = ?'
' %s AND scene_absolute_number != 0' % cond,
[tvid, prodid] + sql_vars)
if rows:
return int(rows[0]['scene_absolute_number'])
return try_int(rows[0]['scene_absolute_number'], None)
def get_indexer_numbering(tvid, prodid, scene_season, scene_episode, fallback_to_xem=True):
@ -358,7 +380,7 @@ def set_scene_numbering(tvid=None, prodid=None, season=None, episode=None, absol
[scene_absolute, tvid, prodid, absolute_number])
def find_xem_numbering(tvid, prodid, season, episode):
def find_xem_numbering(tvid, prodid, season, episode, show_sql=None):
"""
Returns the scene numbering, as retrieved from xem.
Refreshes/Loads as needed.
@ -381,19 +403,29 @@ def find_xem_numbering(tvid, prodid, season, episode):
xem_refresh(tvid, prodid)
my_db = db.DBConnection()
rows = my_db.select(
'SELECT scene_season, scene_episode'
' FROM tv_episodes'
' WHERE indexer = ? AND showid = ?'
' AND season = ? AND episode = ? AND (scene_season OR scene_episode) != 0',
[tvid, prodid, season, episode])
rows = None
if None is not show_sql:
for e in show_sql:
if e['season'] == season and e['episode'] == episode:
if e['scene_season'] or e['scene_episode']:
rows = [e]
break
else:
my_db = db.DBConnection()
rows = my_db.select(
'SELECT scene_season, scene_episode'
' FROM tv_episodes'
' WHERE indexer = ? AND showid = ?'
' AND season = ? AND episode = ? AND (scene_season OR scene_episode) != 0',
[tvid, prodid, season, episode])
if rows:
return int(rows[0]['scene_season']), int(rows[0]['scene_episode'])
s_s, s_e = try_int(rows[0]['scene_season'], None), try_int(rows[0]['scene_episode'], None)
if None is not s_s and None is not s_e:
return s_s, s_e
def find_xem_absolute_numbering(tvid, prodid, absolute_number, season, episode):
def find_xem_absolute_numbering(tvid, prodid, absolute_number, season, episode, show_sql=None):
"""
Returns the scene numbering, as retrieved from xem.
Refreshes/Loads as needed.
@ -418,16 +450,24 @@ def find_xem_absolute_numbering(tvid, prodid, absolute_number, season, episode):
xem_refresh(tvid, prodid)
my_db = db.DBConnection()
rows = my_db.select(
'SELECT scene_absolute_number'
' FROM tv_episodes'
' WHERE indexer = ? AND showid = ?'
' AND season = ? AND episode = ? AND scene_absolute_number != 0',
[tvid, prodid, season, episode])
rows = None
if None is not show_sql:
for e in show_sql:
if e['season'] == season and e['episode'] == episode:
if e['scene_absolute_number']:
rows = [e]
break
else:
my_db = db.DBConnection()
rows = my_db.select(
'SELECT scene_absolute_number'
' FROM tv_episodes'
' WHERE indexer = ? AND showid = ?'
' AND season = ? AND episode = ? AND scene_absolute_number != 0',
[tvid, prodid, season, episode])
if rows:
return int(rows[0]['scene_absolute_number'])
return try_int(rows[0]['scene_absolute_number'], None)
def get_indexer_numbering_for_xem(tvid, prodid, scene_season, scene_episode):
@ -972,11 +1012,12 @@ def set_scene_numbering_helper(tvid, prodid, for_season=None, for_episode=None,
result['errorMessage'] = "Episode couldn't be retrieved, invalid parameters"
if not show_obj.is_anime:
scene_numbering = get_scene_numbering(tvid, prodid, for_season, for_episode)
scene_numbering = get_scene_numbering(tvid, prodid, for_season, for_episode, show_obj=show_obj)
if scene_numbering:
(result['sceneSeason'], result['sceneEpisode']) = scene_numbering
else:
scene_numbering = get_scene_absolute_numbering(tvid, prodid, for_absolute, for_season, for_episode)
scene_numbering = get_scene_absolute_numbering(tvid, prodid, for_absolute, for_season, for_episode,
show_obj=show_obj)
if scene_numbering:
result['sceneAbsolute'] = scene_numbering

77
sickbeard/show_queue.py

@ -34,7 +34,7 @@ from .common import SKIPPED, WANTED, UNAIRED, Quality, statusStrings
from .helpers import should_delete_episode
from .indexermapper import map_indexers_to_show
from .indexers.indexer_config import TVINFO_TVDB, TVINFO_TVRAGE
from .indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
from .name_parser.parser import NameParser
from .tv import TVShow
from six import integer_types
@ -722,31 +722,30 @@ class QueueItemAdd(ShowQueueItem):
if self.show_obj.classification and 'sports' in self.show_obj.classification.lower():
self.show_obj.sports = 1
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_exception):
logger.log(
'Unable to add show due to an error with %s: %s' % (sickbeard.TVInfoAPI(self.tvid).name, ex(e)),
logger.ERROR)
if self.show_obj:
ui.notifications.error('Unable to add %s due to an error with %s'
% (self.show_obj.name, sickbeard.TVInfoAPI(self.tvid).name))
else:
ui.notifications.error(
'Unable to add show due to an error with %s' % sickbeard.TVInfoAPI(self.tvid).name)
self._finishEarly()
return
except BaseTVinfoException as e:
logger.log(
'Unable to add show due to an error with %s: %s' % (sickbeard.TVInfoAPI(self.tvid).name, ex(e)),
logger.ERROR)
if self.show_obj:
ui.notifications.error('Unable to add %s due to an error with %s'
% (self.show_obj.name, sickbeard.TVInfoAPI(self.tvid).name))
else:
ui.notifications.error(
'Unable to add show due to an error with %s' % sickbeard.TVInfoAPI(self.tvid).name)
self._finishEarly()
return
elif check_exception_type(e, exceptions_helper.MultipleShowObjectsException):
logger.log('The show in %s is already in your show list, skipping' % self.showDir, logger.ERROR)
ui.notifications.error('Show skipped', 'The show in %s is already in your show list' % self.showDir)
self._finishEarly()
return
except exceptions_helper.MultipleShowObjectsException:
logger.log('The show in %s is already in your show list, skipping' % self.showDir, logger.ERROR)
ui.notifications.error('Show skipped', 'The show in %s is already in your show list' % self.showDir)
self._finishEarly()
return
else:
logger.log('Error trying to add show: %s' % ex(e), logger.ERROR)
logger.log(traceback.format_exc(), logger.ERROR)
self._finishEarly()
raise
except (BaseException, Exception) as e:
logger.log('Error trying to add show: %s' % ex(e), logger.ERROR)
logger.log(traceback.format_exc(), logger.ERROR)
self._finishEarly()
raise
self.show_obj.load_imdb_info()
@ -1029,17 +1028,14 @@ class QueueItemUpdate(ShowQueueItem):
result = self.show_obj.load_from_tvinfo(cache=not self.force)
if None is not result:
return
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('Unable to contact %s, aborting: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)),
logger.WARNING)
return
elif check_exception_type(e, ExceptionTuples.tvinfo_attributenotfound):
logger.log('Data retrieved from %s was incomplete, aborting: %s' %
(sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.ERROR)
return
else:
raise e
except BaseTVinfoAttributenotfound as e:
logger.log('Data retrieved from %s was incomplete, aborting: %s' %
(sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.ERROR)
return
except BaseTVinfoError as e:
logger.log('Unable to contact %s, aborting: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)),
logger.WARNING)
return
if self.force_web:
self.show_obj.load_imdb_info()
@ -1058,13 +1054,10 @@ class QueueItemUpdate(ShowQueueItem):
logger.log('Loading all episodes from %s' % sickbeard.TVInfoAPI(self.show_obj.tvid).name, logger.DEBUG)
try:
tvinfo_ep_list = self.show_obj.load_episodes_from_tvinfo(cache=not self.force, update=True)
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_exception):
logger.log('Unable to get info from %s, the show info will not be refreshed: %s' %
(sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.ERROR)
tvinfo_ep_list = None
else:
raise e
except BaseTVinfoException as e:
logger.log('Unable to get info from %s, the show info will not be refreshed: %s' %
(sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.ERROR)
tvinfo_ep_list = None
if None is tvinfo_ep_list:
logger.log('No data returned from %s, unable to update episodes for show: %s' %

162
sickbeard/tv.py

@ -54,7 +54,7 @@ from .name_parser.parser import NameParser, InvalidNameException, InvalidShowExc
from .helpers import try_int, try_float
from .indexermapper import del_mapping, save_mapping, MapStatus
from .indexers.indexer_config import TVINFO_TVDB, TVINFO_TVRAGE
from .indexers.indexer_exceptions import BaseTVinfoAttributenotfound, check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
from .sgdatetime import SGDatetime
from .tv_base import TVEpisodeBase, TVShowBase
@ -63,7 +63,8 @@ from six import integer_types, iteritems, itervalues, string_types
# noinspection PyUnreachableCode
if False:
from typing import AnyStr, Dict, List, Optional, Text
from typing import Any, AnyStr, Dict, List, Optional, Text
from tvinfo_base import TVInfoEpisode
concurrent_show_not_found_days = 7
@ -449,7 +450,7 @@ class TVShow(TVShowBase):
:return: List of TVEpisode objects
:rtype: List[TVEpisode]
"""
sql_selection = 'SELECT season, episode'
sql_selection = 'SELECT *'
if check_related_eps:
# subselection to detect multi-episodes early, share_location > 0
@ -475,7 +476,7 @@ class TVShow(TVShowBase):
ep_obj_list = []
for cur_result in results:
ep_obj = self.get_episode(int(cur_result['season']), int(cur_result['episode']))
ep_obj = self.get_episode(int(cur_result['season']), int(cur_result['episode']), ep_sql=[cur_result])
if ep_obj:
ep_obj.related_ep_obj = []
if check_related_eps and ep_obj.location:
@ -491,7 +492,8 @@ class TVShow(TVShowBase):
ep_obj.season, ep_obj.location, ep_obj.episode])
for cur_ep_result in related_ep_result:
related_ep_obj = self.get_episode(int(cur_ep_result['season']),
int(cur_ep_result['episode']))
int(cur_ep_result['episode']),
ep_sql=[cur_ep_result])
if related_ep_obj not in ep_obj.related_ep_obj:
ep_obj.related_ep_obj.append(related_ep_obj)
ep_obj_list.append(ep_obj)
@ -663,7 +665,7 @@ class TVShow(TVShowBase):
my_db = db.DBConnection()
# noinspection SqlResolve
sql_result = my_db.select(
'SELECT season, episode FROM tv_episodes'
'SELECT * FROM tv_episodes'
' WHERE indexer = ? AND showid = ?'
' AND location != ""',
[self.tvid, self.prodid])
@ -674,7 +676,7 @@ class TVShow(TVShowBase):
continue
logger.log('%s: Retrieving/creating episode %sx%s'
% (self.tvid_prodid, cur_result['season'], cur_result['episode']), logger.DEBUG)
ep_obj = self.get_episode(cur_result['season'], cur_result['episode'])
ep_obj = self.get_episode(cur_result['season'], cur_result['episode'], ep_sql=[cur_result])
if not ep_obj.related_ep_obj:
processed += [(cur_result['season'], cur_result['episode'])]
else:
@ -783,7 +785,7 @@ class TVShow(TVShowBase):
my_db = db.DBConnection()
sql_result = my_db.select(
'SELECT season, episode FROM tv_episodes'
'SELECT * FROM tv_episodes'
' WHERE indexer = ? AND showid = ?',
[self.tvid, self.prodid])
@ -801,16 +803,17 @@ class TVShow(TVShowBase):
cachedShow = None
try:
cachedShow = t[self.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('Unable to find cached seasons from %s: %s' % (
sickbeard.TVInfoAPI(self.tvid).name, ex(e)), logger.WARNING)
else:
raise e
cachedShow = t.get_show(self.prodid)
except BaseTVinfoError as e:
logger.log('Unable to find cached seasons from %s: %s' % (
sickbeard.TVInfoAPI(self.tvid).name, ex(e)), logger.WARNING)
if None is cachedShow:
return scannedEps
scene_sql = my_db.select('SELECT * FROM scene_numbering WHERE indexer == ? AND indexer_id = ? ',
[self.tvid, self.prodid])
cachedSeasons = {}
for cur_result in sql_result:
@ -822,13 +825,10 @@ class TVShow(TVShowBase):
if season not in cachedSeasons:
try:
cachedSeasons[season] = cachedShow[season]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_seasonnotfound):
logger.log('Error when trying to load the episode for [%s] from %s: %s' %
(self.name, sickbeard.TVInfoAPI(self.tvid).name, ex(e)), logger.WARNING)
delete_ep = True
else:
raise e
except BaseTVinfoSeasonnotfound as e:
logger.log('Error when trying to load the episode for [%s] from %s: %s' %
(self.name, sickbeard.TVInfoAPI(self.tvid).name, ex(e)), logger.WARNING)
delete_ep = True
if season not in scannedEps:
scannedEps[season] = {}
@ -836,14 +836,14 @@ class TVShow(TVShowBase):
logger.log('Loading episode %sx%s for [%s] from the DB' % (season, episode, self.name), logger.DEBUG)
try:
ep_obj = self.get_episode(season, episode)
ep_obj = self.get_episode(season, episode, ep_sql=[cur_result]) # type: TVEpisode
# if we found out that the ep is no longer on TVDB then delete it from our database too
if delete_ep and helpers.should_delete_episode(ep_obj.status):
ep_obj.delete_episode()
ep_obj.load_from_db(season, episode)
ep_obj.load_from_tvinfo(tvapi=t, update=update)
ep_obj.load_from_db(season, episode, show_sql=[cur_result], scene_sql=scene_sql)
ep_obj.load_from_tvinfo(tvapi=t, update=update, cached_show=cachedShow)
scannedEps[season][episode] = True
except exceptions_helper.EpisodeDeletedException:
logger.log('Tried loading an episode that should have been deleted from the DB [%s], skipping it'
@ -876,18 +876,18 @@ class TVShow(TVShowBase):
try:
t = sickbeard.TVInfoAPI(self.tvid).setup(**tvinfo_config)
show_obj = t[self.prodid]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error):
logger.log('%s timed out, unable to update episodes for [%s] from %s' %
(sickbeard.TVInfoAPI(self.tvid).name, self.name, sickbeard.TVInfoAPI(self.tvid).name),
logger.ERROR)
return None
else:
raise e
show_obj = t.get_show(self.prodid)
except BaseTVinfoError as e:
logger.log('%s timed out, unable to update episodes for [%s] from %s' %
(sickbeard.TVInfoAPI(self.tvid).name, self.name, sickbeard.TVInfoAPI(self.tvid).name),
logger.ERROR)
return None
scannedEps = {}
my_db = db.DBConnection()
show_sql = my_db.select('SELECT * FROM tv_episodes WHERE indexer = ? AND showid = ?',
[self._tvid, self._prodid])
sql_l = []
for season in show_obj:
scannedEps[season] = {}
@ -896,14 +896,14 @@ class TVShow(TVShowBase):
if 0 == episode:
continue
try:
ep_obj = self.get_episode(season, episode)
ep_obj = self.get_episode(season, episode, ep_sql=show_sql) # type: TVEpisode
except exceptions_helper.EpisodeNotFoundException:
logger.log('%s: %s object for %sx%s from [%s] is incomplete, skipping this episode' %
(self.tvid_prodid, sickbeard.TVInfoAPI(self.tvid).name, season, episode, self.name))
continue
else:
try:
ep_obj.load_from_tvinfo(tvapi=t, update=update)
ep_obj.load_from_tvinfo(tvapi=t, update=update, cached_show=show_obj)
except exceptions_helper.EpisodeDeletedException:
logger.log('The episode from [%s] was deleted, skipping the rest of the load' % self.name)
continue
@ -912,7 +912,7 @@ class TVShow(TVShowBase):
logger.log('%s: Loading info from %s for episode %sx%s from [%s]' %
(self.tvid_prodid, sickbeard.TVInfoAPI(self.tvid).name, season, episode, self.name),
logger.DEBUG)
ep_obj.load_from_tvinfo(season, episode, tvapi=t, update=update)
ep_obj.load_from_tvinfo(season, episode, tvapi=t, update=update, cached_show=show_obj)
result = ep_obj.get_sql()
if None is not result:
@ -1130,7 +1130,7 @@ class TVShow(TVShowBase):
if self.lang:
tvinfo_config['language'] = self.lang
t = sickbeard.TVInfoAPI(self.tvid).setup(**tvinfo_config)
cached_show = t[self.prodid]
cached_show = t.get_show(self.prodid, load_episodes=False)
vals = (self.prodid, '' if not cached_show else ' [%s]' % cached_show['seriesname'].strip())
if 0 != len(sql_result):
logger.log('%s: Loading show info%s from database' % vals)
@ -1275,7 +1275,7 @@ class TVShow(TVShowBase):
else:
t = tvapi
ep_info = t[self.prodid, False]
ep_info = t.get_show(self.prodid, load_episodes=False)
if None is ep_info or getattr(t, 'show_not_found', False):
if getattr(t, 'show_not_found', False):
self.inc_not_found_count()
@ -1582,7 +1582,7 @@ class TVShow(TVShowBase):
my_db = db.DBConnection()
# noinspection SqlResolve
sql_result = my_db.select(
'SELECT season, episode, location'
'SELECT *'
' FROM tv_episodes'
' WHERE indexer = ? AND showid = ? AND location != ""'
' ORDER BY season DESC , episode DESC',
@ -1598,7 +1598,7 @@ class TVShow(TVShowBase):
location = ek.ek(os.path.normpath, cur_result['location'])
try:
ep_obj = self.get_episode(season, episode)
ep_obj = self.get_episode(season, episode, ep_sql=[cur_result])
except exceptions_helper.EpisodeDeletedException:
logger.log('The episode from [%s] was deleted while we were refreshing it, moving on to the next one'
% self.name, logger.DEBUG)
@ -2223,7 +2223,7 @@ class TVEpisode(TVEpisodeBase):
raise exceptions_helper.EpisodeNotFoundException(
'Couldn\'t find episode %sx%s' % (season, episode))
def load_from_db(self, season, episode, show_sql=None):
def load_from_db(self, season, episode, show_sql=None, scene_sql=None):
"""
:param season: season number
@ -2240,7 +2240,11 @@ class TVEpisode(TVEpisodeBase):
sql_result = None
if show_sql:
sql_result = [s for s in show_sql if episode == s['episode'] and season == s['season']]
for s in show_sql:
if episode == s['episode'] and season == s['season']:
sql_result = [s]
break
if not sql_result:
my_db = db.DBConnection()
sql_result = my_db.select('SELECT *'
@ -2280,7 +2284,7 @@ class TVEpisode(TVEpisodeBase):
self._status = int(sql_result[0]['status'])
# don't overwrite my location
if sql_result[0]['location'] and sql_result[0]['location']:
if sql_result[0]['location']:
self.location = ek.ek(os.path.normpath, sql_result[0]['location'])
if sql_result[0]['file_size']:
self._file_size = int(sql_result[0]['file_size'])
@ -2312,11 +2316,12 @@ class TVEpisode(TVEpisodeBase):
self.scene_absolute_number = sickbeard.scene_numbering.get_scene_absolute_numbering(
self.show_obj.tvid, self.show_obj.prodid,
absolute_number=self.absolute_number,
season=self.season, episode=episode)
season=self.season, episode=episode, show_sql=show_sql, scene_sql=scene_sql, show_obj=self.show_obj)
if 0 == self.scene_season or 0 == self.scene_episode:
self.scene_season, self.scene_episode = sickbeard.scene_numbering.get_scene_numbering(
self.show_obj.tvid, self.show_obj.prodid, self.season, self.episode)
self.show_obj.tvid, self.show_obj.prodid, self.season, self.episode,
show_sql=show_sql, scene_sql=scene_sql, show_obj=self.show_obj)
if None is not sql_result[0]['release_name']:
self._release_name = sql_result[0]['release_name']
@ -2333,7 +2338,9 @@ class TVEpisode(TVEpisodeBase):
self.dirty = False
return True
def load_from_tvinfo(self, season=None, episode=None, cache=True, tvapi=None, cached_season=None, update=False):
def load_from_tvinfo(self, season=None, episode=None, cache=True, tvapi=None, cached_season=None, update=False,
cached_show=None):
# type: (integer_types, integer_types, bool, Any, Dict, bool, Dict) -> Optional[bool]
"""
:param season: season number
@ -2348,6 +2355,7 @@ class TVEpisode(TVEpisodeBase):
:type cached_season:
:param update:
:type update: bool
:param cached_show:
:return:
:rtype: bool or None
"""
@ -2363,7 +2371,9 @@ class TVEpisode(TVEpisodeBase):
show_lang = self.show_obj.lang
try:
if None is cached_season:
if cached_show:
ep_info = cached_show[season][episode]
elif None is cached_season:
if None is tvapi:
tvinfo_config = sickbeard.TVInfoAPI(self.tvid).api_params.copy()
@ -2379,36 +2389,32 @@ class TVEpisode(TVEpisodeBase):
t = sickbeard.TVInfoAPI(self.tvid).setup(**tvinfo_config)
else:
t = tvapi
ep_info = t[self.show_obj.prodid][season][episode]
ep_info = t.get_show(self.show_obj.prodid)[season][episode] # type: TVInfoEpisode
else:
ep_info = cached_season[episode]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_error, IOError):
logger.log('%s threw up an error: %s' % (sickbeard.TVInfoAPI(self.tvid).name, ex(e)), logger.DEBUG)
# if the episode is already valid just log it, if not throw it up
if UNKNOWN == self.status:
self.status = SKIPPED
if self.name:
logger.log('%s timed out but we have enough info from other sources, allowing the error' %
sickbeard.TVInfoAPI(self.tvid).name, logger.DEBUG)
return
else:
logger.log('%s timed out, unable to create the episode' % sickbeard.TVInfoAPI(self.tvid).name,
logger.ERROR)
return False
elif check_exception_type(e, ExceptionTuples.tvinfo_episodenotfound,
ExceptionTuples.tvinfo_seasonnotfound):
logger.log('Unable to find the episode on %s... has it been removed? Should I delete from db?' %
ep_info = cached_season[episode] # type: TVInfoEpisode
except (BaseTVinfoEpisodenotfound, BaseTVinfoSeasonnotfound):
logger.log('Unable to find the episode on %s... has it been removed? Should I delete from db?' %
sickbeard.TVInfoAPI(self.tvid).name, logger.DEBUG)
# if I'm no longer on the Indexers but I once was then delete myself from the DB
if -1 != self.epid and helpers.should_delete_episode(self.status):
self.delete_episode()
elif UNKNOWN == self.status:
self.status = SKIPPED
return
except (BaseTVinfoError, IOError) as e:
logger.log('%s threw up an error: %s' % (sickbeard.TVInfoAPI(self.tvid).name, ex(e)), logger.DEBUG)
# if the episode is already valid just log it, if not throw it up
if UNKNOWN == self.status:
self.status = SKIPPED
if self.name:
logger.log('%s timed out but we have enough info from other sources, allowing the error' %
sickbeard.TVInfoAPI(self.tvid).name, logger.DEBUG)
# if I'm no longer on the Indexers but I once was then delete myself from the DB
if -1 != self.epid and helpers.should_delete_episode(self.status):
self.delete_episode()
elif UNKNOWN == self.status:
self.status = SKIPPED
return
else:
raise e
logger.log('%s timed out, unable to create the episode' % sickbeard.TVInfoAPI(self.tvid).name,
logger.ERROR)
return False
if getattr(ep_info, 'absolute_number', None) in (None, ''):
logger.log('This episode (%s - %sx%s) has no absolute number on %s' %
@ -2427,10 +2433,10 @@ class TVEpisode(TVEpisodeBase):
self.scene_absolute_number = sickbeard.scene_numbering.get_scene_absolute_numbering(
self.show_obj.tvid, self.show_obj.prodid,
absolute_number=self.absolute_number,
season=self.season, episode=self.episode)
season=self.season, episode=self.episode, show_obj=self.show_obj)
self.scene_season, self.scene_episode = sickbeard.scene_numbering.get_scene_numbering(
self.show_obj.tvid, self.show_obj.prodid, self.season, self.episode)
self.show_obj.tvid, self.show_obj.prodid, self.season, self.episode, show_obj=self.show_obj)
self.description = self.dict_prevent_nonetype(ep_info, 'overview')
@ -2616,10 +2622,10 @@ class TVEpisode(TVEpisodeBase):
self.scene_absolute_number = sickbeard.scene_numbering.get_scene_absolute_numbering(
self.show_obj.tvid, self.show_obj.prodid,
absolute_number=self.absolute_number,
season=self.season, episode=self.episode)
season=self.season, episode=self.episode, show_obj=self.show_obj)
self.scene_season, self.scene_episode = sickbeard.scene_numbering.get_scene_numbering(
self.show_obj.tvid, self.show_obj.prodid, self.season, self.episode)
self.show_obj.tvid, self.show_obj.prodid, self.season, self.episode, show_obj=self.show_obj)
self.description = epDetails.findtext('plot')
if None is self.description:

29
sickbeard/webapi.py

@ -49,7 +49,7 @@ from .common import ARCHIVED, DOWNLOADED, IGNORED, SKIPPED, SNATCHED, SNATCHED_A
from .helpers import remove_article
from .indexers import indexer_api, indexer_config
from .indexers.indexer_config import *
from .indexers.indexer_exceptions import check_exception_type, ExceptionTuples
from tvinfo_base.exceptions import *
from .scene_numbering import set_scene_numbering_helper
from .search_backlog import FORCED_BACKLOG
from .sgdatetime import SGDatetime
@ -2569,12 +2569,9 @@ class CMD_SickGearSearchIndexers(ApiCall):
try:
show_info = t[int(self.prodid), False]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound, ExceptionTuples.tvinfo_error):
self.log(u"Unable to find show with id " + str(self.prodid), logger.WARNING)
return _responds(RESULT_SUCCESS, {"results": [], "langid": lang_id})
else:
raise e
except BaseTVinfoError as e:
self.log(u"Unable to find show with id " + str(self.prodid), logger.WARNING)
return _responds(RESULT_SUCCESS, {"results": [], "langid": lang_id})
if not show_info.data['seriesname']:
self.log(
@ -3234,12 +3231,9 @@ class CMD_SickGearShowAddExisting(ApiCall):
try:
myShow = t[int(self.prodid), False]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound, ExceptionTuples.tvinfo_error):
self.log(u"Unable to find show with id " + str(self.tvid), logger.WARNING)
return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer")
else:
raise e
except BaseTVinfoError as e:
self.log(u"Unable to find show with id " + str(self.tvid), logger.WARNING)
return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer")
indexerName = None
if not myShow.data['seriesname']:
@ -3397,12 +3391,9 @@ class CMD_SickGearShowAddNew(ApiCall):
try:
myShow = t[int(self.prodid), False]
except Exception as e:
if check_exception_type(e, ExceptionTuples.tvinfo_shownotfound, ExceptionTuples.tvinfo_error):
self.log(u"Unable to find show with id " + str(self.tvid), logger.WARNING)
return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer")
else:
raise e
except BaseTVinfoError as e:
self.log(u"Unable to find show with id " + str(self.tvid), logger.WARNING)
return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer")
indexerName = None
if not myShow.data['seriesname']:

12
sickbeard/webserve.py

@ -83,7 +83,7 @@ from lib.libtrakt.exceptions import TraktException, TraktAuthException
from lib.libtrakt.indexerapiinterface import TraktSearchTypes
# noinspection PyPep8Naming
from lib import tmdbsimple as TMDB
from lib.tvdb_api.tvdb_exceptions import TvdbException
from tvinfo_base import BaseTVinfoException
import lib.rarfile.rarfile as rarfile
@ -3365,7 +3365,7 @@ class AddShows(Home):
tvinfo_config['search_type'] = (TraktSearchTypes.tvdb_id, TraktSearchTypes.imdb_id)['tt' in search_id]
t = sickbeard.TVInfoAPI(trakt_id).setup(**tvinfo_config)
resp = t[search_id][0]
resp = t.search_show(search_id)[0]
search_term = resp['seriesname']
tvdb_prodid = resp['ids']['tvdb']
trakt_prodid = resp['ids'].get('trakt')
@ -3387,7 +3387,7 @@ class AddShows(Home):
if bool(tvdb_prodid):
logger.log('Fetching show using id: %s (%s) from tv datasource %s' % (
search_id, search_term, sickbeard.TVInfoAPI(cur_tvid).name), logger.DEBUG)
r = t[tvdb_prodid, False]
r = t.get_show(tvdb_prodid, load_episodes=False)
results.setdefault((cur_tvid, trakt_id)['tt' in search_id], {})[int(tvdb_prodid)] = {
'id': tvdb_prodid, 'seriesname': r['seriesname'], 'firstaired': r['firstaired'],
'network': r['network'], 'overview': r['overview'],
@ -3401,13 +3401,13 @@ class AddShows(Home):
results.setdefault(cur_tvid, {})
for term in terms:
try:
for r in t[term]:
for r in t.search_show(term):
tvdb_prodid = int(r['id'])
if tvdb_prodid not in results[cur_tvid]:
results.setdefault(cur_tvid, {})[tvdb_prodid] = r.copy()
elif r['seriesname'] != results[cur_tvid][tvdb_prodid]['seriesname']:
results[cur_tvid][tvdb_prodid].setdefault('aliases', []).append(r['seriesname'])
except TvdbException:
except BaseTVinfoException:
pass
except (BaseException, Exception):
pass
@ -3424,7 +3424,7 @@ class AddShows(Home):
t = sickbeard.TVInfoAPI(trakt_id).setup(**tvinfo_config)
for term in terms:
result = t[term]
result = t.search_show(term)
resp += result
match = False
for r in result:

Loading…
Cancel
Save