Merge branch 'feature/AddBrowsePersonShows' into dev

This commit is contained in:
JackDandy 2024-06-16 17:12:56 +01:00
commit 8533297323
5 changed files with 157 additions and 15 deletions

View file

@ -23,6 +23,7 @@
* Change growl notifier location for Apprise update refactor
* Change systemd remove py2 and add basic hardening options
* Change alphabetically sort list at edit show/Exclude global ignore/require words
* Add search on TVmaze, TMDb, or Trakt for other shows with the actor that is viewed on Person page
### 3.31.1 (2024-06-14 01:00:00 UTC)

View file

@ -2,6 +2,7 @@
#import re
#import sickgear
#from sickgear import TVInfoAPI
#from sickgear.indexers.indexer_config import TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TVMAZE
#from sickgear.helpers import anon_url
#from sickgear.tv import PersonGenders
#from sg_helpers import spoken_height
@ -145,15 +146,17 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
#end if
<style>
#vitals{clear:both}
.vitals{clear:both}
.details-title{width:90px !important}
.details-info{margin-left:95px !important}
.details-info.akas{max-height:100px; overflow:auto; min-width:300px; word-break:normal}
.details-info i{font-style:normal; font-size:smaller}
.links{display:block; padding:0}
.links{display:block; padding:0; margin:3px 0 0}
.links li{display: inline-block; padding:0 10px 0 0}
.links img{margin-bottom: -1px; vertical-align:initial}
</style>
<div class="#vitals" data-birthdate="$person.birthday" data-deathdate="$person.deathday">
<div class="vitals" data-birthdate="$person.birthday" data-deathdate="$person.deathday">
#if $person.real_name
<div><span class="details-title">Real name</span><span class="details-info">$person.real_name</span></div>
#end if
@ -207,6 +210,22 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
</ul>
</span>
</div>
#end if
#set $src = (($TVINFO_TVMAZE, 'tvm'), ($TVINFO_TMDB, 'tmdb'), ($TVINFO_TRAKT, 'trakt'))
#if any([$person.ids.get($cur_src) for ($cur_src, _) in $src])
<div>
<span class="details-title">Other shows</span>
<span class="details-info">
<ul class="links">
#for ($cur_src, $cur_api) in $src
#if $person.ids.get($cur_src)
<img alt="$TVInfoAPI($cur_src).name" height="16" width="16" src="$sbRoot/images/$TVInfoAPI($cur_src).config['icon']">#slurp
<li><a href="$sbRoot/add-shows/${cur_api}-person?person_${cur_api}_id=$person.ids.get($cur_src)">$TVInfoAPI($cur_src).name</a></li>
#end if
#end for
</ul>
</span>
</div>
#end if
</div>

View file

