Add try to retrieve TVmaze id for persons

Fix person image scaler
Change make a unified generator for the p_chars var in persons search
Change add plays_self to TMDB, TVmaze, Trakt
Fix TMDb person search page for shows without first aired date (future shows for example)
Add also display guest appearances in TVmaze person search
Add parsing of vote_average / rating for main cast
Add genres to person show data
Fix, auto close qTip if mouse is over nav menu
Change add rating, vote_average, vote_count, popularity to TMDB person characters shows
Change TVmaze character name TBA|D to ''
Change add person gender to Trakt
This commit is contained in:
Prinz23 2024-06-17 03:32:37 +01:00 committed by JackDandy
parent 31f5b3ea23
commit 4e52d2da08
8 changed files with 119 additions and 33 deletions

View file

@ -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() {
<style>
#set theme_suffix = ('', '-dark')['dark' == $getVar('sbThemeName', THEME_NAME)]
.bfr{position:absolute;left:-999px;top:-999px}.bfr img{width:16px;height:16px}.spinner{display:inline-block;width:16px;height:16px;background:url(${sg_root}/images/loading16${theme_suffix}.gif) no-repeat 0 0}
#person{height:300px;width:215px;display:block;}
.main-image{float:left;margin:0 20px 20px 0; }
.person-bg{height:300px;width:215px;display:block; background-color:#181818 !important; border:1px solid #181818; object-fit: contain; font-family: 'object-fit: contain;'; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px; margin:0 auto; background:url(/images/poster-person.jpg) center center no-repeat}
</style>
<div class="bfr"><img src="$sg_root/images/loading16${theme_suffix}.gif" /></div>
@ -486,8 +488,10 @@ $(function() {
#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 id="person">
<div id="person-content" class="main-image">
<a class="thumb" href="$sbRoot/imagecache/person?pid=$p_ref&thumb=1" rel="dialog"><img class="person-bg" src="$sbRoot/imagecache/person?pid=$p_ref&thumb=0"></a>
</div>
</div>
#end if
#end if

View file

@ -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,

View file

@ -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)

View file

@ -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'),

View file

@ -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'])

View file

@ -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):

View file

@ -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)

View file

@ -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: