mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-05 17:43:37 +00:00
Change use TVDb API v2.
Add complete actor parsing into _actors. Make returned _banners compatible with old v1 api lib. Add all poster + fanart loading (optional) into poster, fanart subdicts of _banners subdict. Depending on requested image_type set fanart or banners to true. Readd banners parameter to tvdb lib. Add new parameter fanart to tvdb lib. If no firstaired for an episode return None. Remove old imports.
This commit is contained in:
parent
a8fd974afd
commit
06599ac708
6 changed files with 194 additions and 232 deletions
|
@ -57,6 +57,7 @@
|
|||
* Add persistent hide/unhide cards to Add show/Trakt and Add show/IMDb Cards
|
||||
* Change simplify dropdowns at all Add show/Cards
|
||||
* Change cosmetic title on shutdown
|
||||
* Change use TVDb API v2
|
||||
|
||||
|
||||
[develop changelog]
|
||||
|
|
|
@ -5,40 +5,36 @@
|
|||
# repository:http://github.com/dbr/tvdb_api
|
||||
# license:unlicense (http://unlicense.org/)
|
||||
|
||||
import traceback
|
||||
from functools import wraps
|
||||
|
||||
__author__ = 'dbr/Ben'
|
||||
__version__ = '1.9'
|
||||
__version__ = '2.0'
|
||||
__api_version__ = '2.1.2'
|
||||
|
||||
import os
|
||||
import time
|
||||
import getpass
|
||||
import StringIO
|
||||
import tempfile
|
||||
import warnings
|
||||
import logging
|
||||
import zipfile
|
||||
import requests
|
||||
import requests.exceptions
|
||||
|
||||
try:
|
||||
import gzip
|
||||
except ImportError:
|
||||
gzip = None
|
||||
import datetime
|
||||
from sickbeard.helpers import getURL
|
||||
|
||||
from lib.dateutil.parser import parse
|
||||
from lib.cachecontrol import CacheControl, caches
|
||||
|
||||
from lib.etreetodict import ConvertXmlToDict
|
||||
from tvdb_ui import BaseUI, ConsoleUI
|
||||
from tvdb_exceptions import (tvdb_error, tvdb_shownotfound,
|
||||
tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound)
|
||||
|
||||
from sickbeard import logger
|
||||
|
||||
def log():
|
||||
return logging.getLogger('tvdb_api')
|
||||
|
||||
|
||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logr=None):
|
||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
||||
"""Retry calling the decorated function using an exponential backoff.
|
||||
|
||||
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
|
||||
|
@ -54,8 +50,8 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logr=None):
|
|||
:param backoff: backoff multiplier e.g. value of 2 will double the delay
|
||||
each retry
|
||||
:type backoff: int
|
||||
:param logr: logger to use. If None, print
|
||||
:type logr: logging.Logger instance
|
||||
:param logger: logger to use. If None, print
|
||||
:type logger: logging.Logger instance
|
||||
"""
|
||||
|
||||
def deco_retry(f):
|
||||
|
@ -67,9 +63,9 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logr=None):
|
|||
try:
|
||||
return f(*args, **kwargs)
|
||||
except ExceptionToCheck, e:
|
||||
msg = 'TVDB_API :: %s, Retrying in %d seconds...' % (str(e), mdelay)
|
||||
if logr:
|
||||
logger.log(msg, logger.WARNING)
|
||||
msg = '%s, Retrying in %d seconds...' % (str(e), mdelay)
|
||||
if logger:
|
||||
logger.warning(msg)
|
||||
else:
|
||||
print msg
|
||||
time.sleep(mdelay)
|
||||
|
@ -86,7 +82,8 @@ class ShowContainer(dict):
|
|||
"""Simple dict that holds a series of Show instances
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, **kwargs):
|
||||
super(ShowContainer, self).__init__(**kwargs)
|
||||
self._stack = []
|
||||
self._lastgc = time.time()
|
||||
|
||||
|
@ -145,7 +142,7 @@ class Show(dict):
|
|||
# doesn't exist, so attribute error.
|
||||
raise tvdb_attributenotfound('Cannot find attribute %s' % (repr(key)))
|
||||
|
||||
def airedOn(self, date):
|
||||
def aired_on(self, date):
|
||||
ret = self.search(str(date), 'firstaired')
|
||||
if 0 == len(ret):
|
||||
raise tvdb_episodenotfound('Could not find any episodes that aired on %s' % date)
|
||||
|
@ -166,9 +163,9 @@ class Show(dict):
|
|||
Search terms are converted to lower case (unicode) strings.
|
||||
|
||||
# Examples
|
||||
|
||||
|
||||
These examples assume t is an instance of Tvdb():
|
||||
|
||||
|
||||
>> t = Tvdb()
|
||||
>>
|
||||
|
||||
|
@ -212,9 +209,10 @@ class Show(dict):
|
|||
|
||||
|
||||
class Season(dict):
|
||||
def __init__(self, show=None):
|
||||
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):
|
||||
|
@ -251,9 +249,10 @@ class Season(dict):
|
|||
|
||||
|
||||
class Episode(dict):
|
||||
def __init__(self, season=None):
|
||||
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):
|
||||
|
@ -301,7 +300,7 @@ class Episode(dict):
|
|||
raise TypeError('must supply string to search for (contents)')
|
||||
|
||||
term = unicode(term).lower()
|
||||
for cur_key, cur_value in self.items():
|
||||
for cur_key, cur_value in self.iteritems():
|
||||
cur_key, cur_value = unicode(cur_key).lower(), unicode(cur_value).lower()
|
||||
if None is not key and cur_key != key:
|
||||
# Do not search this key
|
||||
|
@ -343,13 +342,12 @@ class Tvdb:
|
|||
debug=False,
|
||||
cache=True,
|
||||
banners=False,
|
||||
fanart=False,
|
||||
actors=False,
|
||||
custom_ui=None,
|
||||
language=None,
|
||||
search_all_languages=False,
|
||||
apikey=None,
|
||||
forceConnect=False,
|
||||
useZip=False,
|
||||
dvdorder=False,
|
||||
proxy=None):
|
||||
|
||||
|
@ -413,16 +411,6 @@ class Tvdb:
|
|||
tvdb_api in a larger application)
|
||||
See http://thetvdb.com/?tab=apiregister to get your own key
|
||||
|
||||
forceConnect (bool):
|
||||
If true it will always try to connect to theTVDB.com even if we
|
||||
recently timed out. By default it will wait one minute before
|
||||
trying again, and any requests within that one minute window will
|
||||
return an exception immediately.
|
||||
|
||||
useZip (bool):
|
||||
Download the zip archive where possibale, instead of the xml.
|
||||
This is only used when all episodes are pulled.
|
||||
And only the main language xml is used, the actor and banner xml are lost.
|
||||
"""
|
||||
|
||||
self.shows = ShowContainer() # Holds all Show classes
|
||||
|
@ -435,6 +423,8 @@ class Tvdb:
|
|||
else:
|
||||
self.config['apikey'] = '0629B785CE550C8D' # tvdb_api's API key
|
||||
|
||||
self.token = {'token': None, 'datetime': datetime.datetime.fromordinal(1)}
|
||||
|
||||
self.config['debug_enabled'] = debug # show debugging messages
|
||||
|
||||
self.config['custom_ui'] = custom_ui
|
||||
|
@ -445,8 +435,6 @@ class Tvdb:
|
|||
|
||||
self.config['search_all_languages'] = search_all_languages
|
||||
|
||||
self.config['useZip'] = useZip
|
||||
|
||||
self.config['dvdorder'] = dvdorder
|
||||
|
||||
self.config['proxy'] = proxy
|
||||
|
@ -463,6 +451,7 @@ class Tvdb:
|
|||
raise ValueError('Invalid value for Cache %r (type was %s)' % (cache, type(cache)))
|
||||
|
||||
self.config['banners_enabled'] = banners
|
||||
self.config['fanart_enabled'] = fanart
|
||||
self.config['actors_enabled'] = actors
|
||||
|
||||
if self.config['debug_enabled']:
|
||||
|
@ -498,26 +487,37 @@ class Tvdb:
|
|||
|
||||
# The following url_ configs are based of the
|
||||
# http://thetvdb.com/wiki/index.php/Programmers_API
|
||||
self.config['base_url'] = 'http://thetvdb.com'
|
||||
self.config['base_url'] = 'https://api.thetvdb.com/'
|
||||
|
||||
if self.config['search_all_languages']:
|
||||
self.config['url_get_series'] = u'%(base_url)s/api/GetSeries.php' % self.config
|
||||
self.config['params_get_series'] = {'seriesname': '', 'language': 'all'}
|
||||
else:
|
||||
self.config['url_get_series'] = u'%(base_url)s/api/GetSeries.php' % self.config
|
||||
self.config['params_get_series'] = {'seriesname': '', 'language': self.config['language']}
|
||||
self.config['url_get_series'] = '%(base_url)s/search/series' % self.config
|
||||
self.config['params_get_series'] = {'name': ''}
|
||||
|
||||
self.config['url_epInfo'] = u'%(base_url)s/api/%(apikey)s/series/%%s/all/%%s.xml' % self.config
|
||||
self.config['url_epInfo_zip'] = u'%(base_url)s/api/%(apikey)s/series/%%s/all/%%s.zip' % self.config
|
||||
self.config['url_epInfo'] = '%(base_url)sseries/%%s/episodes?page=%%s' % self.config
|
||||
|
||||
self.config['url_seriesInfo'] = u'%(base_url)s/api/%(apikey)s/series/%%s/%%s.xml' % self.config
|
||||
self.config['url_actorsInfo'] = u'%(base_url)s/api/%(apikey)s/series/%%s/actors.xml' % self.config
|
||||
self.config['url_seriesInfo'] = '%(base_url)sseries/%%s' % self.config
|
||||
self.config['url_actorsInfo'] = '%(base_url)sseries/%%s/actors' % self.config
|
||||
|
||||
self.config['url_seriesBanner'] = u'%(base_url)s/api/%(apikey)s/series/%%s/banners.xml' % self.config
|
||||
self.config['url_artworkPrefix'] = u'%(base_url)s/banners/%%s' % self.config
|
||||
self.config['url_seriesBanner'] = '%(base_url)sseries/%%s/images/query?keyType=%%s' % self.config
|
||||
self.config['url_artworkPrefix'] = 'https://thetvdb.com/banners/%s'
|
||||
|
||||
def log(self, msg, log_level=logger.DEBUG):
|
||||
logger.log('TVDB_API :: %s' % (msg.replace(self.config['apikey'], '<apikey>')), log_level=log_level)
|
||||
def get_new_token(self):
|
||||
token = None
|
||||
url = '%s%s' % (self.config['base_url'], 'login')
|
||||
params = {'apikey': self.config['apikey']}
|
||||
resp = getURL(url.strip(), post_json=params, json=True)
|
||||
if resp:
|
||||
if 'token' in resp:
|
||||
token = resp['token']
|
||||
|
||||
return {'token': token, 'datetime': datetime.datetime.now()}
|
||||
|
||||
def get_token(self):
|
||||
if self.token.get('token') is None or datetime.datetime.now() - self.token.get(
|
||||
'datetime', datetime.datetime.fromordinal(1)) > datetime.timedelta(hours=23):
|
||||
self.token = self.get_new_token()
|
||||
if not self.token.get('token'):
|
||||
raise tvdb_error('Could not get Authentification Token')
|
||||
return self.token.get('token')
|
||||
|
||||
@staticmethod
|
||||
def _get_temp_dir():
|
||||
|
@ -537,7 +537,7 @@ class Tvdb:
|
|||
|
||||
@retry(tvdb_error)
|
||||
def _load_url(self, url, params=None, language=None):
|
||||
self.log('Retrieving URL %s' % url)
|
||||
log().debug('Retrieving URL %s' % url)
|
||||
|
||||
session = requests.session()
|
||||
|
||||
|
@ -545,56 +545,60 @@ class Tvdb:
|
|||
session = CacheControl(session, cache=caches.FileCache(self.config['cache_location']))
|
||||
|
||||
if self.config['proxy']:
|
||||
self.log('Using proxy for URL: %s' % url)
|
||||
log().debug('Using proxy for URL: %s' % url)
|
||||
session.proxies = {'http': self.config['proxy'], 'https': self.config['proxy']}
|
||||
|
||||
session.headers.update({'Accept-Encoding': 'gzip,deflate'})
|
||||
session.headers.update({'Accept-Encoding': 'gzip,deflate', 'Authorization': 'Bearer %s' % self.get_token(),
|
||||
'Accept': 'application/vnd.thetvdb.v%s' % __api_version__})
|
||||
|
||||
try:
|
||||
resp = session.get(url.strip(), params=params)
|
||||
except requests.exceptions.HTTPError, e:
|
||||
raise tvdb_error('HTTP error %s while loading URL %s' % (e.errno, url))
|
||||
except requests.exceptions.ConnectionError, e:
|
||||
raise tvdb_error('Connection error %s while loading URL %s' % (e.message, url))
|
||||
except requests.exceptions.Timeout, e:
|
||||
raise tvdb_error('Connection timed out %s while loading URL %s' % (e.message, url))
|
||||
except Exception:
|
||||
raise tvdb_error('Unknown exception while loading URL %s: %s' % (url, traceback.format_exc()))
|
||||
if None is not language and language in self.config['valid_languages']:
|
||||
session.headers.update({'Accept-Language': language})
|
||||
|
||||
def process_data(data):
|
||||
te = ConvertXmlToDict(data)
|
||||
if isinstance(te, dict) and 'Data' in te and isinstance(te['Data'], dict) \
|
||||
and 'Series' in te['Data'] and isinstance(te['Data']['Series'], dict) \
|
||||
and 'FirstAired' in te['Data']['Series']:
|
||||
try:
|
||||
value = parse(te['Data']['Series']['FirstAired'], fuzzy=True).strftime('%Y-%m-%d')
|
||||
except (StandardError, Exception):
|
||||
value = None
|
||||
te['Data']['Series']['firstaired'] = value
|
||||
return te
|
||||
resp = getURL(url.strip(), params=params, session=session, json=True)
|
||||
|
||||
if resp.ok:
|
||||
if 'application/zip' in resp.headers.get('Content-Type', ''):
|
||||
try:
|
||||
# TODO: The zip contains actors.xml and banners.xml, which are currently ignored [GH-20]
|
||||
self.log('We received a zip file unpacking now ...')
|
||||
zipdata = StringIO.StringIO()
|
||||
zipdata.write(resp.content)
|
||||
myzipfile = zipfile.ZipFile(zipdata)
|
||||
return process_data(myzipfile.read('%s.xml' % language))
|
||||
except zipfile.BadZipfile:
|
||||
raise tvdb_error('Bad zip file received from thetvdb.com, could not read it')
|
||||
else:
|
||||
try:
|
||||
return process_data(resp.content.strip())
|
||||
except (StandardError, Exception):
|
||||
return dict([(u'data', None)])
|
||||
map_show = {'airstime': 'airs_time', 'airsdayofweek': 'airs_dayofweek', 'imdbid': 'imdb_id'}
|
||||
|
||||
def map_show_keys(data):
|
||||
for k, v in data.iteritems():
|
||||
k_org = k
|
||||
k = k.lower()
|
||||
if None is not v:
|
||||
if k in ['banner', 'fanart', 'poster']:
|
||||
v = self.config['url_artworkPrefix'] % v
|
||||
elif 'genre' == k:
|
||||
v = '|%s|' % '|'.join([self._clean_data(c) for c in v if isinstance(c, basestring)])
|
||||
elif 'firstaired' == k:
|
||||
if v:
|
||||
try:
|
||||
v = parse(v, fuzzy=True).strftime('%Y-%m-%d')
|
||||
except (StandardError, Exception):
|
||||
v = None
|
||||
else:
|
||||
v = None
|
||||
else:
|
||||
v = self._clean_data(v)
|
||||
if k in map_show:
|
||||
k = map_show[k]
|
||||
if k_org is not k:
|
||||
del(data[k_org])
|
||||
data[k] = v
|
||||
return data
|
||||
|
||||
if resp:
|
||||
if isinstance(resp['data'], dict):
|
||||
resp['data'] = map_show_keys(resp['data'])
|
||||
elif isinstance(resp['data'], list):
|
||||
for idx, row in enumerate(resp['data']):
|
||||
if isinstance(row, dict):
|
||||
resp['data'][idx] = map_show_keys(row)
|
||||
return resp
|
||||
return dict([(u'data', None)])
|
||||
|
||||
def _getetsrc(self, url, params=None, language=None):
|
||||
"""Loads a URL using caching, returns an ElementTree of the source
|
||||
"""Loads a URL using caching
|
||||
"""
|
||||
try:
|
||||
src = self._load_url(url, params=params, language=language).values()[0]
|
||||
src = self._load_url(url, params=params, language=language)
|
||||
return src
|
||||
except (StandardError, Exception):
|
||||
return []
|
||||
|
@ -622,12 +626,15 @@ class Tvdb:
|
|||
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):
|
||||
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()
|
||||
self.shows[sid].data[key] = value
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def _clean_data(data):
|
||||
|
@ -639,23 +646,18 @@ class Tvdb:
|
|||
"""
|
||||
return data if not isinstance(data, basestring) else data.strip().replace(u'&', u'&')
|
||||
|
||||
def _get_url_artwork(self, image):
|
||||
return image and (self.config['url_artworkPrefix'] % image) or image
|
||||
|
||||
def search(self, series):
|
||||
"""This searches TheTVDB.com for the series name
|
||||
and returns the result list
|
||||
"""
|
||||
series = series.encode('utf-8')
|
||||
self.log('Searching for show %s' % series)
|
||||
self.config['params_get_series']['seriesname'] = series
|
||||
self.config['params_get_series']['name'] = series
|
||||
log().debug('Searching for show %s' % series)
|
||||
|
||||
try:
|
||||
series_found = self._getetsrc(self.config['url_get_series'], self.config['params_get_series'])
|
||||
series_found = self._getetsrc(self.config['url_get_series'], params=self.config['params_get_series'],
|
||||
language=self.config['language'])
|
||||
if series_found:
|
||||
if not isinstance(series_found['Series'], list):
|
||||
series_found['Series'] = [series_found['Series']]
|
||||
series_found['Series'] = [{k.lower(): v for k, v in s.iteritems()} for s in series_found['Series']]
|
||||
return series_found.values()[0]
|
||||
except (StandardError, Exception):
|
||||
pass
|
||||
|
@ -673,50 +675,31 @@ class Tvdb:
|
|||
all_series = [all_series]
|
||||
|
||||
if 0 == len(all_series):
|
||||
self.log('Series result returned zero')
|
||||
log().debug('Series result returned zero')
|
||||
raise tvdb_shownotfound('Show-name search returned zero results (cannot find show on TVDB)')
|
||||
|
||||
if None is not self.config['custom_ui']:
|
||||
self.log('Using custom UI %s' % (repr(self.config['custom_ui'])))
|
||||
log().debug('Using custom UI %s' % (repr(self.config['custom_ui'])))
|
||||
custom_ui = self.config['custom_ui']
|
||||
ui = custom_ui(config=self.config)
|
||||
else:
|
||||
if not self.config['interactive']:
|
||||
self.log('Auto-selecting first search result using BaseUI')
|
||||
log().debug('Auto-selecting first search result using BaseUI')
|
||||
ui = BaseUI(config=self.config)
|
||||
else:
|
||||
self.log('Interactively selecting show using ConsoleUI')
|
||||
log().debug('Interactively selecting show using ConsoleUI')
|
||||
ui = ConsoleUI(config=self.config)
|
||||
|
||||
return ui.selectSeries(all_series)
|
||||
|
||||
def _parse_banners(self, sid):
|
||||
"""Parses banners XML, from
|
||||
http://thetvdb.com/api/[APIKEY]/series/[SERIES ID]/banners.xml
|
||||
|
||||
Banners are retrieved using t['show name]['_banners'], for example:
|
||||
|
||||
>> t = Tvdb(banners = True)
|
||||
>> t['scrubs']['_banners'].keys()
|
||||
['fanart', 'poster', 'series', 'season']
|
||||
>> t['scrubs']['_banners']['poster']['680x1000']['35308']['_bannerpath']
|
||||
u'http://thetvdb.com/banners/posters/76156-2.jpg'
|
||||
>>
|
||||
|
||||
Any key starting with an underscore has been processed (not the raw
|
||||
data from the XML)
|
||||
|
||||
This interface will be improved in future versions.
|
||||
"""
|
||||
self.log('Getting season banners for %s' % sid)
|
||||
banners_et = self._getetsrc(self.config['url_seriesBanner'] % sid)
|
||||
def _parse_banners(self, sid, img_list):
|
||||
banners = {}
|
||||
|
||||
try:
|
||||
for cur_banner in banners_et['banner']:
|
||||
for cur_banner in img_list:
|
||||
bid = cur_banner['id']
|
||||
btype = cur_banner['bannertype']
|
||||
btype2 = cur_banner['bannertype2']
|
||||
btype = cur_banner['keytype']
|
||||
btype2 = cur_banner['resolution']
|
||||
if None is btype or None is btype2:
|
||||
continue
|
||||
if btype not in banners:
|
||||
|
@ -726,60 +709,37 @@ class Tvdb:
|
|||
if bid not in banners[btype][btype2]:
|
||||
banners[btype][btype2][bid] = {}
|
||||
|
||||
for k, v in cur_banner.items():
|
||||
for k, v in cur_banner.iteritems():
|
||||
if None is k or None is v:
|
||||
continue
|
||||
|
||||
k, v = k.lower(), v.lower()
|
||||
k, v = k.lower(), v.lower() if isinstance(v, (str, unicode)) else v
|
||||
if k == 'filename':
|
||||
k = 'bannerpath'
|
||||
banners[btype][btype2][bid]['_bannerpath'] = self.config['url_artworkPrefix'] % v
|
||||
elif k == 'thumbnail':
|
||||
k = 'thumbnailpath'
|
||||
banners[btype][btype2][bid]['_thumbnailpath'] = self.config['url_artworkPrefix'] % v
|
||||
elif k == 'keytype':
|
||||
k = 'bannertype'
|
||||
banners[btype][btype2][bid][k] = v
|
||||
|
||||
for k, v in banners[btype][btype2][bid].items():
|
||||
if k.endswith('path'):
|
||||
new_key = '_%s' % k
|
||||
self.log('Transforming %s to %s' % (k, new_key))
|
||||
new_url = self._get_url_artwork(v)
|
||||
banners[btype][btype2][bid][new_key] = new_url
|
||||
except (StandardError, Exception):
|
||||
pass
|
||||
|
||||
self._set_show_data(sid, '_banners', banners)
|
||||
self._set_show_data(sid, '_banners', banners, add=True)
|
||||
|
||||
def _parse_actors(self, sid):
|
||||
"""Parsers actors XML, from
|
||||
http://thetvdb.com/api/[APIKEY]/series/[SERIES ID]/actors.xml
|
||||
|
||||
Actors are retrieved using t['show name]['_actors'], for example:
|
||||
|
||||
>> t = Tvdb(actors = True)
|
||||
>> actors = t['scrubs']['_actors']
|
||||
>> type(actors)
|
||||
<class 'tvdb_api.Actors'>
|
||||
>> type(actors[0])
|
||||
<class 'tvdb_api.Actor'>
|
||||
>> actors[0]
|
||||
<Actor "Zach Braff">
|
||||
>> sorted(actors[0].keys())
|
||||
['id', 'image', 'name', 'role', 'sortorder']
|
||||
>> actors[0]['name']
|
||||
u'Zach Braff'
|
||||
>> actors[0]['image']
|
||||
u'http://thetvdb.com/banners/actors/43640.jpg'
|
||||
|
||||
Any key starting with an underscore has been processed (not the raw
|
||||
data from the XML)
|
||||
"""
|
||||
self.log('Getting actors for %s' % sid)
|
||||
actors_et = self._getetsrc(self.config['url_actorsInfo'] % sid)
|
||||
def _parse_actors(self, sid, actor_list):
|
||||
|
||||
cur_actors = Actors()
|
||||
try:
|
||||
for curActorItem in actors_et['actor']:
|
||||
for curActorItem in actor_list:
|
||||
cur_actor = Actor()
|
||||
for k, v in curActorItem.items():
|
||||
for k, v in curActorItem.iteritems():
|
||||
k = k.lower()
|
||||
if None is not v:
|
||||
if 'image' == k:
|
||||
v = self._get_url_artwork(v)
|
||||
v = self.config['url_artworkPrefix'] % v
|
||||
else:
|
||||
v = self._clean_data(v)
|
||||
cur_actor[k] = v
|
||||
|
@ -795,96 +755,94 @@ class Tvdb:
|
|||
shows[series_id][season_number][episode_number]
|
||||
"""
|
||||
|
||||
if None is self.config['language']:
|
||||
self.log('Config language is none, using show language')
|
||||
if None is language:
|
||||
raise tvdb_error('config[\'language\'] was None, this should not happen')
|
||||
get_show_in_language = language
|
||||
else:
|
||||
self.log('Configured language %s override show language of %s' % (self.config['language'], language))
|
||||
get_show_in_language = self.config['language']
|
||||
|
||||
# Parse show information
|
||||
self.log('Getting all series data for %s' % sid)
|
||||
url = (self.config['url_seriesInfo'] % (sid, language), self.config['url_epInfo%s' % ('', '_zip')[self.config['useZip']]] % (sid, language))[get_ep_info]
|
||||
show_data = self._getetsrc(url, language=get_show_in_language)
|
||||
log().debug('Getting all series data for %s' % sid)
|
||||
url = self.config['url_seriesInfo'] % 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 len(show_data) or (isinstance(show_data, dict) and 'SeriesName' not in show_data['Series']):
|
||||
if not isinstance(show_data, dict) or 'seriesname' not in show_data['data']:
|
||||
return False
|
||||
|
||||
for k, v in show_data['Series'].iteritems():
|
||||
if None is not v:
|
||||
if k in ['banner', 'fanart', 'poster']:
|
||||
v = self._get_url_artwork(v)
|
||||
else:
|
||||
v = self._clean_data(v)
|
||||
for k, v in show_data['data'].iteritems():
|
||||
self._set_show_data(sid, k, v)
|
||||
|
||||
self._set_show_data(sid, k.lower(), v)
|
||||
if self.config['banners_enabled']:
|
||||
poster_data = self._getetsrc(self.config['url_seriesBanner'] % (sid, 'poster'), language=language)
|
||||
self._set_show_data(sid, u'poster',
|
||||
('', self.config['url_artworkPrefix'] % poster_data['data'][0]['filename'])[
|
||||
poster_data and 'data' in poster_data and len(poster_data['data']) > 0])
|
||||
if poster_data and 'data' in poster_data and len(poster_data['data']) > 1:
|
||||
self._parse_banners(sid, poster_data['data'])
|
||||
|
||||
if self.config['fanart_enabled']:
|
||||
fanart_data = self._getetsrc(self.config['url_seriesBanner'] % (sid, 'fanart'), language=language)
|
||||
self._set_show_data(sid, u'fanart',
|
||||
('', self.config['url_artworkPrefix'] % fanart_data['data'][0]['filename'])[
|
||||
fanart_data and 'data' in fanart_data and len(fanart_data['data']) > 0])
|
||||
if fanart_data and 'data' in fanart_data and len(fanart_data['data']) > 1:
|
||||
self._parse_banners(sid, fanart_data['data'])
|
||||
|
||||
if self.config['actors_enabled']:
|
||||
actor_data = self._getetsrc(self.config['url_actorsInfo'] % sid, language=language)
|
||||
self._set_show_data(sid, u'actors',
|
||||
('||', '|%s|' % '|'.join([n.get('name', '') for n in sorted(
|
||||
actor_data['data'], key=lambda x: x['sortorder'])]))[
|
||||
actor_data and 'data' in actor_data and len(actor_data['data']) > 0])
|
||||
if actor_data and 'data' in actor_data and len(actor_data['data']) > 0:
|
||||
self._parse_actors(sid, actor_data['data'])
|
||||
|
||||
if get_ep_info:
|
||||
# Parse banners
|
||||
if self.config['banners_enabled']:
|
||||
self._parse_banners(sid)
|
||||
|
||||
# Parse actors
|
||||
if self.config['actors_enabled']:
|
||||
self._parse_actors(sid)
|
||||
|
||||
# Parse episode data
|
||||
self.log('Getting all episodes of %s' % sid)
|
||||
log().debug('Getting all episodes of %s' % sid)
|
||||
|
||||
if 'Episode' not in show_data:
|
||||
return False
|
||||
page = 1
|
||||
episodes = []
|
||||
while page is not None:
|
||||
episode_data = self._getetsrc(self.config['url_epInfo'] % (sid, page), language=language)
|
||||
if isinstance(episode_data, dict) and episode_data['data'] is not None:
|
||||
episodes.extend(episode_data['data'])
|
||||
page = episode_data['links']['next'] if isinstance(episode_data, dict) \
|
||||
and 'links' in episode_data and 'next' in episode_data['links'] else None
|
||||
|
||||
episodes = show_data['Episode']
|
||||
if not isinstance(episodes, list):
|
||||
episodes = [episodes]
|
||||
ep_map_keys = {'absolutenumber': u'absolute_number', 'airedepisodenumber': u'episodenumber',
|
||||
'airedseason': u'seasonnumber', 'airedseasonid': u'seasonid',
|
||||
'dvdepisodenumber': u'dvd_episodenumber', 'dvdseason': u'dvd_season'}
|
||||
|
||||
dvd_order = {'dvd': [], 'network': []}
|
||||
for cur_ep in episodes:
|
||||
if self.config['dvdorder']:
|
||||
use_dvd = cur_ep['DVD_season'] not in (None, '') and cur_ep['DVD_episodenumber'] not in (None, '')
|
||||
log().debug('Using DVD ordering.')
|
||||
use_dvd = None is not cur_ep.get('dvdseason') and None is not cur_ep.get('dvdepisodenumber')
|
||||
else:
|
||||
use_dvd = False
|
||||
|
||||
if use_dvd:
|
||||
elem_seasnum, elem_epno = cur_ep['DVD_season'], cur_ep['DVD_episodenumber']
|
||||
elem_seasnum, elem_epno = cur_ep.get('dvdseason'), cur_ep.get('dvdepisodenumber')
|
||||
else:
|
||||
elem_seasnum, elem_epno = cur_ep['SeasonNumber'], cur_ep['EpisodeNumber']
|
||||
elem_seasnum, elem_epno = cur_ep.get('airedseason'), cur_ep.get('airedepisodenumber')
|
||||
|
||||
if None is elem_seasnum or None is elem_epno:
|
||||
self.log('An episode has incomplete season/episode number (season: %r, episode: %r)' % (
|
||||
elem_seasnum, elem_epno), logger.WARNING)
|
||||
log().warning('An episode has incomplete season/episode number (season: %r, episode: %r)' % (
|
||||
elem_seasnum, elem_epno))
|
||||
continue # Skip to next episode
|
||||
|
||||
# float() is because https://github.com/dbr/tvnamer/issues/95 - should probably be fixed in TVDB data
|
||||
seas_no = int(float(elem_seasnum))
|
||||
ep_no = int(float(elem_epno))
|
||||
|
||||
if self.config['dvdorder']:
|
||||
dvd_order[('network', 'dvd')[use_dvd]] += ['S%02dE%02d' % (seas_no, ep_no)]
|
||||
|
||||
for k, v in cur_ep.items():
|
||||
for k, v in cur_ep.iteritems():
|
||||
k = k.lower()
|
||||
|
||||
if None is not v:
|
||||
if 'filename' == k:
|
||||
v = self._get_url_artwork(v)
|
||||
v = self.config['url_artworkPrefix'] % v
|
||||
else:
|
||||
v = self._clean_data(v)
|
||||
|
||||
if k in ep_map_keys:
|
||||
k = ep_map_keys[k]
|
||||
self._set_item(sid, seas_no, ep_no, k, v)
|
||||
|
||||
if self.config['dvdorder']:
|
||||
num_dvd, num_network = [len(dvd_order[x]) for x in 'dvd', 'network']
|
||||
num_all = num_dvd + num_network
|
||||
if num_all:
|
||||
self.log('Of %s episodes, %s use the DVD order, and %s use the network aired order' % (
|
||||
num_all, num_dvd, num_network))
|
||||
for ep_numbers in [', '.join(dvd_order['dvd'][i:i + 5]) for i in xrange(0, num_dvd, 5)]:
|
||||
self.log('Using DVD order: %s' % ep_numbers)
|
||||
|
||||
return True
|
||||
|
||||
def _name_to_sid(self, name):
|
||||
|
@ -893,10 +851,10 @@ class Tvdb:
|
|||
the correct SID.
|
||||
"""
|
||||
if name in self.corrections:
|
||||
self.log('Correcting %s to %s' % (name, self.corrections[name]))
|
||||
log().debug('Correcting %s to %s' % (name, self.corrections[name]))
|
||||
return self.corrections[name]
|
||||
else:
|
||||
self.log('Getting show %s' % name)
|
||||
log().debug('Getting show %s' % name)
|
||||
selected_series = self._get_series(name)
|
||||
if isinstance(selected_series, dict):
|
||||
selected_series = [selected_series]
|
||||
|
@ -926,7 +884,7 @@ class Tvdb:
|
|||
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 show.items()] for show in selected_series]
|
||||
[[self._set_show_data(show['id'], k, v) for k, v in show.iteritems()] for show in selected_series]
|
||||
return selected_series
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
@ -151,7 +151,7 @@ class Show(dict):
|
|||
# doesn't exist, so attribute error.
|
||||
raise tvrage_attributenotfound("Cannot find attribute %s" % (repr(key)))
|
||||
|
||||
def airedOn(self, date):
|
||||
def aired_on(self, date):
|
||||
ret = self.search(str(date), 'firstaired')
|
||||
if len(ret) == 0:
|
||||
raise tvrage_episodenotfound("Could not find any episodes that aired on %s" % date)
|
||||
|
|
|
@ -23,7 +23,7 @@ indexerConfig = {
|
|||
id=INDEXER_TVDB,
|
||||
name='TheTVDB',
|
||||
module=Tvdb,
|
||||
api_params=dict(apikey='F9C450E78D99172E', language='en', useZip=True),
|
||||
api_params=dict(apikey='F9C450E78D99172E', language='en'),
|
||||
active=True,
|
||||
dupekey='',
|
||||
mapped_only=False,
|
||||
|
|
|
@ -761,7 +761,10 @@ class GenericMetadata():
|
|||
# There's gotta be a better way of doing this but we don't wanna
|
||||
# change the language value elsewhere
|
||||
lINDEXER_API_PARMS = sickbeard.indexerApi(show_obj.indexer).api_params.copy()
|
||||
lINDEXER_API_PARMS['banners'] = True
|
||||
if image_type.startswith('fanart'):
|
||||
lINDEXER_API_PARMS['fanart'] = True
|
||||
else:
|
||||
lINDEXER_API_PARMS['banners'] = True
|
||||
lINDEXER_API_PARMS['dvdorder'] = 0 != show_obj.dvdorder
|
||||
|
||||
if indexer_lang and not 'en' == indexer_lang:
|
||||
|
|
|
@ -272,7 +272,7 @@ class NameParser(object):
|
|||
|
||||
t = sickbeard.indexerApi(show.indexer).indexer(**lindexer_api_parms)
|
||||
|
||||
ep_obj = t[show.indexerid].airedOn(best_result.air_date)[0]
|
||||
ep_obj = t[show.indexerid].aired_on(best_result.air_date)[0]
|
||||
|
||||
season_number = int(ep_obj['seasonnumber'])
|
||||
episode_numbers = [int(ep_obj['episodenumber'])]
|
||||
|
|
Loading…
Reference in a new issue