diff --git a/gui/slick/interfaces/default/cast_person.tmpl b/gui/slick/interfaces/default/cast_person.tmpl index 1c5eddae..30bf5b0c 100644 --- a/gui/slick/interfaces/default/cast_person.tmpl +++ b/gui/slick/interfaces/default/cast_person.tmpl @@ -45,6 +45,7 @@ #person-content .thumb{display:block} #person-content > .main-image{margin-bottom:19px} #person-content > .cast .cast-bg{height:300px; margin:0 auto; background:url(/images/poster-person.jpg) center center no-repeat} +#character-content{margin-left:235px} <% def param(visible=True, rid=None, cache_person=None, cache_char=None, person=None, role=None, tvid_prodid=None, thumb=None, oid=None, pid=None): @@ -64,10 +65,6 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non %>
-
- -
-
#slurp #set $gender = '' #if $PersonGenders.female == $person.gender# @@ -78,6 +75,10 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non

$person.name#if $age #($age)#end if##if $gender #$gender#end if##if $person.deathday # †#end if#

+
+ +
+
@@ -341,7 +413,7 @@ $(document).ready(function(){ #end if #if $use_ratings and $use_votes - + #end if @@ -468,13 +540,15 @@ $(document).ready(function(){

$browse_title

#if $kwargs and $kwargs.get('oldest')
- First aired from $kwargs['oldest'] until $kwargs['newest'] + First aired from $kwargs['oldest'] until $kwargs['newest']
#end if #if $p_ref -
- +
+
+ +
#end if #end if @@ -489,7 +563,7 @@ $(document).ready(function(){ #if 'returning' == $mode #set $overview = '%s: %s' % ( 'Season %s' % $this_show['episode_season'], - $this_show['episode_overview'] or $this_show['overview']) + $this_show[('episode_overview', 'overview')['No overview yet' == $this_show['episode_overview']]]) #else #set $overview = $this_show['overview'] #end if @@ -500,14 +574,13 @@ $(document).ready(function(){ #if $use_ratings: #set $data_rating = $try_float($this_show['rating']) #end if - -
+
$re.sub(r'(?m)\s+\((?:19|20)\d\d\)\s*$', '', $title_html) - #if $this_show['genres']#
($this_show['genres'])
#end if# +
($this_show['genres'])
#if $kwargs and $use_returning#Season $this_show['episode_season'] return#echo ('s', 'ed')[$this_show['return_past']]# $this_show['str_returning']#end if# #if $this_show.get('country') or $this_show.get('language')

@@ -519,8 +592,15 @@ $(document).ready(function(){ #end if

#end if -

#echo re.sub(r'([,\.!][^,\.!]*?)$', '...', re.sub(r'([!\?\.])(?=\w)', r'\1 ', $overview)).replace('.....', '...')#

-

#if $this_show['str_premiered']##if 'Trakt' == $browse_type and $kwargs and 'returning' == $mode#Air#else#First air#end if##echo ('s', 'ed')[$this_show['started_past']]#: $this_show['str_premiered']#end if# + #if $this_show.get('p_chars') +

+ #for $char in $this_show['p_chars'] + as $char[0]#if $RoleTypes.ActorMain != $char[1]# ($char[2]/$char[3] eps)#end if# + #end for +

+ #end if +

$overview

+

#if $this_show['str_premiered']##if 'Trakt' == $browse_type and $kwargs and 'returning' == $mode#Air#else#First air#end if##echo ('s', 'ed')[$this_show['started_past']]#: $this_show['str_premiered']#end if# #if $this_show.get('ended_str')# - Ended: $this_show['ended_str']#end if# #if $this_show.get('network')#On: $this_show['network']#end if#

@@ -538,19 +618,14 @@ $(document).ready(function(){
#echo ((re.sub(r'^((?:A(?!\s+to)n?)|The)\s(\w)', r'\1 \2', $this_show['title']), $this_show['title'])[$sg_var('SORT_ARTICLE')], ' ')['' == $this_show['title']]#
- #if $this_show.get('p_chars') -
- #for $char in $this_show['p_chars'] -
as $char[0]#if $RoleTypes.ActorMain != $char[1]# ($char[2]/$char[3] eps)#end if#
- #end for -
- #end if #if 'Ani' not in $browse_type
#end if
#if $use_ratings or $use_votes

#if $use_ratings#$this_show['rating']#if $re.search(r'^\d+(\.\d+)?$', (str($this_show['rating'])))#%#end if##end if##if $use_votes#$this_show['votes'] $term_vote.lower()#end if#

#slurp# + #else +

 

#end if #if 'url_tvdb' in $this_show and $this_show['url_tvdb'] 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,15 @@ 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 []) + ti_show.show_type = clean_data(( + isinstance(c.show.type, string_types) and [c.show.type.lower()] or + isinstance(c.show.type, list) and [x.lower() for x in c.show.type] or [] + )) if net: ti_show.network = clean_data(net.name) ti_show.network_id = net.maze_id @@ -418,7 +436,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 +475,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 +507,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 +530,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 +629,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..f5e54a1b 100644 --- a/sickgear/webserve.py +++ b/sickgear/webserve.py @@ -4779,7 +4779,7 @@ class AddShows(Home): genres=', '.join(row.get('metadata', {}).get('genres', {})) or 'No genre yet', ids=ids, images='' if not img_uri else images, - overview='No overview yet' if not overview else helpers.xhtml_escape(overview[:250:]), + overview=self.clean_overview(overview), rating=int(helpers.try_float(rating) * 10), title=row.get('primary').get('title'), url_src_db='https://www.imdb.com/%s/' % row.get('primary').get('href').strip('/'), @@ -4855,7 +4855,7 @@ class AddShows(Home): genres='', ids=ids, images='' if not img_uri else images, - overview='No overview yet' if not overview else helpers.xhtml_escape(overview[:250:]), + overview=self.clean_overview(overview), rating=0 if not len(rating) else int(helpers.try_float(rating) * 10), title=title, url_src_db='https://www.imdb.com/%s/' % url_path.strip('/'), @@ -5040,6 +5040,19 @@ class AddShows(Home): f'?releaseYearMin={this_year}&releaseYearMax={this_year}{page}' html = helpers.get_url(url, headers={'User-Agent': browser_ua.get_ua()}) if html: + items_data = [] + try: + items_html = html[6 + html.index('items:[{awards'):] + items_bufr = re.split(r'\btype:"', items_html) + for cur_item in items_bufr[1:]: # iterates from the first true type:"show" record + if not cur_item.startswith('show'): + break + items_data.append(f'type:"{cur_item}') + del items_html + del items_bufr + except (BaseException, Exception): + pass + try: if re.findall('(c-navigationPagination_item--next)', html)[0]: kwargs.update(dict(more=1)) @@ -5053,7 +5066,7 @@ class AddShows(Home): rc_id = re.compile(r'(?i)[^A-Z0-9]') rc_img = re.compile(r'(.*?)(/resize/[^?]+)?(/catalog/provider.*?\.(?:jpg|png)).*') rc_season = re.compile(r'(\d+)(?:[.]\d*?)?$') - for idx, cur_row in enumerate(items): + for cur_idx, cur_row in enumerate(items): try: title = rc_title.sub( '', cur_row.find('div', class_='c-finderProductCard_title').get('data-title').strip()) @@ -5068,6 +5081,24 @@ class AddShows(Home): images = None img_src = (cur_row.find('img') or {}).get('src', '').strip() + if not img_src and items_data: # items_data is the sites' image method from 2024 + buffer_idx = None + if title in items_data[cur_idx]: + buffer_idx = cur_idx + else: + for cur_data_idx, cur_item in enumerate(items_data): + if title in cur_item: + buffer_idx = cur_data_idx + break + if None is not buffer_idx: + try: + img_rel = re.findall( + r'bucketPath[^:]*?:[^"]*?"([^"]+?)"', + items_data[buffer_idx], re.I)[0].encode().decode('unicode-escape') + img_src = f'https://www.metacritic.com/a/img/catalog/{img_rel.strip("/")}' + except (BaseException, Exception): + pass + if img_src: img_uri = rc_img.sub(r'\1\3', img_src) images = dict(poster=dict(thumb=f'imagecache?path=browse/thumb/metac&source={img_uri}')) @@ -5102,7 +5133,7 @@ class AddShows(Home): overview = cur_row.find('div', class_='c-finderProductCard_description') if overview: - overview = helpers.xhtml_escape(overview.get_text().strip()[:250:]) + overview = overview.get_text() try: season = rc_season.findall(url_path)[0] @@ -5117,7 +5148,7 @@ class AddShows(Home): genres='', ids=ids, images=images or '', - overview=overview or 'No overview yet', + overview=self.clean_overview(overview), rating=0 if not rating else rating or 'TBD', rating_user='tbd' if not rating_user else int(helpers.try_float(rating_user) * 10) or 'tbd', title=title, @@ -5273,7 +5304,7 @@ class AddShows(Home): genres = row.find(class_='genre') if genres: - genres = re.sub(r',(\S)', r', \1', genres.get_text(strip=True)) + genres = re.sub(r',(\S)', r', \1', genres.get_text(strip=True)).lower() overview = row.find(class_='summary') if overview: overview = overview.get_text(strip=True) @@ -5293,7 +5324,7 @@ class AddShows(Home): ids=ids, images='' if not img_uri else images, network=network or None, - overview='No overview yet' if not overview else helpers.xhtml_escape(overview[:250:]), + overview=self.clean_overview(overview), rating=(rating, 'TBD')[None is rating], title=title, url_src_db='https://next-episode.net/%s/' % url_path.strip('/'), @@ -5319,9 +5350,21 @@ 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] if ch.name] + + @staticmethod + def allow_browse_mru(mode_or_mru): + # Fix an issue where a default view mixed with a deriviative view that requires a param will break the default + # Disallows default views from using derivative mru's + return 'person' not in mode_or_mru + def tmdb_default(self): method = getattr(self, sickgear.TMDB_MRU, None) - if not callable(method): + if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU): return self.tmdb_upcoming() return method() @@ -5347,7 +5390,7 @@ class AddShows(Home): def tmdb_person(self, person_tmdb_id=None, **kwargs): return self.browse_tmdb( - 'Person at TMDB', mode='get_person', p_id=person_tmdb_id, **kwargs) + 'Person at TMDB', mode='person', p_id=person_tmdb_id, **kwargs) def browse_tmdb(self, browse_title, **kwargs): @@ -5369,21 +5412,20 @@ class AddShows(Home): items = t.get_trending() elif 'trending_week' == mode: items = t.get_trending(time_window='week') - elif 'get_person' == mode: + elif 'person' == mode: items = [] p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson if p_item: 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 else: items = t.discover() @@ -5400,7 +5442,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) @@ -5416,20 +5461,17 @@ class AddShows(Home): language = ((cur_show_info.language and 'jap' in cur_show_info.language.lower()) 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]], ord_premiered=ord_premiered, str_premiered=str_premiered, started_past=started_past, - episode_overview=helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip(), + episode_overview=self.clean_overview(cur_show_info), episode_season=cur_show_info.season, - genres=', '.join(cur_show_info.genre_list) - or (cur_show_info.genre and (cur_show_info.genre.strip('|').replace('|', ', ')) or ''), + genres=(', '.join(cur_show_info.genre_list) + or (cur_show_info.genre and (cur_show_info.genre.strip('|').replace('|', ', ')) or '') + ).lower(), ids=cur_show_info.ids.__dict__, images=images, - overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() - or 'No overview yet'), + overview=self.clean_overview(cur_show_info), title=cur_show_info.seriesname, language=language, language_img=sickgear.MEMCACHE_FLAG_IMAGES.get(language, False), @@ -5437,15 +5479,23 @@ class AddShows(Home): country_img=sickgear.MEMCACHE_FLAG_IMAGES.get(cc.lower(), False), network=network_name, url_src_db=base_url % cur_show_info.id, - votes=cur_show_info.popularity or 0, + rating=0 < (cur_show_info.rating or 0) and + ('%.2f' % (cur_show_info.rating * 10)).replace('.00', '') or 0, + votes=('%.2f' % cur_show_info.popularity) or 0, )) + if p_ref: + filtered[-1].update(dict( + p_name=p_item.name, + p_ref=p_ref, + p_chars=self._make_char_person_list(cur_show_info) + )) except (BaseException, Exception): pass - kwargs.update(dict(oldest=oldest, newest=newest, use_ratings=False, use_filter=True, term_vote='Score')) + kwargs.update(dict(oldest=oldest, newest=newest, use_filter=True, term_vote='Score')) kwargs.update(dict(footnote=footnote, use_networks=use_networks)) - if mode: + if mode and self.allow_browse_mru(mode): func = 'tmdb_%s' % mode if callable(getattr(self, func, None)): sickgear.TMDB_MRU = func @@ -5460,7 +5510,7 @@ class AddShows(Home): def trakt_default(self): method = getattr(self, sickgear.TRAKT_MRU, None) - if not callable(method): + if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU): return self.trakt_trending() return method() @@ -5592,7 +5642,7 @@ class AddShows(Home): if not items: error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' raise ValueError(error_msg) - elif 'get_person' == api_method: + elif 'person' == mode: items = [] p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson if p_item: @@ -5602,11 +5652,7 @@ 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 else: items = t.get_trending() except TraktAuthException as e: @@ -5662,10 +5708,6 @@ class AddShows(Home): images = {} if not image else dict(poster=dict(thumb=image)) 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]], ord_premiered=ord_premiered, str_premiered=str_premiered, ord_returning=ord_returning, @@ -5673,14 +5715,13 @@ class AddShows(Home): started_past=started_past, # air time not yet available 16.11.2015 return_past=return_past, episode_number=episode_info.episodenumber, - episode_overview=helpers.xhtml_escape(episode_info.overview[:250:]).strip('*').strip(), + episode_overview=self.clean_overview(episode_info), episode_season=getattr(episode_info.season, 'number', 1), genres=(', '.join(['%s' % v for v in cur_show_info.genre_list])), ids=cur_show_info.ids.__dict__, images=images, network=network_name, - overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() - or 'No overview yet'), + overview=self.clean_overview(cur_show_info), rating=0 < (cur_show_info.rating or 0) and ('%.2f' % (cur_show_info.rating * 10)).replace('.00', '') or 0, title=(cur_show_info.seriesname or '').strip(), @@ -5692,7 +5733,14 @@ class AddShows(Home): url_tvdb=( '' if not (isinstance(cur_show_info.ids.tvdb, integer_types) and 0 < cur_show_info.ids.tvdb) else sickgear.TVInfoAPI(TVINFO_TVDB).config['show_url'] % cur_show_info.ids.tvdb), - votes=cur_show_info.vote_count or '0')) + votes=cur_show_info.vote_count or '0' + )) + if p_ref: + filtered[-1].update(dict( + p_name=p_item.name, + p_ref=p_ref, + p_chars=self._make_char_person_list(cur_show_info) + )) except (BaseException, Exception): pass @@ -5705,7 +5753,7 @@ class AddShows(Home): return self.browse_trakt( 'get_person', - 'Person on Trakt', + 'Person at Trakt', mode='person', footnote='Note; Expect default placeholder images in this list', p_id=person_trakt_id @@ -5728,14 +5776,11 @@ class AddShows(Home): error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) - if 'get_person' == api_method and filtered: - browse_title = f'{getattr(filtered[0]["p_item"], "name", "")} (Person) on Trakt' - kwargs.update(dict(oldest=oldest, newest=newest, error_msg=error_msg, use_networks=use_networks)) if not any(m in mode for m in ('recommended', 'watchlist', 'person')): mode = mode.split('-') - if mode: + if mode and self.allow_browse_mru(mode): func = 'trakt_%s' % mode[0] if callable(getattr(self, func, None)): param = '' if 1 == len(mode) or mode[1] not in ['year', 'month', 'week', 'all'] else \ @@ -5944,7 +5989,7 @@ class AddShows(Home): network=network or None, ids=ids, images='' if not img_uri else images, - overview='No overview yet' if not overview else helpers.xhtml_escape(overview[:250:]), + overview=self.clean_overview(overview), rating=None, title=title, url_src_db='https://www.pogdesign.co.uk/%s' % url_path.strip('/'), @@ -5972,7 +6017,7 @@ class AddShows(Home): def tvm_default(self): method = getattr(self, sickgear.TVM_MRU, None) - if not callable(method): + if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU): return self.tvm_premieres() return method() @@ -5986,7 +6031,52 @@ class AddShows(Home): def tvm_person(self, person_tvm_id=None, **kwargs): return self.browse_tvm( - 'Person at TVmaze', mode='get_person', p_id=person_tvm_id, **kwargs) + 'Person at TVmaze', mode='person', p_id=person_tvm_id, **kwargs) + + @staticmethod + def clean_overview(info=None): + # type (AnyStr, TVInfoShow) -> AnyStr + text = info if isinstance(info, str) else info.overview + if text: + result = helpers.xhtml_escape(re.sub(r'[\r\n]+', ' ', text[:250:])).strip('*').strip() + result = re.sub(r'([!?.])(?=\w)', r'\1 ', result) + result = re.sub(r'([,.!][^,.!]*?)$', '...', result) + return result.replace('.....', '...') + return 'No overview yet' + + def tvm_get_showinfo(self, tvid_prodid=None, oldest_dt=9999999, newest_dt=0): + result = {} + if 'tvmaze' in tvid_prodid: + tvid = TVINFO_TVMAZE + tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy() + t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] + show_info = t.get_show(int(tvid_prodid.replace('tvmaze:','')), load_episodes=False) + + oldest_dt, newest_dt = int(oldest_dt), int(newest_dt) + ord_premiered, str_premiered, started_past, old_dt, new_dt, oldest, newest, \ + ok_returning, ord_returning, str_returning, return_past \ + = self.sanitise_dates(show_info.firstaired, oldest_dt, newest_dt, None, None) + result = dict( + ord_premiered=ord_premiered, + str_premiered=str_premiered, + #ord_returning=ord_returning, + #str_returning=str_returning, + started_past=started_past, + #return_past=return_past, + genres=((show_info.genre or '') + or ', '.join(show_info.genre_list) + or ', '.join(show_info.show_type) or '').strip('|').replace('|', ', ').lower(), + overview=self.clean_overview(show_info), + network=show_info.network or ', '.join(show_info.networks) or '', + ) + if old_dt < oldest_dt: + result['oldest_dt'] = old_dt + result['oldest'] = oldest + elif new_dt > newest_dt: + result['newest_dt'] = old_dt + result['newest'] = newest, + + return json_dumps(result) def browse_tvm(self, browse_title, **kwargs): @@ -6002,30 +6092,31 @@ class AddShows(Home): t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] if 'premieres' == mode: items = t.get_premieres() - elif 'get_person' == mode: + elif 'person' == mode: items = [] p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson if p_item: 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 else: items = t.get_returning() # handle switching between returning and premieres sickgear.BROWSELIST_MRU.setdefault(browse_type, dict()) - showfilter = ('by_returning', 'by_premiered')['premieres' == mode] - saved_showsort = sickgear.BROWSELIST_MRU[browse_type].get('tvm_%s' % mode) or '*,asc' - showsort = saved_showsort + (',%s' % showfilter, '')[3 == len(saved_showsort.split(','))] - sickgear.BROWSELIST_MRU[browse_type].update(dict(showfilter=showfilter, showsort=showsort)) + if mode in ('premieres', 'returning'): + showfilter = ('by_returning', 'by_premiered')['premieres' == mode] + saved_showsort = sickgear.BROWSELIST_MRU[browse_type].get('tvm_%s' % mode) or '*,asc' + showsort = saved_showsort + (f',{showfilter}', '')[3 == len(saved_showsort.split(','))] + sickgear.BROWSELIST_MRU[browse_type].update(dict(showfilter=showfilter, showsort=showsort)) oldest, newest, oldest_dt, newest_dt, dedupe = None, None, 9999999, 0, [] use_networks = False @@ -6048,7 +6139,7 @@ class AddShows(Home): if 'returning' == mode and not ok_returning: continue - image = self._make_cache_image_url(tvid, cur_show_info, use_source_id='get_person' == mode) + image = self._make_cache_image_url(tvid, cur_show_info, use_source_id='person' == mode) images = {} if not image else dict(poster=dict(thumb=image)) network_name = cur_show_info.network @@ -6061,10 +6152,11 @@ class AddShows(Home): language = (('jap' in (cur_show_info.language or '').lower()) and 'jp' or 'en') + overview = self.clean_overview(cur_show_info) + overview_ajax = ("No overview yet" == overview + and p_ref and not bool(cur_show_info.cast[RoleTypes.ActorMain])) + 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]], ord_premiered=ord_premiered, str_premiered=str_premiered, ord_returning=ord_returning, @@ -6072,14 +6164,15 @@ class AddShows(Home): started_past=started_past, return_past=return_past, episode_number=episode_info.episodenumber or '', - episode_overview=helpers.xhtml_escape(episode_info.overview[:250:]).strip(), + episode_overview=self.clean_overview(episode_info), episode_season=getattr(episode_info.season, 'number', episode_info.seasonnumber), - genres=((cur_show_info.genre or '').strip('|').replace('|', ', ') - or ', '.join(cur_show_info.show_type) or ''), + genres=((cur_show_info.genre or '') + or ', '.join(cur_show_info.genre_list) + or ', '.join(cur_show_info.show_type) or '').strip('|').replace('|', ', ').lower(), ids=cur_show_info.ids.__dict__, images=images, - overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() - or 'No overview yet'), + overview_ajax=(0, 1)[overview_ajax], + overview=overview, rating=cur_show_info.rating or cur_show_info.popularity or 0, title=cur_show_info.seriesname, language=language, @@ -6089,13 +6182,23 @@ class AddShows(Home): network=network_name, url_src_db=base_url % cur_show_info.id, )) + if p_ref: + filtered[-1].update(dict( + p_name=p_item.name or None, + p_ref=p_ref, + p_chars=self._make_char_person_list(cur_show_info) + )) except (BaseException, Exception): pass - kwargs.update(dict(oldest=oldest, newest=newest)) - kwargs.update(dict(footnote=footnote, use_votes=False, use_networks=use_networks)) + kwargs.update(dict(oldest=oldest, newest=newest, oldest_dt=oldest_dt, newest_dt=newest_dt)) - if mode: + params = dict(footnote=footnote, use_votes=False, use_networks=use_networks) + if p_ref: + params.update(dict(use_ratings=False)) + kwargs.update(params) + + if mode and self.allow_browse_mru(mode): func = 'tvm_%s' % mode if callable(getattr(self, func, None)): sickgear.TVM_MRU = func @@ -6110,6 +6213,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: @@ -6189,7 +6295,8 @@ class AddShows(Home): t = PageTemplate(web_handler=self, file='home_browseShows.tmpl') t.submenu = self.home_menu() t.browse_type = browse_type - t.browse_title = browse_title + t.browse_title = browse_title if ('person' != kwargs.get('mode') or not shows) \ + else f'{shows[0].get("p_name", "")} (Person) on {browse_type}' t.p_ref = (0 < len(shows) and shows[0].get('p_ref')) or None t.saved_showfilter = sickgear.BROWSELIST_MRU.get(browse_type, {}).get('showfilter', '') t.saved_showsort = sickgear.BROWSELIST_MRU.get(browse_type, {}).get('showsort', '*,asc,by_order')