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() {
@@ -486,8 +488,10 @@ $(function() { #end if #if $p_ref -
- +
+
+ +
#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: