You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
501 lines
19 KiB
501 lines
19 KiB
from couchpotato.core.event import addEvent
|
|
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
|
from couchpotato.core.logger import CPLog
|
|
from couchpotato.core.providers.show.base import ShowProvider
|
|
from tvdb_api import tvdb_api, tvdb_exceptions
|
|
from datetime import datetime
|
|
import traceback
|
|
|
|
log = CPLog(__name__)
|
|
|
|
# XXX: I return None in alot of functions when there is error or no value; check if I
|
|
# should be returning an empty list or dictionary
|
|
# XXX: Consider grabbing zips to put less strain on tvdb
|
|
# XXX: Consider a cache; not implenented everywhere yet or at all
|
|
# XXX: Search by language; now ists defualt of "en"
|
|
# XXX: alternate titles do exist for show and episodes; add them
|
|
# XXX: Unicode stuff
|
|
# XXX: we have a getShow function but it it being used?
|
|
class TheTVDb(ShowProvider):
|
|
|
|
def __init__(self):
|
|
#addEvent('show.by_hash', self.byHash)
|
|
addEvent('show.search', self.search, priority = 1)
|
|
addEvent('show.info', self.getShowInfo, priority = 1)
|
|
addEvent('season.info', self.getSeasonInfo, priority = 1)
|
|
addEvent('episode.info', self.getEpisodeInfo, priority = 1)
|
|
#addEvent('show.info_by_thetvdb', self.getInfoByTheTVDBId)
|
|
|
|
tvdb_api_parms = {
|
|
'apikey' : self.conf('api_key'),
|
|
'banners' : True
|
|
}
|
|
|
|
self.tvdb = tvdb_api.Tvdb(**tvdb_api_parms)
|
|
|
|
#def byHash(self, file):
|
|
#''' Find show by hash '''
|
|
|
|
#if self.isDisabled():
|
|
#return False
|
|
|
|
#cache_key = 'tmdb.cache.%s' % simplifyString(file)
|
|
#results = self.getCache(cache_key)
|
|
|
|
#if not results:
|
|
#log.debug('Searching for show by hash: %s', file)
|
|
#try:
|
|
#raw = tmdb.searchByHashingFile(file)
|
|
|
|
#results = []
|
|
#if raw:
|
|
#try:
|
|
#results = self.parseShow(raw)
|
|
#log.info('Found: %s', results['titles'][0] + ' (' + str(results.get('year', 0)) + ')')
|
|
|
|
#self.setCache(cache_key, results)
|
|
#return results
|
|
#except SyntaxError, e:
|
|
#log.error('Failed to parse XML response: %s', e)
|
|
#return False
|
|
#except:
|
|
#log.debug('No shows known by hash for: %s', file)
|
|
#pass
|
|
|
|
#return results
|
|
|
|
def search(self, q, limit = 12):
|
|
''' Find show by name
|
|
show = { 'id': 74713,
|
|
'language': 'en',
|
|
'lid': 7,
|
|
'seriesid': '74713',
|
|
'seriesname': u'Breaking Bad',}
|
|
'''
|
|
|
|
if self.isDisabled():
|
|
return False
|
|
|
|
search_string = simplifyString(q)
|
|
cache_key = 'thetvdb.cache.%s.%s' % (search_string, limit)
|
|
results = self.getCache(cache_key)
|
|
# TODO: cache is not returned
|
|
|
|
if not results:
|
|
log.debug('Searching for show: %s', q)
|
|
|
|
raw = None
|
|
try:
|
|
raw = self.tvdb.search(search_string)
|
|
except (tvdb_exceptions.tvdb_error, IOError), e:
|
|
log.error('Failed searching TheTVDB for "%s": %s', (search_string, traceback.format_exc()))
|
|
return None
|
|
|
|
results = []
|
|
if raw:
|
|
try:
|
|
nr = 0
|
|
|
|
for show in raw:
|
|
show = self.tvdb[int(show['id'])]
|
|
results.append(self.parseShow(show))
|
|
|
|
nr += 1
|
|
if nr == limit:
|
|
break
|
|
|
|
log.info('Found: %s', [result['titles'][0] + ' (' + str(result.get('year', 0)) + ')' for result in results])
|
|
|
|
self.setCache(cache_key, results)
|
|
return results
|
|
#except SyntaxError, e:
|
|
# log.error('Failed to parse XML response: %s', e)
|
|
# return False
|
|
except (tvdb_exceptions.tvdb_error, IOError), e:
|
|
log.error('Failed parsing TheTVDB for "%s": %s', (show, traceback.format_exc()))
|
|
return False
|
|
|
|
return results
|
|
|
|
def getShow(self, identifier = None):
|
|
show = None
|
|
try:
|
|
log.debug('Getting show: %s', identifier)
|
|
show = self.tvdb[int(identifier)]
|
|
except (tvdb_exceptions.tvdb_error, IOError), e:
|
|
log.error('Failed to getShowInfo for show id "%s": %s', (identifier, traceback.format_exc()))
|
|
return None
|
|
|
|
return show
|
|
|
|
def getSeasonInfo(self, identifier=None, season_identifier=None):
|
|
"""Either return a list of all seasons or a single season by number.
|
|
identifier is the show 'id'
|
|
"""
|
|
if not identifier:
|
|
return None
|
|
|
|
# season_identifier must contain the 'show id : season number' since there is no tvdb id
|
|
# for season and we need a reference to both the show id and season number
|
|
if season_identifier:
|
|
try: season_identifier = int(season_identifier.split(':')[1])
|
|
except: return None
|
|
|
|
cache_key = 'thetvdb.cache.%s.%s' % (identifier, season_identifier)
|
|
log.debug('Getting SeasonInfo: %s', cache_key)
|
|
result = self.getCache(cache_key) or {}
|
|
if result:
|
|
return result
|
|
|
|
try:
|
|
show = self.tvdb[int(identifier)]
|
|
except (tvdb_exceptions.tvdb_error, IOError), e:
|
|
log.error('Failed parsing TheTVDB SeasonInfo for "%s" id "%s": %s', (show, identifier, traceback.format_exc()))
|
|
return None
|
|
|
|
result = []
|
|
for number, season in show.items():
|
|
if season_identifier is not None and number == season_identifier:
|
|
result = self.parseSeason(show, (number, season))
|
|
self.setCache(cache_key, result)
|
|
return result
|
|
else:
|
|
result.append(self.parseSeason(show, (number, season)))
|
|
|
|
self.setCache(cache_key, result)
|
|
return result
|
|
|
|
def getEpisodeInfo(self, identifier=None, season_identifier=None, episode_identifier=None):
|
|
"""Either return a list of all episodes or a single episode.
|
|
If episode_identifer contains an episode number to search for
|
|
"""
|
|
if not identifier and season_identifier is None:
|
|
return None
|
|
|
|
# season_identifier must contain the 'show id : season number' since there is no tvdb id
|
|
# for season and we need a reference to both the show id and season number
|
|
if season_identifier:
|
|
try:
|
|
identifier, season_identifier = season_identifier.split(':')
|
|
season_identifier = int(season_identifier)
|
|
except: return None
|
|
|
|
cache_key = 'thetvdb.cache.%s.%s.%s' % (identifier, episode_identifier, season_identifier)
|
|
log.debug('Getting EpisodeInfo: %s', cache_key)
|
|
result = self.getCache(cache_key) or {}
|
|
if result:
|
|
return result
|
|
|
|
try:
|
|
show = self.tvdb[int(identifier)]
|
|
except (tvdb_exceptions.tvdb_error, IOError), e:
|
|
log.error('Failed parsing TheTVDB EpisodeInfo for "%s" id "%s": %s', (show, identifier, traceback.format_exc()))
|
|
return None
|
|
|
|
result = []
|
|
for number, season in show.items():
|
|
if season_identifier is not None and number != season_identifier:
|
|
continue
|
|
|
|
for episode in season.values():
|
|
if episode_identifier is not None and episode['id'] == toUnicode(episode_identifier):
|
|
result = self.parseEpisode(show, episode)
|
|
self.setCache(cache_key, result)
|
|
return result
|
|
else:
|
|
result.append(self.parseEpisode(show, episode))
|
|
|
|
self.setCache(cache_key, result)
|
|
return result
|
|
|
|
def getShowInfo(self, identifier = None):
|
|
if not identifier:
|
|
return None
|
|
|
|
cache_key = 'thetvdb.cache.%s' % identifier
|
|
log.debug('Getting showInfo: %s', cache_key)
|
|
result = self.getCache(cache_key) or {}
|
|
if result:
|
|
return result
|
|
|
|
show = self.getShow(identifier=identifier)
|
|
if show:
|
|
result = self.parseShow(show)
|
|
self.setCache(cache_key, result)
|
|
|
|
return result
|
|
|
|
#def getInfoByTheTVDBId(self, id = None):
|
|
|
|
#cache_key = 'thetvdb.cache.%s' % id
|
|
#result = self.getCache(cache_key)
|
|
|
|
#if not result:
|
|
#result = {}
|
|
#show = None
|
|
|
|
#try:
|
|
#log.debug('Getting info: %s', cache_key)
|
|
#show = tmdb.getShowInfo(id = id)
|
|
#except:
|
|
#pass
|
|
|
|
#if show:
|
|
#result = self.parseShow(show)
|
|
#self.setCache(cache_key, result)
|
|
|
|
#return result
|
|
|
|
def parseShow(self, show):
|
|
"""
|
|
show[74713] = {
|
|
'actors': u'|Bryan Cranston|Aaron Paul|Dean Norris|RJ Mitte|Betsy Brandt|Anna Gunn|Laura Fraser|Jesse Plemons|Christopher Cousins|Steven Michael Quezada|Jonathan Banks|Giancarlo Esposito|Bob Odenkirk|',
|
|
'added': None,
|
|
'addedby': None,
|
|
'airs_dayofweek': u'Sunday',
|
|
'airs_time': u'9:00 PM',
|
|
'banner': u'http://thetvdb.com/banners/graphical/81189-g13.jpg',
|
|
'contentrating': u'TV-MA',
|
|
'fanart': u'http://thetvdb.com/banners/fanart/original/81189-28.jpg',
|
|
'firstaired': u'2008-01-20',
|
|
'genre': u'|Crime|Drama|Suspense|',
|
|
'id': u'81189',
|
|
'imdb_id': u'tt0903747',
|
|
'language': u'en',
|
|
'lastupdated': u'1376620212',
|
|
'network': u'AMC',
|
|
'networkid': None,
|
|
'overview': u"Walter White, a struggling high school chemistry teacher is diagnosed with advanced lung cancer. He turns to a life of crime, producing and selling methamphetamine accompanied by a former student, Jesse Pinkman with the aim of securing his family's financial future before he dies.",
|
|
'poster': u'http://thetvdb.com/banners/posters/81189-22.jpg',
|
|
'rating': u'9.3',
|
|
'ratingcount': u'473',
|
|
'runtime': u'60',
|
|
'seriesid': u'74713',
|
|
'seriesname': u'Breaking Bad',
|
|
'status': u'Continuing',
|
|
'zap2it_id': u'SH01009396'}
|
|
"""
|
|
|
|
# Make sure we have a valid show id, not '' or None
|
|
#if len (show['id']) is 0:
|
|
# return None
|
|
|
|
## Images
|
|
poster = self.getImage(show, type = 'poster', size = 'cover')
|
|
backdrop = self.getImage(show, type = 'fanart', size = 'w1280')
|
|
#poster_original = self.getImage(show, type = 'poster', size = 'original')
|
|
#backdrop_original = self.getImage(show, type = 'backdrop', size = 'original')
|
|
|
|
genres = [] if show['genre'] is None else show['genre'].strip('|').split('|')
|
|
if show['firstaired'] is not None:
|
|
try: year = datetime.strptime(show['firstaired'], '%Y-%m-%d').year
|
|
except: year = None
|
|
else:
|
|
year = None
|
|
|
|
show_data = {
|
|
'id': int(show['id']),
|
|
'type': 'show',
|
|
'primary_provider': 'thetvdb',
|
|
'titles': [show['seriesname'], ],
|
|
'original_title': show['seriesname'],
|
|
'images': {
|
|
'poster': [poster] if poster else [],
|
|
'backdrop': [backdrop] if backdrop else [],
|
|
'poster_original': [],
|
|
'backdrop_original': [],
|
|
},
|
|
'year': year,
|
|
'genres': genres,
|
|
'imdb': show['imdb_id'],
|
|
'zap2it_id': show['zap2it_id'],
|
|
'seriesid': show['seriesid'],
|
|
'network': show['network'],
|
|
'networkid': show['networkid'],
|
|
'airs_dayofweek': show['airs_dayofweek'],
|
|
'airs_time': show['airs_time'],
|
|
'firstaired': show['firstaired'],
|
|
'released': show['firstaired'],
|
|
'runtime': show['runtime'],
|
|
'contentrating': show['contentrating'],
|
|
'rating': show['rating'],
|
|
'ratingcount': show['ratingcount'],
|
|
'actors': show['actors'],
|
|
'lastupdated': show['lastupdated'],
|
|
'status': show['status'],
|
|
'language': show['language'],
|
|
}
|
|
|
|
show_data = dict((k, v) for k, v in show_data.iteritems() if v)
|
|
|
|
## Add alternative names
|
|
#for alt in ['original_name', 'alternative_name']:
|
|
#alt_name = toUnicode(show['alt))
|
|
#if alt_name and not alt_name in show_data['titles'] and alt_name.lower() != 'none' and alt_name != None:
|
|
#show_data['titles'].append(alt_name)
|
|
|
|
return show_data
|
|
|
|
def parseSeason(self, show, season_tuple):
|
|
"""
|
|
contains no data
|
|
"""
|
|
|
|
number, season = season_tuple
|
|
title = toUnicode('%s - Season %s' % (show['seriesname'], str(number)))
|
|
|
|
# XXX: work on title; added defualt_title to fix an error
|
|
season_data = {
|
|
'id': (show['id'] + ':' + str(number)),
|
|
'type': 'season',
|
|
'primary_provider': 'thetvdb',
|
|
'titles': [title, ],
|
|
'original_title': title,
|
|
'via_thetvdb': True,
|
|
'parent_identifier': show['id'],
|
|
'seasonnumber': str(number),
|
|
'images': {
|
|
'poster': [],
|
|
'backdrop': [],
|
|
'poster_original': [],
|
|
'backdrop_original': [],
|
|
},
|
|
'year': None,
|
|
'genres': None,
|
|
'imdb': None,
|
|
}
|
|
|
|
season_data = dict((k, v) for k, v in season_data.iteritems() if v)
|
|
return season_data
|
|
|
|
def parseEpisode(self, show, episode):
|
|
"""
|
|
('episodenumber', u'1'),
|
|
('thumb_added', None),
|
|
('rating', u'7.7'),
|
|
('overview',
|
|
u'Experienced waitress Max Black meets her new co-worker, former rich-girl Caroline Channing, and puts her skills to the test at an old but re-emerging Brooklyn diner. Despite her initial distaste for Caroline, Max eventually softens and the two team up for a new business venture.'),
|
|
('dvd_episodenumber', None),
|
|
('dvd_discid', None),
|
|
('combined_episodenumber', u'1'),
|
|
('epimgflag', u'7'),
|
|
('id', u'4099506'),
|
|
('seasonid', u'465948'),
|
|
('thumb_height', u'225'),
|
|
('tms_export', u'1374789754'),
|
|
('seasonnumber', u'1'),
|
|
('writer', u'|Michael Patrick King|Whitney Cummings|'),
|
|
('lastupdated', u'1371420338'),
|
|
('filename', u'http://thetvdb.com/banners/episodes/248741/4099506.jpg'),
|
|
('absolute_number', u'1'),
|
|
('ratingcount', u'102'),
|
|
('combined_season', u'1'),
|
|
('thumb_width', u'400'),
|
|
('imdb_id', u'tt1980319'),
|
|
('director', u'James Burrows'),
|
|
('dvd_chapter', None),
|
|
('dvd_season', None),
|
|
('gueststars',
|
|
u'|Brooke Lyons|Noah Mills|Shoshana Bush|Cale Hartmann|Adam Korson|Alex Enriquez|Matt Cook|Bill Parks|Eugene Shaw|Sergey Brusilovsky|Greg Lewis|Cocoa Brown|Nick Jameson|'),
|
|
('seriesid', u'248741'),
|
|
('language', u'en'),
|
|
('productioncode', u'296793'),
|
|
('firstaired', u'2011-09-19'),
|
|
('episodename', u'Pilot')]
|
|
"""
|
|
|
|
## Images
|
|
#poster = self.getImage(episode, type = 'poster', size = 'cover')
|
|
#backdrop = self.getImage(episode, type = 'fanart', size = 'w1280')
|
|
##poster_original = self.getImage(episode, type = 'poster', size = 'original')
|
|
##backdrop_original = self.getImage(episode, type = 'backdrop', size = 'original')
|
|
|
|
poster = episode['filename'] or []
|
|
backdrop = []
|
|
genres = []
|
|
plot = "%s - %sx%s - %s" % (show['seriesname'],
|
|
episode['seasonnumber'],
|
|
episode['episodenumber'],
|
|
episode['overview'])
|
|
if episode['firstaired'] is not None:
|
|
try: year = datetime.strptime(episode['firstaired'], '%Y-%m-%d').year
|
|
except: year = None
|
|
else:
|
|
year = None
|
|
|
|
episode_data = {
|
|
'id': int(episode['id']),
|
|
'type': 'episode',
|
|
'primary_provider': 'thetvdb',
|
|
'via_thetvdb': True,
|
|
'thetvdb_id': int(episode['id']),
|
|
'titles': [episode['episodename'], ],
|
|
'original_title': episode['episodename'] ,
|
|
'images': {
|
|
'poster': [poster] if poster else [],
|
|
'backdrop': [backdrop] if backdrop else [],
|
|
'poster_original': [],
|
|
'backdrop_original': [],
|
|
},
|
|
'imdb': episode['imdb_id'],
|
|
'runtime': None,
|
|
'released': episode['firstaired'],
|
|
'year': year,
|
|
'plot': plot,
|
|
'genres': genres,
|
|
'parent_identifier': show['id'],
|
|
'seasonnumber': episode['seasonnumber'],
|
|
'episodenumber': episode['episodenumber'],
|
|
'combined_episodenumber': episode['combined_episodenumber'],
|
|
'absolute_number': episode['absolute_number'],
|
|
'combined_season': episode['combined_season'],
|
|
'productioncode': episode['productioncode'],
|
|
'seriesid': episode['seriesid'],
|
|
'seasonid': episode['seasonid'],
|
|
'firstaired': episode['firstaired'],
|
|
'thumb_added': episode['thumb_added'],
|
|
'thumb_height': episode['thumb_height'],
|
|
'thumb_width': episode['thumb_width'],
|
|
'rating': episode['rating'],
|
|
'ratingcount': episode['ratingcount'],
|
|
'epimgflag': episode['epimgflag'],
|
|
'dvd_episodenumber': episode['dvd_episodenumber'],
|
|
'dvd_discid': episode['dvd_discid'],
|
|
'dvd_chapter': episode['dvd_chapter'],
|
|
'dvd_season': episode['dvd_season'],
|
|
'tms_export': episode['tms_export'],
|
|
'writer': episode['writer'],
|
|
'director': episode['director'],
|
|
'gueststars': episode['gueststars'],
|
|
'lastupdated': episode['lastupdated'],
|
|
'language': episode['language'],
|
|
}
|
|
|
|
episode_data = dict((k, v) for k, v in episode_data.iteritems() if v)
|
|
|
|
## Add alternative names
|
|
#for alt in ['original_name', 'alternative_name']:
|
|
#alt_name = toUnicode(episode['alt))
|
|
#if alt_name and not alt_name in episode_data['titles'] and alt_name.lower() != 'none' and alt_name != None:
|
|
#episode_data['titles'].append(alt_name)
|
|
|
|
return episode_data
|
|
|
|
def getImage(self, show, type = 'poster', size = 'cover'):
|
|
""""""
|
|
# XXX: Need to implement size
|
|
image_url = ''
|
|
|
|
for res, res_data in show['_banners'].get(type, {}).items():
|
|
for bid, banner_info in res_data.items():
|
|
image_url = banner_info.get('_bannerpath', '')
|
|
break
|
|
|
|
return image_url
|
|
|
|
def isDisabled(self):
|
|
if self.conf('api_key') == '':
|
|
log.error('No API key provided.')
|
|
True
|
|
else:
|
|
False
|
|
|