Merge branch 'feature/FixScaleImage' into dev

This commit is contained in:
JackDandy 2024-06-25 21:10:06 +01:00
commit 0384e218aa
9 changed files with 367 additions and 109 deletions

View file

@ -45,6 +45,7 @@
#person-content .thumb{display:block} #person-content .thumb{display:block}
#person-content > .main-image{margin-bottom:19px} #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} #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}
</style> </style>
<% <%
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): 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
%> %>
<div id="person"> <div id="person">
<div id="person-content"> <div id="person-content">
<div class="main-image cast">
<a class="thumb" href="$sbRoot/$param(rid=$person.ref_id(), cache_person=True, thumb=0, oid=$person.id)" rel="dialog"><img src="$sbRoot/$param(False, rid=$person.id, cache_person=True)" class="cast-bg"></a>
</div>
<div class="intro">#slurp <div class="intro">#slurp
#set $gender = '' #set $gender = ''
#if $PersonGenders.female == $person.gender# #if $PersonGenders.female == $person.gender#
@ -78,6 +75,10 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
<h2><span class="name">$person.name</span>#if $age #<span class="age">($age)</span>#end if##if $gender #<span class="gender" title="Biological gender">$gender</span>#end if##if $person.deathday # &dagger;#end if#</h2> <h2><span class="name">$person.name</span>#if $age #<span class="age">($age)</span>#end if##if $gender #<span class="gender" title="Biological gender">$gender</span>#end if##if $person.deathday # &dagger;#end if#</h2>
</div> </div>
<div class="main-image cast">
<a class="thumb" href="$sbRoot/$param(rid=$person.ref_id(), cache_person=True, thumb=0, oid=$person.id)" rel="dialog"><img src="$sbRoot/$param(False, rid=$person.id, cache_person=True)" class="cast-bg"></a>
</div>
<style> <style>
#character-content .cast-bg{display:block; background-color:#181818; border:1px solid #181818; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px} #character-content .cast-bg{display:block; background-color:#181818; border:1px solid #181818; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px}
#character-content .cast .cast-bg{height:200px; background:url(/images/poster-person.jpg) center center no-repeat} #character-content .cast .cast-bg{height:200px; background:url(/images/poster-person.jpg) center center no-repeat}
@ -98,7 +99,7 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
#if not $section_header #if not $section_header
#set $section_header = True #set $section_header = True
<div id="character-content"> <div id="character-content">
<div style="margin:40px 0 7px">is known in your show list as,</div> <div style="margin:0 0 7px">is known in your show list as,</div>
#end if #end if
<div class="role-panel"> <div class="role-panel">

View file