@ -3,6 +3,7 @@
#from sickgear import WEB_ROOT, THEME_NAME
#from sickgear.common import *
#from sickgear.helpers import anon_url, try_float
#from lib.tvinfo_base import RoleTypes
#from _23 import quote
<% def sg_var(varname, default=False): return getattr(sickgear, varname, default) %>#slurp#
<% def sg_str(varname, default=''): return getattr(sickgear, varname, default) %>#slurp#
@ -10,7 +11,7 @@
#set $mode = $kwargs and $kwargs.get('mode', '')
#set $use_network = $kwargs.get('use_networks', False)
#set $use_returning = 'returning' == mode
#set $use_filter = $kwargs and $kwargs.get('use_filter', True)
#set $use_filter = $kwargs and $kwargs.get('use_filter', True) and not $p_ref
#set $use_ratings = $kwargs and $kwargs.get('use_ratings', True)
#set $use_votes = $kwargs and $kwargs.get('use_votes', True)
#set $term_vote = $kwargs and $kwargs.get('term_vote', 'Votes')
@ -463,12 +464,19 @@ $(document).ready(function(){
<input id="search_show_name" class="search form-control form-control-inline input-sm input200" type="search" placeholder="Filter Show Name#if $use_network#/Network#end if#">
&nbsp;<button type="button" class="resetshows btn btn-inline">Reset Filter</button>
</div>
<h4 style="float:left;margin:0 0 0 2px">$browse_title</h4>
#if $kwargs and $kwargs.get('oldest')
<div class="grey-text" style="clear:left;margin-left:2px;font-size:0.85em">
First aired from $kwargs['oldest'] until $kwargs['newest']
</div>
#end if
#if $p_ref
<div class="browse-image" style="margin: 10px 2px 30px; border-radius: 5px">
<a class="browse-image" href="$sbRoot/imagecache/person?pid=$p_ref&thumb=1" rel="dialog"><img class="browse-image" src="$sbRoot/imagecache/person?pid=$p_ref&thumb=0"></a>
</div>
#end if
#end if
<div id="container">
@ -530,6 +538,13 @@ $(document).ready(function(){
<div class="show-title">
#echo ((re.sub(r'^((?:A(?!\s+to)n?)|The)\s(\w)', r'<span class="article">\1</span> \2', $this_show['title']), $this_show['title'])[$sg_var('SORT_ARTICLE')], '<span>&nbsp;</span>')['' == $this_show['title']]#
</div>
#if $this_show.get('p_chars')
<div class="show-title">
#for $char in $this_show['p_chars']
<div>as $char[0]#if $RoleTypes.ActorMain != $char[1]# ($char[2]/$char[3] eps)#end if#</div>
#end for
</div>
#end if
#if 'Ani' not in $browse_type
<a class="show-toggle-hide" href="$sg_root/add-shows/show-toggle-hide?ids=$show_id" title="#echo ('H', 'Unh')[any($hide)]#ide"><i class="sgicon-delete"></i></a>
#end if

View file

@ -321,6 +321,11 @@ class TraktIndexer(TVInfoBase):
ti_show.imdb_id = c['show']['ids'].get('imdb')
ti_show.runtime = c['show']['runtime']
ti_show.genre_list = c['show']['genres']
ti_show.slug = c['show'].get('ids', {}).get('slug')
ti_show.language = c['show'].get('language')
ti_show.network_country = c['show'].get('country')
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],

View file

@ -94,7 +94,7 @@ except ImportError as e:
from lib.fuzzywuzzy import fuzz
from lib.api_trakt import TraktAPI
from lib.api_trakt.exceptions import TraktException, TraktAuthException
from lib.tvinfo_base import TVInfoEpisode
from lib.tvinfo_base import TVInfoEpisode, RoleTypes
from lib.tvinfo_base.base import tv_src_names
import lib.rarfile.rarfile as rarfile
@ -108,7 +108,7 @@ if False:
from typing import Any, AnyStr, Dict, List, Optional, Set, Tuple, Union
from sickgear.providers.generic import TorrentProvider
# prevent pyc TVInfoBase resolution by typing the derived used class to TVInfoAPI instantiation
from lib.tvinfo_base import TVInfoBase, TVInfoShow
from lib.tvinfo_base import TVInfoBase, TVInfoCharacter, TVInfoPerson, TVInfoShow
# from api_imdb.imdb_api import IMDbIndexer
from api_tmdb.tmdb_api import TmdbIndexer
from api_trakt.indexerapiinterface import TraktIndexer
@ -4324,12 +4324,15 @@ class AddShows(Home):
return json_dumps({'results': final_results})
@staticmethod
def _make_cache_image_url(iid, show_info, default_transparent_img=True):
def _make_cache_image_url(iid, show_info, default_transparent_img=True, use_source_id=False):
img_url = ''
trans_param = ('1', '0')[not default_transparent_img]
if TVINFO_TRAKT == iid:
img_url = 'imagecache?path=browse/thumb/trakt&filename=%s&trans=%s&tmdbid=%s&tvdbid=%s' % \
('%s.jpg' % show_info['ids'].trakt, trans_param, show_info['ids'].tmdb, show_info['ids'].tvdb)
elif use_source_id and TVINFO_TVMAZE == iid:
img_url = 'imagecache?path=browse/thumb/tvmaze&filename=%s&trans=%s&tvmazeid=%s' % \
('%s.jpg' % show_info['ids'].tvmaze, trans_param, show_info['ids'].tvmaze)
elif iid in (TVINFO_TVDB, TVINFO_TVMAZE, TVINFO_TMDB) and show_info.get('poster'):
img_url = 'imagecache?path=browse/thumb/%s&filename=%s&trans=%s&source=%s' % \
(tv_src_names[iid], '%s.jpg' % show_info['id'], trans_param, show_info['poster'])
@ -5342,6 +5345,10 @@ class AddShows(Home):
return self.browse_tmdb(
'Trending this week at TMDB', mode='trending_week', **kwargs)
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)
def browse_tmdb(self, browse_title, **kwargs):
browse_type = 'TMDB'
@ -5349,6 +5356,7 @@ class AddShows(Home):
footnote = None
filtered = []
p_ref = None
tvid = TVINFO_TMDB
tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy()
@ -5361,6 +5369,21 @@ class AddShows(Home):
items = t.get_trending()
elif 'trending_week' == mode:
items = t.get_trending(time_window='week')
elif 'get_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
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.discover()
@ -5393,6 +5416,9 @@ 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,
@ -5533,6 +5559,8 @@ class AddShows(Home):
mode = kwargs.get('mode', '')
items, filtered = ([], [])
error_msg = None
p_item = None
p_ref = None
tvid = TVINFO_TRAKT
tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy()
t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TraktIndexer, TVInfoBase]
@ -5564,6 +5592,21 @@ 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:
items = []
p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson
if p_item:
p_ref = f'{TVINFO_TRAKT}:{p_item.id}'
dup = {} # type: Dict[int, TVInfoShow]
for c in p_item.characters: # type: TVInfoCharacter
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:
@ -5603,9 +5646,10 @@ class AddShows(Home):
except(BaseException, Exception):
episode_info = TVInfoEpisode()
if rx_ignore.search(cur_show_info.seriesname.strip()) or \
not (language_en or country_ok) or \
not (cur_show_info.overview or episode_info.overview):
if 'get_person' != api_method and \
(rx_ignore.search(cur_show_info.seriesname.strip()) or
not (language_en or country_ok) or
not (cur_show_info.overview or episode_info.overview)):
continue
try:
ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, \
@ -5618,6 +5662,10 @@ 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,
@ -5653,6 +5701,16 @@ class AddShows(Home):
return filtered, oldest, newest
def trakt_person(self, person_trakt_id=None):
return self.browse_trakt(
'get_person',
'Person on Trakt',
mode='person',
footnote='Note; Expect default placeholder images in this list',
p_id=person_trakt_id
)
def browse_trakt(self, api_method, browse_title, **kwargs):
browse_type = 'Trakt'
@ -5670,9 +5728,12 @@ 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 'recommended' not in mode and 'watchlist' not in mode:
if not any(m in mode for m in ('recommended', 'watchlist', 'person')):
mode = mode.split('-')
if mode:
func = 'trakt_%s' % mode[0]
@ -5923,6 +5984,10 @@ class AddShows(Home):
return self.browse_tvm(
'Returning at TVmaze', mode='returning', **kwargs)
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)
def browse_tvm(self, browse_title, **kwargs):
browse_type = 'TVmaze'
@ -5930,12 +5995,28 @@ class AddShows(Home):
footnote = None
filtered = []
p_ref = None
tvid = TVINFO_TVMAZE
tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy()
t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase]
if 'premieres' == mode:
items = t.get_premieres()
elif 'get_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
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_returning()
@ -5967,7 +6048,7 @@ class AddShows(Home):
if 'returning' == mode and not ok_returning:
continue
image = self._make_cache_image_url(tvid, cur_show_info)
image = self._make_cache_image_url(tvid, cur_show_info, use_source_id='get_person' == mode)
images = {} if not image else dict(poster=dict(thumb=image))
network_name = cur_show_info.network
@ -5981,6 +6062,9 @@ class AddShows(Home):
language = (('jap' in (cur_show_info.language or '').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,
ord_returning=ord_returning,
@ -5990,7 +6074,7 @@ class AddShows(Home):
episode_number=episode_info.episodenumber or '',
episode_overview=helpers.xhtml_escape(episode_info.overview[:250:]).strip(),
episode_season=getattr(episode_info.season, 'number', episode_info.seasonnumber),
genres=(cur_show_info.genre.strip('|').replace('|', ', ')
genres=((cur_show_info.genre or '').strip('|').replace('|', ', ')
or ', '.join(cur_show_info.show_type) or ''),
ids=cur_show_info.ids.__dict__,
images=images,
@ -6106,6 +6190,7 @@ class AddShows(Home):
t.submenu = self.home_menu()
t.browse_type = browse_type
t.browse_title = browse_title
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')
showsort = t.saved_showsort.split(',')
@ -9863,7 +9948,7 @@ class CachedImages(MainHandler):
for f in ['tmdb', 'tvdb', 'tvmaze']:
CachedImages.delete_dummy_image('%s.%s.dummy' % (os.path.splitext(filename)[0], f))
def index(self, path='', source=None, filename=None, tmdbid=None, tvdbid=None, trans=True):
def index(self, path='', source=None, filename=None, tmdbid=None, tvdbid=None, trans=True, tvmazeid=None):
path = path.strip('/')
file_name = ''
@ -9878,8 +9963,25 @@ class CachedImages(MainHandler):
helpers.make_path(basepath)
poster_url = ''
tmdb_image = False
tvmaze_image = False
if None is not source and source in sickgear.CACHE_IMAGE_URL_LIST:
poster_url = source
if None is source and tvmazeid not in [None, 'None', 0, '0'] \
and self.should_try_image(image_file, 'tvmaze'):
tvmaze_image = True
try:
tvinfo_config = sickgear.TVInfoAPI(TVINFO_TVMAZE).api_params.copy()
t = sickgear.TVInfoAPI(TVINFO_TVMAZE).setup(**tvinfo_config)
show_obj = t.get_show(tvmazeid, load_episodes=False, posters=True)
if show_obj and show_obj.poster:
poster_url = show_obj.poster
except (BaseException, Exception):
poster_url = ''
if poster_url:
sg_helpers.download_file(poster_url, image_file, nocache=True)
if tvmaze_image and not os.path.isfile(image_file):
self.create_dummy_image(image_file, 'tvmaze')
if None is source and tmdbid not in [None, 'None', 0, '0'] \
and self.should_try_image(image_file, 'tmdb'):
tmdb_image = True