SickGear/lib/api_tvmaze/tvmaze_api.py
Prinz23 7a6936823e 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.
2023-05-03 00:43:59 +01:00

815 lines
40 KiB
Python

# encoding:utf-8
# author:Prinz23
# project:tvmaze_api
__author__ = 'Prinz23'
__version__ = '1.0'
__api_version__ = '1.0.0'
import datetime
import logging
import re
import requests
from requests.adapters import HTTPAdapter
# noinspection PyProtectedMember
from tornado._locale_data import LOCALE_NAMES
from urllib3.util.retry import Retry
from sg_helpers import clean_data, enforce_type, get_url, try_int
from lib.dateutil.parser import parser
# noinspection PyProtectedMember
from lib.dateutil.tz.tz import _datetime_to_timestamp
from lib.exceptions_helper import ConnectionSkipException, ex
from lib.pytvmaze import tvmaze
# from .tvmaze_exceptions import *
from lib.tvinfo_base import TVInfoBase, TVInfoImage, TVInfoImageSize, TVInfoImageType, TVInfoCharacter, Crew, \
crew_type_names, TVInfoPerson, RoleTypes, TVInfoShow, TVInfoEpisode, TVInfoIDs, TVInfoNetwork, TVInfoSeason, \
PersonGenders, TVINFO_TVMAZE, TVINFO_TVDB, TVINFO_IMDB
from six import integer_types, iteritems, string_types
# noinspection PyUnreachableCode
if False:
from typing import Any, AnyStr, Dict, List, Optional
from lib.pytvmaze.tvmaze import Episode as TVMazeEpisode, Show as TVMazeShow
log = logging.getLogger('tvmaze.api')
log.addHandler(logging.NullHandler())
# Query TVmaze free endpoints
def tvmaze_endpoint_standard_get(url):
s = requests.Session()
retries = Retry(total=5,
backoff_factor=0.1,
status_forcelist=[429])
# noinspection HttpUrlsUsage
s.mount('http://', HTTPAdapter(max_retries=retries))
s.mount('https://', HTTPAdapter(max_retries=retries))
# noinspection PyProtectedMember
return get_url(url, json=True, session=s, hooks={'response': tvmaze._record_hook}, raise_skip_exception=True)
tvmaze.TVmaze.endpoint_standard_get = staticmethod(tvmaze_endpoint_standard_get)
tvm_obj = tvmaze.TVmaze()
empty_ep = TVInfoEpisode()
empty_se = TVInfoSeason()
tz_p = parser()
img_type_map = {
'poster': TVInfoImageType.poster,
'banner': TVInfoImageType.banner,
'background': TVInfoImageType.fanart,
'typography': TVInfoImageType.typography,
}
img_size_map = {
'original': TVInfoImageSize.original,
'medium': TVInfoImageSize.medium,
}
show_map = {
'id': 'maze_id',
'ids': 'externals',
# 'slug': '',
'seriesid': 'maze_id',
'seriesname': 'name',
'aliases': 'akas',
# 'season': '',
'classification': 'type',
# 'genre': '',
'genre_list': 'genres',
# 'actors': '',
# 'cast': '',
# 'show_type': '',
# 'network': 'network',
# 'network_id': '',
# 'network_timezone': '',
# 'network_country': '',
# 'network_country_code': '',
# 'network_is_stream': '',
# 'runtime': 'runtime',
'language': 'language',
'official_site': 'official_site',
# 'imdb_id': '',
# 'zap2itid': '',
# 'airs_dayofweek': '',
# 'airs_time': '',
# 'time': '',
'firstaired': 'premiered',
# 'added': '',
# 'addedby': '',
# 'siteratingcount': '',
# 'lastupdated': '',
# 'contentrating': '',
# 'rating': 'rating',
'status': 'status',
'overview': 'summary',
# 'poster': 'image',
# 'poster_thumb': '',
# 'banner': '',
# 'banner_thumb': '',
# 'fanart': '',
# 'banners': '',
'updated_timestamp': 'updated',
}
season_map = {
'id': 'id',
'number': 'season_number',
'name': 'name',
# 'actors': '',
# 'cast': '',
# 'network': '',
# 'network_id': '',
# 'network_timezone': '',
# 'network_country': '',
# 'network_country_code': '',
# 'network_is_stream': '',
'ordered': '',
'start_date': 'premiere_date',
'end_date': 'end_date',
# 'poster': '',
'summery': 'summary',
'episode_order': 'episode_order',
}
class TvMaze(TVInfoBase):
supported_id_searches = [TVINFO_TVMAZE, TVINFO_TVDB, TVINFO_IMDB]
supported_person_id_searches = [TVINFO_TVMAZE]
def __init__(self, *args, **kwargs):
super(TvMaze, self).__init__(*args, **kwargs)
def _search_show(self, name=None, ids=None, **kwargs):
def _make_result_dict(s):
# type: (tvmaze.Show) -> Dict
language = s.language and clean_data(s.language.lower())
language_country_code = None
if language:
for cur_locale in iteritems(LOCALE_NAMES):
if language in cur_locale[1]['name_en'].lower():
language_country_code = cur_locale[0].split('_')[1].lower()
break
ti_show = TVInfoShow()
show_type = clean_data(s.type)
if show_type:
show_type = [show_type]
else:
show_type = []
ti_show.seriesname, ti_show.id, ti_show.firstaired, ti_show.network, ti_show.genre_list, ti_show.overview, \
ti_show.language, ti_show.runtime, ti_show.show_type, ti_show.airs_dayofweek, ti_show. status, \
ti_show.official_site, ti_show.aliases, ti_show.poster, ti_show.ids = clean_data(s.name), s.id, \
clean_data(s.premiered), \
clean_data((s.network and s.network.name) or (s.web_channel and s.web_channel.name)), \
isinstance(s.genres, list) and [clean_data(g.lower()) for g in s.genres], \
enforce_type(clean_data(s.summary), str, ''), clean_data(s.language), \
s.average_runtime or s.runtime, show_type, ', '.join(s.schedule['days'] or []), clean_data(s.status), \
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 = []
if ids:
for t, p in iteritems(ids):
if t in self.supported_id_searches:
cache_id_key = 's-id-%s-%s' % (t, ids[t])
is_none, shows = self._get_cache_entry(cache_id_key)
if t == TVINFO_TVDB:
if not self.config.get('cache_search') or (None is shows and not is_none):
try:
show = tvmaze.lookup_tvdb(p)
self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire)
except (BaseException, Exception):
continue
else:
show = shows
elif t == TVINFO_IMDB:
if not self.config.get('cache_search') or (None is shows and not is_none):
try:
show = tvmaze.lookup_imdb((p, 'tt%07d' % p)[not str(p).startswith('tt')])
self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire)
except (BaseException, Exception):
continue
else:
show = shows
elif t == TVINFO_TVMAZE:
if not self.config.get('cache_search') or (None is shows and not is_none):
try:
show = tvm_obj.get_show(maze_id=p)
self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire)
except (BaseException, Exception):
continue
else:
show = shows
else:
continue
if show:
try:
if show.id not in [i['id'] for i in results]:
results.append(_make_result_dict(show))
except (BaseException, Exception) as e:
log.debug('Error creating result dict: %s' % ex(e))
if name:
for n in ([name], name)[isinstance(name, list)]:
cache_name_key = 's-name-%s' % n
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):
try:
shows = tvmaze.show_search(n)
except (BaseException, Exception) as e:
log.debug('Error searching for show: %s' % ex(e))
continue
results.extend([_make_result_dict(s) for s in shows or []])
seen = set()
results = [seen.add(r['id']) or r for r in results if r['id'] not in seen]
return results
def _set_episode(self, sid, ep_obj):
for _k, _s in (
('seasonnumber', 'season_number'), ('episodenumber', 'episode_number'),
('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'),
('airtime', 'airtime'), ('runtime', 'runtime'),
('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'), ('filename', 'image')):
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 = image.get('original') or image.get('medium')
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number or 0, _k, image)
else:
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))))
if ep_obj.airstamp:
try:
at = _datetime_to_timestamp(tz_p.parse(ep_obj.airstamp))
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number or 0, 'timestamp', at)
except (BaseException, Exception):
pass
@staticmethod
def _set_network(ti_obj, network, is_stream):
ti_obj.network = clean_data(network.name)
ti_obj.network_timezone = clean_data(network.timezone)
ti_obj.network_country = clean_data(network.country)
ti_obj.network_country_code = clean_data(network.code)
ti_obj.network_id = clean_data(network.maze_id)
ti_obj.network_is_stream = is_stream
def _set_images(self, ti_show, show_data, p_set):
# type: (TVInfoShow, TVMazeShow, bool) -> None
"""
Populate TVInfoShow with images show data
:param ti_show:
:param show_data:
:param p_set:
"""
b_set, f_set = False, False
for cur_img in show_data.images:
img_type = img_type_map.get(cur_img.type, TVInfoImageType.other)
img_width, img_height, img_url = ([cur_img.resolutions['original'].get(this)
for this in ('width', 'height', 'url')])
img_ar = img_width and img_height and float(img_width) / float(img_height)
img_ar_type = self._which_type(img_width, img_ar)
if TVInfoImageType.poster == img_type and img_ar and img_ar_type != img_type and \
ti_show.poster == img_url:
p_set = False
ti_show.poster = None
ti_show.poster_thumb = None
img_type = (TVInfoImageType.other, img_type)[
not img_ar or img_ar_type == img_type or
img_type not in (TVInfoImageType.banner, TVInfoImageType.poster, TVInfoImageType.fanart)]
img_src = {}
for cur_res, cur_img_url in iteritems(cur_img.resolutions):
img_size = img_size_map.get(cur_res)
if img_size:
img_src[img_size] = cur_img_url.get('url')
ti_show.images.setdefault(img_type, []).append(
TVInfoImage(
image_type=img_type, sizes=img_src, img_id=cur_img.id, main_image=cur_img.main,
type_str=cur_img.type, width=img_width, height=img_height, aspect_ratio=img_ar))
if not p_set and TVInfoImageType.poster == img_type:
p_set = True
ti_show.poster = img_url
ti_show.poster_thumb = img_url
elif not b_set and 'banner' == cur_img.type and TVInfoImageType.banner == img_type:
b_set = True
ti_show.banner = img_url
ti_show.banner_thumb = cur_img.resolutions.get('medium')['url']
elif not f_set and 'background' == cur_img.type and TVInfoImageType.fanart == img_type:
f_set = True
ti_show.fanart = img_url
def _get_tvm_show(self, show_id, get_ep_info):
try:
self.show_not_found = False
return tvm_obj.get_show(maze_id=show_id, embed='cast%s' % ('', ',episodeswithspecials')[get_ep_info])
except tvmaze.ShowNotFound:
self.show_not_found = True
except (BaseException, Exception):
log.debug('Error getting data for TVmaze show id: %s' % show_id)
def _get_show_data(self, sid, language, get_ep_info=False, banners=False, posters=False, seasons=False,
seasonwides=False, fanart=False, actors=False, **kwargs):
log.debug('Getting all series data for %s' % sid)
show_data = self._get_tvm_show(sid, get_ep_info)
if not show_data:
return False
ti_show = self.ti_shows[sid] # type: TVInfoShow
self._show_info_loader(
sid, show_data, ti_show,
load_images=banners or posters or fanart or
any(self.config.get('%s_enabled' % t, False) for t in ('banners', 'posters', 'fanart')),
load_actors=(actors or self.config['actors_enabled'])
)
if get_ep_info and not getattr(self.ti_shows.get(sid), 'ep_loaded', False):
log.debug('Getting all episodes of %s' % sid)
if None is show_data:
show_data = self._get_tvm_show(sid, get_ep_info)
if not show_data:
return False
if show_data.episodes:
specials = []
for cur_ep in show_data.episodes:
if cur_ep.is_special():
specials.append(cur_ep)
else:
self._set_episode(sid, cur_ep)
if specials:
specials.sort(key=lambda ep: ep.airstamp or 'Last')
for cur_ep_num, cur_sp in enumerate(specials, start=1):
cur_sp.season_number, cur_sp.episode_number = 0, cur_ep_num
self._set_episode(sid, cur_sp)
if show_data.seasons:
networks = {}
for _, cur_season in iteritems(show_data.seasons):
ti_season = None
if cur_season.season_number not in ti_show:
if all(_e.is_special() for _e in cur_season.episodes or []):
ti_season = ti_show[0]
else:
log.error('error episodes have no numbers')
ti_season = ti_season or ti_show[cur_season.season_number] # type: TVInfoSeason
for k, v in iteritems(season_map):
setattr(ti_season, k, clean_data(getattr(cur_season, v, None)) or empty_se.get(v))
if cur_season.network:
self._set_network(ti_season, cur_season.network, False)
elif cur_season.web_channel:
self._set_network(ti_season, cur_season.web_channel, True)
if ti_season.network:
networks[cur_season.season_number] = TVInfoNetwork(
name=ti_season.network, country=ti_season.network_country,
country_code=ti_season.network_country_code, n_id=ti_season.network_id,
stream=ti_season.network_is_stream, timezone=ti_season.network_timezone
)
if cur_season.image:
ti_season.poster = cur_season.image.get('original')
ti_show.season_images_loaded = True
seen = set()
ti_show.networks = [seen.add(r[1].name) or r[1]
for r in sorted(iteritems(networks), key=lambda a: a[0], reverse=True)
if r[1].name not in seen]
ti_show.ep_loaded = True
return True
def get_updated_shows(self):
# type: (...) -> Dict[integer_types, integer_types]
return {sid: v.seconds_since_epoch for sid, v in iteritems(tvmaze.show_updates().updates)}
def _convert_person(self, tvmaze_person_obj):
# type: (tvmaze.Person) -> TVInfoPerson
ch = []
_dupes = []
for c in tvmaze_person_obj.castcredits or []:
ti_show = TVInfoShow()
ti_show.seriesname = clean_data(c.show.name)
ti_show.id = c.show.id
ti_show.firstaired = clean_data(c.show.premiered)
ti_show.ids = TVInfoIDs(ids={TVINFO_TVMAZE: ti_show.id})
ti_show.overview = clean_data(c.show.summary)
ti_show.status = clean_data(c.show.status)
net = c.show.network or c.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_country_code = clean_data(net.code)
ti_show.network_timezone = clean_data(net.timezone)
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))
try:
birthdate = tvmaze_person_obj.birthday and tz_p.parse(tvmaze_person_obj.birthday).date()
except (BaseException, Exception):
birthdate = None
try:
deathdate = tvmaze_person_obj.death_day and tz_p.parse(tvmaze_person_obj.death_day).date()
except (BaseException, Exception):
deathdate = None
_ti_person_obj = TVInfoPerson(
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),
birthdate=birthdate, deathdate=deathdate,
country=tvmaze_person_obj.country and clean_data(tvmaze_person_obj.country.get('name')),
country_code=tvmaze_person_obj.country and clean_data(tvmaze_person_obj.country.get('code')),
country_timezone=tvmaze_person_obj.country and clean_data(tvmaze_person_obj.country.get('timezone')),
thumb_url=tvmaze_person_obj.image and tvmaze_person_obj.image.get('medium'),
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):
# type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson]
urls, result, ids = [], [], ids or {}
for tv_src in self.supported_person_id_searches:
if tv_src in ids:
if TVINFO_TVMAZE == tv_src:
try:
r = self.get_person(ids[tv_src])
except ConnectionSkipException as e:
raise e
except (BaseException, Exception):
r = None
if r:
result.append(r)
if name:
try:
r = tvmaze.people_search(name)
except ConnectionSkipException as e:
raise e
except (BaseException, Exception):
r = None
if r:
for p in r:
if not any(1 for ep in result if p.id == ep.id):
result.append(self._convert_person(p))
return result
def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs):
# type: (integer_types, bool, bool, Any) -> Optional[TVInfoPerson]
if not p_id:
return
kw = {}
to_embed = []
if get_show_credits:
to_embed.append('castcredits')
if to_embed:
kw['embed'] = ','.join(to_embed)
try:
p = tvmaze.person_main_info(p_id, **kw)
except ConnectionSkipException as e:
raise e
except (BaseException, Exception):
p = None
if p:
return self._convert_person(p)
def get_premieres(self, **kwargs):
# type: (...) -> List[TVInfoShow]
return [_e.show for _e in self._filtered_schedule(**kwargs).get('premieres')]
def get_returning(self, **kwargs):
# type: (...) -> List[TVInfoShow]
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, show_obj=None):
# type: (TVMazeEpisode, TVMazeShow, bool, bool, TVInfoShow) -> 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.seriesname = clean_data(show_data.name)
ti_show.id = show_data.maze_id
ti_show.seriesid = ti_show.id
ti_show.language = clean_data(show_data.language)
ti_show.overview = enforce_type(clean_data(show_data.summary), str, '')
ti_show.firstaired = clean_data(show_data.premiered)
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.rating = ti_show.vote_average
ti_show.popularity = show_data.weight
ti_show.genre_list = clean_data(show_data.genres or [])
ti_show.genre = '|'.join(ti_show.genre_list).lower()
ti_show.official_site = clean_data(show_data.official_site)
ti_show.status = clean_data(show_data.status)
ti_show.show_type = clean_data((isinstance(show_data.type, string_types) and [show_data.type.lower()] or
isinstance(show_data.type, list) and [x.lower() for x in show_data.type] or []))
ti_show.lastupdated = show_data.updated
ti_show.poster = show_data.image and show_data.image.get('original')
if get_akas:
ti_show.aliases = [clean_data(a.name) for a in show_data.akas]
if show_data.schedule and 'days' in show_data.schedule:
ti_show.airs_dayofweek = ', '.join(clean_data(show_data.schedule['days']))
network = show_data.network or show_data.web_channel
if network:
ti_show.network_is_stream = None is not show_data.web_channel
ti_show.network = clean_data(network.name)
ti_show.network_id = network.maze_id
ti_show.network_country = clean_data(network.country)
ti_show.network_country_code = clean_data(network.code)
ti_show.network_timezone = clean_data(network.timezone)
if get_images and show_data.images:
self._set_images(ti_show, show_data, False)
ti_show.ids = TVInfoIDs(
tvdb=show_data.externals.get('thetvdb'), rage=show_data.externals.get('tvrage'), tvmaze=show_data.id,
imdb=clean_data(show_data.externals.get('imdb') and
try_int(show_data.externals.get('imdb').replace('tt', ''), None)))
ti_show.imdb_id = clean_data(show_data.externals.get('imdb'))
if isinstance(ti_show.imdb_id, integer_types):
ti_show.imdb_id = 'tt%07d' % ti_show.imdb_id
ti_episode = TVInfoEpisode(show=ti_show)
ti_episode.id = episode_data.maze_id
ti_episode.seasonnumber = episode_data.season_number
ti_episode.episodenumber = episode_data.episode_number or 0
ti_episode.episodename = clean_data(episode_data.title)
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)
if episode_data.airstamp:
try:
at = _datetime_to_timestamp(tz_p.parse(episode_data.airstamp))
ti_episode.timestamp = at
except (BaseException, Exception):
pass
ti_episode.filename = episode_data.image and (episode_data.image.get('original') or
episode_data.image.get('medium'))
ti_episode.is_special = episode_data.is_special()
ti_episode.overview = enforce_type(clean_data(episode_data.summary), str, '')
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
def _filtered_schedule(self, **kwargs):
cache_name_key = 'tvmaze_schedule'
is_none, schedule = self._get_cache_entry(cache_name_key)
if None is schedule and not is_none:
schedule = []
try:
schedule = tvmaze.get_full_schedule()
except(BaseException, Exception):
pass
premieres = []
returning = []
rc_lang = re.compile('(?i)eng|jap')
for cur_show in filter(lambda s: 1 == s.episode_number and (
None is s.show.language or rc_lang.search(s.show.language)), schedule):
if 1 == cur_show.season_number:
premieres += [cur_show]
else:
returning += [cur_show]
premieres = [self._make_episode(r, r.show, **kwargs)
for r in sorted(premieres, key=lambda e: e.show.premiered)]
returning = [self._make_episode(r, r.show, **kwargs)
for r in sorted(returning, key=lambda e: e.airstamp)]
schedule = dict(premieres=premieres, returning=returning)
self._set_cache_entry(cache_name_key, schedule, expire=self.schedule_cache_expire)
return schedule