@ -24,7 +24,10 @@
#set sg_root = $getVar('sbRoot', WEB_ROOT) #set sg_root = $getVar('sbRoot', WEB_ROOT)
## ##
#import os.path #import os.path
#set global $inc_ofi = True
#include $os.path.join($sg_str('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sg_str('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/cast.js?v=$sbPID"></script>
<script> <script>
var config = { var config = {
homeSearchFocus: #echo ['!1','!0'][$sg_var('HOME_SEARCH_FOCUS', True)]#, homeSearchFocus: #echo ['!1','!0'][$sg_var('HOME_SEARCH_FOCUS', True)]#,
@ -39,6 +42,62 @@
$(this).css('cursor', 'help'); $(this).css('cursor', 'help');
$(this).qtip({ $(this).qtip({
show: {solo:true}, show: {solo:true},
// Change qTip to manual hide when it contains many roles to scroll
hide: {event:(5 < $(this).closest('div.show-card').attr('data-nroles')) ? 'unfocus' : 'mouseleave'},
events: { // Callback events
render: function(event, api) {
// Grab the tooltip element from the API
var tooltip = api.elements.tooltip
tooltip.bind('tooltipshow', function(event, api) {
var showcardEl = $(api.target).closest('div.show-card')
if ('1' === showcardEl.attr('data-ajax')) { // do a one time fetch
var qtipEl = $(this).find('.qtip-content'),
premiereEl = qtipEl.find('.premiere'),
genreEl = qtipEl.find('.genre'),
overviewEl = qtipEl.find('.overview'),
oldestEl = $('#oldest'),
newestEl = $('#newest');
// Set initial text
overviewEl.html('Fetching overview...');
$.getJSON($.SickGear.Root + '/add-shows/tvm-get-showinfo', {
tvid_prodid: showcardEl.attr('data-id'),
oldest_dt: $('#oldest').attr('data-oldest-dt'),
newest_dt: $('#newest').attr('data-newest-dt'),
},
function (data) {
if (undefined !== data.overview) {
showcardEl.attr('data-ajax', '0'); // mark one time fetch as completed
if (undefined !== data.oldest) {
oldestEl.attr('data-oldest-dt', data.oldest_dt)
oldestEl.html(data.oldest);
} else if (undefined !== data.newest) {
newestEl.attr('data-newest-dt', data.newest_dt)
newestEl.html(data.newest);
}
var premiere = '';
if (data.str_premiered.length) {
showcardEl.attr('data-premiered', data.ord_premiered);
premiere = "<span style='font-weight:bold;font-size:0.9em;color:#888'><em>First air" + (data.started_past ? 'ed' : 's') + ": " + data.str_premiered + "</em></span>";
}
if (data.genres) {
genreEl.css('display', 'block');
genreEl.find('em').html(data.genres);
}
overviewEl.html(data.overview);
if (data.network.length) {
premiere += "<span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#888'><em>On: " + data.network + "</em></span>";
}
premiereEl.html(premiere);
} else {
overviewEl.html('Failed to fetch TVmaze overview' );
}
}
)
}
})
}
},
position: {viewport:$(window), my:'left center', adjust:{y: -10,x: 2 }}, position: {viewport:$(window), my:'left center', adjust:{y: -10,x: 2 }},
style: {tip: {corner:true, method:'polygon'}, classes:'qtip-rounded qtip-bootstrap qtip-shadow ui-tooltip-sb'} style: {tip: {corner:true, method:'polygon'}, classes:'qtip-rounded qtip-bootstrap qtip-shadow ui-tooltip-sb'}
}); });
@ -47,6 +106,8 @@
$.ll.handleScroll(); $.ll.handleScroll();
}); });
$('.nav').on('mouseover', function() {$('.service, .browse-image').qtip('hide')})
savePrefs = (function(){ savePrefs = (function(){
var showsort = [], showfilter = []; var showsort = [], showfilter = [];
@ -63,7 +124,7 @@
}); });
}); });
$(document).ready(function(){ $(function() {
// initialise combos for dirty page refreshes // initialise combos for dirty page refreshes
$('#showsort').val('#end raw#$saved_showsort_view#raw#'); $('#showsort').val('#end raw#$saved_showsort_view#raw#');
@ -259,7 +320,7 @@ $(document).ready(function(){
} }
}); });
$('.service, .browse-image').each(addQTip); $('.service, a.browse-image').each(addQTip);
if (config.homeSearchFocus) { if (config.homeSearchFocus) {
$('#search_show_name').focus(); $('#search_show_name').focus();
@ -288,6 +349,13 @@ $(document).ready(function(){
input.focus(); input.focus();
} }
}); });
objectFitImages();
$('#person .person-bg').each(function(i, oImage){
removeImageBackground(oImage);
scaleImage(oImage);
});
}); });
#end raw #end raw
@ -297,6 +365,10 @@ $(document).ready(function(){
<style> <style>
#set theme_suffix = ('', '-dark')['dark' == $getVar('sbThemeName', THEME_NAME)] #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} .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{min-height:130px; height:auto; width:215px; margin:auto; display:block}
.main-image{margin:15px auto}
.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; background:url(/images/poster-person.jpg) center center no-repeat}
.person-bg{margin:0 auto !important}
</style> </style>
<div class="bfr"><img src="$sg_root/images/loading16${theme_suffix}.gif" /></div> <div class="bfr"><img src="$sg_root/images/loading16${theme_suffix}.gif" /></div>
@ -341,7 +413,7 @@ $(document).ready(function(){
<option value="by_rating"#if 'by_rating' in $saved_showsort_sortby#$selected>>&nbsp;#else#>#end if#% Rating</option> <option value="by_rating"#if 'by_rating' in $saved_showsort_sortby#$selected>>&nbsp;#else#>#end if#% Rating</option>
#end if #end if
#if $use_ratings and $use_votes #if $use_ratings and $use_votes
<option value="by_rating_votes"#if 'by_rating_votes' in $saved_showsort_sortby#$selected>>&nbsp;#else#>#end if#% Rating > Votes</option> <option value="by_rating_votes"#if 'by_rating_votes' in $saved_showsort_sortby#$selected>>&nbsp;#else#>#end if#% Rating > $term_vote</option>
#end if #end if
</optgroup> </optgroup>
</select> </select>
@ -468,13 +540,15 @@ $(document).ready(function(){
<h4 style="float:left;margin:0 0 0 2px">$browse_title</h4> <h4 style="float:left;margin:0 0 0 2px">$browse_title</h4>
#if $kwargs and $kwargs.get('oldest') #if $kwargs and $kwargs.get('oldest')
<div class="grey-text" style="clear:left;margin-left:2px;font-size:0.85em"> <div class="grey-text" style="clear:left;margin-left:2px;font-size:0.85em">
First aired from $kwargs['oldest'] until $kwargs['newest'] First aired from <span id="oldest" data-oldest-dt="$kwargs.get('oldest_dt', '')">$kwargs['oldest']</span> until <span id="newest" data-newest-dt="$kwargs.get('newest_dt', '')">$kwargs['newest']</span>
</div> </div>
#end if #end if
#if $p_ref #if $p_ref
<div class="browse-image" style="margin: 10px 2px 30px; border-radius: 5px"> <div id="person">
<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-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> </div>
#end if #end if
#end if #end if
@ -489,7 +563,7 @@ $(document).ready(function(){
#if 'returning' == $mode #if 'returning' == $mode
#set $overview = '%s: %s' % ( #set $overview = '%s: %s' % (
'Season %s' % $this_show['episode_season'], '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 #else
#set $overview = $this_show['overview'] #set $overview = $this_show['overview']
#end if #end if
@ -500,14 +574,13 @@ $(document).ready(function(){
#if $use_ratings: #if $use_ratings:
#set $data_rating = $try_float($this_show['rating']) #set $data_rating = $try_float($this_show['rating'])
#end if #end if
<div class="show-card ${hide}${known}inlibrary" data-name="#echo re.sub(r'([\'\"])', r'', $this_show['title'])#" data-id="$show_id" data-ajax="$this_show.get('overview_ajax', '0')" data-nroles="#echo len($this_show.get('p_chars', []))#" #if $use_ratings# data-rating="$data_rating"#end if##if $use_votes# data-votes="$this_show['votes']"#end if# data-premiered="$this_show['ord_premiered']"#if $use_returning# data-returning="$this_show['ord_returning']"#end if# data-order="$this_show['order']"#if $use_network# data-network="$this_show['network']"#end if#>
<div class="show-card ${hide}${known}inlibrary" data-name="#echo re.sub(r'([\'\"])', r'', $this_show['title'])#" data_id="$show_id"#if $use_ratings# data-rating="$data_rating"#end if##if $use_votes# data-votes="$this_show['votes']"#end if# data-premiered="$this_show['ord_premiered']"#if $use_returning# data-returning="$this_show['ord_returning']"#end if# data-order="$this_show['order']"#if $use_network# data-network="$this_show['network']"#end if#>
<div class="show-card-inner"> <div class="show-card-inner">
<div class="browse-image"> <div class="browse-image">
<a class="browse-image" href="<%= anon_url(this_show['url_src_db']) %>" target="_blank" <a class="browse-image" href="<%= anon_url(this_show['url_src_db']) %>" target="_blank"
title="<span style='color: rgb(66, 139, 202)'>$re.sub(r'(?m)\s+\((?:19|20)\d\d\)\s*$', '', $title_html)</span> title="<span style='color: #226baa'>$re.sub(r'(?m)\s+\((?:19|20)\d\d\)\s*$', '', $title_html)</span>
#if $this_show['genres']#<br><div style='font-weight:bold'>(<em>$this_show['genres']</em>)</div>#end if# <div class='genre' style='display:#echo ('none', 'block')[bool($this_show['genres'])]#;font-weight:bold'>(<em>$this_show['genres']</em>)</div>
#if $kwargs and $use_returning#<span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#888'><em>Season $this_show['episode_season'] return#echo ('s', 'ed')[$this_show['return_past']]# $this_show['str_returning']</em></span>#end if# #if $kwargs and $use_returning#<span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#888'><em>Season $this_show['episode_season'] return#echo ('s', 'ed')[$this_show['return_past']]# $this_show['str_returning']</em></span>#end if#
#if $this_show.get('country') or $this_show.get('language') #if $this_show.get('country') or $this_show.get('language')
<p style='line-height:15px;margin-bottom:2px'> <p style='line-height:15px;margin-bottom:2px'>
@ -519,8 +592,15 @@ $(document).ready(function(){
#end if #end if
</p> </p>
#end if #end if
<p style='margin:0 0 2px'>#echo re.sub(r'([,\.!][^,\.!]*?)$', '...', re.sub(r'([!\?\.])(?=\w)', r'\1 ', $overview)).replace('.....', '...')#</p> #if $this_show.get('p_chars')
<p>#if $this_show['str_premiered']#<span style='font-weight:bold;font-size:0.9em;color:#888'><em>#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']</em></span>#end if# <p style='overflow-y:auto;max-height:152px'>
#for $char in $this_show['p_chars']
<span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#393'>as $char[0]#if $RoleTypes.ActorMain != $char[1]# ($char[2]/$char[3] eps)#end if#</span>
#end for
</p>
#end if
<p class='overview' style='margin:0 0 2px'>$overview</p>
<p class='premiere'>#if $this_show['str_premiered']#<span style='font-weight:bold;font-size:0.9em;color:#888'><em>#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']</em></span>#end if#
#if $this_show.get('ended_str')# - <span style='font-weight:bold;font-size:0.9em;color:#888'><em>Ended: $this_show['ended_str']</em></span>#end if# #if $this_show.get('ended_str')# - <span style='font-weight:bold;font-size:0.9em;color:#888'><em>Ended: $this_show['ended_str']</em></span>#end if#
#if $this_show.get('network')#<span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#888'><em>On: $this_show['network']</em></span>#end if# #if $this_show.get('network')#<span style='display:block;clear:both;font-weight:bold;font-size:0.9em;color:#888'><em>On: $this_show['network']</em></span>#end if#
</p> </p>
@ -538,19 +618,14 @@ $(document).ready(function(){
<div class="show-title"> <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']]# #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> </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 #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> <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 #end if
<div class="clearfix"> <div class="clearfix">
#if $use_ratings or $use_votes #if $use_ratings or $use_votes
<p>#if $use_ratings#<span class="rating">$this_show['rating']#if $re.search(r'^\d+(\.\d+)?$', (str($this_show['rating'])))#%</span>#end if##end if##if $use_votes#<i class="heart icon-glyph"></i><i>$this_show['votes'] $term_vote.lower()</i>#end if#</p>#slurp# <p>#if $use_ratings#<span class="rating">$this_show['rating']#if $re.search(r'^\d+(\.\d+)?$', (str($this_show['rating'])))#%</span>#end if##end if##if $use_votes#<i class="heart icon-glyph"></i><i>$this_show['votes'] $term_vote.lower()</i>#end if#</p>#slurp#
#else
<p>&nbsp;</p>
#end if #end if
#if 'url_tvdb' in $this_show and $this_show['url_tvdb'] #if 'url_tvdb' in $this_show and $this_show['url_tvdb']
<a class="service" href="<%= anon_url(this_show['url_tvdb']) %>" onclick="window.open(this.href, '_blank'); return false;" <a class="service" href="<%= anon_url(this_show['url_tvdb']) %>" onclick="window.open(this.href, '_blank'); return false;"

View file

@ -310,8 +310,9 @@ class TmdbIndexer(TVInfoBase):
self.img_base_url, self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.medium], self.img_base_url, self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.medium],
tmdb_person_obj['profile_path']) tmdb_person_obj['profile_path'])
clean_person_name = clean_data(tmdb_person_obj.get('name'))
_it_person_obj = TVInfoPerson( _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 [])), akas=clean_data(set(tmdb_person_obj.get('also_known_as') or [])),
bio=clean_data(tmdb_person_obj.get('biography')), gender=gender, bio=clean_data(tmdb_person_obj.get('biography')), gender=gender,
image=main_image, images=image_list, thumb_url=main_thumb, 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.overview = self._enforce_text(character.get('overview'))
ti_show.firstaired = clean_data(character.get('first_air_date')) ti_show.firstaired = clean_data(character.get('first_air_date'))
ti_show.language = clean_data(character.get('original_language')) 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 = [] ti_show.genre_list = []
for g in character.get('genre_ids') or []: for g in character.get('genre_ids') or []:
if g in self.tv_genres: if g in self.tv_genres:
@ -350,9 +355,13 @@ class TmdbIndexer(TVInfoBase):
(self.img_base_url, (self.img_base_url,
self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.original], self.size_map[TVInfoImageType.person_poster][TVInfoImageSize.original],
character['backdrop_path']) character['backdrop_path'])
clean_char_name = clean_data(character.get('character'))
clean_lower_person_name = (clean_person_name or '').lower() or None
characters.append( characters.append(
TVInfoCharacter(name=clean_data(character.get('character')), ti_show=ti_show, person=[_it_person_obj], TVInfoCharacter(name=clean_char_name, ti_show=ti_show, person=[_it_person_obj],
episode_count=character.get('episode_count')) 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 _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, for character in sorted(list(filter(lambda b: b['credit_id'] in main_cast_credit_ids,
person_obj.get('roles', []) or [])), person_obj.get('roles', []) or [])),
key=lambda c: c['episode_count'], reverse=True): 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( 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=[ person=[
TVInfoPerson( 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']}), ids=TVInfoIDs(ids={TVINFO_TMDB: person_obj['id']}),
image='%s%s%s' % ( image='%s%s%s' % (
self.img_base_url, self.img_base_url,

View file

@ -6,7 +6,7 @@ from exceptions_helper import ConnectionSkipException, ex
from six import iteritems from six import iteritems
from .trakt import TraktAPI from .trakt import TraktAPI
from lib.tvinfo_base.exceptions import BaseTVinfoShownotfound 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, \ TVINFO_SLUG, TVInfoPerson, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_INSTAGRAM, TVInfoCharacter, \
TVInfoShow, TVInfoIDs, TVInfoSocialIDs, TVINFO_TRAKT_SLUG, TVInfoEpisode, TVInfoSeason, RoleTypes TVInfoShow, TVInfoIDs, TVInfoSocialIDs, TVINFO_TRAKT_SLUG, TVInfoEpisode, TVInfoSeason, RoleTypes
from sg_helpers import clean_data, enforce_type, try_int from sg_helpers import clean_data, enforce_type, try_int
@ -262,6 +262,7 @@ class TraktIndexer(TVInfoBase):
deathdate=deathdate, deathdate=deathdate,
homepage=person_obj['homepage'], homepage=person_obj['homepage'],
birthplace=person_obj['birthplace'], birthplace=person_obj['birthplace'],
gender=PersonGenders.trakt_map.get(person_obj['gender'], PersonGenders.unknown),
social_ids=TVInfoSocialIDs( social_ids=TVInfoSocialIDs(
ids={TVINFO_TWITTER: person_obj['social_ids']['twitter'], ids={TVINFO_TWITTER: person_obj['social_ids']['twitter'],
TVINFO_FACEBOOK: person_obj['social_ids']['facebook'], TVINFO_FACEBOOK: person_obj['social_ids']['facebook'],
@ -308,6 +309,7 @@ class TraktIndexer(TVInfoBase):
if resp: if resp:
if show_credits: if show_credits:
pc = [] pc = []
clean_lower_person_name = (result.name or '').lower()
for c in resp.get('cast') or []: for c in resp.get('cast') or []:
ti_show = TVInfoShow() ti_show = TVInfoShow()
ti_show.id = c['show']['ids'].get('trakt') ti_show.id = c['show']['ids'].get('trakt')
@ -327,9 +329,11 @@ class TraktIndexer(TVInfoBase):
ti_show.rating = c['show'].get('rating') ti_show.rating = c['show'].get('rating')
ti_show.vote_count = c['show'].get('votes') ti_show.vote_count = c['show'].get('votes')
for ch in c.get('characters') or []: for ch in c.get('characters') or []:
_ti_character = TVInfoCharacter(name=ch, regular=c.get('series_regular'), clean_ch = clean_data(ch)
ti_show=ti_show, person=[result], _ti_character = TVInfoCharacter(
episode_count=c.get('episode_count')) 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) pc.append(_ti_character)
ti_show.cast[(RoleTypes.ActorGuest, RoleTypes.ActorMain)[ ti_show.cast[(RoleTypes.ActorGuest, RoleTypes.ActorMain)[
c.get('series_regular', False)]].append(_ti_character) c.get('series_regular', False)]].append(_ti_character)

View file

@ -57,6 +57,8 @@ empty_ep = TVInfoEpisode()
empty_se = TVInfoSeason() empty_se = TVInfoSeason()
tz_p = parser() tz_p = parser()
character_clean_regex = re.compile(r'^tb(a|d)$', flags=re.I)
img_type_map = { img_type_map = {
'poster': TVInfoImageType.poster, 'poster': TVInfoImageType.poster,
'banner': TVInfoImageType.banner, 'banner': TVInfoImageType.banner,
@ -397,6 +399,14 @@ class TvMaze(TVInfoBase):
# type: (...) -> Dict[integer_types, integer_types] # type: (...) -> Dict[integer_types, integer_types]
return {sid: v.seconds_since_epoch for sid, v in iteritems(tvmaze.show_updates().updates)} 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): def _convert_person(self, tvmaze_person_obj, load_credits=True):
# type: (tvmaze.Person, bool) -> TVInfoPerson # type: (tvmaze.Person, bool) -> TVInfoPerson
ch = [] ch = []
@ -410,7 +420,15 @@ class TvMaze(TVInfoBase):
ti_show.ids = TVInfoIDs(ids={TVINFO_TVMAZE: ti_show.id}) ti_show.ids = TVInfoIDs(ids={TVINFO_TVMAZE: ti_show.id})
ti_show.overview = clean_data(c.show.summary) ti_show.overview = clean_data(c.show.summary)
ti_show.status = clean_data(c.show.status) 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 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: if net:
ti_show.network = clean_data(net.name) ti_show.network = clean_data(net.name)
ti_show.network_id = net.maze_id 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_country_code = clean_data(net.code)
ti_show.network_timezone = clean_data(net.timezone) ti_show.network_timezone = clean_data(net.timezone)
ti_show.network_is_stream = None is not c.show.web_channel 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: try:
birthdate = tvmaze_person_obj.birthday and tz_p.parse(tvmaze_person_obj.birthday).date() birthdate = tvmaze_person_obj.birthday and tz_p.parse(tvmaze_person_obj.birthday).date()
except (BaseException, Exception): except (BaseException, Exception):
@ -446,7 +475,7 @@ class TvMaze(TVInfoBase):
(tvmaze_person_obj.guestcastcredits or [], False)]: (tvmaze_person_obj.guestcastcredits or [], False)]:
for c in c_t: # type: tvmaze.CastCredit for c in c_t: # type: tvmaze.CastCredit
_show = c.show or c.episode.show _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() ti_show = TVInfoShow()
if None is not _show: if None is not _show:
_clean_show_name = clean_data(_show.name) _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.ids = TVInfoIDs(ids={TVINFO_TVMAZE: ti_show.id})
ti_show.overview = enforce_type(clean_data(_show.summary), str, '') ti_show.overview = enforce_type(clean_data(_show.summary), str, '')
ti_show.status = clean_data(_show.status) 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 net = _show.network or _show.web_channel
if net: if net:
ti_show.network = clean_data(net.name) 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]}} _g_kw = {'guest_episodes_numbers': {c.episode.season_number: [c.episode.episode_number or 0]}}
else: else:
_g_kw = {} _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, 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 _ti_person_obj.characters = ch
return _ti_person_obj return _ti_person_obj
@ -588,7 +629,7 @@ class TvMaze(TVInfoBase):
else: else:
_s_o.cast[RoleTypes.ActorMain].append( _s_o.cast[RoleTypes.ActorMain].append(
TVInfoCharacter(image=cur_ch.image and cur_ch.image.get('original'), 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}), ids=TVInfoIDs({TVINFO_TVMAZE: cur_ch.id}),
p_id=cur_ch.id, person=[person], plays_self=cur_ch.plays_self, p_id=cur_ch.id, person=[person], plays_self=cur_ch.plays_self,
thumb_url=cur_ch.image and cur_ch.image.get('medium'), 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 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): class Show(object):
def __init__(self, data): def __init__(self, data):
self.status = data.get('status') # type: Optional[AnyStr] self.status = data.get('status') # type: Optional[AnyStr]
@ -36,7 +48,7 @@ class Show(object):
self.runtime = data.get('runtime') # type: Optional[int] self.runtime = data.get('runtime') # type: Optional[int]
self.average_runtime = data.get('averageRuntime') self.average_runtime = data.get('averageRuntime')
self.type = data.get('type') # type: Optional[AnyStr] 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 self.maze_id = self.id # type: int
if data.get('network'): if data.get('network'):
self.network = Network(data.get('network')) # type: Optional[Network] self.network = Network(data.get('network')) # type: Optional[Network]
@ -428,9 +440,12 @@ class CastCredit(object):
def populate(self, data): def populate(self, data):
if data.get('_embedded'): if data.get('_embedded'):
if data['_embedded'].get('character'): 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'): if data['_embedded'].get('show'):
self.show = Show(data['_embedded']['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'): if data['_embedded'].get('episode'):
self.episode = Episode(data['_embedded']['episode']) self.episode = Episode(data['_embedded']['episode'])

View file

@ -884,6 +884,7 @@ class PersonGenders(object):
tmdb_map = {0: unknown, 1: female, 2: male} tmdb_map = {0: unknown, 1: female, 2: male}
imdb_map = {'female': female, 'male': male} imdb_map = {'female': female, 'male': male}
tvdb_map = {0: unknown, 1: male, 2: female, 3: unknown} # 3 is technically: other tvdb_map = {0: unknown, 1: male, 2: female, 3: unknown} # 3 is technically: other
trakt_map = {'female': female, 'male': male}
class Crew(PersonBase): class Crew(PersonBase):

View file

@ -724,7 +724,7 @@ class Person(Referential):
self._data_fetched = True self._data_fetched = True
tvsrc_result, found_persons, found_on_src, search_sources, \ tvsrc_result, found_persons, found_on_src, search_sources, \
found_ids, ids_to_check, imdb_confirmed, source_confirmed = \ 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, {} set([_k for _k, _v in iteritems(self.ids) if _v] + ['text']), {}, False, {}
# confirmed_character = False # confirmed_character = False
max_search_src = len(search_sources) max_search_src = len(search_sources)

View file

@ -4779,7 +4779,7 @@ class AddShows(Home):
genres=', '.join(row.get('metadata', {}).get('genres', {})) or 'No genre yet', genres=', '.join(row.get('metadata', {}).get('genres', {})) or 'No genre yet',
ids=ids, ids=ids,
images='' if not img_uri else images, 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), rating=int(helpers.try_float(rating) * 10),
title=row.get('primary').get('title'), title=row.get('primary').get('title'),
url_src_db='https://www.imdb.com/%s/' % row.get('primary').get('href').strip('/'), url_src_db='https://www.imdb.com/%s/' % row.get('primary').get('href').strip('/'),
@ -4855,7 +4855,7 @@ class AddShows(Home):
genres='', genres='',
ids=ids, ids=ids,
images='' if not img_uri else images, 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), rating=0 if not len(rating) else int(helpers.try_float(rating) * 10),
title=title, title=title,
url_src_db='https://www.imdb.com/%s/' % url_path.strip('/'), 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}' f'?releaseYearMin={this_year}&releaseYearMax={this_year}{page}'
html = helpers.get_url(url, headers={'User-Agent': browser_ua.get_ua()}) html = helpers.get_url(url, headers={'User-Agent': browser_ua.get_ua()})
if html: 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: try:
if re.findall('(c-navigationPagination_item--next)', html)[0]: if re.findall('(c-navigationPagination_item--next)', html)[0]:
kwargs.update(dict(more=1)) kwargs.update(dict(more=1))
@ -5053,7 +5066,7 @@ class AddShows(Home):
rc_id = re.compile(r'(?i)[^A-Z0-9]') rc_id = re.compile(r'(?i)[^A-Z0-9]')
rc_img = re.compile(r'(.*?)(/resize/[^?]+)?(/catalog/provider.*?\.(?:jpg|png)).*') rc_img = re.compile(r'(.*?)(/resize/[^?]+)?(/catalog/provider.*?\.(?:jpg|png)).*')
rc_season = re.compile(r'(\d+)(?:[.]\d*?)?$') rc_season = re.compile(r'(\d+)(?:[.]\d*?)?$')
for idx, cur_row in enumerate(items): for cur_idx, cur_row in enumerate(items):
try: try:
title = rc_title.sub( title = rc_title.sub(
'', cur_row.find('div', class_='c-finderProductCard_title').get('data-title').strip()) '', cur_row.find('div', class_='c-finderProductCard_title').get('data-title').strip())
@ -5068,6 +5081,24 @@ class AddShows(Home):
images = None images = None
img_src = (cur_row.find('img') or {}).get('src', '').strip() 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: if img_src:
img_uri = rc_img.sub(r'\1\3', 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}')) 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') overview = cur_row.find('div', class_='c-finderProductCard_description')
if overview: if overview:
overview = helpers.xhtml_escape(overview.get_text().strip()[:250:]) overview = overview.get_text()
try: try:
season = rc_season.findall(url_path)[0] season = rc_season.findall(url_path)[0]
@ -5117,7 +5148,7 @@ class AddShows(Home):
genres='', genres='',
ids=ids, ids=ids,
images=images or '', images=images or '',
overview=overview or 'No overview yet', overview=self.clean_overview(overview),
rating=0 if not rating else rating or 'TBD', 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', rating_user='tbd' if not rating_user else int(helpers.try_float(rating_user) * 10) or 'tbd',
title=title, title=title,
@ -5273,7 +5304,7 @@ class AddShows(Home):
genres = row.find(class_='genre') genres = row.find(class_='genre')
if genres: 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') overview = row.find(class_='summary')
if overview: if overview:
overview = overview.get_text(strip=True) overview = overview.get_text(strip=True)
@ -5293,7 +5324,7 @@ class AddShows(Home):
ids=ids, ids=ids,
images='' if not img_uri else images, images='' if not img_uri else images,
network=network or None, 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], rating=(rating, 'TBD')[None is rating],
title=title, title=title,
url_src_db='https://next-episode.net/%s/' % url_path.strip('/'), 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) 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): def tmdb_default(self):
method = getattr(self, sickgear.TMDB_MRU, None) 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 self.tmdb_upcoming()
return method() return method()
@ -5347,7 +5390,7 @@ class AddShows(Home):
def tmdb_person(self, person_tmdb_id=None, **kwargs): def tmdb_person(self, person_tmdb_id=None, **kwargs):
return self.browse_tmdb( 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): def browse_tmdb(self, browse_title, **kwargs):
@ -5369,21 +5412,20 @@ class AddShows(Home):
items = t.get_trending() items = t.get_trending()
elif 'trending_week' == mode: elif 'trending_week' == mode:
items = t.get_trending(time_window='week') items = t.get_trending(time_window='week')
elif 'get_person' == mode: elif 'person' == mode:
items = [] items = []
p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson
if p_item: if p_item:
p_ref = f'{TVINFO_TMDB}:{p_item.id}' p_ref = f'{TVINFO_TMDB}:{p_item.id}'
dup = {} # type: Dict[int, TVInfoShow] dup = {} # type: Dict[int, TVInfoShow]
for c in p_item.characters: # type: TVInfoCharacter for c in p_item.characters: # type: TVInfoCharacter
c.ti_show.cast[RoleTypes.ActorMain].append(c)
if c.ti_show.id not in dup: if c.ti_show.id not in dup:
dup[c.ti_show.id] = c.ti_show dup[c.ti_show.id] = c.ti_show
items.append(c.ti_show) items.append(c.ti_show)
else: 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 del dup
else:
p_item = None
else: else:
items = t.discover() items = t.discover()
@ -5400,7 +5442,10 @@ class AddShows(Home):
airtime = cur_show_info.airs_time airtime = cur_show_info.airs_time
if not airtime or (0, 0) == (airtime.hour, airtime.minute): if not airtime or (0, 0) == (airtime.hour, airtime.minute):
airtime = dateutil.parser.parse('23:59').time() airtime = dateutil.parser.parse('23:59').time()
try:
dt = datetime.combine(dateutil.parser.parse(cur_show_info.firstaired, parseinfo).date(), airtime) 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, _, _, _, _ \ ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, _, _, _, _ \
= self.sanitise_dates(dt, 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()) language = ((cur_show_info.language and 'jap' in cur_show_info.language.lower())
and 'jp' or 'en') and 'jp' or 'en')
filtered.append(dict( 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, ord_premiered=ord_premiered,
str_premiered=str_premiered, str_premiered=str_premiered,
started_past=started_past, 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, episode_season=cur_show_info.season,
genres=', '.join(cur_show_info.genre_list) genres=(', '.join(cur_show_info.genre_list)
or (cur_show_info.genre and (cur_show_info.genre.strip('|').replace('|', ', ')) or ''), or (cur_show_info.genre and (cur_show_info.genre.strip('|').replace('|', ', ')) or '')
).lower(),
ids=cur_show_info.ids.__dict__, ids=cur_show_info.ids.__dict__,
images=images, images=images,
overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() overview=self.clean_overview(cur_show_info),
or 'No overview yet'),
title=cur_show_info.seriesname, title=cur_show_info.seriesname,
language=language, language=language,
language_img=sickgear.MEMCACHE_FLAG_IMAGES.get(language, False), 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), country_img=sickgear.MEMCACHE_FLAG_IMAGES.get(cc.lower(), False),
network=network_name, network=network_name,
url_src_db=base_url % cur_show_info.id, 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): except (BaseException, Exception):
pass 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)) kwargs.update(dict(footnote=footnote, use_networks=use_networks))
if mode: if mode and self.allow_browse_mru(mode):
func = 'tmdb_%s' % mode func = 'tmdb_%s' % mode
if callable(getattr(self, func, None)): if callable(getattr(self, func, None)):
sickgear.TMDB_MRU = func sickgear.TMDB_MRU = func
@ -5460,7 +5510,7 @@ class AddShows(Home):
def trakt_default(self): def trakt_default(self):
method = getattr(self, sickgear.TRAKT_MRU, None) 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 self.trakt_trending()
return method() return method()
@ -5592,7 +5642,7 @@ class AddShows(Home):
if not items: if not items:
error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website'
raise ValueError(error_msg) raise ValueError(error_msg)
elif 'get_person' == api_method: elif 'person' == mode:
items = [] items = []
p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson
if p_item: if p_item:
@ -5602,11 +5652,7 @@ class AddShows(Home):
if c.ti_show.id not in dup: if c.ti_show.id not in dup:
dup[c.ti_show.id] = c.ti_show dup[c.ti_show.id] = c.ti_show
items.append(c.ti_show) items.append(c.ti_show)
else:
dup[c.ti_show.id].cast.update(c.ti_show.cast)
del dup del dup
else:
p_item = None
else: else:
items = t.get_trending() items = t.get_trending()
except TraktAuthException as e: except TraktAuthException as e:
@ -5662,10 +5708,6 @@ class AddShows(Home):
images = {} if not image else dict(poster=dict(thumb=image)) images = {} if not image else dict(poster=dict(thumb=image))
filtered.append(dict( 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, ord_premiered=ord_premiered,
str_premiered=str_premiered, str_premiered=str_premiered,
ord_returning=ord_returning, ord_returning=ord_returning,
@ -5673,14 +5715,13 @@ class AddShows(Home):
started_past=started_past, # air time not yet available 16.11.2015 started_past=started_past, # air time not yet available 16.11.2015
return_past=return_past, return_past=return_past,
episode_number=episode_info.episodenumber, 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), episode_season=getattr(episode_info.season, 'number', 1),
genres=(', '.join(['%s' % v for v in cur_show_info.genre_list])), genres=(', '.join(['%s' % v for v in cur_show_info.genre_list])),
ids=cur_show_info.ids.__dict__, ids=cur_show_info.ids.__dict__,
images=images, images=images,
network=network_name, network=network_name,
overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() overview=self.clean_overview(cur_show_info),
or 'No overview yet'),
rating=0 < (cur_show_info.rating or 0) and rating=0 < (cur_show_info.rating or 0) and
('%.2f' % (cur_show_info.rating * 10)).replace('.00', '') or 0, ('%.2f' % (cur_show_info.rating * 10)).replace('.00', '') or 0,
title=(cur_show_info.seriesname or '').strip(), title=(cur_show_info.seriesname or '').strip(),
@ -5692,7 +5733,14 @@ class AddShows(Home):
url_tvdb=( url_tvdb=(
'' if not (isinstance(cur_show_info.ids.tvdb, integer_types) and 0 < cur_show_info.ids.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), 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): except (BaseException, Exception):
pass pass
@ -5705,7 +5753,7 @@ class AddShows(Home):
return self.browse_trakt( return self.browse_trakt(
'get_person', 'get_person',
'Person on Trakt', 'Person at Trakt',
mode='person', mode='person',
footnote='Note; Expect default placeholder images in this list', footnote='Note; Expect default placeholder images in this list',
p_id=person_trakt_id 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' 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) 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)) 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')): if not any(m in mode for m in ('recommended', 'watchlist', 'person')):
mode = mode.split('-') mode = mode.split('-')
if mode: if mode and self.allow_browse_mru(mode):
func = 'trakt_%s' % mode[0] func = 'trakt_%s' % mode[0]
if callable(getattr(self, func, None)): if callable(getattr(self, func, None)):
param = '' if 1 == len(mode) or mode[1] not in ['year', 'month', 'week', 'all'] else \ 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, network=network or None,
ids=ids, ids=ids,
images='' if not img_uri else images, 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, rating=None,
title=title, title=title,
url_src_db='https://www.pogdesign.co.uk/%s' % url_path.strip('/'), url_src_db='https://www.pogdesign.co.uk/%s' % url_path.strip('/'),
@ -5972,7 +6017,7 @@ class AddShows(Home):
def tvm_default(self): def tvm_default(self):
method = getattr(self, sickgear.TVM_MRU, None) 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 self.tvm_premieres()
return method() return method()
@ -5986,7 +6031,52 @@ class AddShows(Home):
def tvm_person(self, person_tvm_id=None, **kwargs): def tvm_person(self, person_tvm_id=None, **kwargs):
return self.browse_tvm( 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): def browse_tvm(self, browse_title, **kwargs):
@ -6002,29 +6092,30 @@ class AddShows(Home):
t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase]
if 'premieres' == mode: if 'premieres' == mode:
items = t.get_premieres() items = t.get_premieres()
elif 'get_person' == mode: elif 'person' == mode:
items = [] items = []
p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson p_item = t.get_person(get_show_credits=True, **kwargs) # type: TVInfoPerson
if p_item: if p_item:
p_ref = f'{TVINFO_TVMAZE}:{p_item.id}' p_ref = f'{TVINFO_TVMAZE}:{p_item.id}'
dup = {} # type: Dict[int, TVInfoShow] dup = {} # type: Dict[int, TVInfoShow]
for c in p_item.characters: # type: TVInfoCharacter 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: if c.ti_show.id not in dup:
dup[c.ti_show.id] = c.ti_show dup[c.ti_show.id] = c.ti_show
items.append(c.ti_show) items.append(c.ti_show)
else: 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 del dup
else:
p_item = None
else: else:
items = t.get_returning() items = t.get_returning()
# handle switching between returning and premieres # handle switching between returning and premieres
sickgear.BROWSELIST_MRU.setdefault(browse_type, dict()) sickgear.BROWSELIST_MRU.setdefault(browse_type, dict())
if mode in ('premieres', 'returning'):
showfilter = ('by_returning', 'by_premiered')['premieres' == mode] showfilter = ('by_returning', 'by_premiered')['premieres' == mode]
saved_showsort = sickgear.BROWSELIST_MRU[browse_type].get('tvm_%s' % mode) or '*,asc' saved_showsort = sickgear.BROWSELIST_MRU[browse_type].get('tvm_%s' % mode) or '*,asc'
showsort = saved_showsort + (',%s' % showfilter, '')[3 == len(saved_showsort.split(','))] showsort = saved_showsort + (f',{showfilter}', '')[3 == len(saved_showsort.split(','))]
sickgear.BROWSELIST_MRU[browse_type].update(dict(showfilter=showfilter, showsort=showsort)) sickgear.BROWSELIST_MRU[browse_type].update(dict(showfilter=showfilter, showsort=showsort))
oldest, newest, oldest_dt, newest_dt, dedupe = None, None, 9999999, 0, [] oldest, newest, oldest_dt, newest_dt, dedupe = None, None, 9999999, 0, []
@ -6048,7 +6139,7 @@ class AddShows(Home):
if 'returning' == mode and not ok_returning: if 'returning' == mode and not ok_returning:
continue 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)) images = {} if not image else dict(poster=dict(thumb=image))
network_name = cur_show_info.network 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') 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( 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, ord_premiered=ord_premiered,
str_premiered=str_premiered, str_premiered=str_premiered,
ord_returning=ord_returning, ord_returning=ord_returning,
@ -6072,14 +6164,15 @@ class AddShows(Home):
started_past=started_past, started_past=started_past,
return_past=return_past, return_past=return_past,
episode_number=episode_info.episodenumber or '', 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), episode_season=getattr(episode_info.season, 'number', episode_info.seasonnumber),
genres=((cur_show_info.genre or '').strip('|').replace('|', ', ') genres=((cur_show_info.genre or '')
or ', '.join(cur_show_info.show_type) 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__, ids=cur_show_info.ids.__dict__,
images=images, images=images,
overview=(helpers.xhtml_escape(cur_show_info.overview[:250:]).strip('*').strip() overview_ajax=(0, 1)[overview_ajax],
or 'No overview yet'), overview=overview,
rating=cur_show_info.rating or cur_show_info.popularity or 0, rating=cur_show_info.rating or cur_show_info.popularity or 0,
title=cur_show_info.seriesname, title=cur_show_info.seriesname,
language=language, language=language,
@ -6089,13 +6182,23 @@ class AddShows(Home):
network=network_name, network=network_name,
url_src_db=base_url % cur_show_info.id, 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): except (BaseException, Exception):
pass 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 func = 'tvm_%s' % mode
if callable(getattr(self, func, None)): if callable(getattr(self, func, None)):
sickgear.TVM_MRU = func sickgear.TVM_MRU = func
@ -6110,6 +6213,9 @@ class AddShows(Home):
@staticmethod @staticmethod
def sanitise_dates(date, oldest_dt, newest_dt, oldest, newest, episode_info=None, combine_ep_airtime=False): 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) parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True)
dt = date if isinstance(date, datetime) else dateutil.parser.parse(date) dt = date if isinstance(date, datetime) else dateutil.parser.parse(date)
if episode_info: if episode_info:
@ -6189,7 +6295,8 @@ class AddShows(Home):
t = PageTemplate(web_handler=self, file='home_browseShows.tmpl') t = PageTemplate(web_handler=self, file='home_browseShows.tmpl')
t.submenu = self.home_menu() t.submenu = self.home_menu()
t.browse_type = browse_type 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.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_showfilter = sickgear.BROWSELIST_MRU.get(browse_type, {}).get('showfilter', '')
t.saved_showsort = sickgear.BROWSELIST_MRU.get(browse_type, {}).get('showsort', '*,asc,by_order') t.saved_showsort = sickgear.BROWSELIST_MRU.get(browse_type, {}).get('showsort', '*,asc,by_order')