diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl
index 8781973b..a8215764 100644
--- a/gui/slick/interfaces/default/home_browseShows.tmpl
+++ b/gui/slick/interfaces/default/home_browseShows.tmpl
@@ -50,6 +50,8 @@
$.ll.handleScroll();
});
+ $('.nav').on('mouseover', function() {$('.service, .browse-image').qtip('hide')})
+
savePrefs = (function(){
var showsort = [], showfilter = [];
@@ -294,11 +296,8 @@ $(function() {
objectFitImages();
- $('#content').find('img.browse-image').each(function(i, oImage){
+ $('#person .person-bg').each(function(i, oImage){
removeImageBackground(oImage);
- });
-
- $('#content').find('img.browse-image').each(function (i, oImage){
scaleImage(oImage);
});
});
@@ -310,6 +309,9 @@ $(function() {
-
+
+
+
+
#end if
#end if
diff --git a/lib/api_tmdb/tmdb_api.py b/lib/api_tmdb/tmdb_api.py
index 38ce0bed..64516a8d 100644
--- a/lib/api_tmdb/tmdb_api.py
+++ b/lib/api_tmdb/tmdb_api.py
@@ -310,8 +310,9 @@ class TmdbIndexer(TVInfoBase):
self.img_base_url, self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.medium],
tmdb_person_obj['profile_path'])
+ clean_person_name = clean_data(tmdb_person_obj.get('name'))
_it_person_obj = TVInfoPerson(
- p_id=tmdb_person_obj.get('id'), ids=TVInfoIDs(ids=person_ids), name=clean_data(tmdb_person_obj.get('name')),
+ p_id=tmdb_person_obj.get('id'), ids=TVInfoIDs(ids=person_ids), name=clean_person_name,
akas=clean_data(set(tmdb_person_obj.get('also_known_as') or [])),
bio=clean_data(tmdb_person_obj.get('biography')), gender=gender,
image=main_image, images=image_list, thumb_url=main_thumb,
@@ -331,6 +332,10 @@ class TmdbIndexer(TVInfoBase):
ti_show.overview = self._enforce_text(character.get('overview'))
ti_show.firstaired = clean_data(character.get('first_air_date'))
ti_show.language = clean_data(character.get('original_language'))
+ ti_show.popularity = character.get('popularity')
+ ti_show.vote_count = character.get('vote_count')
+ ti_show.vote_average = character.get('vote_average')
+ ti_show.rating = ti_show.vote_average
ti_show.genre_list = []
for g in character.get('genre_ids') or []:
if g in self.tv_genres:
@@ -350,9 +355,13 @@ class TmdbIndexer(TVInfoBase):
(self.img_base_url,
self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.original],
character['backdrop_path'])
+ clean_char_name = clean_data(character.get('character'))
+ clean_lower_person_name = (clean_person_name or '').lower() or None
characters.append(
- TVInfoCharacter(name=clean_data(character.get('character')), ti_show=ti_show, person=[_it_person_obj],
- episode_count=character.get('episode_count'))
+ TVInfoCharacter(name=clean_char_name, ti_show=ti_show, person=[_it_person_obj],
+ episode_count=character.get('episode_count'),
+ plays_self=clean_char_name and
+ (clean_char_name or '').lower() in ('self', clean_lower_person_name))
)
_it_person_obj.characters = characters
@@ -754,11 +763,16 @@ class TmdbIndexer(TVInfoBase):
for character in sorted(list(filter(lambda b: b['credit_id'] in main_cast_credit_ids,
person_obj.get('roles', []) or [])),
key=lambda c: c['episode_count'], reverse=True):
+ clean_char_name = clean_data(character['character'])
+ clean_person_name = clean_data(person_obj['name'])
+ clean_lower_person_name = (clean_person_name or '').lower() or None
character_obj = TVInfoCharacter(
- name=clean_data(character['character']),
+ name=clean_char_name,
+ plays_self=clean_char_name and
+ (clean_char_name or '').lower() in ('self', clean_lower_person_name),
person=[
TVInfoPerson(
- p_id=person_obj['id'], name=clean_data(person_obj['name']),
+ p_id=person_obj['id'], name=clean_person_name,
ids=TVInfoIDs(ids={TVINFO_TMDB: person_obj['id']}),
image='%s%s%s' % (
self.img_base_url,
diff --git a/lib/api_trakt/indexerapiinterface.py b/lib/api_trakt/indexerapiinterface.py
index 7b279dbd..0318901c 100644
--- a/lib/api_trakt/indexerapiinterface.py
+++ b/lib/api_trakt/indexerapiinterface.py
@@ -6,7 +6,7 @@ from exceptions_helper import ConnectionSkipException, ex
from six import iteritems
from .trakt import TraktAPI
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 PersonGenders, TVInfoBase, TVINFO_TRAKT, TVINFO_TMDB, TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_IMDB, \
TVINFO_SLUG, TVInfoPerson, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_INSTAGRAM, TVInfoCharacter, \
TVInfoShow, TVInfoIDs, TVInfoSocialIDs, TVINFO_TRAKT_SLUG, TVInfoEpisode, TVInfoSeason, RoleTypes
from sg_helpers import clean_data, enforce_type, try_int
@@ -262,6 +262,7 @@ class TraktIndexer(TVInfoBase):
deathdate=deathdate,
homepage=person_obj['homepage'],
birthplace=person_obj['birthplace'],
+ gender=PersonGenders.trakt_map.get(person_obj['gender'], PersonGenders.unknown),
social_ids=TVInfoSocialIDs(
ids={TVINFO_TWITTER: person_obj['social_ids']['twitter'],
TVINFO_FACEBOOK: person_obj['social_ids']['facebook'],
@@ -308,6 +309,7 @@ class TraktIndexer(TVInfoBase):
if resp:
if show_credits:
pc = []
+ clean_lower_person_name = (result.name or '').lower()
for c in resp.get('cast') or []:
ti_show = TVInfoShow()
ti_show.id = c['show']['ids'].get('trakt')
@@ -327,9 +329,11 @@ class TraktIndexer(TVInfoBase):
ti_show.rating = c['show'].get('rating')
ti_show.vote_count = c['show'].get('votes')
for ch in c.get('characters') or []:
- _ti_character = TVInfoCharacter(name=ch, regular=c.get('series_regular'),
- ti_show=ti_show, person=[result],
- episode_count=c.get('episode_count'))
+ clean_ch = clean_data(ch)
+ _ti_character = TVInfoCharacter(
+ name=clean_ch, regular=c.get('series_regular'), ti_show=ti_show, person=[result],
+ episode_count=c.get('episode_count'),
+ plays_self=(clean_ch or '').lower() in ('self', clean_lower_person_name))
pc.append(_ti_character)
ti_show.cast[(RoleTypes.ActorGuest, RoleTypes.ActorMain)[
c.get('series_regular', False)]].append(_ti_character)
diff --git a/lib/api_tvmaze/tvmaze_api.py b/lib/api_tvmaze/tvmaze_api.py
index 56609e0c..14ffbbf9 100644
--- a/lib/api_tvmaze/tvmaze_api.py
+++ b/lib/api_tvmaze/tvmaze_api.py
@@ -57,6 +57,8 @@ empty_ep = TVInfoEpisode()
empty_se = TVInfoSeason()
tz_p = parser()
+character_clean_regex = re.compile(r'^tb(a|d)$', flags=re.I)
+
img_type_map = {
'poster': TVInfoImageType.poster,
'banner': TVInfoImageType.banner,
@@ -397,6 +399,14 @@ class TvMaze(TVInfoBase):
# type: (...) -> Dict[integer_types, integer_types]
return {sid: v.seconds_since_epoch for sid, v in iteritems(tvmaze.show_updates().updates)}
+ @staticmethod
+ def _clean_character_name(name):
+ # type: (Optional[str]) -> str
+ name = clean_data(name)
+ if isinstance(name, str):
+ return enforce_type(character_clean_regex.sub('', name), str, '')
+ return enforce_type(name, str, '')
+
def _convert_person(self, tvmaze_person_obj, load_credits=True):
# type: (tvmaze.Person, bool) -> TVInfoPerson
ch = []
@@ -410,7 +420,11 @@ class TvMaze(TVInfoBase):
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)
+ ti_show.vote_average = clean_data((c.show.rating and c.show.rating.get('average'))) or None
+ ti_show.rating = ti_show.vote_average
net = c.show.network or c.show.web_channel
+ ti_show.genre_list = clean_data(c.show.genres or [])
+ ti_show.genre = '|'.join(ti_show.genre_list or [])
if net:
ti_show.network = clean_data(net.name)
ti_show.network_id = net.maze_id
@@ -418,7 +432,18 @@ class TvMaze(TVInfoBase):
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))
+ _images = None
+ if c.character.image and all(i_s in c.character.image and c.character.image[i_s]
+ for i_s in ('original', 'medium')):
+ _images = [TVInfoImage(TVInfoImageType.poster,
+ sizes={TVInfoImageSize.original: c.character.image['original'],
+ TVInfoImageSize.medium: c.character.image['medium']})]
+ ch.append(TVInfoCharacter(name=self._clean_character_name(c.character.name),
+ ti_show=ti_show, episode_count=1, plays_self=c.character.plays_self,
+ voice=c.character.voice,
+ image= c.character.image and c.character.image.get('original'),
+ thumb_url= c.character.image and c.character.image.get('medium'),
+ p_id=c.character.id, images=_images))
try:
birthdate = tvmaze_person_obj.birthday and tz_p.parse(tvmaze_person_obj.birthday).date()
except (BaseException, Exception):
@@ -446,7 +471,7 @@ class TvMaze(TVInfoBase):
(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)
+ _clean_char_name = self._clean_character_name(c.character.name)
ti_show = TVInfoShow()
if None is not _show:
_clean_show_name = clean_data(_show.name)
@@ -478,6 +503,8 @@ class TvMaze(TVInfoBase):
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)
+ ti_show.vote_average = clean_data(_show.rating and _show.rating.get('average')) or None
+ ti_show.rating = ti_show.vote_average
net = _show.network or _show.web_channel
if net:
ti_show.network = clean_data(net.name)
@@ -499,8 +526,18 @@ class TvMaze(TVInfoBase):
_g_kw = {'guest_episodes_numbers': {c.episode.season_number: [c.episode.episode_number or 0]}}
else:
_g_kw = {}
+ _images = None
+ if c.character.image and all(i_s in c.character.image and c.character.image[i_s]
+ for i_s in ('original', 'medium')):
+ _images = [TVInfoImage(TVInfoImageType.poster,
+ sizes={TVInfoImageSize.original: c.character.image['original'],
+ TVInfoImageSize.medium: c.character.image['medium']})]
ch.append(TVInfoCharacter(name=_clean_char_name, ti_show=ti_show, regular=regular, episode_count=1,
- person=[_ti_person_obj], **_g_kw))
+ person=[_ti_person_obj], plays_self=c.character.plays_self,
+ voice=c.character.voice,
+ image=c.character.image and c.character.image.get('original'),
+ thumb_url=c.character.image and c.character.image.get('medium'),
+ p_id=c.character.id, images=_images, **_g_kw))
_ti_person_obj.characters = ch
return _ti_person_obj
@@ -588,7 +625,7 @@ class TvMaze(TVInfoBase):
else:
_s_o.cast[RoleTypes.ActorMain].append(
TVInfoCharacter(image=cur_ch.image and cur_ch.image.get('original'),
- name=clean_data(cur_ch.name),
+ name=self._clean_character_name(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'),
diff --git a/lib/pytvmaze/tvmaze.py b/lib/pytvmaze/tvmaze.py
index 3e4d46dc..150f225e 100644
--- a/lib/pytvmaze/tvmaze.py
+++ b/lib/pytvmaze/tvmaze.py
@@ -12,6 +12,18 @@ if False:
from typing import Any, AnyStr, Dict, List, Optional, Union
+url_maze_id_regex = re.compile(r'^https?://(?:(?:api|wwww)\.)?tvmaze\.com/shows/(\d+)', flags=re.I)
+
+
+def _parse_show_id_from_url(url):
+ # type: (str) -> Optional[int]
+ if isinstance(url, str):
+ try:
+ return int(url_maze_id_regex.search(url).group(1))
+ except (BaseException, Exception):
+ pass
+
+
class Show(object):
def __init__(self, data):
self.status = data.get('status') # type: Optional[AnyStr]
@@ -36,7 +48,7 @@ class Show(object):
self.runtime = data.get('runtime') # type: Optional[int]
self.average_runtime = data.get('averageRuntime')
self.type = data.get('type') # type: Optional[AnyStr]
- self.id = data.get('id') # type: int
+ self.id = data.get('id') or ('href' in data and _parse_show_id_from_url(data['href'])) or None # type: int
self.maze_id = self.id # type: int
if data.get('network'):
self.network = Network(data.get('network')) # type: Optional[Network]
@@ -428,9 +440,12 @@ class CastCredit(object):
def populate(self, data):
if data.get('_embedded'):
if data['_embedded'].get('character'):
- self.character = Character(data['_embedded']['character'])
+ self.character = Character(data['_embedded']['character'], base_data=data)
if data['_embedded'].get('show'):
self.show = Show(data['_embedded']['show'])
+ elif ('episode' in data['_embedded'] and '_links' in data['_embedded']['episode'] and
+ 'show' in data['_embedded']['episode']['_links']):
+ self.show = Show(data['_embedded']['episode']['_links']['show'])
if data['_embedded'].get('episode'):
self.episode = Episode(data['_embedded']['episode'])
diff --git a/lib/tvinfo_base/base.py b/lib/tvinfo_base/base.py
index 9e210ab5..9b148c44 100644
--- a/lib/tvinfo_base/base.py
+++ b/lib/tvinfo_base/base.py
@@ -884,6 +884,7 @@ class PersonGenders(object):
tmdb_map = {0: unknown, 1: female, 2: male}
imdb_map = {'female': female, 'male': male}
tvdb_map = {0: unknown, 1: male, 2: female, 3: unknown} # 3 is technically: other
+ trakt_map = {'female': female, 'male': male}
class Crew(PersonBase):
diff --git a/sickgear/tv.py b/sickgear/tv.py
index c3df7072..aa723c32 100644
--- a/sickgear/tv.py
+++ b/sickgear/tv.py
@@ -724,7 +724,7 @@ class Person(Referential):
self._data_fetched = True
tvsrc_result, found_persons, found_on_src, search_sources, \
found_ids, ids_to_check, imdb_confirmed, source_confirmed = \
- None, {}, set(), [TVINFO_TRAKT, TVINFO_TMDB, TVINFO_IMDB, TVINFO_TVDB], \
+ None, {}, set(), [TVINFO_TRAKT, TVINFO_TMDB, TVINFO_IMDB, TVINFO_TVDB, TVINFO_TVMAZE], \
set([_k for _k, _v in iteritems(self.ids) if _v] + ['text']), {}, False, {}
# confirmed_character = False
max_search_src = len(search_sources)
diff --git a/sickgear/webserve.py b/sickgear/webserve.py
index 541fa2c4..e99efec7 100644
--- a/sickgear/webserve.py
+++ b/sickgear/webserve.py
@@ -5319,6 +5319,13 @@ class AddShows(Home):
return self.new_show('|'.join(['', '', '', show_name]), use_show_name=True)
+ @staticmethod
+ def _make_char_person_list(cur_show_info):
+ # type: (TVInfoShow) -> List[Tuple[str, int, str, int]]
+ return [(ch.name.replace('"', "'"), r_t, RoleTypes.reverse[r_t], ch.episode_count)
+ for r_t in cur_show_info.cast or [] for ch in cur_show_info.cast[r_t]]
+
+
def tmdb_default(self):
method = getattr(self, sickgear.TMDB_MRU, None)
if not callable(method):
@@ -5376,11 +5383,12 @@ class AddShows(Home):
p_ref = f'{TVINFO_TMDB}:{p_item.id}'
dup = {} # type: Dict[int, TVInfoShow]
for c in p_item.characters: # type: TVInfoCharacter
+ c.ti_show.cast[RoleTypes.ActorMain].append(c)
if c.ti_show.id not in dup:
dup[c.ti_show.id] = c.ti_show
items.append(c.ti_show)
else:
- dup[c.ti_show.id].cast.update(c.ti_show.cast)
+ dup[c.ti_show.id].cast[RoleTypes.ActorMain].extend(c.ti_show.cast[RoleTypes.ActorMain])
del dup
else:
p_item = None
@@ -5400,7 +5408,10 @@ class AddShows(Home):
airtime = cur_show_info.airs_time
if not airtime or (0, 0) == (airtime.hour, airtime.minute):
airtime = dateutil.parser.parse('23:59').time()
- dt = datetime.combine(dateutil.parser.parse(cur_show_info.firstaired, parseinfo).date(), airtime)
+ try:
+ dt = datetime.combine(dateutil.parser.parse(cur_show_info.firstaired, parseinfo).date(), airtime)
+ except (BaseException, Exception):
+ dt = None
ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, _, _, _, _ \
= self.sanitise_dates(dt, oldest_dt, newest_dt, oldest, newest)
@@ -5417,8 +5428,7 @@ class AddShows(Home):
and 'jp' or 'en')
filtered.append(dict(
p_ref=p_ref,
- p_chars=[(ch.name, r_t, RoleTypes.reverse[r_t], ch.episode_count)
- for r_t in cur_show_info.cast or [] for ch in cur_show_info.cast[r_t]],
+ p_chars=self._make_char_person_list(cur_show_info),
ord_premiered=ord_premiered,
str_premiered=str_premiered,
started_past=started_past,
@@ -5602,8 +5612,6 @@ class AddShows(Home):
if c.ti_show.id not in dup:
dup[c.ti_show.id] = c.ti_show
items.append(c.ti_show)
- else:
- dup[c.ti_show.id].cast.update(c.ti_show.cast)
del dup
else:
p_item = None
@@ -5664,8 +5672,7 @@ class AddShows(Home):
filtered.append(dict(
p_ref=p_ref,
p_item=p_item,
- p_chars=[(ch.name, r_t, RoleTypes.reverse[r_t], ch.episode_count)
- for r_t in cur_show_info.cast or [] for ch in cur_show_info.cast[r_t]],
+ p_chars=self._make_char_person_list(cur_show_info),
ord_premiered=ord_premiered,
str_premiered=str_premiered,
ord_returning=ord_returning,
@@ -6009,11 +6016,13 @@ class AddShows(Home):
p_ref = f'{TVINFO_TVMAZE}:{p_item.id}'
dup = {} # type: Dict[int, TVInfoShow]
for c in p_item.characters: # type: TVInfoCharacter
+ c.ti_show.cast[(RoleTypes.ActorGuest, RoleTypes.ActorMain)[True is c.regular]].append(c)
if c.ti_show.id not in dup:
dup[c.ti_show.id] = c.ti_show
items.append(c.ti_show)
else:
- dup[c.ti_show.id].cast.update(c.ti_show.cast)
+ dup[c.ti_show.id].cast[RoleTypes.ActorMain].extend(c.ti_show.cast[RoleTypes.ActorMain])
+ dup[c.ti_show.id].cast[RoleTypes.ActorGuest].extend(c.ti_show.cast[RoleTypes.ActorGuest])
del dup
else:
p_item = None
@@ -6063,8 +6072,7 @@ class AddShows(Home):
filtered.append(dict(
p_ref=p_ref,
- p_chars=[(ch.name, r_t, RoleTypes.reverse[r_t], ch.episode_count)
- for r_t in cur_show_info.cast or [] for ch in cur_show_info.cast[r_t]],
+ p_chars=self._make_char_person_list(cur_show_info),
ord_premiered=ord_premiered,
str_premiered=str_premiered,
ord_returning=ord_returning,
@@ -6110,6 +6118,9 @@ class AddShows(Home):
@staticmethod
def sanitise_dates(date, oldest_dt, newest_dt, oldest, newest, episode_info=None, combine_ep_airtime=False):
+ # in case of person search (tvmaze) guest starring entires have only show name/id, no dates
+ if None is date:
+ return 9, '', True, oldest_dt, newest_dt, oldest, newest, True, 9, 'TBC', False
parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True)
dt = date if isinstance(date, datetime) else dateutil.parser.parse(date)
if episode_info: