Change improve tmdb_api, trakt_api, and TVInfoShow object.

Add `spoken_languages` to tmdb API and TVInfoShow object.
Add `trailers`, `homepage` to trakt API and TVInfoShow object.
Add trakt episode data if returned from api.
Add trakt API methods.
- get_most_played
- get_most_watched
- get_most_collected
- get_anticipated
- get_recommended
- get_trending
- get_popular
- get_recommended_for_account
- get_new_shows
- get_new_seasons
- get_watchlisted_for_account
- get_similar
- hide_recommended_for_account (to hide/remove recommended shows for account)
- unhide_recommended_for_account
- list_hidden_recommended_for_account

Fix caching tmdb language list over different runtime instances.
Add episode_count and fix ti_show in tmdb_api person object.
Change set additional properties in get_person trakt_api.

Add tmdb API methods and tvinfo_base.
- get_recommended_for_show
- get_similar
---
fix supported language caching
improve print output (source name) of tvinfo_api_tests
fix tvinfo_api_tests data creation
---
Add code so that it runs with all_test

use mock today() and now() dates
add option to only get new urls mock data
try also to make object creation only when needed
fix person parser in tmdb_api
add search_person test in tvinfo_api_tests
restore mocked methods at the end of the tvinfo_api_tests to prevent other tests to fail when called via all_tests
switch gzip with better lzma compression for mock files (default lib in py3)
move mock files in test unit sub folder
---
Fix trakt method `get_recommended`.
Fix browse trakt tests in tvinfo_api_tests.
Change set episode id in trakt api.
---
Add test_browse_endpoints to tvinfo_api_tests.
---
Add enforce_type to sg_helpers.
Change use enforce str for overviews.
Change remove `if PY2` code sections
Add support for  datetime.time in _make_airtime in tv.py
Refactor tvmaze_api show data setter.
Change test to not allow None for seriesname.
Add additional missing showdata with caller load_data().
Add load_data() to TVInfoShow.
Add guestcast, guestcrew to episodes in pytvmaze lib.
---
Change make seriesid of TVInfoShow a alias property of id.

Add tvinfo tests.
Add search tests.
Add show, person tests.
Change add trakt tests.
Change add tmdb search tests.
tvmaze_api exclude rating from mapping.
Allow None for seriesname.
Fix origin_countries in trakt_api search.
Fix show_type in tvmaze_api.
Fix airtime for episodes in tvmaze_api.
---
Change switch to property instead of legacy dict-like use for trakt search results.
Change optimize speed of get() function.
Fix make BaseTVinfoSeasonnotfound and BaseTVinfoAttributenotfound also a subclass of AttributeError and KeyError.
Change mock get() to work with and without default args just like dict get().
Change add language to tmdb_api search results.
Change improve person search by remote id, by getting the complete persons data when there is only 1 result.
Change trakt API search results to tvinfoshow.
Change search results to TVInfoShow objs in tvmaze_api.
Change simplify poster URL generation for search results.
Change search results to TVInfoShow objs.

Change add tvdb genre links to displayShow.

Change workaround for missing data in person data (series set to None).

Fix add show to characters of person if there is no name on IMDb (set to 'unknown name').

Change add config and icons for linkedin, reddit, wikidata, youtube.

Add TVInfoIDs, TVInfoSocialIDs to Trakt.
Add TVInfoIDs to tmdb_api.
Add TVInfoIDs to tvmaze_api.
add TVInfoIDs to imdb_api.

Change make character name '' if None.

Fix for 'unknown name' persons and characters.

Add contentrating.

Change fill in new fields to get_person results.

----

Change set new in/active dates to network.

Change add active_date, inactive_date to TVInfoNetwork class.

Change add default kwargs to tmdb discover method if no kwargs are set.
Change default: English language shows with first air date greater then today.

Change add slug field to returned data from discover.

Change add 'score' mapped to rating to discover returned results.

Fix valid_data for discover method.

Change add result_count to discover.

Change add _sanitise_image_uri to discover method.

Fix convert_person.

Change add missing  _sanitise_image_uri for images in some places.

Fix crew.

Change return type of tvinfo base: discover to list tvinfoshow.

Fix people remote id search.
Change add tmdb person id search.

Change fix people endpoint fieldname changes.

Change add biography to person object.

Change move 401 expired token handling into TvdbAuth class.

Change get new token if old token is expired.

Change add raise error if episodes fallback fails to load data.
Change add break if no valid_data to absolute and alternative numberings.
Change add filter only networks.
Change add new required parameter meta=translations to get translated (includes the original language) show overviews.
Change add check if show is set for person compare.
Fix person update properties with no show set.
Change add person image.

Change add alternative episode orders.
Change add alt_ep_numbering to TVINFO_Show.
Change add old interface for dvd order.

Change add trakt slug tvinfo search test cases.

Change add mock for old tvdb get new token.

Change old lib to newer tvinfo data.

Fix person id (not available on old api).

Change more places to new TVInfoAPI interface.
This commit is contained in:
Prinz23 2021-09-16 21:03:19 +01:00 committed by JackDandy
parent 5ff18a8652
commit 7a6936823e
293 changed files with 2158 additions and 426 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

View file

@ -77,7 +77,7 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
#elif $PersonGenders.male == $cur_person.gender# #elif $PersonGenders.male == $cur_person.gender#
#set $gender = 'himself' #set $gender = 'himself'
#end if# #end if#
#set $name = ($cur_person.name, $gender)[$rc_clean.sub('', $cur_person.name.lower()) == $rc_clean.sub('', $character.name.lower())] #set $name = ($cur_person.name, $gender)[$rc_clean.sub('', $cur_person.name.lower()) == $rc_clean.sub('', ($character.name or 'unknown name').lower())]
<a href="$sbRoot/home/person?$param(person=$cur_person)">$name</a>#if 2 <= $num_people and $cur_enum + 1 == $num_people# and #elif 2 < $num_people and $cur_enum < $num_people#<span>, </span>#end if# <a href="$sbRoot/home/person?$param(person=$cur_person)">$name</a>#if 2 <= $num_people and $cur_enum + 1 == $num_people# and #elif 2 < $num_people and $cur_enum < $num_people#<span>, </span>#end if#
#end for #end for
</div> </div>

View file

@ -168,7 +168,7 @@
<h2 class="title" id="scene_exception_$show_obj.tvid_prodid"><span>$show_obj.name</span>#echo ('', '<em id="title-status"> (ended)</em>')[$show_ended]#</h2> <h2 class="title" id="scene_exception_$show_obj.tvid_prodid"><span>$show_obj.name</span>#echo ('', '<em id="title-status"> (ended)</em>')[$show_ended]#</h2>
#set $genres_done = False #set $genres_done = False
#if $sg_var('USE_IMDB_INFO') and 'genres' in $show_obj.imdb_info and '' != $show_obj.imdb_info['genres'] #if $sg_var('USE_IMDB_INFO') and 'genres' in $show_obj.imdb_info and '' != $show_obj.imdb_info['genres']
#for $imdbgenre in $show_obj.imdb_info['genres'].split('|') #for $imdbgenre in [$g for $g in $show_obj.imdb_info['genres'].split('|') if $g]
#set $genres_done = True #set $genres_done = True
<span class="label"><a href="<%= anon_url('http://www.imdb.com/search/title?at=0&genres=', imdbgenre.lower().replace('-','_'),'&amp;sort=moviemeter,asc&amp;title_type=tv_series') %>" target="_blank" title="View other popular $imdbgenre shows on imdb.com" class="addQTip">$imdbgenre.replace('Sci-Fi','Science-Fiction')</a></span> <span class="label"><a href="<%= anon_url('http://www.imdb.com/search/title?at=0&genres=', imdbgenre.lower().replace('-','_'),'&amp;sort=moviemeter,asc&amp;title_type=tv_series') %>" target="_blank" title="View other popular $imdbgenre shows on imdb.com" class="addQTip">$imdbgenre.replace('Sci-Fi','Science-Fiction')</a></span>
#end for #end for
@ -176,7 +176,13 @@
#if not $genres_done and $show_obj.genre #if not $genres_done and $show_obj.genre
#for $genre in $show_obj.genre.split('|') #for $genre in $show_obj.genre.split('|')
#set $genres_done = True #set $genres_done = True
<span class="label">$genre</span> <span class="label">
#if $TVINFO_TVDB == $show_obj.tvid
<a href="https://thetvdb.com/genres/$genre" target="_blank" title="View other popular $genre shows on thetvdb.com" class="addQTip">$genre</a>
#else
$genre
#end if
</span>
#end for# #end for#
#end if #end if
#if not $genres_done #if not $genres_done

View file

@ -20,9 +20,13 @@ from lib.tvinfo_base import (
# TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TMDB, TVINFO_TRAKT, # TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TMDB, TVINFO_TRAKT,
# TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_TWITTER, TVINFO_WIKIPEDIA, # TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_TWITTER, TVINFO_WIKIPEDIA,
TVInfoBase, TVInfoIDs, TVInfoShow) TVInfoBase, TVInfoIDs, TVInfoShow)
from sg_helpers import get_url, try_int from sg_helpers import clean_data, enforce_type, get_url, try_int
from json_helper import json_loads
from six import iteritems from six import iteritems
from six.moves import http_client as httplib
from six.moves.urllib.parse import urlencode, urljoin, quote, unquote
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
if False: if False:
@ -34,6 +38,37 @@ log = logging.getLogger('imdb.api')
log.addHandler(logging.NullHandler()) log.addHandler(logging.NullHandler())
def _get_imdb(self, url, query=None, params=None):
headers = {'Accept-Language': self.locale}
if params:
full_url = '{0}?{1}'.format(url, urlencode(params))
else:
full_url = url
headers.update(self.get_auth_headers(full_url))
resp = get_url(url, headers=headers, params=params, return_response=True)
if not resp.ok:
if resp.status_code == httplib.NOT_FOUND:
raise LookupError('Resource {0} not found'.format(url))
else:
msg = '{0} {1}'.format(resp.status_code, resp.text)
raise imdbpie.ImdbAPIError(msg)
resp_data = resp.content.decode('utf-8')
try:
resp_dict = json_loads(resp_data)
except ValueError:
resp_dict = self._parse_dirty_json(
data=resp_data, query=query
)
if resp_dict.get('error'):
return None
return resp_dict
imdbpie.Imdb._get = _get_imdb
class IMDbIndexer(TVInfoBase): class IMDbIndexer(TVInfoBase):
# supported_id_searches = [TVINFO_IMDB] # supported_id_searches = [TVINFO_IMDB]
supported_person_id_searches = [TVINFO_IMDB] supported_person_id_searches = [TVINFO_IMDB]
@ -71,9 +106,9 @@ class IMDbIndexer(TVInfoBase):
ti_show = TVInfoShow() ti_show = TVInfoShow()
ti_show.seriesname, ti_show.id, ti_show.firstaired, ti_show.genre_list, ti_show.overview, \ ti_show.seriesname, ti_show.id, ti_show.firstaired, ti_show.genre_list, ti_show.overview, \
ti_show.poster, ti_show.ids = \ ti_show.poster, ti_show.ids = \
s['title'], imdb_id, s.get('releaseDetails', {}).get('date') or s.get('year'), s.get('genres'), \ clean_data(s['title']), imdb_id, s.get('releaseDetails', {}).get('date') or s.get('year'), \
s.get('plot', {}).get('outline', {}).get('text'), s.get('image') and s['image'].get('url'), \ s.get('genres'), enforce_type(clean_data(s.get('plot', {}).get('outline', {}).get('text')), str, ''), \
TVInfoIDs(imdb=imdb_id) s.get('image') and s['image'].get('url'), TVInfoIDs(imdb=imdb_id)
return ti_show return ti_show
results = [] results = []
@ -108,12 +143,12 @@ class IMDbIndexer(TVInfoBase):
def _convert_person(person_obj, filmography=None, bio=None): def _convert_person(person_obj, filmography=None, bio=None):
if isinstance(person_obj, dict) and 'imdb_id' in person_obj: if isinstance(person_obj, dict) and 'imdb_id' in person_obj:
imdb_id = try_int(re.search(r'(\d+)', person_obj['imdb_id']).group(1)) imdb_id = try_int(re.search(r'(\d+)', person_obj['imdb_id']).group(1))
return TVInfoPerson(p_id=imdb_id, name=person_obj['name'], ids={TVINFO_IMDB: imdb_id}) return TVInfoPerson(p_id=imdb_id, name=person_obj['name'], ids=TVInfoIDs(ids={TVINFO_IMDB: imdb_id}))
characters = [] characters = []
for known_for in (filmography and filmography['filmography']) or []: for known_for in (filmography and filmography['filmography']) or []:
if known_for['titleType'] not in ('tvSeries', 'tvMiniSeries'): if known_for['titleType'] not in ('tvSeries', 'tvMiniSeries'):
continue continue
for character in known_for.get('characters') or []: for character in known_for.get('characters') or ['unknown name']:
ti_show = TVInfoShow() ti_show = TVInfoShow()
ti_show.id = try_int(re.search(r'(\d+)', known_for.get('id')).group(1)) ti_show.id = try_int(re.search(r'(\d+)', known_for.get('id')).group(1))
ti_show.ids.imdb = ti_show.id ti_show.ids.imdb = ti_show.id
@ -133,7 +168,7 @@ class IMDbIndexer(TVInfoBase):
deathdate = None deathdate = None
imdb_id = try_int(re.search(r'(\d+)', person_obj['id']).group(1)) imdb_id = try_int(re.search(r'(\d+)', person_obj['id']).group(1))
return TVInfoPerson( return TVInfoPerson(
p_id=imdb_id, ids={TVINFO_IMDB: imdb_id}, characters=characters, p_id=imdb_id, ids=TVInfoIDs(ids={TVINFO_IMDB: imdb_id}), characters=characters,
name=person_obj['base'].get('name'), real_name=person_obj['base'].get('realName'), name=person_obj['base'].get('name'), real_name=person_obj['base'].get('realName'),
nicknames=set((person_obj['base'].get('nicknames') and person_obj['base'].get('nicknames')) or []), nicknames=set((person_obj['base'].get('nicknames') and person_obj['base'].get('nicknames')) or []),
akas=set((person_obj['base'].get('akas') and person_obj['base'].get('akas')) or []), akas=set((person_obj['base'].get('akas') and person_obj['base'].get('akas')) or []),

View file

@ -18,13 +18,13 @@ from lib.tvinfo_base import CastList, PersonGenders, RoleTypes, \
TVINFO_IMDB, TVINFO_TMDB, TVINFO_TVDB, \ TVINFO_IMDB, TVINFO_TMDB, TVINFO_TVDB, \
TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TWITTER TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TWITTER
from json_helper import json_dumps from json_helper import json_dumps
from sg_helpers import clean_data, get_url, iterate_chunk, try_int from sg_helpers import clean_data, enforce_type, get_url, iterate_chunk, try_int
from six import iteritems from six import iteritems
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
if False: if False:
from typing import Any, AnyStr, Dict, List, Optional from typing import Any, AnyStr, Dict, List, Optional, Union
from six import integer_types from six import integer_types
log = logging.getLogger('tmdb.api') log = logging.getLogger('tmdb.api')
@ -179,17 +179,19 @@ class TmdbIndexer(TVInfoBase):
self.size_map = response.get('size_map') self.size_map = response.get('size_map')
self.tv_genres = response.get('genres') self.tv_genres = response.get('genres')
def _search_show(self, name=None, ids=None, **kwargs): def _search_show(self, name=None, ids=None, lang=None, **kwargs):
# type: (AnyStr, Dict[integer_types, integer_types], Optional[Any]) -> List[TVInfoShow] # type: (Union[AnyStr, List[AnyStr]], Dict[integer_types, integer_types], Optional[string_types], Optional[Any]) -> List[Dict]
"""This searches TMDB for the series name, """This searches TMDB for the series name,
""" """
tmdb_lang = ('en-US', lang)[lang in self._tmdb_supported_lang_list]
def _make_result_dict(s): def _make_result_dict(s):
ti_show = TVInfoShow() ti_show = TVInfoShow()
ti_show.seriesname, ti_show.id, ti_show.seriesid, ti_show.firstaired, ti_show.genre_list, \ ti_show.seriesname, ti_show.id, ti_show.seriesid, ti_show.firstaired, ti_show.genre_list, \
ti_show.overview, ti_show.poster, ti_show.ids, ti_show.language, ti_show.popularity, ti_show.rating = \ ti_show.overview, ti_show.poster, ti_show.ids, ti_show.language, ti_show.popularity, ti_show.rating = \
clean_data(s['name']), s['id'], s['id'], clean_data(s.get('first_air_date')) or None, \ clean_data(s['name']), s['id'], s['id'], clean_data(s.get('first_air_date')) or None, \
clean_data([self.tv_genres.get(g) for g in s.get('genre_ids') or []]), \ clean_data([self.tv_genres.get(g) for g in s.get('genre_ids') or []]), \
clean_data(s.get('overview')), s.get('poster_path') and '%s%s%s' % ( enforce_type(clean_data(s.get('overview')), str, ''), s.get('poster_path') and '%s%s%s' % (
self.img_base_url, self.size_map[TVInfoImageType.poster][TVInfoImageSize.original], self.img_base_url, self.size_map[TVInfoImageType.poster][TVInfoImageSize.original],
s.get('poster_path')), \ s.get('poster_path')), \
TVInfoIDs(tvdb=s.get('external_ids') and s['external_ids'].get('tvdb_id'), TVInfoIDs(tvdb=s.get('external_ids') and s['external_ids'].get('tvdb_id'),
@ -209,7 +211,7 @@ class TmdbIndexer(TVInfoBase):
is_none, shows = self._get_cache_entry(cache_id_key) is_none, shows = self._get_cache_entry(cache_id_key)
if not self.config.get('cache_search') or (None is shows and not is_none): if not self.config.get('cache_search') or (None is shows and not is_none):
try: try:
show = tmdbsimple.TV(id=p).info(append_to_response='external_ids') show = tmdbsimple.TV(id=p).info(append_to_response='external_ids', language=tmdb_lang)
except (BaseException, Exception): except (BaseException, Exception):
continue continue
self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire) self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire)
@ -223,10 +225,10 @@ class TmdbIndexer(TVInfoBase):
if not self.config.get('cache_search') or (None is shows and not is_none): if not self.config.get('cache_search') or (None is shows and not is_none):
try: try:
show = tmdbsimple.Find(id=(p, 'tt%07d' % p)[t == TVINFO_IMDB]).info( show = tmdbsimple.Find(id=(p, 'tt%07d' % p)[t == TVINFO_IMDB]).info(
external_source=id_map[t]) external_source=id_map[t], language=tmdb_lang)
if show.get('tv_results') and 1 == len(show['tv_results']): if show.get('tv_results') and 1 == len(show['tv_results']):
show = tmdbsimple.TV(id=show['tv_results'][0]['id']).info( show = tmdbsimple.TV(id=show['tv_results'][0]['id']).info(
append_to_response='external_ids') append_to_response='external_ids', language=tmdb_lang)
except (BaseException, Exception): except (BaseException, Exception):
continue continue
self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire) self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire)
@ -241,7 +243,7 @@ class TmdbIndexer(TVInfoBase):
is_none, shows = self._get_cache_entry(cache_name_key) is_none, shows = self._get_cache_entry(cache_name_key)
if not self.config.get('cache_search') or (None is shows and not is_none): if not self.config.get('cache_search') or (None is shows and not is_none):
try: try:
shows = tmdbsimple.Search().tv(query=n) shows = tmdbsimple.Search().tv(query=n, language=tmdb_lang)
self._set_cache_entry(cache_name_key, shows, expire=self.search_cache_expire) self._set_cache_entry(cache_name_key, shows, expire=self.search_cache_expire)
results.extend([_make_result_dict(s) for s in shows.get('results') or []]) results.extend([_make_result_dict(s) for s in shows.get('results') or []])
except (BaseException, Exception) as e: except (BaseException, Exception) as e:
@ -252,32 +254,23 @@ class TmdbIndexer(TVInfoBase):
results = [seen.add(r.id) or r for r in results if r.id not in seen] results = [seen.add(r.id) or r for r in results if r.id not in seen]
return results return results
def _convert_person_obj(self, person_obj): def _convert_person_obj(self, tmdb_person_obj):
gender = PersonGenders.tmdb_map.get(person_obj.get('gender'), PersonGenders.unknown) gender = PersonGenders.tmdb_map.get(tmdb_person_obj.get('gender'), PersonGenders.unknown)
try: try:
birthdate = person_obj.get('birthday') and tz_p.parse(person_obj.get('birthday')).date() birthdate = tmdb_person_obj.get('birthday') and tz_p.parse(tmdb_person_obj.get('birthday')).date()
except (BaseException, Exception): except (BaseException, Exception):
birthdate = None birthdate = None
try: try:
deathdate = person_obj.get('deathday') and tz_p.parse(person_obj.get('deathday')).date() deathdate = tmdb_person_obj.get('deathday') and tz_p.parse(tmdb_person_obj.get('deathday')).date()
except (BaseException, Exception): except (BaseException, Exception):
deathdate = None deathdate = None
cast = person_obj.get('cast') or person_obj.get('tv_credits', {}).get('cast') person_imdb_id = tmdb_person_obj.get('imdb_id') and try_int(tmdb_person_obj['imdb_id'].replace('nm', ''), None)
person_ids = {TVINFO_TMDB: tmdb_person_obj.get('id')}
if person_imdb_id:
person_ids.update({TVINFO_IMDB: person_imdb_id})
characters = [] pi = tmdb_person_obj.get('images')
for character in cast or []:
ti_show = TVInfoShow()
ti_show.id = character.get('id')
ti_show.ids = TVInfoIDs(ids={TVINFO_TMDB: ti_show.id})
ti_show.seriesname = clean_data(character.get('original_name'))
ti_show.overview = clean_data(character.get('overview'))
ti_show.firstaired = clean_data(character.get('first_air_date'))
characters.append(
TVInfoCharacter(name=clean_data(character.get('character')), show=ti_show)
)
pi = person_obj.get('images')
image_url, main_image, thumb_url, main_thumb, image_list = None, None, None, None, [] image_url, main_image, thumb_url, main_thumb, image_list = None, None, None, None, []
if pi: if pi:
for i in sorted(pi['profiles'], key=lambda a: a['vote_average'] or 0, reverse=True): for i in sorted(pi['profiles'], key=lambda a: a['vote_average'] or 0, reverse=True):
@ -308,20 +301,62 @@ class TmdbIndexer(TVInfoBase):
rating=i['vote_average'], rating=i['vote_average'],
votes=i['vote_count'] votes=i['vote_count']
)) ))
elif tmdb_person_obj.get('profile_path'):
main_image = '%s%s%s' % (
self.img_base_url, self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.original],
tmdb_person_obj['profile_path'])
main_thumb = '%s%s%s' % (
self.img_base_url, self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.medium],
tmdb_person_obj['profile_path'])
person_imdb_id = person_obj.get('imdb_id') and try_int(person_obj['imdb_id'].replace('nm', ''), None) _it_person_obj = TVInfoPerson(
person_ids = {TVINFO_TMDB: person_obj.get('id')} p_id=tmdb_person_obj.get('id'), ids=TVInfoIDs(ids=person_ids), name=clean_data(tmdb_person_obj.get('name')),
if person_imdb_id: akas=clean_data(set(tmdb_person_obj.get('also_known_as') or [])),
person_ids.update({TVINFO_IMDB: person_imdb_id}) bio=clean_data(tmdb_person_obj.get('biography')), gender=gender,
return TVInfoPerson(
p_id=person_obj.get('id'), ids=person_ids, characters=characters,
name=clean_data(person_obj.get('name')), akas=clean_data(set(person_obj.get('also_known_as') or [])),
bio=clean_data(person_obj.get('biography')), gender=gender,
image=main_image, images=image_list, thumb_url=main_thumb, image=main_image, images=image_list, thumb_url=main_thumb,
birthdate=birthdate, birthplace=clean_data(person_obj.get('place_of_birth')), birthdate=birthdate, birthplace=clean_data(tmdb_person_obj.get('place_of_birth')),
deathdate=deathdate, homepage=person_obj.get('homepage') deathdate=deathdate, homepage=tmdb_person_obj.get('homepage')
) )
cast = tmdb_person_obj.get('cast') or tmdb_person_obj.get('tv_credits', {}).get('cast') or \
tmdb_person_obj.get('known_for')
characters = []
for character in cast or []:
ti_show = TVInfoShow()
ti_show.id = character.get('id')
ti_show.ids = TVInfoIDs(ids={TVINFO_TMDB: ti_show.id})
ti_show.seriesname = enforce_type(clean_data(character.get('original_name')), str, '')
ti_show.overview = enforce_type(clean_data(character.get('overview')), str, '')
ti_show.firstaired = clean_data(character.get('first_air_date'))
ti_show.language = clean_data(character.get('original_language'))
ti_show.genre_list = []
for g in character.get('genre_ids') or []:
if g in self.tv_genres:
ti_show.genre_list.append(self.tv_genres.get(g))
ti_show.genre = '|'.join(ti_show.genre_list)
if character.get('poster_path'):
ti_show.poster = '%s%s%s' % \
(self.img_base_url,
self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.original],
character['poster_path'])
ti_show.poster_thumb = '%s%s%s' % \
(self.img_base_url,
self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.medium],
character['poster_path'])
if character.get('backdrop_path'):
ti_show.fanart = '%s%s%s' % \
(self.img_base_url,
self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.original],
character['backdrop_path'])
characters.append(
TVInfoCharacter(name=clean_data(character.get('character')), ti_show=ti_show, person=[_it_person_obj],
episode_count=character.get('episode_count'))
)
_it_person_obj.characters = characters
return _it_person_obj
def _search_person(self, name=None, ids=None): def _search_person(self, name=None, ids=None):
# type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson] # type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson]
""" """
@ -420,7 +455,8 @@ class TmdbIndexer(TVInfoBase):
ti_show.id = show_dict.get('id') ti_show.id = show_dict.get('id')
ti_show.seriesid = ti_show.id ti_show.seriesid = ti_show.id
ti_show.language = clean_data(show_dict.get('original_language')) ti_show.language = clean_data(show_dict.get('original_language'))
ti_show.overview = clean_data(show_dict.get('overview')) ti_show.spoken_languages = [_l['iso_639_1'] for _l in show_dict.get('spoken_languages') or []]
ti_show.overview = enforce_type(clean_data(show_dict.get('overview')), str, '')
ti_show.status = clean_data(show_dict.get('status', '')) ti_show.status = clean_data(show_dict.get('status', ''))
ti_show.show_type = clean_data((show_dict.get('type') and [show_dict['type']]) or []) ti_show.show_type = clean_data((show_dict.get('type') and [show_dict['type']]) or [])
ti_show.firstaired = clean_data(show_dict.get('first_air_date')) ti_show.firstaired = clean_data(show_dict.get('first_air_date'))
@ -469,8 +505,9 @@ class TmdbIndexer(TVInfoBase):
ti_show.ids = TVInfoIDs(tvdb=show_dict.get('external_ids', {}).get('tvdb_id'), ti_show.ids = TVInfoIDs(tvdb=show_dict.get('external_ids', {}).get('tvdb_id'),
tmdb=show_dict['id'], tmdb=show_dict['id'],
rage=show_dict.get('external_ids', {}).get('tvrage_id'), rage=show_dict.get('external_ids', {}).get('tvrage_id'),
imdb=show_dict.get('external_ids', {}).get('imdb_id') and imdb=show_dict.get('external_ids', {}).get('imdb_id')
try_int(show_dict.get('external_ids', {}).get('imdb_id', '').replace('tt', ''), None)) and try_int(
show_dict.get('external_ids', {}).get('imdb_id', '').replace('tt', ''), None))
ti_show.social_ids = TVInfoSocialIDs(twitter=show_dict.get('external_ids', {}).get('twitter_id'), ti_show.social_ids = TVInfoSocialIDs(twitter=show_dict.get('external_ids', {}).get('twitter_id'),
instagram=show_dict.get('external_ids', {}).get('instagram_id'), instagram=show_dict.get('external_ids', {}).get('instagram_id'),
facebook=show_dict.get('external_ids', {}).get('facebook_id')) facebook=show_dict.get('external_ids', {}).get('facebook_id'))
@ -498,7 +535,26 @@ class TmdbIndexer(TVInfoBase):
pass pass
return result[:result_count] return result[:result_count]
def get_similar(self, tvid, result_count=100, **kwargs):
# type: (integer_types, int, Any) -> List[TVInfoShow]
"""
list of similar shows to the provided tv id
:param tvid: id to find similar shows for
:param result_count: result count to returned
"""
return self._get_show_list(tmdbsimple.TV(id=tvid).similar, result_count)
def get_recommended_for_show(self, tvid, result_count=100, **kwargs):
# type: (integer_types, int, Any) -> List[TVInfoShow]
"""
list of recommended shows to the provided tv id
:param tvid: id to find recommended shows for
:param result_count: result count to returned
"""
return self._get_show_list(tmdbsimple.TV(id=tvid).recommendations, result_count)
def get_trending(self, result_count=100, time_window='day', **kwargs): def get_trending(self, result_count=100, time_window='day', **kwargs):
# type: (int, str, Any) -> List[TVInfoShow]
""" """
list of trending tv shows for day or week list of trending tv shows for day or week
:param result_count: :param result_count:
@ -508,12 +564,15 @@ class TmdbIndexer(TVInfoBase):
return self._get_show_list(tmdbsimple.Trending(media_type='tv', time_window=t_windows).info, result_count) return self._get_show_list(tmdbsimple.Trending(media_type='tv', time_window=t_windows).info, result_count)
def get_popular(self, result_count=100, **kwargs): def get_popular(self, result_count=100, **kwargs):
# type: (int, Any) -> List[TVInfoShow]
return self._get_show_list(tmdbsimple.TV().popular, result_count) return self._get_show_list(tmdbsimple.TV().popular, result_count)
def get_top_rated(self, result_count=100, **kwargs): def get_top_rated(self, result_count=100, **kwargs):
# type: (int, Any) -> List[TVInfoShow]
return self._get_show_list(tmdbsimple.TV().top_rated, result_count) return self._get_show_list(tmdbsimple.TV().top_rated, result_count)
def discover(self, result_count=100, **kwargs): def discover(self, result_count=100, **kwargs):
# type: (int, Any) -> List[TVInfoShow]
""" """
Discover TV shows by different types of data like average rating, Discover TV shows by different types of data like average rating,
number of votes, genres, the network they aired on and air dates. number of votes, genres, the network they aired on and air dates.
@ -596,6 +655,12 @@ class TmdbIndexer(TVInfoBase):
:param result_count: :param result_count:
""" """
if not kwargs:
# use default if now kwargs are set = return all future airdate shows with language set to 'en'
kwargs.update({'sort_by': 'first_air_date.asc',
'first_air_date.gte': datetime.date.today().strftime('%Y-%m-%d'),
'with_original_language': 'en',
})
return self._get_show_list(tmdbsimple.Discover().tv, result_count, **kwargs) return self._get_show_list(tmdbsimple.Discover().tv, result_count, **kwargs)
def _get_show_data(self, sid, language, get_ep_info=False, banners=False, posters=False, seasons=False, def _get_show_data(self, sid, language, get_ep_info=False, banners=False, posters=False, seasons=False,
@ -693,6 +758,7 @@ class TmdbIndexer(TVInfoBase):
person=[ person=[
TVInfoPerson( TVInfoPerson(
p_id=person_obj['id'], name=clean_data(person_obj['name']), p_id=person_obj['id'], name=clean_data(person_obj['name']),
ids=TVInfoIDs(ids={TVINFO_TMDB: person_obj['id']}),
image='%s%s%s' % ( image='%s%s%s' % (
self.img_base_url, self.img_base_url,
self.size_map[TVInfoImageType.person_poster][ self.size_map[TVInfoImageType.person_poster][

View file

@ -1,14 +1,15 @@
import datetime
import logging import logging
import re import re
from .exceptions import TraktException from .exceptions import TraktException, TraktAuthException
from exceptions_helper import ConnectionSkipException, ex from exceptions_helper import ConnectionSkipException, ex
from six import iteritems from six import iteritems
from .trakt import TraktAPI from .trakt import TraktAPI
from lib.tvinfo_base.exceptions import BaseTVinfoShownotfound from lib.tvinfo_base.exceptions import BaseTVinfoShownotfound
from lib.tvinfo_base import TVInfoBase, TVINFO_TRAKT, TVINFO_TMDB, TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_IMDB, \ from lib.tvinfo_base import TVInfoBase, TVINFO_TRAKT, TVINFO_TMDB, TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_IMDB, \
TVINFO_SLUG, TVInfoPerson, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_INSTAGRAM, TVInfoCharacter, TVInfoShow, \ TVINFO_SLUG, TVInfoPerson, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_INSTAGRAM, TVInfoCharacter, \
TVInfoIDs, TVINFO_TRAKT_SLUG TVInfoShow, TVInfoIDs, TVInfoSocialIDs, TVINFO_TRAKT_SLUG, TVInfoEpisode, TVInfoSeason, RoleTypes
from sg_helpers import try_int from sg_helpers import clean_data, enforce_type, try_int
from lib.dateutil.parser import parser from lib.dateutil.parser import parser
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
@ -33,6 +34,7 @@ log.addHandler(logging.NullHandler())
def _convert_imdb_id(src, s_id): def _convert_imdb_id(src, s_id):
# type: (int, integer_types) -> integer_types
if TVINFO_IMDB == src: if TVINFO_IMDB == src:
try: try:
return try_int(re.search(r'(\d+)', s_id).group(1), s_id) return try_int(re.search(r'(\d+)', s_id).group(1), s_id)
@ -100,16 +102,29 @@ class TraktIndexer(TVInfoBase):
@staticmethod @staticmethod
def _make_result_obj(shows, results): def _make_result_obj(shows, results):
# type: (List[Dict], List[TVInfoShow]) -> None
if shows: if shows:
try: try:
for s in shows: for s in shows:
if s['ids']['trakt'] not in [i['ids'].trakt for i in results]: if s['ids']['trakt'] not in [i['ids'].trakt for i in results]:
s['id'] = s['ids']['trakt'] ti_show = TVInfoShow()
s['ids'] = TVInfoIDs( countries = clean_data(s['country'])
trakt=s['ids']['trakt'], tvdb=s['ids']['tvdb'], tmdb=s['ids']['tmdb'], if countries:
countries = [countries]
else:
countries = []
ti_show.id, ti_show.seriesname, ti_show.overview, ti_show.firstaired, ti_show.airs_dayofweek, \
ti_show.runtime, ti_show.network, ti_show.origin_countries, ti_show.official_site, \
ti_show.status, ti_show.rating, ti_show.genre_list, ti_show.ids = s['ids']['trakt'], \
clean_data(s['title']), enforce_type(clean_data(s['overview']), str, ''), s['firstaired'], \
(isinstance(s['airs'], dict) and s['airs']['day']) or '', \
s['runtime'], s['network'], countries, s['homepage'], s['status'], s['rating'], \
s['genres_list'], \
TVInfoIDs(trakt=s['ids']['trakt'], tvdb=s['ids']['tvdb'], tmdb=s['ids']['tmdb'],
rage=s['ids']['tvrage'], rage=s['ids']['tvrage'],
imdb=s['ids']['imdb'] and try_int(s['ids']['imdb'].replace('tt', ''), None)) imdb=s['ids']['imdb'] and try_int(s['ids']['imdb'].replace('tt', ''), None))
results.append(s) ti_show.genre = '|'.join(ti_show.genre_list or [])
results.append(ti_show)
except (BaseException, Exception) as e: except (BaseException, Exception) as e:
log.debug('Error creating result dict: %s' % ex(e)) log.debug('Error creating result dict: %s' % ex(e))
@ -119,7 +134,7 @@ class TraktIndexer(TVInfoBase):
If a custom_ui UI is configured, it uses this to select the correct If a custom_ui UI is configured, it uses this to select the correct
series. series.
""" """
results = [] results = [] # type: List[TVInfoShow]
if ids: if ids:
for t, p in iteritems(ids): for t, p in iteritems(ids):
if t in self.supported_id_searches: if t in self.supported_id_searches:
@ -168,13 +183,13 @@ class TraktIndexer(TVInfoBase):
else: else:
self._make_result_obj(all_series, results) self._make_result_obj(all_series, results)
final_result = [] final_result = [] # type: List[TVInfoShow]
seen = set() seen = set()
film_type = re.compile(r'(?i)films?\)$') film_type = re.compile(r'(?i)films?\)$')
for r in results: for r in results:
if r['id'] not in seen: if r.id not in seen:
seen.add(r['id']) seen.add(r.id)
title = r.get('title') or '' title = r.seriesname or ''
if not film_type.search(title): if not film_type.search(title):
final_result.append(r) final_result.append(r)
else: else:
@ -247,17 +262,19 @@ class TraktIndexer(TVInfoBase):
deathdate=deathdate, deathdate=deathdate,
homepage=person_obj['homepage'], homepage=person_obj['homepage'],
birthplace=person_obj['birthplace'], birthplace=person_obj['birthplace'],
social_ids={TVINFO_TWITTER: person_obj['social_ids']['twitter'], social_ids=TVInfoSocialIDs(
ids={TVINFO_TWITTER: person_obj['social_ids']['twitter'],
TVINFO_FACEBOOK: person_obj['social_ids']['facebook'], TVINFO_FACEBOOK: person_obj['social_ids']['facebook'],
TVINFO_INSTAGRAM: person_obj['social_ids']['instagram'], TVINFO_INSTAGRAM: person_obj['social_ids']['instagram'],
TVINFO_WIKIPEDIA: person_obj['social_ids']['wikipedia'] TVINFO_WIKIPEDIA: person_obj['social_ids']['wikipedia']
}, }),
ids={TVINFO_TRAKT: person_obj['ids']['trakt'], TVINFO_SLUG: person_obj['ids']['slug'], ids=TVInfoIDs(ids={
TVINFO_TRAKT: person_obj['ids']['trakt'], TVINFO_SLUG: person_obj['ids']['slug'],
TVINFO_IMDB: TVINFO_IMDB:
person_obj['ids']['imdb'] and person_obj['ids']['imdb'] and
try_int(person_obj['ids']['imdb'].replace('nm', ''), None), try_int(person_obj['ids']['imdb'].replace('nm', ''), None),
TVINFO_TMDB: person_obj['ids']['tmdb'], TVINFO_TMDB: person_obj['ids']['tmdb'],
TVINFO_TVRAGE: person_obj['ids']['tvrage']}) TVINFO_TVRAGE: person_obj['ids']['tvrage']}))
def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs): def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs):
# type: (integer_types, bool, bool, Any) -> Optional[TVInfoPerson] # type: (integer_types, bool, bool, Any) -> Optional[TVInfoPerson]
@ -279,7 +296,7 @@ class TraktIndexer(TVInfoBase):
if not urls: if not urls:
return return
result = None result = None # type: Optional[TVInfoPerson]
for url, show_credits in urls: for url, show_credits in urls:
try: try:
@ -299,15 +316,18 @@ class TraktIndexer(TVInfoBase):
for src, sid in iteritems(c['show']['ids']) if src in id_map}) for src, sid in iteritems(c['show']['ids']) if src in id_map})
ti_show.network = c['show']['network'] ti_show.network = c['show']['network']
ti_show.firstaired = c['show']['first_aired'] ti_show.firstaired = c['show']['first_aired']
ti_show.overview = c['show']['overview'] ti_show.overview = enforce_type(clean_data(c['show']['overview']), str, '')
ti_show.status = c['show']['status'] ti_show.status = c['show']['status']
ti_show.imdb_id = c['show']['ids'].get('imdb') ti_show.imdb_id = c['show']['ids'].get('imdb')
ti_show.runtime = c['show']['runtime'] ti_show.runtime = c['show']['runtime']
ti_show.genre_list = c['show']['genres'] ti_show.genre_list = c['show']['genres']
for ch in c.get('characters') or []: for ch in c.get('characters') or []:
pc.append( _ti_character = TVInfoCharacter(name=ch, regular=c.get('series_regular'),
TVInfoCharacter(name=ch, regular=c.get('series_regular'), ti_show=ti_show) ti_show=ti_show, person=[result],
) episode_count=c.get('episode_count'))
pc.append(_ti_character)
ti_show.cast[(RoleTypes.ActorGuest, RoleTypes.ActorMain)[
c.get('series_regular', False)]].append(_ti_character)
result.characters = pc result.characters = pc
else: else:
result = self._convert_person_obj(resp) result = self._convert_person_obj(resp)
@ -353,3 +373,268 @@ class TraktIndexer(TVInfoBase):
log.debug('Could not connect to Trakt service: %s' % ex(e)) log.debug('Could not connect to Trakt service: %s' % ex(e))
return result return result
@staticmethod
def _convert_episode(episode_data, show_obj, season_obj):
# type: (Dict, TVInfoShow, TVInfoSeason) -> TVInfoEpisode
ti_episode = TVInfoEpisode(show=show_obj)
ti_episode.season = season_obj
ti_episode.id, ti_episode.episodename, ti_episode.seasonnumber, ti_episode.episodenumber, \
ti_episode.absolute_number, ti_episode.overview, ti_episode.firstaired, ti_episode.runtime, \
ti_episode.rating, ti_episode.vote_count = episode_data.get('ids', {}).get('trakt'), \
clean_data(episode_data.get('title')), episode_data.get('season'), episode_data.get('number'), \
episode_data.get('number_abs'), enforce_type(clean_data(episode_data.get('overview')), str, ''), \
re.sub('T.+$', '', episode_data.get('first_aired') or ''), \
episode_data['runtime'], episode_data.get('rating'), episode_data.get('votes')
if episode_data.get('available_translations'):
ti_episode.language = clean_data(episode_data['available_translations'][0])
ti_episode.ids = TVInfoIDs(ids={id_map[src]: _convert_imdb_id(id_map[src], sid)
for src, sid in iteritems(episode_data['ids']) if src in id_map})
return ti_episode
@staticmethod
def _convert_show(show_data):
# type: (Dict) -> TVInfoShow
_s_d = (show_data, show_data.get('show'))['show' in show_data]
ti_show = TVInfoShow()
ti_show.seriesname, ti_show.id, ti_show.firstaired, ti_show.overview, ti_show.runtime, ti_show.network, \
ti_show.network_country, ti_show.status, ti_show.genre_list, ti_show.language, ti_show.watcher_count, \
ti_show.play_count, ti_show.collected_count, ti_show.collector_count, ti_show.vote_count, \
ti_show.vote_average, ti_show.rating, ti_show.contentrating, ti_show.official_site, ti_show.slug = \
clean_data(_s_d['title']), _s_d['ids']['trakt'], \
re.sub('T.+$', '', _s_d.get('first_aired') or '') or _s_d.get('year'), \
enforce_type(clean_data(_s_d.get('overview')), str, ''), _s_d.get('runtime'), _s_d.get('network'), \
_s_d.get('country'), _s_d.get('status'), _s_d.get('genres', []), _s_d.get('language'), \
show_data.get('watcher_count'), show_data.get('play_count'), show_data.get('collected_count'), \
show_data.get('collector_count'), _s_d.get('votes'), _s_d.get('rating'), _s_d.get('rating'), \
_s_d.get('certification'), _s_d.get('homepage'), _s_d['ids']['slug']
ti_show.ids = TVInfoIDs(ids={id_map[src]: _convert_imdb_id(id_map[src], sid)
for src, sid in iteritems(_s_d['ids']) if src in id_map})
ti_show.genre = '|'.join(ti_show.genre_list or [])
if _s_d.get('trailer'):
ti_show.trailers = {'any': _s_d['trailer']}
if 'episode' in show_data:
ep_data = show_data['episode']
ti_show.next_season_airdate = re.sub('T.+$', '', ep_data.get('first_aired') or '')
ti_season = TVInfoSeason(show=ti_show)
ti_season.number = ep_data['season']
ti_season[ep_data['number']] = TraktIndexer._convert_episode(ep_data, ti_show, ti_season)
ti_show[ep_data['season']] = ti_season
return ti_show
def _get_show_lists(self, url, account=None):
# type: (str, Any) -> List[TVInfoShow]
result = []
if account:
from sickgear import TRAKT_ACCOUNTS
if account in TRAKT_ACCOUNTS and TRAKT_ACCOUNTS[account].active:
kw = {'send_oauth': account}
else:
raise TraktAuthException('Account missing or disabled')
else:
kw = {}
resp = TraktAPI().trakt_request(url, **kw)
if resp:
for _show in resp:
result.append(self._convert_show(_show))
return result
def get_most_played(self, result_count=100, period='weekly', **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most played shows
:param period: possible values: 'daily', 'weekly', 'monthly', 'yearly', 'all'
:param result_count: how many results are suppose to be returned
"""
use_period = ('weekly', period)[period in ('daily', 'weekly', 'monthly', 'yearly', 'all')]
return self._get_show_lists('shows/played/%s?extended=full&page=%d&limit=%d' % (use_period, 1, result_count))
def get_most_watched(self, result_count=100, period='weekly', **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most watched shows
:param period: possible values: 'daily', 'weekly', 'monthly', 'yearly', 'all'
:param result_count: how many results are suppose to be returned
"""
use_period = ('weekly', period)[period in ('daily', 'weekly', 'monthly', 'yearly', 'all')]
return self._get_show_lists('shows/watched/%s?extended=full&page=%d&limit=%d' % (use_period, 1, result_count))
def get_most_collected(self, result_count=100, period='weekly', **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most collected shows
:param period: possible values: 'daily', 'weekly', 'monthly', 'yearly', 'all'
:param result_count: how many results are suppose to be returned
"""
use_period = ('weekly', period)[period in ('daily', 'weekly', 'monthly', 'yearly', 'all')]
return self._get_show_lists('shows/collected/%s?extended=full&page=%d&limit=%d' % (use_period, 1, result_count))
def get_recommended(self, result_count=100, period='weekly', **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most recommended shows
:param period: possible values: 'daily', 'weekly', 'monthly', 'yearly', 'all'
:param result_count: how many results are suppose to be returned
"""
use_period = ('weekly', period)[period in ('daily', 'weekly', 'monthly', 'yearly', 'all')]
return self._get_show_lists('shows/recommended/%s?extended=full&page=%d&limit=%d' % (use_period, 1, result_count))
def get_recommended_for_account(self, account, result_count=100, ignore_collected=False, ignore_watchlisted=False,
**kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most recommended shows for account
:param account: account to get recommendations for
:param result_count: how many results are suppose to be returned
:param ignore_collected: exclude colleded shows
:param ignore_watchlisted: exclude watchlisted shows
"""
from sickgear import TRAKT_ACCOUNTS
if not account or account not in TRAKT_ACCOUNTS or not TRAKT_ACCOUNTS[account].active:
raise TraktAuthException('Account missing or disabled')
extra_param = []
if ignore_collected:
extra_param.append('ignore_collected=true')
if ignore_watchlisted:
extra_param.append('ignore_watchlisted=true')
return self._get_show_lists('recommendations/shows?extended=full&page=%d&limit=%d%s' %
(1, result_count, ('', '&%s' % '&'.join(extra_param))[0 < len(extra_param)]),
account=account)
def hide_recommended_for_account(self, account, show_ids, **kwargs):
# type: (integer_types, List[integer_types], Any) -> List[integer_types]
"""
hide recommended show for account
:param account: account to get recommendations for
:param show_ids: list of show_ids to no longer recommend for account
:return: list of added ids
"""
from sickgear import TRAKT_ACCOUNTS
if not account or account not in TRAKT_ACCOUNTS or not TRAKT_ACCOUNTS[account].active:
raise TraktAuthException('Account missing or disabled')
if not isinstance(show_ids, list) or not show_ids or any(not isinstance(_i, int) for _i in show_ids):
raise TraktException('list of show_ids (trakt id) required')
resp = TraktAPI().trakt_request('users/hidden/recommendations', send_oauth=account,
data={'shows': [{'ids': {'trakt': _i}} for _i in show_ids]})
if resp and isinstance(resp, dict) and 'added' in resp and 'shows' in resp['added']:
if len(show_ids) == resp['added']['shows']:
return show_ids
if 'not_found' in resp and 'shows' in resp['not_found']:
not_found = [_i['ids']['trakt'] for _i in resp['not_found']['shows']]
else:
not_found = []
return [_i for _i in show_ids if _i not in not_found]
return []
def unhide_recommended_for_account(self, account, show_ids, **kwargs):
# type: (integer_types, List[integer_types], Any) -> List[integer_types]
"""
unhide recommended show for account
:param account: account to get recommendations for
:param show_ids: list of show_ids to be included in possible recommend for account
:return: list of removed ids
"""
from sickgear import TRAKT_ACCOUNTS
if not account or account not in TRAKT_ACCOUNTS or not TRAKT_ACCOUNTS[account].active:
raise TraktAuthException('Account missing or disabled')
if not isinstance(show_ids, list) or not show_ids or any(not isinstance(_i, int) for _i in show_ids):
raise TraktException('list of show_ids (trakt id) required')
resp = TraktAPI().trakt_request('users/hidden/recommendations/remove', send_oauth=account,
data={'shows': [{'ids': {'trakt': _i}} for _i in show_ids]})
if resp and isinstance(resp, dict) and 'deleted' in resp and 'shows' in resp['deleted']:
if len(show_ids) == resp['deleted']['shows']:
return show_ids
if 'not_found' in resp and 'shows' in resp['not_found']:
not_found = [_i['ids']['trakt'] for _i in resp['not_found']['shows']]
else:
not_found = []
return [_i for _i in show_ids if _i not in not_found]
return []
def list_hidden_recommended_for_account(self, account, **kwargs):
# type: (integer_types, Any) -> List[TVInfoShow]
"""
list hidden recommended show for account
:param account: account to get recommendations for
:return: list of hidden shows
"""
from sickgear import TRAKT_ACCOUNTS
if not account or account not in TRAKT_ACCOUNTS or not TRAKT_ACCOUNTS[account].active:
raise TraktAuthException('Account missing or disabled')
return self._get_show_lists('users/hidden/recommendations?type=show', account=account)
def get_watchlisted_for_account(self, account, result_count=100, sort='rank', **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get watchlisted shows for the account
:param account: account to get recommendations for
:param result_count: how many results are suppose to be returned
:param sort: possible values: 'rank', 'added', 'released', 'title'
"""
from sickgear import TRAKT_ACCOUNTS
if not account or account not in TRAKT_ACCOUNTS or not TRAKT_ACCOUNTS[account].active:
raise TraktAuthException('Account missing or disabled')
sort = ('rank', sort)[sort in ('rank', 'added', 'released', 'title')]
return self._get_show_lists('users/%s/watchlist/shows/%s?extended=full&page=%d&limit=%d' %
(TRAKT_ACCOUNTS[account].slug, sort, 1, result_count), account=account)
def get_anticipated(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most anticipated shows
:param result_count: how many results are suppose to be returned
"""
return self._get_show_lists('shows/anticipated?extended=full&page=%d&limit=%d' % (1, result_count))
def get_trending(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get trending shows
:param result_count: how many results are suppose to be returned
"""
return self._get_show_lists('shows/trending?extended=full&page=%d&limit=%d' % (1, result_count))
def get_popular(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get all popular shows
:param result_count: how many results are suppose to be returned
"""
return self._get_show_lists('shows/popular?extended=full&page=%d&limit=%d' % (1, result_count))
def get_similar(self, tvid, result_count=100, **kwargs):
# type: (integer_types, int, Any) -> List[TVInfoShow]
"""
return list of similar shows to given id
:param tvid: id to give similar shows for
:param result_count: count of results requested
"""
if not isinstance(tvid, int):
raise TraktException('tvid/trakt id for show required')
return self._get_show_lists('shows/%d/related?extended=full&page=%d&limit=%d' % (tvid, 1, result_count))
def get_new_shows(self, result_count=100, start_date=None, days=32, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get new shows
:param result_count: how many results are suppose to be returned
:param start_date: start date for returned data in format: '2014-09-01'
:param days: number of days to return from start date
"""
if None is start_date:
start_date = (datetime.datetime.now() + datetime.timedelta(days=-16)).strftime('%Y-%m-%d')
return self._get_show_lists('calendars/all/shows/new/%s/%s?extended=full&page=%d&limit=%d' %
(start_date, days, 1, result_count))
def get_new_seasons(self, result_count=100, start_date=None, days=32, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get new seasons
:param result_count: how many results are suppose to be returned
:param start_date: start date for returned data in format: '2014-09-01'
:param days: number of days to return from start date
"""
if None is start_date:
start_date = (datetime.datetime.now() + datetime.timedelta(days=-16)).strftime('%Y-%m-%d')
return self._get_show_lists('calendars/all/shows/premieres/%s/%s?extended=full&page=%d&limit=%d' %
(start_date, days, 1, result_count))

View file

@ -33,7 +33,7 @@ from lib.cachecontrol import CacheControl, caches
from lib.dateutil.parser import parse from lib.dateutil.parser import parse
from lib.exceptions_helper import ConnectionSkipException from lib.exceptions_helper import ConnectionSkipException
from lib.tvinfo_base import CastList, TVInfoCharacter, CrewList, TVInfoPerson, RoleTypes, \ from lib.tvinfo_base import CastList, TVInfoCharacter, CrewList, TVInfoPerson, RoleTypes, \
TVINFO_TVDB, TVINFO_TVDB_SLUG, TVInfoBase, TVInfoIDs TVINFO_TVDB, TVINFO_TVDB_SLUG, TVInfoBase, TVInfoIDs, TVInfoNetwork, TVInfoShow
from .tvdb_exceptions import TvdbError, TvdbShownotfound, TvdbTokenexpired from .tvdb_exceptions import TvdbError, TvdbShownotfound, TvdbTokenexpired
from .tvdb_ui import BaseUI, ConsoleUI from .tvdb_ui import BaseUI, ConsoleUI
@ -44,7 +44,6 @@ from six import integer_types, iteritems, PY2, string_types
if False: if False:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from typing import Any, AnyStr, Dict, List, Optional, Union from typing import Any, AnyStr, Dict, List, Optional, Union
from lib.tvinfo_base import TVInfoShow
THETVDB_V2_API_TOKEN = {'token': None, 'datetime': datetime.datetime.fromordinal(1)} THETVDB_V2_API_TOKEN = {'token': None, 'datetime': datetime.datetime.fromordinal(1)}
@ -334,13 +333,15 @@ class Tvdb(TVInfoBase):
def _search_show(self, name=None, ids=None, **kwargs): def _search_show(self, name=None, ids=None, **kwargs):
# type: (AnyStr, Dict[integer_types, integer_types], Optional[Any]) -> List[TVInfoShow] # type: (AnyStr, Dict[integer_types, integer_types], Optional[Any]) -> List[TVInfoShow]
def map_data(data): def make_tvinfoshow(data):
if not data.get('poster'): _ti_show = TVInfoShow()
data['poster'] = data.get('image') _ti_show.id, _ti_show.banner, _ti_show.firstaired, _ti_show.poster, _ti_show.network, _ti_show.overview, \
data['ids'] = TVInfoIDs( _ti_show.seriesname, _ti_show.slug, _ti_show.status, _ti_show.aliases, _ti_show.ids = \
tvdb=data.get('id'), clean_data(data['id']), clean_data(data.get('banner')), clean_data(data.get('firstaired')), \
imdb=data.get('imdb_id') and try_int(data.get('imdb_id', '').replace('tt', ''), None)) clean_data(data.get('poster')), clean_data(data.get('network')), clean_data(data.get('overview')), \
return data clean_data(data.get('seriesname')), clean_data(data.get('slug')), clean_data(data.get('status')), \
clean_data((data.get('aliases'))), TVInfoIDs(tvdb=try_int(clean_data(data['id'])))
return _ti_show
results = [] results = []
if ids: if ids:
@ -356,7 +357,7 @@ class Tvdb(TVInfoBase):
else: else:
d_m = shows d_m = shows
if d_m: if d_m:
results = list(map(map_data, [d_m['data']])) results.append(make_tvinfoshow(d_m['data']))
if ids.get(TVINFO_TVDB_SLUG): if ids.get(TVINFO_TVDB_SLUG):
cache_id_key = 's-id-%s-%s' % (TVINFO_TVDB, ids[TVINFO_TVDB_SLUG]) cache_id_key = 's-id-%s-%s' % (TVINFO_TVDB, ids[TVINFO_TVDB_SLUG])
is_none, shows = self._get_cache_entry(cache_id_key) is_none, shows = self._get_cache_entry(cache_id_key)
@ -371,7 +372,7 @@ class Tvdb(TVInfoBase):
if d_m: if d_m:
for r in d_m: for r in d_m:
if ids.get(TVINFO_TVDB_SLUG) == r['slug']: if ids.get(TVINFO_TVDB_SLUG) == r['slug']:
results = list(map(map_data, [r])) results.append(make_tvinfoshow(r))
break break
if name: if name:
for n in ([name], name)[isinstance(name, list)]: for n in ([name], name)[isinstance(name, list)]:
@ -388,7 +389,7 @@ class Tvdb(TVInfoBase):
if r: if r:
if not isinstance(r, list): if not isinstance(r, list):
r = [r] r = [r]
results.extend(list(map(map_data, r))) results.extend([make_tvinfoshow(_s) for _s in r])
seen = set() seen = set()
results = [seen.add(r['id']) or r for r in results if r['id'] not in seen] results = [seen.add(r['id']) or r for r in results if r['id'] not in seen]
@ -947,9 +948,6 @@ class Tvdb(TVInfoBase):
role_image = self._make_image(self.config['url_artworks'], role_image) role_image = self._make_image(self.config['url_artworks'], role_image)
character_name = n.get('role', '').strip() or alts.get(n['id'], {}).get('role', '') character_name = n.get('role', '').strip() or alts.get(n['id'], {}).get('role', '')
person_name = n.get('name', '').strip() or alts.get(n['id'], {}).get('name', '') person_name = n.get('name', '').strip() or alts.get(n['id'], {}).get('name', '')
try:
person_id = try_int(re.search(r'^person/(\d+)/', n.get('image', '')).group(1), None)
except (BaseException, Exception):
person_id = None person_id = None
person_id = person_id or alts.get(n['id'], {}).get('person_id') person_id = person_id or alts.get(n['id'], {}).get('person_id')
character_id = n.get('id', None) or alts.get(n['id'], {}).get('rid') character_id = n.get('id', None) or alts.get(n['id'], {}).get('rid')
@ -971,7 +969,7 @@ class Tvdb(TVInfoBase):
cast[RoleTypes.ActorMain].append( cast[RoleTypes.ActorMain].append(
TVInfoCharacter( TVInfoCharacter(
p_id=character_id, name=character_name, person=[TVInfoPerson(p_id=person_id, name=person_name)], p_id=character_id, name=character_name, person=[TVInfoPerson(p_id=person_id, name=person_name)],
image=role_image, show=self.shows[sid])) image=role_image, show=self.ti_shows[sid]))
except (BaseException, Exception): except (BaseException, Exception):
pass pass
self._set_show_data(sid, 'actors', a) self._set_show_data(sid, 'actors', a)
@ -1020,9 +1018,9 @@ class Tvdb(TVInfoBase):
self.ti_shows[sid].__dict__[loaded_name] = True self.ti_shows[sid].__dict__[loaded_name] = True
# fallback image thumbnail for none excluded_main_data if artwork is not found # 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): if not excluded_main_data and show_data.get(image_type):
self._set_show_data(sid, f'{image_type}_thumb', self._set_show_data(sid, f'{image_type}_thumb',
re.sub(r'\.jpg$', '_t.jpg', show_data['data'][image_type], flags=re.I)) re.sub(r'\.jpg$', '_t.jpg', show_data[image_type], flags=re.I))
def _get_show_data(self, def _get_show_data(self,
sid, # type: integer_types sid, # type: integer_types
@ -1044,7 +1042,8 @@ class Tvdb(TVInfoBase):
# Parse show information # Parse show information
url = self.config['url_series_info'] % sid url = self.config['url_series_info'] % sid
if direct_data or sid not in self.shows or None is self.shows[sid].id or language != self.shows[sid].language: if direct_data or sid not in self.ti_shows or None is self.ti_shows[sid].id or \
language != self.ti_shows[sid].language:
log.debug('Getting all series data for %s' % sid) log.debug('Getting all series data for %s' % sid)
show_data = self._getetsrc(url, language=language) show_data = self._getetsrc(url, language=language)
if not show_data or not show_data.get('data'): if not show_data or not show_data.get('data'):
@ -1056,13 +1055,34 @@ class Tvdb(TVInfoBase):
if not (show_data and 'seriesname' in show_data.get('data', {}) or {}): if not (show_data and 'seriesname' in show_data.get('data', {}) or {}):
return False return False
for k, v in iteritems(show_data['data']): show_data = show_data['data']
self._set_show_data(sid, k, v) ti_show = self.ti_shows[sid] # type: TVInfoShow
self._set_show_data(sid, 'ids', ti_show.banner_loaded = ti_show.poster_loaded = ti_show.fanart_loaded = True
TVInfoIDs( ti_show.id = show_data['id']
tvdb=show_data['data'].get('id'), ti_show.seriesname = clean_data(show_data.get('seriesname'))
imdb=show_data['data'].get('imdb_id') ti_show.slug = clean_data(show_data.get('slug'))
and try_int(show_data['data'].get('imdb_id', '').replace('tt', ''), None))) ti_show.poster = clean_data(show_data.get('poster'))
ti_show.banner = clean_data(show_data.get('banner'))
ti_show.fanart = clean_data(show_data.get('fanart'))
ti_show.firstaired = clean_data(show_data.get('firstAired'))
ti_show.rating = show_data.get('rating')
ti_show.contentrating = clean_data(show_data.get('contentRatings'))
ti_show.aliases = show_data.get('aliases') or []
ti_show.status = clean_data(show_data['status'])
if clean_data(show_data.get('network')):
ti_show.network = clean_data(show_data['network'])
ti_show.networks = [TVInfoNetwork(clean_data(show_data['network']),
n_id=clean_data(show_data.get('networkid')))]
ti_show.runtime = try_int(show_data.get('runtime'), 0)
ti_show.language = clean_data(show_data.get('language'))
ti_show.genre = clean_data(show_data.get('genre'))
ti_show.genre_list = clean_data(show_data.get('genre_list')) or []
ti_show.overview = clean_data(show_data.get('overview'))
ti_show.imdb_id = clean_data(show_data.get('imdb_id')) or None
ti_show.airs_time = clean_data(show_data.get('airs_time'))
ti_show.airs_dayofweek = clean_data(show_data.get('airs_dayofweek'))
ti_show.ids = TVInfoIDs(tvdb=ti_show.id, imdb=try_int(ti_show.imdb_id.replace('tt', ''), None))
else: else:
show_data = {'data': {}} show_data = {'data': {}}
@ -1225,7 +1245,7 @@ class Tvdb(TVInfoBase):
try: try:
for guest in cur_ep.get('gueststars_list', []): for guest in cur_ep.get('gueststars_list', []):
cast[RoleTypes.ActorGuest].append(TVInfoCharacter(person=[TVInfoPerson(name=guest)], cast[RoleTypes.ActorGuest].append(TVInfoCharacter(person=[TVInfoPerson(name=guest)],
show=self.shows[sid])) show=self.ti_shows[sid]))
except (BaseException, Exception): except (BaseException, Exception):
pass pass
try: try:
@ -1258,6 +1278,11 @@ class Tvdb(TVInfoBase):
self.corrections.update(dict([(x['seriesname'], int(x['id'])) for x in selected_series])) self.corrections.update(dict([(x['seriesname'], int(x['id'])) for x in selected_series]))
return sids return sids
def _get_languages(self):
if not Tvdb._supported_languages:
Tvdb._supported_languages = [{'id': _l, 'name': None, 'nativeName': None, 'sg_lang': _l}
for _l in self.config['valid_languages']]
def main(): def main():
"""Simple example of using tvdb_api - it just """Simple example of using tvdb_api - it just

View file

@ -103,7 +103,7 @@ show_map = {
# 'siteratingcount': '', # 'siteratingcount': '',
# 'lastupdated': '', # 'lastupdated': '',
# 'contentrating': '', # 'contentrating': '',
'rating': 'rating', # 'rating': 'rating',
'status': 'status', 'status': 'status',
'overview': 'summary', 'overview': 'summary',
# 'poster': 'image', # 'poster': 'image',
@ -152,21 +152,28 @@ class TvMaze(TVInfoBase):
if language in cur_locale[1]['name_en'].lower(): if language in cur_locale[1]['name_en'].lower():
language_country_code = cur_locale[0].split('_')[1].lower() language_country_code = cur_locale[0].split('_')[1].lower()
break break
return {'seriesname': clean_data(s.name), 'id': s.id, 'firstaired': clean_data(s.premiered), ti_show = TVInfoShow()
'network': clean_data((s.network and s.network.name) or (s.web_channel and s.web_channel.name)), show_type = clean_data(s.type)
'genres': clean_data(isinstance(s.genres, list) and '|'.join(g.lower() for g in s.genres) or if show_type:
s.genres), show_type = [show_type]
'overview': clean_data(s.summary), 'language': clean_data(s.language), else:
'language_country_code': clean_data(language_country_code), show_type = []
'runtime': s.average_runtime or s.runtime, ti_show.seriesname, ti_show.id, ti_show.firstaired, ti_show.network, ti_show.genre_list, ti_show.overview, \
'type': clean_data(s.type), 'schedule': s.schedule, 'status': clean_data(s.status), ti_show.language, ti_show.runtime, ti_show.show_type, ti_show.airs_dayofweek, ti_show. status, \
'official_site': clean_data(s.official_site), ti_show.official_site, ti_show.aliases, ti_show.poster, ti_show.ids = clean_data(s.name), s.id, \
'aliases': [clean_data(a.name) for a in s.akas], 'image': s.image and s.image.get('original'), clean_data(s.premiered), \
'poster': s.image and s.image.get('original'), clean_data((s.network and s.network.name) or (s.web_channel and s.web_channel.name)), \
'ids': TVInfoIDs( isinstance(s.genres, list) and [clean_data(g.lower()) for g in s.genres], \
tvdb=s.externals.get('thetvdb'), rage=s.externals.get('tvrage'), tvmaze=s.id, enforce_type(clean_data(s.summary), str, ''), clean_data(s.language), \
imdb=clean_data(s.externals.get('imdb') and try_int(s.externals.get('imdb').replace('tt', ''), s.average_runtime or s.runtime, show_type, ', '.join(s.schedule['days'] or []), clean_data(s.status), \
None)))} clean_data(s.official_site), [clean_data(a.name) for a in s.akas], \
s.image and s.image.get('original'), \
TVInfoIDs(tvdb=s.externals.get('thetvdb'), rage=s.externals.get('tvrage'), tvmaze=s.id,
imdb=clean_data(s.externals.get('imdb') and
try_int(s.externals.get('imdb').replace('tt', ''), None)))
ti_show.genre = '|'.join(ti_show.genre_list or [])
return ti_show
results = [] results = []
if ids: if ids:
for t, p in iteritems(ids): for t, p in iteritems(ids):
@ -230,18 +237,24 @@ class TvMaze(TVInfoBase):
('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'), ('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'),
('airtime', 'airtime'), ('runtime', 'runtime'), ('airtime', 'airtime'), ('runtime', 'runtime'),
('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'), ('filename', 'image')): ('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'), ('filename', 'image')):
if 'filename' == _k: if 'airtime' == _k:
try:
airtime = datetime.time.fromisoformat(clean_data(getattr(ep_obj, _s, getattr(empty_ep, _k))))
except (BaseException, Exception):
airtime = None
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number or 0, _k, airtime)
elif 'filename' == _k:
image = getattr(ep_obj, _s, {}) or {} image = getattr(ep_obj, _s, {}) or {}
image = image.get('original') or image.get('medium') image = image.get('original') or image.get('medium')
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number, _k, image) self._set_item(sid, ep_obj.season_number, ep_obj.episode_number or 0, _k, image)
else: else:
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number, _k, self._set_item(sid, ep_obj.season_number, ep_obj.episode_number or 0, _k,
clean_data(getattr(ep_obj, _s, getattr(empty_ep, _k)))) clean_data(getattr(ep_obj, _s, getattr(empty_ep, _k))))
if ep_obj.airstamp: if ep_obj.airstamp:
try: try:
at = _datetime_to_timestamp(tz_p.parse(ep_obj.airstamp)) at = _datetime_to_timestamp(tz_p.parse(ep_obj.airstamp))
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number, 'timestamp', at) self._set_item(sid, ep_obj.season_number, ep_obj.episode_number or 0, 'timestamp', at)
except (BaseException, Exception): except (BaseException, Exception):
pass pass
@ -318,137 +331,12 @@ class TvMaze(TVInfoBase):
return False return False
ti_show = self.ti_shows[sid] # type: TVInfoShow ti_show = self.ti_shows[sid] # type: TVInfoShow
show_obj = ti_show.__dict__ self._show_info_loader(
for k, v in iteritems(show_obj): sid, show_data, ti_show,
if k not in ('cast', 'crew', 'images', 'aliases'): load_images=banners or posters or fanart or
show_obj[k] = getattr(show_data, show_map.get(k, k), clean_data(show_obj[k])) any(self.config.get('%s_enabled' % t, False) for t in ('banners', 'posters', 'fanart')),
ti_show.aliases = [clean_data(a.name) for a in show_data.akas] load_actors=(actors or self.config['actors_enabled'])
ti_show.runtime = show_data.average_runtime or show_data.runtime
p_set = False
if show_data.image:
p_set = True
ti_show.poster = show_data.image.get('original')
ti_show.poster_thumb = show_data.image.get('medium')
if (banners or posters or fanart or
any(self.config.get('%s_enabled' % t, False) for t in ('banners', 'posters', 'fanart'))) and \
not all(getattr(ti_show, '%s_loaded' % t, False) for t in ('poster', 'banner', 'fanart')):
if show_data.images:
ti_show.poster_loaded = True
ti_show.banner_loaded = True
ti_show.fanart_loaded = True
self._set_images(ti_show, show_data, p_set)
if show_data.schedule:
if 'time' in show_data.schedule:
ti_show.airs_time = show_data.schedule['time']
try:
h, m = show_data.schedule['time'].split(':')
h, m = try_int(h, None), try_int(m, None)
if None is not h and None is not m:
ti_show.time = datetime.time(hour=h, minute=m)
except (BaseException, Exception):
pass
if 'days' in show_data.schedule:
ti_show.airs_dayofweek = ', '.join(show_data.schedule['days'])
if show_data.genres:
ti_show.genre = '|'.join(show_data.genres).lower()
if (actors or self.config['actors_enabled']) and not getattr(self.ti_shows.get(sid), 'actors_loaded', False):
if show_data.cast:
character_person_ids = {}
for cur_ch in ti_show.cast[RoleTypes.ActorMain]:
character_person_ids.setdefault(cur_ch.id, []).extend([p.id for p in cur_ch.person])
for cur_ch in show_data.cast.characters:
existing_character = next((c for c in ti_show.cast[RoleTypes.ActorMain] if c.id == cur_ch.id),
None) # type: Optional[TVInfoCharacter]
person = self._convert_person(cur_ch.person)
if existing_character:
existing_person = next((p for p in existing_character.person
if person.id == p.ids.get(TVINFO_TVMAZE)),
None) # type: TVInfoPerson
if existing_person:
try:
character_person_ids[cur_ch.id].remove(existing_person.id)
except (BaseException, Exception):
print('error')
pass
(existing_person.p_id, existing_person.name, existing_person.image, existing_person.gender,
existing_person.birthdate, existing_person.deathdate, existing_person.country,
existing_person.country_code, existing_person.country_timezone, existing_person.thumb_url,
existing_person.url, existing_person.ids) = \
(cur_ch.person.id, clean_data(cur_ch.person.name),
cur_ch.person.image and cur_ch.person.image.get('original'),
PersonGenders.named.get(
cur_ch.person.gender and cur_ch.person.gender.lower(), PersonGenders.unknown),
person.birthdate, person.deathdate,
cur_ch.person.country and clean_data(cur_ch.person.country.get('name')),
cur_ch.person.country and clean_data(cur_ch.person.country.get('code')),
cur_ch.person.country and clean_data(cur_ch.person.country.get('timezone')),
cur_ch.person.image and cur_ch.person.image.get('medium'),
cur_ch.person.url, {TVINFO_TVMAZE: cur_ch.person.id})
else:
existing_character.person.append(person)
else:
ti_show.cast[RoleTypes.ActorMain].append(
TVInfoCharacter(image=cur_ch.image and cur_ch.image.get('original'), name=clean_data(cur_ch.name),
p_id=cur_ch.id, person=[person], plays_self=cur_ch.plays_self,
thumb_url=cur_ch.image and cur_ch.image.get('medium')
))
if character_person_ids:
for cur_ch, cur_p_ids in iteritems(character_person_ids):
if cur_p_ids:
char = next((mc for mc in ti_show.cast[RoleTypes.ActorMain] if mc.id == cur_ch),
None) # type: Optional[TVInfoCharacter]
if char:
char.person = [p for p in char.person if p.id not in cur_p_ids]
if show_data.cast:
ti_show.actors = [
{'character': {'id': ch.id,
'name': clean_data(ch.name),
'url': 'https://www.tvmaze.com/character/view?id=%s' % ch.id,
'image': ch.image and ch.image.get('original'),
},
'person': {'id': ch.person and ch.person.id,
'name': ch.person and clean_data(ch.person.name),
'url': ch.person and 'https://www.tvmaze.com/person/view?id=%s' % ch.person.id,
'image': ch.person and ch.person.image and ch.person.image.get('original'),
'birthday': None, # not sure about format
'deathday': None, # not sure about format
'gender': ch.person and ch.person.gender and ch.person.gender,
'country': ch.person and ch.person.country and
clean_data(ch.person.country.get('name')),
},
} for ch in show_data.cast.characters]
if show_data.crew:
for cur_cw in show_data.crew:
rt = crew_type_names.get(cur_cw.type.lower(), RoleTypes.CrewOther)
ti_show.crew[rt].append(
Crew(p_id=cur_cw.person.id, name=clean_data(cur_cw.person.name),
image=cur_cw.person.image and cur_cw.person.image.get('original'),
gender=cur_cw.person.gender,
birthdate=cur_cw.person.birthday, deathdate=cur_cw.person.death_day,
country=cur_cw.person.country and cur_cw.person.country.get('name'),
country_code=cur_cw.person.country and clean_data(cur_cw.person.country.get('code')),
country_timezone=cur_cw.person.country
and clean_data(cur_cw.person.country.get('timezone')),
crew_type_name=cur_cw.type,
) )
)
if show_data.externals:
ti_show.ids = TVInfoIDs(tvdb=show_data.externals.get('thetvdb'),
rage=show_data.externals.get('tvrage'),
imdb=clean_data(show_data.externals.get('imdb') and
try_int(show_data.externals.get('imdb').replace('tt', ''), None)))
if show_data.network:
self._set_network(ti_show, show_data.network, False)
elif show_data.web_channel:
self._set_network(ti_show, show_data.web_channel, True)
if get_ep_info and not getattr(self.ti_shows.get(sid), 'ep_loaded', False): if get_ep_info and not getattr(self.ti_shows.get(sid), 'ep_loaded', False):
log.debug('Getting all episodes of %s' % sid) log.debug('Getting all episodes of %s' % sid)
@ -509,10 +397,10 @@ class TvMaze(TVInfoBase):
# type: (...) -> Dict[integer_types, integer_types] # type: (...) -> Dict[integer_types, integer_types]
return {sid: v.seconds_since_epoch for sid, v in iteritems(tvmaze.show_updates().updates)} return {sid: v.seconds_since_epoch for sid, v in iteritems(tvmaze.show_updates().updates)}
@staticmethod def _convert_person(self, tvmaze_person_obj):
def _convert_person(person_obj):
# type: (tvmaze.Person) -> TVInfoPerson # type: (tvmaze.Person) -> TVInfoPerson
ch = [] ch = []
_dupes = []
for c in tvmaze_person_obj.castcredits or []: for c in tvmaze_person_obj.castcredits or []:
ti_show = TVInfoShow() ti_show = TVInfoShow()
ti_show.seriesname = clean_data(c.show.name) ti_show.seriesname = clean_data(c.show.name)
@ -531,25 +419,240 @@ class TvMaze(TVInfoBase):
ti_show.network_is_stream = None is not c.show.web_channel ti_show.network_is_stream = None is not c.show.web_channel
ch.append(TVInfoCharacter(name=clean_data(c.character.name), ti_show=ti_show, episode_count=1)) ch.append(TVInfoCharacter(name=clean_data(c.character.name), ti_show=ti_show, episode_count=1))
try: try:
birthdate = person_obj.birthday and tz_p.parse(person_obj.birthday).date() birthdate = tvmaze_person_obj.birthday and tz_p.parse(tvmaze_person_obj.birthday).date()
except (BaseException, Exception): except (BaseException, Exception):
birthdate = None birthdate = None
try: try:
deathdate = person_obj.death_day and tz_p.parse(person_obj.death_day).date() deathdate = tvmaze_person_obj.death_day and tz_p.parse(tvmaze_person_obj.death_day).date()
except (BaseException, Exception): except (BaseException, Exception):
deathdate = None deathdate = None
return TVInfoPerson(p_id=person_obj.id, name=clean_data(person_obj.name),
image=person_obj.image and person_obj.image.get('original'), _ti_person_obj = TVInfoPerson(
gender=PersonGenders.named.get(person_obj.gender and person_obj.gender.lower(), p_id=tvmaze_person_obj.id, name=clean_data(tvmaze_person_obj.name),
image=tvmaze_person_obj.image and tvmaze_person_obj.image.get('original'),
gender=PersonGenders.named.get(tvmaze_person_obj.gender and tvmaze_person_obj.gender.lower(),
PersonGenders.unknown), PersonGenders.unknown),
birthdate=birthdate, deathdate=deathdate, birthdate=birthdate, deathdate=deathdate,
country=person_obj.country and clean_data(person_obj.country.get('name')), country=tvmaze_person_obj.country and clean_data(tvmaze_person_obj.country.get('name')),
country_code=person_obj.country and clean_data(person_obj.country.get('code')), country_code=tvmaze_person_obj.country and clean_data(tvmaze_person_obj.country.get('code')),
country_timezone=person_obj.country and clean_data(person_obj.country.get('timezone')), country_timezone=tvmaze_person_obj.country and clean_data(tvmaze_person_obj.country.get('timezone')),
thumb_url=person_obj.image and person_obj.image.get('medium'), thumb_url=tvmaze_person_obj.image and tvmaze_person_obj.image.get('medium'),
url=person_obj.url, ids={TVINFO_TVMAZE: person_obj.id}, characters=ch url=tvmaze_person_obj.url, ids=TVInfoIDs(ids={TVINFO_TVMAZE: tvmaze_person_obj.id})
) )
for (c_t, regular) in [(tvmaze_person_obj.castcredits or [], True),
(tvmaze_person_obj.guestcastcredits or [], False)]:
for c in c_t: # type: tvmaze.CastCredit
_show = c.show or c.episode.show
_clean_char_name = clean_data(c.character.name)
ti_show = TVInfoShow()
if None is not _show:
_clean_show_name = clean_data(_show.name)
_clean_show_id = clean_data(_show.id)
_cur_dup = (_clean_char_name, _clean_show_id)
if _cur_dup in _dupes:
_co = next((_c for _c in ch if _clean_show_id == _c.ti_show.id
and _c.name == _clean_char_name), None)
if None is not _co:
ti_show = _co.ti_show
_co.episode_count += 1
if not regular:
ep_no = c.episode.episode_number or 0
_co.guest_episodes_numbers.setdefault(c.episode.season_number, []).append(ep_no)
if c.episode.season_number not in ti_show:
season = TVInfoSeason(show=ti_show, number=c.episode.season_number)
ti_show[c.episode.season_number] = season
else:
season = ti_show[c.episode.season_number]
episode = self._make_episode(c.episode, show_obj=ti_show)
episode.season = season
ti_show[c.episode.season_number][ep_no] = episode
continue
else:
_dupes.append(_cur_dup)
ti_show.seriesname = clean_data(_show.name)
ti_show.id = _show.id
ti_show.firstaired = clean_data(_show.premiered)
ti_show.ids = TVInfoIDs(ids={TVINFO_TVMAZE: ti_show.id})
ti_show.overview = enforce_type(clean_data(_show.summary), str, '')
ti_show.status = clean_data(_show.status)
net = _show.network or _show.web_channel
if net:
ti_show.network = clean_data(net.name)
ti_show.network_id = net.maze_id
ti_show.network_country = clean_data(net.country)
ti_show.network_timezone = clean_data(net.timezone)
ti_show.network_country_code = clean_data(net.code)
ti_show.network_is_stream = None is not _show.web_channel
if c.episode:
ti_show.show_loaded = False
ti_show.load_method = self._show_info_loader
season = TVInfoSeason(show=ti_show, number=c.episode.season_number)
ti_show[c.episode.season_number] = season
episode = self._make_episode(c.episode, show_obj=ti_show)
episode.season = season
ti_show[c.episode.season_number][c.episode.episode_number or 0] = episode
if not regular:
_g_kw = {'guest_episodes_numbers': {c.episode.season_number: [c.episode.episode_number or 0]}}
else:
_g_kw = {}
ch.append(TVInfoCharacter(name=_clean_char_name, ti_show=ti_show, regular=regular, episode_count=1,
person=[_ti_person_obj], **_g_kw))
_ti_person_obj.characters = ch
return _ti_person_obj
def _show_info_loader(self, show_id, show_data=None, show_obj=None, load_images=True, load_actors=True):
# type: (int, TVMazeShow, TVInfoShow, bool, bool) -> TVInfoShow
try:
_s_d = show_data or tvmaze.show_main_info(show_id, embed='cast')
if _s_d:
if None is not show_obj:
_s_o = show_obj
else:
_s_o = TVInfoShow()
show_dict = _s_o.__dict__
for k, v in iteritems(show_dict):
if k not in ('cast', 'crew', 'images', 'aliases', 'rating'):
show_dict[k] = getattr(_s_d, show_map.get(k, k), clean_data(show_dict[k]))
_s_o.aliases = [clean_data(a.name) for a in _s_d.akas]
_s_o.runtime = _s_d.average_runtime or _s_d.runtime
p_set = False
if _s_d.image:
p_set = True
_s_o.poster = _s_d.image.get('original')
_s_o.poster_thumb = _s_d.image.get('medium')
if load_images and \
not all(getattr(_s_o, '%s_loaded' % t, False) for t in ('poster', 'banner', 'fanart')):
if _s_d.images:
_s_o.poster_loaded = True
_s_o.banner_loaded = True
_s_o.fanart_loaded = True
self._set_images(_s_o, _s_d, p_set)
if _s_d.schedule:
if 'time' in _s_d.schedule:
_s_o.airs_time = _s_d.schedule['time']
try:
h, m = _s_d.schedule['time'].split(':')
h, m = try_int(h, None), try_int(m, None)
if None is not h and None is not m:
_s_o.time = datetime.time(hour=h, minute=m)
except (BaseException, Exception):
pass
if 'days' in _s_d.schedule:
_s_o.airs_dayofweek = ', '.join(_s_d.schedule['days'])
if load_actors and not _s_o.actors_loaded:
if _s_d.cast:
character_person_ids = {}
for cur_ch in _s_o.cast[RoleTypes.ActorMain]:
character_person_ids.setdefault(cur_ch.id, []).extend([p.id for p in cur_ch.person])
for cur_ch in _s_d.cast.characters:
existing_character = next(
(c for c in _s_o.cast[RoleTypes.ActorMain] if c.id == cur_ch.id),
None) # type: Optional[TVInfoCharacter]
person = self._convert_person(cur_ch.person)
if existing_character:
existing_person = next((p for p in existing_character.person
if person.id == p.ids.get(TVINFO_TVMAZE)),
None) # type: TVInfoPerson
if existing_person:
try:
character_person_ids[cur_ch.id].remove(existing_person.id)
except (BaseException, Exception):
print('error')
pass
(existing_person.p_id, existing_person.name, existing_person.image,
existing_person.gender,
existing_person.birthdate, existing_person.deathdate, existing_person.country,
existing_person.country_code, existing_person.country_timezone,
existing_person.thumb_url,
existing_person.url, existing_person.ids) = \
(cur_ch.person.id, clean_data(cur_ch.person.name),
cur_ch.person.image and cur_ch.person.image.get('original'),
PersonGenders.named.get(
cur_ch.person.gender and cur_ch.person.gender.lower(),
PersonGenders.unknown),
person.birthdate, person.deathdate,
cur_ch.person.country and clean_data(cur_ch.person.country.get('name')),
cur_ch.person.country and clean_data(cur_ch.person.country.get('code')),
cur_ch.person.country and clean_data(cur_ch.person.country.get('timezone')),
cur_ch.person.image and cur_ch.person.image.get('medium'),
cur_ch.person.url, {TVINFO_TVMAZE: cur_ch.person.id})
else:
existing_character.person.append(person)
else:
_s_o.cast[RoleTypes.ActorMain].append(
TVInfoCharacter(image=cur_ch.image and cur_ch.image.get('original'),
name=clean_data(cur_ch.name),
ids=TVInfoIDs({TVINFO_TVMAZE: cur_ch.id}),
p_id=cur_ch.id, person=[person], plays_self=cur_ch.plays_self,
thumb_url=cur_ch.image and cur_ch.image.get('medium'),
ti_show=_s_o
))
if character_person_ids:
for cur_ch, cur_p_ids in iteritems(character_person_ids):
if cur_p_ids:
char = next((mc for mc in _s_o.cast[RoleTypes.ActorMain] if mc.id == cur_ch),
None) # type: Optional[TVInfoCharacter]
if char:
char.person = [p for p in char.person if p.id not in cur_p_ids]
if _s_d.cast:
_s_o.actors = [
{'character': {'id': ch.id,
'name': clean_data(ch.name),
'url': 'https://www.tvmaze.com/character/view?id=%s' % ch.id,
'image': ch.image and ch.image.get('original'),
},
'person': {'id': ch.person and ch.person.id,
'name': ch.person and clean_data(ch.person.name),
'url': ch.person and 'https://www.tvmaze.com/person/view?id=%s' % ch.person.id,
'image': ch.person and ch.person.image and ch.person.image.get('original'),
'birthday': None, # not sure about format
'deathday': None, # not sure about format
'gender': ch.person and ch.person.gender and ch.person.gender,
'country': ch.person and ch.person.country and
clean_data(ch.person.country.get('name')),
},
} for ch in _s_d.cast.characters]
if _s_d.crew:
for cur_cw in _s_d.crew:
rt = crew_type_names.get(cur_cw.type.lower(), RoleTypes.CrewOther)
_s_o.crew[rt].append(
Crew(p_id=cur_cw.person.id, name=clean_data(cur_cw.person.name),
image=cur_cw.person.image and cur_cw.person.image.get('original'),
gender=cur_cw.person.gender,
birthdate=cur_cw.person.birthday, deathdate=cur_cw.person.death_day,
country=cur_cw.person.country and cur_cw.person.country.get('name'),
country_code=cur_cw.person.country and clean_data(
cur_cw.person.country.get('code')),
country_timezone=cur_cw.person.country
and clean_data(cur_cw.person.country.get('timezone')),
crew_type_name=cur_cw.type,
)
)
if _s_d.externals:
_s_o.ids = TVInfoIDs(tvdb=_s_d.externals.get('thetvdb'),
rage=_s_d.externals.get('tvrage'),
imdb=clean_data(_s_d.externals.get('imdb') and
try_int(_s_d.externals.get('imdb').replace('tt', ''),
None)))
if _s_d.network:
self._set_network(_s_o, _s_d.network, False)
elif _s_d.web_channel:
self._set_network(_s_o, _s_d.web_channel, True)
return _s_o
except (BaseException, Exception):
pass
def _search_person(self, name=None, ids=None): def _search_person(self, name=None, ids=None):
# type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson] # type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson]
urls, result, ids = [], [], ids or {} urls, result, ids = [], [], ids or {}
@ -597,27 +700,31 @@ class TvMaze(TVInfoBase):
return self._convert_person(p) return self._convert_person(p)
def get_premieres(self, **kwargs): def get_premieres(self, **kwargs):
# type: (...) -> List[TVInfoEpisode] # type: (...) -> List[TVInfoShow]
return self._filtered_schedule(**kwargs).get('premieres') return [_e.show for _e in self._filtered_schedule(**kwargs).get('premieres')]
def get_returning(self, **kwargs): def get_returning(self, **kwargs):
# type: (...) -> List[TVInfoEpisode] # type: (...) -> List[TVInfoShow]
return self._filtered_schedule(**kwargs).get('returning') return [_e.show for _e in self._filtered_schedule(**kwargs).get('returning')]
def _make_episode(self, episode_data, show_data=None, get_images=False, get_akas=False): def _make_episode(self, episode_data, show_data=None, get_images=False, get_akas=False, show_obj=None):
# type: (TVMazeEpisode, TVMazeShow, bool, bool) -> TVInfoEpisode # type: (TVMazeEpisode, TVMazeShow, bool, bool, TVInfoShow) -> TVInfoEpisode
""" """
make out of TVMazeEpisode object and optionally TVMazeShow a TVInfoEpisode make out of TVMazeEpisode object and optionally TVMazeShow a TVInfoEpisode
""" """
if None is not show_obj:
ti_show = show_obj
else:
ti_show = TVInfoShow() ti_show = TVInfoShow()
ti_show.seriesname = clean_data(show_data.name) ti_show.seriesname = clean_data(show_data.name)
ti_show.id = show_data.maze_id ti_show.id = show_data.maze_id
ti_show.seriesid = ti_show.id ti_show.seriesid = ti_show.id
ti_show.language = clean_data(show_data.language) ti_show.language = clean_data(show_data.language)
ti_show.overview = clean_data(show_data.summary) ti_show.overview = enforce_type(clean_data(show_data.summary), str, '')
ti_show.firstaired = clean_data(show_data.premiered) ti_show.firstaired = clean_data(show_data.premiered)
ti_show.runtime = show_data.average_runtime or show_data.runtime ti_show.runtime = show_data.average_runtime or show_data.runtime
ti_show.vote_average = show_data.rating and show_data.rating.get('average') ti_show.vote_average = show_data.rating and show_data.rating.get('average')
ti_show.rating = ti_show.vote_average
ti_show.popularity = show_data.weight ti_show.popularity = show_data.weight
ti_show.genre_list = clean_data(show_data.genres or []) ti_show.genre_list = clean_data(show_data.genres or [])
ti_show.genre = '|'.join(ti_show.genre_list).lower() ti_show.genre = '|'.join(ti_show.genre_list).lower()
@ -629,7 +736,7 @@ class TvMaze(TVInfoBase):
ti_show.poster = show_data.image and show_data.image.get('original') ti_show.poster = show_data.image and show_data.image.get('original')
if get_akas: if get_akas:
ti_show.aliases = [clean_data(a.name) for a in show_data.akas] ti_show.aliases = [clean_data(a.name) for a in show_data.akas]
if 'days' in show_data.schedule: if show_data.schedule and 'days' in show_data.schedule:
ti_show.airs_dayofweek = ', '.join(clean_data(show_data.schedule['days'])) ti_show.airs_dayofweek = ', '.join(clean_data(show_data.schedule['days']))
network = show_data.network or show_data.web_channel network = show_data.network or show_data.web_channel
if network: if network:
@ -652,9 +759,12 @@ class TvMaze(TVInfoBase):
ti_episode = TVInfoEpisode(show=ti_show) ti_episode = TVInfoEpisode(show=ti_show)
ti_episode.id = episode_data.maze_id ti_episode.id = episode_data.maze_id
ti_episode.seasonnumber = episode_data.season_number ti_episode.seasonnumber = episode_data.season_number
ti_episode.episodenumber = episode_data.episode_number ti_episode.episodenumber = episode_data.episode_number or 0
ti_episode.episodename = clean_data(episode_data.title) ti_episode.episodename = clean_data(episode_data.title)
ti_episode.airtime = clean_data(episode_data.airtime) try:
ti_episode.airtime = datetime.time.fromisoformat(clean_data(episode_data.airtime))
except (BaseException, Exception):
ti_episode.airtime = None
ti_episode.firstaired = clean_data(episode_data.airdate) ti_episode.firstaired = clean_data(episode_data.airdate)
if episode_data.airstamp: if episode_data.airstamp:
try: try:
@ -665,8 +775,13 @@ class TvMaze(TVInfoBase):
ti_episode.filename = episode_data.image and (episode_data.image.get('original') or ti_episode.filename = episode_data.image and (episode_data.image.get('original') or
episode_data.image.get('medium')) episode_data.image.get('medium'))
ti_episode.is_special = episode_data.is_special() ti_episode.is_special = episode_data.is_special()
ti_episode.overview = clean_data(episode_data.summary) ti_episode.overview = enforce_type(clean_data(episode_data.summary), str, '')
ti_episode.runtime = episode_data.runtime ti_episode.runtime = episode_data.runtime
if ti_episode.seasonnumber not in ti_show:
season = TVInfoSeason(show=ti_show, number=ti_episode.seasonnumber)
ti_show[ti_episode.seasonnumber] = season
ti_episode.season = season
ti_show[ti_episode.seasonnumber][ti_episode.episodenumber] = ti_episode
return ti_episode return ti_episode
def _filtered_schedule(self, **kwargs): def _filtered_schedule(self, **kwargs):

View file

@ -62,7 +62,7 @@ if False:
from sickgear import db, notifiers as NOTIFIERS from sickgear import db, notifiers as NOTIFIERS
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from typing import Any, AnyStr, Dict, Generator, NoReturn, integer_types, Iterable, Iterator, List, Optional, \ from typing import Any, AnyStr, Dict, Generator, NoReturn, integer_types, Iterable, Iterator, List, Optional, \
Tuple, Union Tuple, Type, Union
html_convert_fractions = {0: '', 25: '&frac14;', 50: '&frac12;', 75: '&frac34;', 100: 1} html_convert_fractions = {0: '', 25: '&frac14;', 50: '&frac12;', 75: '&frac34;', 100: 1}
@ -1758,3 +1758,16 @@ def is_virtualenv():
"""Get base/real prefix, or `sys.prefix` if there is none.""" """Get base/real prefix, or `sys.prefix` if there is none."""
get_base_prefix_compat = getattr(sys, 'base_prefix', None) or getattr(sys, 'real_prefix', None) or sys.prefix get_base_prefix_compat = getattr(sys, 'base_prefix', None) or getattr(sys, 'real_prefix', None) or sys.prefix
return get_base_prefix_compat != sys.prefix return get_base_prefix_compat != sys.prefix
def enforce_type(value, allowed_types, default):
# type: (Any, Union[Type, Tuple[Type]], Any) -> Any
"""
enforces that value is given type(s)
:param value: value to check
:param allowed_types: type or tuple of types allowed
:param default: value to return if other type
"""
if not isinstance(value, allowed_types):
return default
return value

View file

@ -1,13 +1,19 @@
import copy import copy
import datetime import datetime
import diskcache import diskcache
import itertools
import logging import logging
import threading import threading
import shutil import shutil
import time import time
from collections import deque
from exceptions_helper import ex from exceptions_helper import ex
from six import integer_types, iteritems, iterkeys, string_types, text_type from six import integer_types, iteritems, iterkeys, string_types, text_type
from six import PY2 # deprecate after rebase ?
from _23 import list_keys, list_items, list_values, izip # deprecate after rebase ?
from typing import Callable
from lib.tvinfo_base.exceptions import * from lib.tvinfo_base.exceptions import *
from sg_helpers import calc_age, make_path from sg_helpers import calc_age, make_path
@ -44,6 +50,11 @@ TVINFO_INSTAGRAM = 250002
TVINFO_WIKIPEDIA = 250003 TVINFO_WIKIPEDIA = 250003
TVINFO_REDDIT = 250004 TVINFO_REDDIT = 250004
TVINFO_YOUTUBE = 250005 TVINFO_YOUTUBE = 250005
TVINFO_WIKIDATA = 250006
TVINFO_TIKTOK = 250007
TVINFO_LINKEDIN = 25008
TVINFO_OFFICIALSITE = 250009
TVINFO_FANSITE = 250010
tv_src_names = { tv_src_names = {
TVINFO_TVDB: 'tvdb', TVINFO_TVDB: 'tvdb',
@ -64,10 +75,25 @@ tv_src_names = {
TVINFO_INSTAGRAM: 'instagram', TVINFO_INSTAGRAM: 'instagram',
TVINFO_WIKIPEDIA: 'wikipedia', TVINFO_WIKIPEDIA: 'wikipedia',
TVINFO_REDDIT: 'reddit', TVINFO_REDDIT: 'reddit',
TVINFO_YOUTUBE: 'youtube' TVINFO_YOUTUBE: 'youtube',
TVINFO_WIKIDATA: 'wikidata',
TVINFO_TIKTOK: 'tiktok',
TVINFO_LINKEDIN: 'linkedin',
TVINFO_OFFICIALSITE: 'officialsite',
TVINFO_FANSITE: 'fansite'
} }
TVINFO_MID_SEASON_FINALE = 1
TVINFO_SEASON_FINALE = 2
TVINFO_SERIES_FINALE = 3
final_types = {
TVINFO_MID_SEASON_FINALE: 'mid-season',
TVINFO_SEASON_FINALE: 'season',
TVINFO_SERIES_FINALE: 'series'
}
log = logging.getLogger('TVInfo') log = logging.getLogger('TVInfo')
log.addHandler(logging.NullHandler()) log.addHandler(logging.NullHandler())
TVInfoShowContainer = {} # type: Union[ShowContainer, Dict] TVInfoShowContainer = {} # type: Union[ShowContainer, Dict]
@ -141,20 +167,42 @@ class TVInfoIDs(object):
return {TVINFO_TVDB: self.tvdb, TVINFO_TMDB: self.tmdb, TVINFO_TVMAZE: self.tvmaze, return {TVINFO_TVDB: self.tvdb, TVINFO_TMDB: self.tmdb, TVINFO_TVMAZE: self.tvmaze,
TVINFO_IMDB: self.imdb, TVINFO_TRAKT: self.trakt, TVINFO_TVRAGE: self.rage}.get(key) TVINFO_IMDB: self.imdb, TVINFO_TRAKT: self.trakt, TVINFO_TVRAGE: self.rage}.get(key)
def __setitem__(self, key, value):
self.__dict__[{
TVINFO_TVDB: 'tvdb', TVINFO_TMDB: 'tmdb', TVINFO_TVMAZE: 'tvmaze',
TVINFO_IMDB: 'imdb', TVINFO_TRAKT: 'trakt', TVINFO_TVRAGE: 'rage'
}[key]] = value
def get(self, key): def get(self, key):
return self.__getitem__(key) return self.__getitem__(key)
def keys(self):
for k, v in iter(((TVINFO_TVDB, self.tvdb), (TVINFO_TMDB, self.tmdb), (TVINFO_TVMAZE, self.tvmaze),
(TVINFO_IMDB, self.imdb), (TVINFO_TRAKT, self.trakt), (TVINFO_TVRAGE, self.rage))):
if None is not v:
yield k
def __iter__(self): def __iter__(self):
for s, v in [(TVINFO_TVDB, self.tvdb), (TVINFO_TMDB, self.tmdb), (TVINFO_TVMAZE, self.tvmaze), for s, v in iter(((TVINFO_TVDB, self.tvdb), (TVINFO_TMDB, self.tmdb), (TVINFO_TVMAZE, self.tvmaze),
(TVINFO_IMDB, self.imdb), (TVINFO_TRAKT, self.trakt), (TVINFO_TVRAGE, self.rage)]: (TVINFO_IMDB, self.imdb), (TVINFO_TRAKT, self.trakt), (TVINFO_TVRAGE, self.rage))):
if None is not v:
yield s, v yield s, v
def __len__(self):
counter = itertools.count()
deque(izip(self.__iter__(), counter), maxlen=0) # (consume at C speed)
return next(counter)
def __str__(self): def __str__(self):
return ', '.join('%s: %s' % (tv_src_names.get(k, k), v) for k, v in self.__iter__()) return ', '.join('%s: %s' % (tv_src_names.get(k, k), v) for k, v in self.__iter__())
def __eq__(self, other):
return self.__dict__ == other.__dict__
__repr__ = __str__ __repr__ = __str__
iteritems = __iter__ iteritems = __iter__
items = __iter__ items = __iter__
iterkeys = keys
class TVInfoSocialIDs(object): class TVInfoSocialIDs(object):
@ -166,7 +214,11 @@ class TVInfoSocialIDs(object):
wikipedia=None, # type: str_int wikipedia=None, # type: str_int
ids=None, # type: Dict[int, str_int] ids=None, # type: Dict[int, str_int]
reddit=None, # type: str_int reddit=None, # type: str_int
youtube=None # type: AnyStr youtube=None, # type: AnyStr
wikidata=None, # type: AnyStr
tiktok=None, # type: AnyStr
linkedin=None, # type: AnyStr
fansite=None # type: AnyStr
): ):
ids = ids or {} ids = ids or {}
self.twitter = twitter or ids.get(TVINFO_TWITTER) self.twitter = twitter or ids.get(TVINFO_TWITTER)
@ -175,23 +227,60 @@ class TVInfoSocialIDs(object):
self.wikipedia = wikipedia or ids.get(TVINFO_WIKIPEDIA) self.wikipedia = wikipedia or ids.get(TVINFO_WIKIPEDIA)
self.reddit = reddit or ids.get(TVINFO_REDDIT) self.reddit = reddit or ids.get(TVINFO_REDDIT)
self.youtube = youtube or ids.get(TVINFO_YOUTUBE) self.youtube = youtube or ids.get(TVINFO_YOUTUBE)
self.wikidata = wikidata or ids.get(TVINFO_WIKIDATA)
self.tiktok = tiktok or ids.get(TVINFO_TIKTOK)
self.linkedin = linkedin or ids.get(TVINFO_LINKEDIN)
self.fansite = fansite or ids.get(TVINFO_FANSITE)
def __getitem__(self, key): def __getitem__(self, key):
return {TVINFO_TWITTER: self.twitter, TVINFO_INSTAGRAM: self.instagram, TVINFO_FACEBOOK: self.facebook, return {TVINFO_TWITTER: self.twitter, TVINFO_INSTAGRAM: self.instagram, TVINFO_FACEBOOK: self.facebook,
TVINFO_WIKIPEDIA: self.wikipedia, TVINFO_REDDIT: self.reddit, TVINFO_YOUTUBE: self.youtube}.get(key) TVINFO_WIKIDATA: self.wikidata, TVINFO_WIKIPEDIA: self.wikipedia, TVINFO_REDDIT: self.reddit,
TVINFO_TIKTOK: self.tiktok, TVINFO_LINKEDIN: self.linkedin, TVINFO_FANSITE: self.fansite,
TVINFO_YOUTUBE: self.youtube}.get(key)
def __setitem__(self, key, value):
self.__dict__[{
TVINFO_TWITTER: 'twitter', TVINFO_INSTAGRAM: 'instagram', TVINFO_FACEBOOK: 'facebook',
TVINFO_WIKIPEDIA: 'wikipedia', TVINFO_REDDIT: 'reddit', TVINFO_YOUTUBE: 'youtube',
TVINFO_WIKIDATA: 'wikidata', TVINFO_TIKTOK: 'tiktok', TVINFO_LINKEDIN: 'linkedin', TVINFO_FANSITE: 'fansite'
}[key]] = value
def get(self, key):
return self.__getitem__(key)
def keys(self):
for k, v in iter(((TVINFO_TWITTER, self.twitter), (TVINFO_INSTAGRAM, self.instagram),
(TVINFO_FACEBOOK, self.facebook), (TVINFO_TIKTOK, self.tiktok),
(TVINFO_WIKIPEDIA, self.wikipedia), (TVINFO_WIKIDATA, self.wikidata),
(TVINFO_REDDIT, self.reddit), (TVINFO_YOUTUBE, self.youtube),
(TVINFO_LINKEDIN, self.linkedin), (TVINFO_FANSITE, self.fansite))):
if None is not v:
yield k
def __iter__(self): def __iter__(self):
for s, v in [(TVINFO_TWITTER, self.twitter), (TVINFO_INSTAGRAM, self.instagram), for s, v in iter(((TVINFO_TWITTER, self.twitter), (TVINFO_INSTAGRAM, self.instagram),
(TVINFO_FACEBOOK, self.facebook), (TVINFO_WIKIPEDIA, self.wikipedia), (TVINFO_FACEBOOK, self.facebook), (TVINFO_TIKTOK, self.tiktok),
(TVINFO_REDDIT, self.reddit), (TVINFO_YOUTUBE, self.youtube)]: (TVINFO_WIKIPEDIA, self.wikipedia), (TVINFO_WIKIDATA, self.wikidata),
(TVINFO_REDDIT, self.reddit), (TVINFO_YOUTUBE, self.youtube),
(TVINFO_LINKEDIN, self.linkedin), (TVINFO_FANSITE, self.fansite))):
if None is not v:
yield s, v yield s, v
def __len__(self):
counter = itertools.count()
deque(izip(self.__iter__(), counter), maxlen=0) # (consume at C speed)
return next(counter)
def __str__(self): def __str__(self):
return ', '.join('%s: %s' % (tv_src_names.get(k, k), v) for k, v in self.__iter__()) return ', '.join('%s: %s' % (tv_src_names.get(k, k), v) for k, v in self.__iter__())
def __eq__(self, other):
return self.__dict__ == other.__dict__
__repr__ = __str__ __repr__ = __str__
iteritems = __iter__ iteritems = __iter__
items = __iter__ items = __iter__
iterkeys = keys
class TVInfoImageType(object): class TVInfoImageType(object):
@ -242,7 +331,7 @@ class TVInfoImageSize(object):
class TVInfoImage(object): class TVInfoImage(object):
def __init__(self, image_type, sizes, img_id=None, main_image=False, type_str='', rating=None, votes=None, def __init__(self, image_type, sizes, img_id=None, main_image=False, type_str='', rating=None, votes=None,
lang=None, height=None, width=None, aspect_ratio=None): lang=None, height=None, width=None, aspect_ratio=None, updated_at=None):
self.img_id = img_id # type: Optional[integer_types] self.img_id = img_id # type: Optional[integer_types]
self.image_type = image_type # type: integer_types self.image_type = image_type # type: integer_types
self.sizes = sizes # type: Union[TVInfoImageSize, Dict] self.sizes = sizes # type: Union[TVInfoImageSize, Dict]
@ -254,6 +343,10 @@ class TVInfoImage(object):
self.height = height # type: Optional[integer_types] self.height = height # type: Optional[integer_types]
self.width = width # type: Optional[integer_types] self.width = width # type: Optional[integer_types]
self.aspect_ratio = aspect_ratio # type: Optional[Union[float, integer_types]] self.aspect_ratio = aspect_ratio # type: Optional[Union[float, integer_types]]
self.updated_at = updated_at # type: Optional[integer_types]
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __str__(self): def __str__(self):
return '<TVInfoImage %s [%s]>' % (TVInfoImageType.reverse_str.get(self.image_type, 'unknown'), return '<TVInfoImage %s [%s]>' % (TVInfoImageType.reverse_str.get(self.image_type, 'unknown'),
@ -263,13 +356,20 @@ class TVInfoImage(object):
class TVInfoNetwork(object): class TVInfoNetwork(object):
def __init__(self, name, n_id=None, country=None, country_code=None, timezone=None, stream=None): def __init__(self, name, n_id=None, country=None, country_code=None, timezone=None, stream=None, active_date=None,
inactive_date=None):
# type: (AnyStr, integer_types, AnyStr, AnyStr, AnyStr, bool, AnyStr, AnyStr) -> None
self.name = name # type: AnyStr self.name = name # type: AnyStr
self.id = n_id # type: Optional[integer_types] self.id = n_id # type: Optional[integer_types]
self.country = country # type: Optional[AnyStr] self.country = country # type: Optional[AnyStr]
self.country_code = country_code # type: Optional[AnyStr] self.country_code = country_code # type: Optional[AnyStr]
self.timezone = timezone # type: Optional[AnyStr] self.timezone = timezone # type: Optional[AnyStr]
self.stream = stream # type: Optional[bool] self.stream = stream # type: Optional[bool]
self.active_date = active_date # type: Optional[AnyStr]
self.inactive_date = inactive_date # type: Optional[AnyStr]
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __str__(self): def __str__(self):
return '<Network (%s)>' % ', '.join('%s' % s for s in [self.name, self.id, self.country, self.country_code, return '<Network (%s)>' % ', '.join('%s' % s for s in [self.name, self.id, self.country, self.country_code,
@ -282,7 +382,7 @@ class TVInfoShow(dict):
"""Holds a dict of seasons, and show data. """Holds a dict of seasons, and show data.
""" """
def __init__(self): def __init__(self, show_loaded=True):
dict.__init__(self) dict.__init__(self)
self.lock = threading.RLock() self.lock = threading.RLock()
self.data = {} # type: Dict self.data = {} # type: Dict
@ -298,7 +398,6 @@ class TVInfoShow(dict):
self.ids = TVInfoIDs() # type: TVInfoIDs self.ids = TVInfoIDs() # type: TVInfoIDs
self.social_ids = TVInfoSocialIDs() # type: TVInfoSocialIDs self.social_ids = TVInfoSocialIDs() # type: TVInfoSocialIDs
self.slug = None # type: Optional[AnyStr] self.slug = None # type: Optional[AnyStr]
self.seriesid = None # type: integer_types
self.seriesname = None # type: Optional[AnyStr] self.seriesname = None # type: Optional[AnyStr]
self.aliases = [] # type: List[AnyStr] self.aliases = [] # type: List[AnyStr]
self.season = None # type: integer_types self.season = None # type: integer_types
@ -318,6 +417,7 @@ class TVInfoShow(dict):
self.network_is_stream = None # type: Optional[bool] self.network_is_stream = None # type: Optional[bool]
self.runtime = None # type: integer_types self.runtime = None # type: integer_types
self.language = None # type: Optional[AnyStr] self.language = None # type: Optional[AnyStr]
self.spoken_languages = [] # type: List[string_types]
self.official_site = None # type: Optional[AnyStr] self.official_site = None # type: Optional[AnyStr]
self.imdb_id = None # type: Optional[AnyStr] self.imdb_id = None # type: Optional[AnyStr]
self.zap2itid = None # type: Optional[AnyStr] self.zap2itid = None # type: Optional[AnyStr]
@ -332,7 +432,7 @@ class TVInfoShow(dict):
self.contentrating = None # type: Optional[AnyStr] self.contentrating = None # type: Optional[AnyStr]
self.rating = None # type: Union[integer_types, float] self.rating = None # type: Union[integer_types, float]
self.status = None # type: Optional[AnyStr] self.status = None # type: Optional[AnyStr]
self.overview = None # type: Optional[AnyStr] self.overview = '' # type: AnyStr
self.poster = None # type: Optional[AnyStr] self.poster = None # type: Optional[AnyStr]
self.poster_thumb = None # type: Optional[AnyStr] self.poster_thumb = None # type: Optional[AnyStr]
self.banner = None # type: Optional[AnyStr] self.banner = None # type: Optional[AnyStr]
@ -347,6 +447,33 @@ class TVInfoShow(dict):
self.vote_average = None # type: Optional[Union[integer_types, float]] self.vote_average = None # type: Optional[Union[integer_types, float]]
self.origin_countries = [] # type: List[AnyStr] self.origin_countries = [] # type: List[AnyStr]
self.requested_language = '' # type: AnyStr self.requested_language = '' # type: AnyStr
self.alt_ep_numbering = {} # type: Dict[Any, Dict[integer_types, Dict[integer_types, TVInfoEpisode]]]
self.watcher_count = None # type: integer_types
self.play_count = None # type: integer_types
self.collected_count = None # type: integer_types
self.collector_count = None # type: integer_types
self.next_season_airdate = None # type: Optional[string_types]
# trailers dict containing: {language: trailer url} , 'any' for unknown langauge
self.trailers = {} # type: Dict[string_types, string_types]
self.show_loaded = show_loaded # type: bool
self.load_method = None # type: Optional[Callable]
def load_data(self):
if not self.show_loaded and self.id and isinstance(self.load_method, Callable):
_new_show_data = self.load_method(self.id, load_actors=False)
if isinstance(_new_show_data, TVInfoShow):
self.__dict__.update(_new_show_data.__dict__)
self.show_loaded = True
@property
def seriesid(self):
# type: (...) -> integer_types
return self.id
@seriesid.setter
def seriesid(self, val):
# type: (integer_types) -> None
self.id = val
def __str__(self): def __str__(self):
nr_seasons = len(self) nr_seasons = len(self)
@ -363,7 +490,7 @@ class TVInfoShow(dict):
raise AttributeError raise AttributeError
def __getitem__(self, key, raise_error=True): def __getitem__(self, key):
if isinstance(key, string_types) and key in self.__dict__: if isinstance(key, string_types) and key in self.__dict__:
return self.__dict__[key] return self.__dict__[key]
@ -375,7 +502,6 @@ class TVInfoShow(dict):
# Non-numeric request is for show-data # Non-numeric request is for show-data
return dict.__getitem__(self.data, key) return dict.__getitem__(self.data, key)
if raise_error:
# Data wasn't found, raise appropriate error # Data wasn't found, raise appropriate error
if isinstance(key, integer_types) or isinstance(key, string_types) and key.isdigit(): if isinstance(key, integer_types) or isinstance(key, string_types) and key.isdigit():
# Episode number x was not found # Episode number x was not found
@ -385,8 +511,12 @@ class TVInfoShow(dict):
# doesn't exist, so attribute error. # doesn't exist, so attribute error.
raise BaseTVinfoAttributenotfound('Cannot find attribute %s' % (repr(key))) raise BaseTVinfoAttributenotfound('Cannot find attribute %s' % (repr(key)))
def get(self, __key, __default=None): def get(self, __key, *args):
return self.__getitem__(__key, raise_error=None is __default) or __default try:
return self.__getitem__(__key)
except (BaseException, Exception):
if 0 != len(args):
return args[0]
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
cls = self.__class__ cls = self.__class__
@ -395,6 +525,8 @@ class TVInfoShow(dict):
for k, v in self.__dict__.items(): for k, v in self.__dict__.items():
if 'lock' == k: if 'lock' == k:
setattr(result, k, threading.RLock()) setattr(result, k, threading.RLock())
elif 'load_method' == k:
setattr(result, k, None)
else: else:
setattr(result, k, copy.deepcopy(v, memo)) setattr(result, k, copy.deepcopy(v, memo))
for k, v in self.items(): for k, v in self.items():
@ -434,8 +566,9 @@ class TVInfoShow(dict):
def __getstate__(self): def __getstate__(self):
d = dict(self.__dict__) d = dict(self.__dict__)
for d_a in ('lock', 'load_method'):
try: try:
del d['lock'] del d[d_a]
except (BaseException, Exception): except (BaseException, Exception):
pass pass
return d return d
@ -443,24 +576,25 @@ class TVInfoShow(dict):
def __setstate__(self, d): def __setstate__(self, d):
self.__dict__ = d self.__dict__ = d
self.lock = threading.RLock() self.lock = threading.RLock()
self.load_method = None
__repr__ = __str__ __repr__ = __str__
__nonzero__ = __bool__ __nonzero__ = __bool__
class TVInfoSeason(dict): class TVInfoSeason(dict):
def __init__(self, show=None, **kwargs): def __init__(self, show=None, number=None, **kwargs):
"""The show attribute points to the parent show """The show attribute points to the parent show
""" """
super(TVInfoSeason, self).__init__(**kwargs) super(TVInfoSeason, self).__init__(**kwargs)
self.show = show # type: TVInfoShow self.show = show # type: TVInfoShow
self.id = None # type: integer_types self.id = None # type: integer_types
self.number = None # type: integer_types self.number = number # type: integer_types
self.name = None # type: Optional[AnyStr] self.name = None # type: Optional[AnyStr]
self.actors = [] # type: List[Dict] self.actors = [] # type: List[Dict]
self.cast = CastList() # type: Dict[integer_types, TVInfoCharacter] self.cast = CastList() # type: Dict[integer_types, TVInfoCharacter]
self.network = None # type: Optional[AnyStr] self.network = None # type: Optional[AnyStr]
self.network_id = None # type: integer_types self.network_id = None # type: Optional[integer_types]
self.network_timezone = None # type: Optional[AnyStr] self.network_timezone = None # type: Optional[AnyStr]
self.network_country = None # type: Optional[AnyStr] self.network_country = None # type: Optional[AnyStr]
self.network_country_code = None # type: Optional[AnyStr] self.network_country_code = None # type: Optional[AnyStr]
@ -536,7 +670,7 @@ class TVInfoEpisode(dict):
self.writers = [] # type: List[AnyStr] self.writers = [] # type: List[AnyStr]
self.crew = CrewList() # type: CrewList self.crew = CrewList() # type: CrewList
self.episodename = None # type: Optional[AnyStr] self.episodename = None # type: Optional[AnyStr]
self.overview = None # type: Optional[AnyStr] self.overview = '' # type: AnyStr
self.language = {'episodeName': None, 'overview': None} # type: Dict[AnyStr, Optional[AnyStr]] self.language = {'episodeName': None, 'overview': None} # type: Dict[AnyStr, Optional[AnyStr]]
self.productioncode = None # type: Optional[AnyStr] self.productioncode = None # type: Optional[AnyStr]
self.showurl = None # type: Optional[AnyStr] self.showurl = None # type: Optional[AnyStr]
@ -564,17 +698,21 @@ class TVInfoEpisode(dict):
self.contentrating = None # type: Optional[AnyStr] self.contentrating = None # type: Optional[AnyStr]
self.thumbadded = None # type: Optional[AnyStr] self.thumbadded = None # type: Optional[AnyStr]
self.rating = None # type: Union[integer_types, float] self.rating = None # type: Union[integer_types, float]
self.vote_count = None # type: integer_types
self.siteratingcount = None # type: integer_types self.siteratingcount = None # type: integer_types
self.show = show # type: Optional[TVInfoShow] self.show = show # type: Optional[TVInfoShow]
self.alt_nums = {} # type: Dict[AnyStr, Dict[integer_types, integer_types]]
self.finale_type = None # type: Optional[integer_types]
def __str__(self): def __str__(self):
show_name = (self.show and self.show.seriesname and '<Show %s> - ' % self.show.seriesname) or '' show_name = (self.show and self.show.seriesname and '<Show %s> - ' % self.show.seriesname) or ''
seasno, epno = int(getattr(self, 'seasonnumber', 0)), int(getattr(self, 'episodenumber', 0)) seasno, epno = int(getattr(self, 'seasonnumber', 0) or 0), int(getattr(self, 'episodenumber', 0) or 0)
epname = getattr(self, 'episodename', '') epname = getattr(self, 'episodename', '')
finale_str = (self.finale_type and ' (%s finale)' % final_types.get(self.finale_type).capitalize()) or ''
if None is not epname: if None is not epname:
return '%s<Episode %02dx%02d - %r>' % (show_name, seasno, epno, epname) return '%s<Episode %02dx%02d - %r%s>' % (show_name, seasno, epno, epname, finale_str)
else: else:
return '%s<Episode %02dx%02d>' % (show_name, seasno, epno) return '%s<Episode %02dx%02d%s>' % (show_name, seasno, epno, finale_str)
def __getattr__(self, key): def __getattr__(self, key):
if key in self: if key in self:
@ -696,7 +834,7 @@ class PersonBase(dict):
country=None, # type: AnyStr country=None, # type: AnyStr
country_code=None, # type: AnyStr country_code=None, # type: AnyStr
country_timezone=None, # type: AnyStr country_timezone=None, # type: AnyStr
ids=None, # type: Dict ids=None, # type: TVInfoIDs
thumb_url=None, # type: AnyStr thumb_url=None, # type: AnyStr
**kwargs # type: Dict **kwargs # type: Dict
): ):
@ -713,7 +851,7 @@ class PersonBase(dict):
self.country = country # type: Optional[AnyStr] self.country = country # type: Optional[AnyStr]
self.country_code = country_code # type: Optional[AnyStr] self.country_code = country_code # type: Optional[AnyStr]
self.country_timezone = country_timezone # type: Optional[AnyStr] self.country_timezone = country_timezone # type: Optional[AnyStr]
self.ids = ids or {} # type: Dict[int, integer_types] self.ids = ids or TVInfoIDs() # type: TVInfoIDs
def calc_age(self, date=None): def calc_age(self, date=None):
# type: (Optional[datetime.date]) -> Optional[int] # type: (Optional[datetime.date]) -> Optional[int]
@ -778,9 +916,9 @@ class TVInfoPerson(PersonBase):
country=None, # type: AnyStr country=None, # type: AnyStr
country_code=None, # type: AnyStr country_code=None, # type: AnyStr
country_timezone=None, # type: AnyStr country_timezone=None, # type: AnyStr
ids=None, # type: Dict ids=None, # type: TVInfoIDs
homepage=None, # type: Optional[AnyStr] homepage=None, # type: Optional[AnyStr]
social_ids=None, # type: Dict social_ids=None, # type: TVInfoSocialIDs
birthplace=None, # type: AnyStr birthplace=None, # type: AnyStr
deathplace=None, # type: AnyStr deathplace=None, # type: AnyStr
url=None, # type: AnyStr url=None, # type: AnyStr
@ -797,7 +935,7 @@ class TVInfoPerson(PersonBase):
country_code=country_code, country_timezone=country_timezone, ids=ids, **kwargs) country_code=country_code, country_timezone=country_timezone, ids=ids, **kwargs)
self.credits = [] # type: List self.credits = [] # type: List
self.homepage = homepage # type: Optional[AnyStr] self.homepage = homepage # type: Optional[AnyStr]
self.social_ids = social_ids or {} # type: Dict self.social_ids = social_ids or TVInfoSocialIDs() # type: TVInfoSocialIDs
self.birthplace = birthplace # type: Optional[AnyStr] self.birthplace = birthplace # type: Optional[AnyStr]
self.deathplace = deathplace # type: Optional[AnyStr] self.deathplace = deathplace # type: Optional[AnyStr]
self.nicknames = nicknames or set() # type: Set[AnyStr] self.nicknames = nicknames or set() # type: Set[AnyStr]
@ -815,9 +953,9 @@ class TVInfoPerson(PersonBase):
class TVInfoCharacter(PersonBase): class TVInfoCharacter(PersonBase):
def __init__(self, person=None, voice=None, plays_self=None, regular=None, ti_show=None, start_year=None, def __init__(self, person=None, voice=None, plays_self=None, regular=None, ti_show=None, start_year=None,
end_year=None, **kwargs): end_year=None, ids=None, name=None, episode_count=None, guest_episodes_numbers=None, **kwargs):
# type: (List[TVInfoPerson], bool, bool, bool, TVInfoShow, int, int, Dict) -> None # type: (List[TVInfoPerson], bool, bool, bool, TVInfoShow, int, int, TVInfoIDs, AnyStr, int, Dict[int, List[int]], ...) -> None
super(TVInfoCharacter, self).__init__(**kwargs) super(TVInfoCharacter, self).__init__(ids=ids, **kwargs)
self.person = person # type: List[TVInfoPerson] self.person = person # type: List[TVInfoPerson]
self.voice = voice # type: Optional[bool] self.voice = voice # type: Optional[bool]
self.plays_self = plays_self # type: Optional[bool] self.plays_self = plays_self # type: Optional[bool]
@ -825,14 +963,19 @@ class TVInfoCharacter(PersonBase):
self.ti_show = ti_show # type: Optional[TVInfoShow] self.ti_show = ti_show # type: Optional[TVInfoShow]
self.start_year = start_year # type: Optional[integer_types] self.start_year = start_year # type: Optional[integer_types]
self.end_year = end_year # type: Optional[integer_types] self.end_year = end_year # type: Optional[integer_types]
self.name = name # type: Optional[AnyStr]
self.episode_count = episode_count # type: Optional[int]
self.guest_episodes_numbers = guest_episodes_numbers or {} # type: Dict[int, List[int]]
def __str__(self): def __str__(self):
pn = [] pn = []
char_type = ('', ' [Guest]')[False is self.regular]
char_show = None is not self.ti_show and ' [%s]' % self.ti_show.seriesname
if None is not self.person: if None is not self.person:
for p in self.person: for p in self.person:
if getattr(p, 'name', None): if getattr(p, 'name', None):
pn.append(p.name) pn.append(p.name)
return '<Character "%s%s">' % (self.name, ('', ' - (%s)' % ', '.join(pn))[bool(pn)]) return '<Character%s "%s%s%s">' % (char_type, self.name, ('', ' - (%s)' % ', '.join(pn))[bool(pn)], char_show)
__repr__ = __str__ __repr__ = __str__
@ -889,6 +1032,12 @@ class RoleTypes(object):
crew_type_names = {c.lower(): v for v, c in iteritems(RoleTypes.reverse) if v >= RoleTypes.crew_limit} crew_type_names = {c.lower(): v for v, c in iteritems(RoleTypes.reverse) if v >= RoleTypes.crew_limit}
class TVInfoSeasonTypes(object):
default = 'default'
official = 'official'
dvd = 'dvd'
class TVInfoBase(object): class TVInfoBase(object):
supported_id_searches = [] supported_id_searches = []
supported_person_id_searches = [] supported_person_id_searches = []
@ -900,7 +1049,7 @@ class TVInfoBase(object):
reverse_map_languages = {v: k for k, v in iteritems(map_languages)} reverse_map_languages = {v: k for k, v in iteritems(map_languages)}
def __init__(self, banners=False, posters=False, seasons=False, seasonwides=False, fanart=False, actors=False, def __init__(self, banners=False, posters=False, seasons=False, seasonwides=False, fanart=False, actors=False,
*args, **kwargs): dvdorder=False, *args, **kwargs):
global TVInfoShowContainer global TVInfoShowContainer
if self.__class__.__name__ not in TVInfoShowContainer: if self.__class__.__name__ not in TVInfoShowContainer:
TVInfoShowContainer[self.__class__.__name__] = ShowContainer() TVInfoShowContainer[self.__class__.__name__] = ShowContainer()
@ -934,6 +1083,7 @@ class TVInfoBase(object):
'fanart_enabled': fanart, 'fanart_enabled': fanart,
'actors_enabled': actors, 'actors_enabled': actors,
'cache_search': kwargs.get('cache_search'), 'cache_search': kwargs.get('cache_search'),
'dvdorder': dvdorder,
} # type: Dict[AnyStr, Any] } # type: Dict[AnyStr, Any]
def _must_load_data(self, sid, load_episodes, banners, posters, seasons, seasonwides, fanart, actors, lang): def _must_load_data(self, sid, load_episodes, banners, posters, seasons, seasonwides, fanart, actors, lang):
@ -1148,7 +1298,22 @@ class TVInfoBase(object):
if None is self.ti_shows[show_id].id: if None is self.ti_shows[show_id].id:
with self.ti_shows.lock: with self.ti_shows.lock:
del self.ti_shows[show_id] del self.ti_shows[show_id]
return None if show_id not in self.ti_shows else copy.deepcopy(self.ti_shows[show_id]) if show_id not in self.ti_shows:
return None
else:
show_copy = copy.deepcopy(self.ti_shows[show_id]) # type: TVInfoShow
# provide old call compatibility for dvd order
if self.config.get('dvdorder') and TVInfoSeasonTypes.dvd in show_copy.alt_ep_numbering:
org_seasons, dvd_seasons = list_keys(show_copy), \
list_keys(show_copy.alt_ep_numbering[TVInfoSeasonTypes.dvd])
for r_season in set(org_seasons) - set(dvd_seasons):
try:
del show_copy[r_season]
except (BaseException, Exception):
continue
for ti_season in dvd_seasons:
show_copy[ti_season] = show_copy.alt_ep_numbering[TVInfoSeasonTypes.dvd][ti_season]
return show_copy
finally: finally:
try: try:
if None is self.ti_shows[show_id].id: if None is self.ti_shows[show_id].id:
@ -1166,12 +1331,13 @@ class TVInfoBase(object):
self._old_config = None self._old_config = None
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
def _search_show(self, name=None, ids=None, **kwargs): def _search_show(self, name=None, ids=None, lang=None, **kwargs):
# type: (Union[AnyStr, List[AnyStr]], Dict[integer_types, integer_types], Optional[Any]) -> List[Dict] # type: (Union[AnyStr, List[AnyStr]], Dict[integer_types, integer_types], Optional[string_types], Optional[Any]) -> List[Dict]
""" """
internal search function to find shows, should be overwritten in class internal search function to find shows, should be overwritten in class
:param name: name to search for :param name: name to search for
:param ids: dict of ids {tvid: prodid} to search for :param ids: dict of ids {tvid: prodid} to search for
:param lang: language code
""" """
return [] return []
@ -1190,6 +1356,7 @@ class TVInfoBase(object):
self, self,
name=None, # type: Union[AnyStr, List[AnyStr]] name=None, # type: Union[AnyStr, List[AnyStr]]
ids=None, # type: Dict[integer_types, integer_types] ids=None, # type: Dict[integer_types, integer_types]
lang=None, # type: Optional[string_types]
# **kwargs # type: Optional[Any] # **kwargs # type: Optional[Any]
): ):
# type: (...) -> List[Dict] # type: (...) -> List[Dict]
@ -1198,8 +1365,13 @@ class TVInfoBase(object):
:param name: series name or list of names to search for :param name: series name or list of names to search for
:param ids: dict of ids {tvid: prodid} to search for :param ids: dict of ids {tvid: prodid} to search for
:param lang: language code
:return: combined list of series results :return: combined list of series results
""" """
if None is lang:
if self.config.get('language'):
lang = self.config['language']
lang = self.map_languages.get(lang, lang)
if not name and not ids: if not name and not ids:
log.debug('Nothing to search') log.debug('Nothing to search')
raise BaseTVinfoShownotfound('Nothing to search') raise BaseTVinfoShownotfound('Nothing to search')
@ -1208,14 +1380,15 @@ class TVInfoBase(object):
if not name and not any(1 for i in ids if i in self.supported_id_searches): if not name and not any(1 for i in ids if i in self.supported_id_searches):
log.debug('Id type not supported') log.debug('Id type not supported')
raise BaseTVinfoShownotfound('Id type not supported') raise BaseTVinfoShownotfound('Id type not supported')
selected_series = self._search_show(name=name, ids=ids) selected_series = self._search_show(name=name, ids=ids, lang=lang)
elif name: elif name:
selected_series = self._search_show(name) selected_series = self._search_show(name, lang=lang)
if isinstance(selected_series, dict): if isinstance(selected_series, dict):
selected_series = [selected_series] selected_series = [selected_series]
if not isinstance(selected_series, list) or 0 == len(selected_series): if not isinstance(selected_series, list) or 0 == len(selected_series):
log.debug('Series result returned zero') log.debug('Series result returned zero')
raise BaseTVinfoShownotfound('Show-name search returned zero results (cannot find show on TVDB)') raise BaseTVinfoShownotfound('Show-name search returned zero results (cannot find show on %s)' %
self.__class__.__name__)
return selected_series return selected_series
def _set_item(self, sid, seas, ep, attrib, value): def _set_item(self, sid, seas, ep, attrib, value):
@ -1278,6 +1451,24 @@ class TVInfoBase(object):
""" """
return {} return {}
def get_similar(self, tvid, result_count=100, **kwargs):
# type: (integer_types, int, Any) -> List[TVInfoShow]
"""
return list of similar shows to given id
:param tvid: id to give similar shows for
:param result_count: count of results requested
"""
return []
def get_recommended_for_show(self, tvid, result_count=100, **kwargs):
# type: (integer_types, int, Any) -> List[TVInfoShow]
"""
list of recommended shows to the provided tv id
:param tvid: id to find recommended shows for
:param result_count: result count to returned
"""
return []
def get_trending(self, result_count=100, **kwargs): def get_trending(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
@ -1296,16 +1487,30 @@ class TVInfoBase(object):
def get_top_rated(self, result_count=100, **kwargs): def get_top_rated(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get all latest shows get top rated shows
"""
return []
def get_new_shows(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get new shows
"""
return []
def get_new_seasons(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get new seasons
""" """
return [] return []
def discover(self, result_count=100, get_extra_images=False, **kwargs): def discover(self, result_count=100, get_extra_images=False, **kwargs):
# type: (...) -> List[TVInfoEpisode] # type: (...) -> List[TVInfoShow]
return [] return []
def get_premieres(self, **kwargs): def get_premieres(self, **kwargs):
# type: (...) -> List[TVInfoEpisode] # type: (...) -> List[TVInfoShow]
""" """
get all premiering shows get all premiering shows
""" """
@ -1318,6 +1523,93 @@ class TVInfoBase(object):
""" """
return [] return []
def get_most_played(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most played shows
:param result_count: how many results are suppose to be returned
"""
return []
def get_most_watched(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most watched shows
:param result_count: how many results are suppose to be returned
"""
return []
def get_most_collected(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most collected shows
:param result_count: how many results are suppose to be returned
"""
return []
def get_recommended(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most recommended shows
:param result_count: how many results are suppose to be returned
"""
return []
def get_recommended_for_account(self, account, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get recommended shows for account
:param account: account to get recommendations for
:param result_count: how many results are suppose to be returned
"""
return []
def hide_recommended_for_account(self, account, show_ids, **kwargs):
# type: (integer_types, List[integer_types], Any) -> List[integer_types]
"""
hide recommended show for account
:param account: account to get recommendations for
:param show_ids: list of show_ids to no longer recommend for account
:return: list of added ids
"""
return []
def unhide_recommended_for_account(self, account, show_ids, **kwargs):
# type: (integer_types, List[integer_types], Any) -> List[integer_types]
"""
unhide recommended show for account
:param account: account to get recommendations for
:param show_ids: list of show_ids to be included in possible recommend for account
:return: list of removed ids
"""
return []
def list_hidden_recommended_for_account(self, account, **kwargs):
# type: (integer_types, Any) -> List[TVInfoShow]
"""
list hidden recommended show for account
:param account: account to get recommendations for
:return: list of hidden shows
"""
return []
def get_watchlisted_for_account(self, account, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get most watchlisted shows for account
:param account: account to get recommendations for
:param result_count: how many results are suppose to be returned
"""
return []
def get_anticipated(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow]
"""
get anticipated shows
:param result_count: how many results are suppose to be returned
"""
return []
def __getitem__(self, item): def __getitem__(self, item):
# type: (Union[AnyStr, integer_types, Tuple[integer_types, bool]]) -> Union[TVInfoShow, List[Dict], None] # type: (Union[AnyStr, integer_types, Tuple[integer_types, bool]]) -> Union[TVInfoShow, List[Dict], None]
"""Legacy handler (use get_show or search_show instead) """Legacy handler (use get_show or search_show instead)

View file

@ -33,7 +33,7 @@ class BaseTVinfoShownotfound(BaseTVinfoError):
pass pass
class BaseTVinfoSeasonnotfound(BaseTVinfoError): class BaseTVinfoSeasonnotfound(BaseTVinfoError, AttributeError, KeyError):
"""Season cannot be found """Season cannot be found
""" """
pass pass
@ -45,7 +45,7 @@ class BaseTVinfoEpisodenotfound(BaseTVinfoError):
pass pass
class BaseTVinfoAttributenotfound(BaseTVinfoError): class BaseTVinfoAttributenotfound(BaseTVinfoError, AttributeError, KeyError):
"""Raised if an episode does not have the requested """Raised if an episode does not have the requested
attribute (such as a episode name) attribute (such as a episode name)
""" """

View file

@ -290,7 +290,10 @@ def search_infosrc_for_show_id(reg_show_name, tvid=None, prodid=None, ui=None):
logger.debug('Trying to find %s on %s' % (cur_name, sickgear.TVInfoAPI(cur_tvid).name)) logger.debug('Trying to find %s on %s' % (cur_name, sickgear.TVInfoAPI(cur_tvid).name))
try: try:
show_info_list = t[prodid] if prodid else t[cur_name] if prodid:
show_info_list = t.get_show(prodid)
else:
show_info_list = t.search_show(cur_name)
show_info_list = show_info_list if isinstance(show_info_list, list) else [show_info_list] show_info_list = show_info_list if isinstance(show_info_list, list) else [show_info_list]
except (BaseException, Exception): except (BaseException, Exception):
continue continue
@ -989,7 +992,7 @@ def validate_show(show_obj, season=None, episode=None):
if season is None and episode is None: if season is None and episode is None:
return t return t
return t[show_obj.prodid][season][episode] return t.get_show(show_obj.prodid, language=show_obj.lang)[season][episode]
except (BaseTVinfoEpisodenotfound, BaseTVinfoSeasonnotfound, TypeError): except (BaseTVinfoEpisodenotfound, BaseTVinfoSeasonnotfound, TypeError):
pass pass

View file

@ -7,7 +7,8 @@ from lib.api_imdb.imdb_api import IMDbIndexer
from lib.tvinfo_base import ( from lib.tvinfo_base import (
TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_WIKIPEDIA, TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_WIKIPEDIA,
TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TVDB, TVINFO_TVMAZE, TVINFO_TVRAGE, TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TVDB, TVINFO_TVMAZE, TVINFO_TVRAGE,
TVINFO_TRAKT_SLUG, TVINFO_TVDB_SLUG TVINFO_TRAKT_SLUG, TVINFO_TVDB_SLUG, TVINFO_TIKTOK, TVINFO_WIKIDATA, TVINFO_LINKEDIN, TVINFO_FANSITE,
TVINFO_REDDIT, TVINFO_YOUTUBE
) )
init_config = { init_config = {
@ -134,6 +135,72 @@ tvinfo_config = {
show_url=None, show_url=None,
people_only=True, people_only=True,
icon='wikipedia16.png' icon='wikipedia16.png'
),
TVINFO_TIKTOK: dict(
id=TVINFO_TIKTOK,
name='TikTok',
module=None,
active=False,
mapped_only=True,
people_url='https://www.tiktok.com/@%s',
show_url=None,
people_only=True,
icon='tiktok16.png'
),
TVINFO_WIKIDATA: dict(
id=TVINFO_WIKIDATA,
name='Wikidata',
module=None,
active=False,
mapped_only=True,
people_url='https://www.wikidata.org/wiki/%s',
show_url=None,
people_only=True,
icon='wikidata16.png'
),
TVINFO_REDDIT: dict(
id=TVINFO_REDDIT,
name='Reddit',
module=None,
active=False,
mapped_only=True,
people_url='http://www.reddit.com/r/%s',
show_url=None,
people_only=True,
icon='reddit16.png'
),
TVINFO_YOUTUBE: dict(
id=TVINFO_YOUTUBE,
name='Reddit',
module=None,
active=False,
mapped_only=True,
people_url='https://www.youtube.com/c/%s',
show_url=None,
people_only=True,
icon='youtube16.png'
),
TVINFO_FANSITE: dict(
id=TVINFO_FANSITE,
name='Fansite',
module=None,
active=False,
mapped_only=True,
people_url='%s',
show_url=None,
people_only=True,
icon='fansite16.png'
),
TVINFO_LINKEDIN: dict(
id=TVINFO_LINKEDIN,
name='Linkedin',
module=None,
active=False,
mapped_only=True,
people_url='https://www.linkedin.com/in/%s',
show_url=None,
people_only=True,
icon='linkedin16.png'
) )
} }

View file

@ -123,7 +123,7 @@ class KODIMetadata(generic.GenericMetadata):
tv_node = etree.Element('tvshow') tv_node = etree.Element('tvshow')
try: try:
show_info = t.get_show(show_obj.prodid, language=show_obj.lang) show_info = t.get_show(show_id, language=show_obj.lang)
except BaseTVinfoShownotfound as e: except BaseTVinfoShownotfound as e:
logger.error(f'Unable to find show with id {show_id} on {sickgear.TVInfoAPI(show_obj.tvid).name},' logger.error(f'Unable to find show with id {show_id} on {sickgear.TVInfoAPI(show_obj.tvid).name},'
f' skipping it') f' skipping it')

View file

@ -375,7 +375,8 @@ class NameParser(object):
t = sickgear.TVInfoAPI(show_obj.tvid).setup(**tvinfo_config) t = sickgear.TVInfoAPI(show_obj.tvid).setup(**tvinfo_config)
ep_obj = t[show_obj.prodid].aired_on(best_result.air_date)[0] ep_obj = t.get_show(show_obj.prodid, language=show_obj.lang).aired_on(
best_result.air_date)[0]
season_number = int(ep_obj['seasonnumber']) season_number = int(ep_obj['seasonnumber'])
episode_numbers = [int(ep_obj['episodenumber'])] episode_numbers = [int(ep_obj['episodenumber'])]

View file

@ -57,11 +57,12 @@ from lib import imdbpie, subliminal
from lib.dateutil import tz from lib.dateutil import tz
from lib.dateutil.parser import parser as du_parser from lib.dateutil.parser import parser as du_parser
from lib.fuzzywuzzy import fuzz from lib.fuzzywuzzy import fuzz
from lib.tvinfo_base import RoleTypes, TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_SLUG, TVINFO_TWITTER, TVINFO_WIKIPEDIA from lib.tvinfo_base import RoleTypes, TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_SLUG, TVINFO_TWITTER, \
TVINFO_WIKIPEDIA, TVINFO_TIKTOK, TVINFO_FANSITE, TVINFO_YOUTUBE, TVINFO_REDDIT, TVINFO_LINKEDIN, TVINFO_WIKIDATA
from lib.tvinfo_base.exceptions import * from lib.tvinfo_base.exceptions import *
from sg_helpers import calc_age, int_to_time, remove_file_perm, time_to_int from sg_helpers import calc_age, int_to_time, remove_file_perm, time_to_int
from six import integer_types, iteritems, itervalues, moves, string_types from six import integer_types, iteritems, iterkeys, itervalues, moves, string_types
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
if False: if False:
@ -622,10 +623,12 @@ class Person(Referential):
p_ids = {} p_ids = {}
for cur_ids in (cur_person['p_ids'] and cur_person['p_ids'].split(';;;')) or []: for cur_ids in (cur_person['p_ids'] and cur_person['p_ids'].split(';;;')) or []:
k, v = cur_ids.split(':') k, v = cur_ids.split(':', 1)
k = try_int(k, None) k = try_int(k, None)
if v and None is not k: if v and None is not k:
p_ids[k] = v if k in (TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_WIKIPEDIA) \ p_ids[k] = v if k in (TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_WIKIPEDIA,
TVINFO_TIKTOK, TVINFO_FANSITE, TVINFO_YOUTUBE, TVINFO_REDDIT,
TVINFO_LINKEDIN, TVINFO_WIKIDATA) \
else try_int(v, None) else try_int(v, None)
(self._data_failure, self._data_fetched, (self._data_failure, self._data_fetched,
@ -657,7 +660,8 @@ class Person(Referential):
if person_obj.characters and char_obj and char_obj.show_obj \ if person_obj.characters and char_obj and char_obj.show_obj \
and char_obj.show_obj.ids.get(TVINFO_IMDB, {}).get('id'): and char_obj.show_obj.ids.get(TVINFO_IMDB, {}).get('id'):
p_char = [_pc for _pc in person_obj.characters p_char = [_pc for _pc in person_obj.characters
if _pc.ti_show.ids.imdb == char_obj.show_obj.ids.get(TVINFO_IMDB, {}).get('id')] if _pc.ti_show and
_pc.ti_show.ids.imdb == char_obj.show_obj.ids.get(TVINFO_IMDB, {}).get('id')]
p_count = len(p_char) p_count = len(p_char)
if 1 == p_count: if 1 == p_count:
if (p_char[0].start_year or p_char[0].end_year) and getattr(self, 'id', None): if (p_char[0].start_year or p_char[0].end_year) and getattr(self, 'id', None):
@ -717,7 +721,7 @@ class Person(Referential):
self._data_fetched = True self._data_fetched = True
tvsrc_result, found_persons, found_on_src, search_sources, \ tvsrc_result, found_persons, found_on_src, search_sources, \
found_ids, ids_to_check, imdb_confirmed, source_confirmed = \ found_ids, ids_to_check, imdb_confirmed, source_confirmed = \
None, {}, set(), [TVINFO_TRAKT, TVINFO_TMDB, TVINFO_IMDB], \ None, {}, set(), [TVINFO_TRAKT, TVINFO_TMDB, TVINFO_IMDB, TVINFO_TVDB], \
set([_k for _k, _v in iteritems(self.ids) if _v] + ['text']), {}, False, {} set([_k for _k, _v in iteritems(self.ids) if _v] + ['text']), {}, False, {}
# confirmed_character = False # confirmed_character = False
max_search_src = len(search_sources) max_search_src = len(search_sources)
@ -768,8 +772,10 @@ class Person(Referential):
if show_obj and None is not pd and pd.characters: if show_obj and None is not pd and pd.characters:
clean_show_name = indexermapper.clean_show_name(show_obj.name.lower()) clean_show_name = indexermapper.clean_show_name(show_obj.name.lower())
for cur_ch in pd.characters or []: # type: TVInfoCharacter for cur_ch in pd.characters or []: # type: TVInfoCharacter
if clean_show_name == indexermapper.clean_show_name( if not cur_ch.ti_show:
cur_ch.ti_show.seriesname.lower()): continue
if cur_ch.ti_show.seriesname and clean_show_name == indexermapper.clean_show_name(
cur_ch.ti_show.seriesname and cur_ch.ti_show.seriesname.lower()):
rp = pd rp = pd
confirmed_on_src = True confirmed_on_src = True
# confirmed_character = True # confirmed_character = True
@ -807,7 +813,9 @@ class Person(Referential):
found_ids.add(cur_i) found_ids.add(cur_i)
self.dirty_ids = True self.dirty_ids = True
for cur_i in (TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA): for cur_i in (TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA,
TVINFO_TIKTOK, TVINFO_FANSITE, TVINFO_YOUTUBE, TVINFO_REDDIT, TVINFO_LINKEDIN,
TVINFO_WIKIDATA):
if not rp.social_ids.get(cur_i): if not rp.social_ids.get(cur_i):
continue continue
if rp.social_ids.get(cur_i) and not self.ids.get(cur_i) or \ if rp.social_ids.get(cur_i) and not self.ids.get(cur_i) or \
@ -1137,7 +1145,7 @@ class Character(Referential):
for cur_row in (sql_result or []): for cur_row in (sql_result or []):
c_ids = {} c_ids = {}
for cur_ids in (cur_row['c_ids'] and cur_row['c_ids'].split(';;;')) or []: for cur_ids in (cur_row['c_ids'] and cur_row['c_ids'].split(';;;')) or []:
k, v = cur_ids.split(':') k, v = cur_ids.split(':', 1)
v = try_int(v, None) v = try_int(v, None)
if v: if v:
c_ids[int(k)] = try_int(v, None) c_ids[int(k)] = try_int(v, None)
@ -1846,21 +1854,22 @@ class TVShow(TVShowBase):
deathdate = deathdate and datetime.date.fromordinal(cur_row['deathdate']) deathdate = deathdate and datetime.date.fromordinal(cur_row['deathdate'])
p_years = {} p_years = {}
for cur_p in (cur_row['p_years'] and cur_row['p_years'].split(';;;')) or []: for cur_p in (cur_row['p_years'] and cur_row['p_years'].split(';;;')) or []:
p_id, py = cur_p.split(':') p_id, py = cur_p.split(':', 1)
start, end = py.split('-') start, end = py.split('-', 1)
p_years[int(p_id)] = {'start': try_int(start, None), 'end': try_int(end, None)} p_years[int(p_id)] = {'start': try_int(start, None), 'end': try_int(end, None)}
p_ids, c_ids = {}, {} p_ids, c_ids = {}, {}
for cur_i in (cur_row['p_ids'] and cur_row['p_ids'].split(';;;')) or []: for cur_i in (cur_row['p_ids'] and cur_row['p_ids'].split(';;;')) or []:
k, v = cur_i.split(':') k, v = cur_i.split(':', 1)
k = try_int(k, None) k = try_int(k, None)
if v: if v:
if k in (TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA): if k in (TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_TIKTOK,
TVINFO_FANSITE, TVINFO_YOUTUBE, TVINFO_REDDIT, TVINFO_LINKEDIN, TVINFO_WIKIDATA):
p_ids[k] = v p_ids[k] = v
else: else:
p_ids[k] = try_int(v, None) p_ids[k] = try_int(v, None)
for cur_i in (cur_row['c_ids'] and cur_row['c_ids'].split(';;;')) or []: for cur_i in (cur_row['c_ids'] and cur_row['c_ids'].split(';;;')) or []:
k, v = cur_i.split(':') k, v = cur_i.split(':', 1)
v = try_int(v, None) v = try_int(v, None)
if v: if v:
c_ids[int(k)] = try_int(v, None) c_ids[int(k)] = try_int(v, None)
@ -2735,6 +2744,8 @@ class TVShow(TVShowBase):
def _make_airtime(self, airtime=None): def _make_airtime(self, airtime=None):
# type: (Optional[integer_types]) -> Optional[datetime.time] # type: (Optional[integer_types]) -> Optional[datetime.time]
if isinstance(airtime, datetime.time):
return airtime
if isinstance(airtime, integer_types): if isinstance(airtime, integer_types):
return int_to_time(airtime) return int_to_time(airtime)
if self._airs: if self._airs:
@ -2935,7 +2946,7 @@ class TVShow(TVShowBase):
mc = next((_c for _c in cast_list or [] mc = next((_c for _c in cast_list or []
if (None is not cur_cast.id and _c.ids.get(self.tvid) == cur_cast.id) if (None is not cur_cast.id and _c.ids.get(self.tvid) == cur_cast.id)
or (unique_name and cur_cast.name and _c.name == cur_cast.name) or (unique_name and cur_cast.name and _c.name == cur_cast.name)
or any(_c.ids.get(_src) == cur_cast.ids.get(_src) for _src in cur_cast.ids or {})), or any(_c.ids.get(_src) == cur_cast.ids.get(_src) for _src in iterkeys(cur_cast.ids) or {})),
None) # type: Optional[Character] None) # type: Optional[Character]
if not mc: if not mc:
unique_person = not any(1 for _cp in unique_person = not any(1 for _cp in

View file

@ -3365,7 +3365,7 @@ class CMD_SickGearShowAddExisting(ApiCall):
t = sickgear.TVInfoAPI(self.tvid).setup(**lINDEXER_API_PARMS) t = sickgear.TVInfoAPI(self.tvid).setup(**lINDEXER_API_PARMS)
try: try:
myShow = t[int(self.prodid), False] myShow = t.get_show(self.prodid, load_episodes=False)
except BaseTVinfoError as e: except BaseTVinfoError as e:
self.log(f'Unable to find show with id {self.tvid}', logger.WARNING) self.log(f'Unable to find show with id {self.tvid}', logger.WARNING)
return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer") return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer")
@ -3528,7 +3528,7 @@ class CMD_SickGearShowAddNew(ApiCall):
t = sickgear.TVInfoAPI(self.tvid).setup(**lINDEXER_API_PARMS) t = sickgear.TVInfoAPI(self.tvid).setup(**lINDEXER_API_PARMS)
try: try:
myShow = t[int(self.prodid), False] myShow = t.get_show(self.prodid, load_episodes=False)
except BaseTVinfoError as e: except BaseTVinfoError as e:
self.log(f'Unable to find show with id {self.tvid}', logger.WARNING) self.log(f'Unable to find show with id {self.tvid}', logger.WARNING)
return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer") return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer")

View file

@ -2177,6 +2177,17 @@ class Home(MainHandler):
return json_dumps({'success': t.respond()}) return json_dumps({'success': t.respond()})
@staticmethod
def fix_show_obj_db_data(show_obj):
# adjust show_obj db data
if 'genres' not in show_obj.imdb_info or None is show_obj.imdb_info.get('genres'):
show_obj.imdb_info['genres'] = ''
if show_obj.genre and not show_obj.genre[1:-1]:
show_obj.genre = ''
if 'country_codes' not in show_obj.imdb_info or None is show_obj.imdb_info.get('country_codes'):
show_obj.imdb_info['country_codes'] = ''
return show_obj
def view_show(self, tvid_prodid=None): def view_show(self, tvid_prodid=None):
if None is tvid_prodid: if None is tvid_prodid:
@ -2186,6 +2197,8 @@ class Home(MainHandler):
if None is show_obj: if None is show_obj:
return self._generic_message('Error', 'Show not in show list') return self._generic_message('Error', 'Show not in show list')
show_obj = self.fix_show_obj_db_data(show_obj)
t = PageTemplate(web_handler=self, file='displayShow.tmpl') t = PageTemplate(web_handler=self, file='displayShow.tmpl')
t.submenu = [{'title': 'Edit', 'path': 'home/edit-show?tvid_prodid=%s' % tvid_prodid}] t.submenu = [{'title': 'Edit', 'path': 'home/edit-show?tvid_prodid=%s' % tvid_prodid}]
@ -3579,7 +3592,7 @@ class Home(MainHandler):
# type: (AnyStr) -> AnyStr # type: (AnyStr) -> AnyStr
"""Return a text list of items separated by comma instead of '/' """ """Return a text list of items separated by comma instead of '/' """
return re.sub(r'\b\s?/\s?\b', ', ', text) return (isinstance(text, string_types) and re.sub(r'\b\s?/\s?\b', ', ', text)) or text
def role(self, rid, tvid_prodid, **kwargs): def role(self, rid, tvid_prodid, **kwargs):
_ = kwargs.get('oid') # suppress pyc non used var highlight, oid (original id) is a visual ui key _ = kwargs.get('oid') # suppress pyc non used var highlight, oid (original id) is a visual ui key
@ -3609,7 +3622,7 @@ class Home(MainHandler):
known = {} known = {}
main_role_known = False main_role_known = False
rc_clean = re.compile(r'[^a-z0-9]') rc_clean = re.compile(r'[^a-z0-9]')
char_name = rc_clean.sub('', character.name.lower()) char_name = rc_clean.sub('', (character.name or 'unknown name').lower())
for cur_person in (character.person or []): for cur_person in (character.person or []):
person, roles, msg = self.cast(cur_person.id) person, roles, msg = self.cast(cur_person.id)
if not msg: if not msg:
@ -3704,7 +3717,7 @@ class Home(MainHandler):
ref_id = cur_ref_id ref_id = cur_ref_id
roles.append({ roles.append({
'character_name': self.csv_items(cur_char['name']), 'character_name': self.csv_items(cur_char['name']) or 'unknown name',
'character_id': cur_char['id'], 'character_id': cur_char['id'],
'character_rid': ref_id, 'character_rid': ref_id,
'show_obj': helpers.find_show_by_id({cur_char['c_tvid']: cur_char['c_prodid']}), 'show_obj': helpers.find_show_by_id({cur_char['c_tvid']: cur_char['c_prodid']}),
@ -4105,7 +4118,8 @@ class AddShows(Home):
t = sickgear.TVInfoAPI(cur_tvid).setup(**tvinfo_config) t = sickgear.TVInfoAPI(cur_tvid).setup(**tvinfo_config)
results.setdefault(cur_tvid, {}) results.setdefault(cur_tvid, {})
try: try:
for cur_result in t.search_show(list(used_search_term), ids=ids_search_used): # type: TVInfoShow for cur_result in t.search_show(list(used_search_term), ids=ids_search_used,
lang=lang): # type: TVInfoShow
if TVINFO_TRAKT == cur_tvid and not cur_result['ids'].tvdb: if TVINFO_TRAKT == cur_tvid and not cur_result['ids'].tvdb:
continue continue
tv_src_id = int(cur_result['id']) tv_src_id = int(cur_result['id'])
@ -4262,18 +4276,11 @@ class AddShows(Home):
img_url = '' img_url = ''
if TVINFO_TRAKT == iid: if TVINFO_TRAKT == iid:
img_url = 'imagecache?path=browse/thumb/trakt&filename=%s&trans=0&tmdbid=%s&tvdbid=%s' % \ img_url = 'imagecache?path=browse/thumb/trakt&filename=%s&trans=0&tmdbid=%s&tvdbid=%s' % \
('%s.jpg' % show_info['ids'].trakt, show_info.get('tmdb_id'), show_info['ids'].tvdb) ('%s.jpg' % show_info['ids'].trakt, show_info['ids'].tmdb, show_info['ids'].tvdb)
elif TVINFO_TVDB == iid and 'poster' in show_info and show_info['poster']: elif iid in (TVINFO_TVDB, TVINFO_TVMAZE, TVINFO_TMDB) and 'poster' in show_info and show_info['poster']:
img_url = 'imagecache?path=browse/thumb/tvdb&filename=%s&trans=0&source=%s' % \ img_url = 'imagecache?path=browse/thumb/%s&filename=%s&trans=0&source=%s' % \
('%s.jpg' % show_info['id'], show_info['poster']) ({TVINFO_TVDB: 'tvdb', TVINFO_TVMAZE: 'tvmaze', TVINFO_TMDB: 'tmdb'}[iid],
sickgear.CACHE_IMAGE_URL_LIST.add_url(show_info['poster']) '%s.jpg' % show_info['id'], show_info['poster'])
elif TVINFO_TVMAZE == iid and show_info.get('image'):
img_url = 'imagecache?path=browse/thumb/tvmaze&filename=%s&trans=0&source=%s' % \
('%s.jpg' % show_info['id'], show_info['image'])
sickgear.CACHE_IMAGE_URL_LIST.add_url(show_info['image'])
elif TVINFO_TMDB == iid and 'poster' in show_info and show_info['poster']:
img_url = 'imagecache?path=browse/thumb/tmdb&filename=%s&trans=0&source=%s' % \
('%s.jpg' % show_info['id'], show_info['poster'])
sickgear.CACHE_IMAGE_URL_LIST.add_url(show_info['poster']) sickgear.CACHE_IMAGE_URL_LIST.add_url(show_info['poster'])
return img_url return img_url

Some files were not shown because too many files have changed in this diff Show more