Browse Source

Add ability to search tvid:prodid as found in URLs and at other UI places.

Update glide 3.3.0 to 3.4.0 (f7ff0dd).
Change put real name first on person page.
Add lang_ordered_akas to tv.py for cast English/non-English lists on UI.
Change rearrange English before any non English person akas.
Change enable tvinfo on Bulk Change.
Fix prevent slidetime from one tab overwriting the slidetime from another when it should not.
Change reinstate original db perf gains and remove memory savings as the two are mutually exclusive here.
Change match sequence of params even though it it kv based, for readability.
Fix cast transparent background image border - border was larger than width of trans image during the cast image loading process which resulted in a grey border line that was the width of the image container instead of wrapping the trans image.
Change moved icons up 5px after seeing a full width cast image.
Fix remove dupe id in html and escape showname string used for config object.
Fix usage for upgraded Fancybox.
Change flip api fields from ... `deleted unwanted` to `keep wanted` so that data is not mistakenly returned to the API if new data fields are added to get_daily_schedule(), the inclusion list was created by first running the old loop function that removed unwanted fields, and then copying the filtered list of keys into a sorted list for the new logic.
Add imdb miniseries average runtime to view-show.
Fix property initialisation.
Change if cast image calculates to appear lower than header name text, instead align it to top.
Change cleanup cast bio text.
Change DRY CachedImages and better var names.
tags/release_0.25.1
JackDandy 4 years ago
parent
commit
f288df6409
  1. 2
      gui/slick/css/lib/glide.core.min.css
  2. 15
      gui/slick/css/style.css
  3. 13
      gui/slick/interfaces/default/cast_person.tmpl
  4. 3
      gui/slick/interfaces/default/cast_role.tmpl
  5. 10
      gui/slick/interfaces/default/config_general.tmpl
  6. 5
      gui/slick/interfaces/default/displayShow.tmpl
  7. 2
      gui/slick/interfaces/default/manage.tmpl
  8. 3
      gui/slick/interfaces/default/manage_showProcesses.tmpl
  9. 14
      gui/slick/js/cast.js
  10. 56
      gui/slick/js/config.js
  11. 15
      gui/slick/js/displayShow.js
  12. 895
      gui/slick/js/fancybox/jquery.fancybox.css
  13. 5632
      gui/slick/js/fancybox/jquery.fancybox.js
  14. 1
      gui/slick/js/fancybox/jquery.fancybox.min.css
  15. 13
      gui/slick/js/fancybox/jquery.fancybox.min.js
  16. 6
      gui/slick/js/glide/glide.min.js
  17. 2
      gui/slick/js/newShow.js
  18. 5
      gui/slick/js/script.js
  19. 2
      lib/tvdb_api/tvdb_api.py
  20. 4
      sickbeard/databases/mainDB.py
  21. 28
      sickbeard/db.py
  22. 25
      sickbeard/show_queue.py
  23. 33
      sickbeard/tv.py
  24. 25
      sickbeard/webapi.py
  25. 200
      sickbeard/webserve.py
  26. 1
      sickgear.py

2
gui/slick/css/lib/glide.core.min.css

@ -1 +1 @@
.glide{position:relative;width:100%;box-sizing:border-box}.glide *{box-sizing:inherit}.glide__track{overflow:hidden}.glide__slides{position:relative;width:100%;list-style:none;backface-visibility:hidden;transform-style:preserve-3d;touch-action:pan-Y;overflow:hidden;padding:0;white-space:nowrap;display:flex;flex-wrap:nowrap;will-change:transform}.glide__slides--dragging{user-select:none}.glide__slide{width:100%;height:100%;flex-shrink:0;white-space:normal;user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}.glide__slide a{user-select:none;-webkit-user-drag:none;-moz-user-select:none;-ms-user-select:none}.glide__arrows{-webkit-touch-callout:none;user-select:none}.glide__bullets{-webkit-touch-callout:none;user-select:none}.glide--rtl{direction:rtl} .glide{position:relative;width:100%;box-sizing:border-box}.glide *{box-sizing:inherit}.glide__track{overflow:hidden}.glide__slides{position:relative;width:100%;list-style:none;backface-visibility:hidden;transform-style:preserve-3d;touch-action:pan-Y;overflow:hidden;margin:0;padding:0;white-space:nowrap;display:flex;flex-wrap:nowrap;will-change:transform}.glide__slides--dragging{user-select:none}.glide__slide{width:100%;height:100%;flex-shrink:0;white-space:normal;user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}.glide__slide a{user-select:none;-webkit-user-drag:none;-moz-user-select:none;-ms-user-select:none}.glide__arrows{-webkit-touch-callout:none;user-select:none}.glide__bullets{-webkit-touch-callout:none;user-select:none}.glide--rtl{direction:rtl}

15
gui/slick/css/style.css

@ -1951,7 +1951,6 @@ a.addQTip{
#display-show .cast-holder{display:block; margin:5px 0 0; padding:0; position:relative; width:100%; box-sizing:border-box} #display-show .cast-holder{display:block; margin:5px 0 0; padding:0; position:relative; width:100%; box-sizing:border-box}
#display-show .cast-header{margin-bottom:3px; font-weight:bolder} #display-show .cast-header{margin-bottom:3px; font-weight:bolder}
#display-show .cast-panel{white-space:nowrap} #display-show .cast-panel{white-space:nowrap}
#display-show .cast-panel ul{margin-bottom:0}
#display-show .cast{display:inline-block; vertical-align:top; position:relative; top:0; width:100%;/*170px*/ height:auto; padding:6px 0; margin:0 0 10px; text-align:center} #display-show .cast{display:inline-block; vertical-align:top; position:relative; top:0; width:100%;/*170px*/ height:auto; padding:6px 0; margin:0 0 10px; text-align:center}
#display-show .cast, #display-show .cast,
#display-show .cast .thumb .cast-bg, #display-show .cast .thumb .cast-bg,
@ -1959,19 +1958,19 @@ a.addQTip{
#pin-glide{-moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px} #pin-glide{-moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px}
#display-show .cast .thumb{display:block} #display-show .cast .thumb{display:block}
#display-show .cast .thumb, #display-show .cast .thumb,
#display-show .cast .thumb .cast-bg{height:150px} #display-show .cast .thumb .cast-bg{height:150px; min-width:102px}
#display-show .cast .thumb .cast-bg{display:block; margin:0 auto; background:url(../images/poster-person150.jpg) center center no-repeat; border:1px solid #181818} #display-show .cast .thumb .cast-bg{display:inline-block; margin:0 auto; background:url(../images/poster-person150.jpg) center center no-repeat; border:1px solid #181818}
#display-show .back-art.pro.ii .cast .thumb .cast-bg{opacity:0.85; filter:alpha(opacity=85)} #display-show .back-art.pro.ii .cast .thumb .cast-bg{opacity:0.85; filter:alpha(opacity=85)}
#display-show .role, #display-show .role,
#display-show .person{width:auto; overflow:hidden; margin:0 5px; text-overflow:ellipsis; white-space:nowrap} #display-show .person{width:auto; overflow:hidden; margin:0 5px; text-overflow:ellipsis; white-space:nowrap}
.glide-arrows button{cursor:pointer; padding:1px 0 0; border:0; outline:0; position:absolute; top:110px; z-index:1; width:22px; height:21px; margin-top:-9px; -webkit-box-shadow:0 .5rem 4rem 0 rgba(0,0,0,.5); box-shadow:0 .5rem 4rem 0 rgba(0,0,0,.5)} .glide-arrows button{cursor:pointer; padding:1px 0 0; border:0; outline:0; position:absolute; top:105px; z-index:1; width:22px; height:21px; margin-top:-9px; -webkit-box-shadow:0 .5rem 4rem 0 rgba(0,0,0,.5); box-shadow:0 .5rem 4rem 0 rgba(0,0,0,.5)}
.glide-arrows button{background-color:rgb(255,255,255)} .glide-arrows button{background-color:rgb(255,255,255)}
.glide-arrows button svg{padding:1px} .glide-arrows button svg{padding:1px}
.glide-next{right:0.7rem} .glide-next{right:0.7rem}
.glide-prev{left:0.7rem} .glide-prev{left:0.7rem}
#pin-glide, #pin-glide,
#pin-glide i{display:block; width:22px; height:21px; z-index:99} #pin-glide i{display:block; width:22px; height:21px; z-index:99}
#pin-glide{position:absolute; top:135px; left:0.7rem; background-color:rgba(0,0,0,0.5); border:0; padding:0} #pin-glide{position:absolute; top:130px; left:0.7rem; background-color:rgba(0,0,0,0.5); border:0; padding:0}
.contain-glide #pin-glide{cursor:pointer} .contain-glide #pin-glide{cursor:pointer}
.glide-arrows button:hover, .glide-arrows button:hover,
#pin-glide:hover{background-color:#ed145b; -webkit-transition:all .2s ease-in-out; transition:all .2s ease-in-out; opacity:0.5; filter:alpha(opacity=50)} #pin-glide:hover{background-color:#ed145b; -webkit-transition:all .2s ease-in-out; transition:all .2s ease-in-out; opacity:0.5; filter:alpha(opacity=50)}
@ -3334,6 +3333,7 @@ select .selected:before{
background-position:-104px 0 background-position:-104px 0
} }
#api-keys > #qr-body{display:none}
#api-keys > div{display:inline-block} #api-keys > div{display:inline-block}
#api-keys span{float:left} #api-keys span{float:left}
#api-keys span, #generate-result{line-height:22px} #api-keys span, #generate-result{line-height:22px}
@ -3341,10 +3341,11 @@ select .selected:before{
#api-keys .app-name{width:135px} #api-keys .app-name{width:135px}
.qr-btn{margin-right:6px} .qr-btn{margin-right:6px}
.qr-btn .glyphicon-qrcode{cursor:pointer;font-size:15px} .qr-btn .glyphicon-qrcode{cursor:pointer;font-size:15px}
.apikey-qr-dlg .qr-title{padding:0 25px 5px 25px;text-align:right} .apikey-qr-dlg .qr-title{text-align:right}
.apikey-qr-dlg .qr-title em{color:#999;font-weight:bolder} .apikey-qr-dlg .qr-title em{color:#999;font-weight:bolder}
.apikey-qr-dlg .qr-title span{color:#333} .apikey-qr-dlg .qr-title span{color:#333}
.apikey-qr-dlg .qr-body{padding:25px 25px 0} .apikey-qr-dlg #qr-body{padding:30px 25px 5px; overflow:hidden; min-width:350px; min-height:350px}
.apikey-qr-dlg #qr-body .fancybox-close-small{top:-6px; right:-8px}
/* ======================================================================= /* =======================================================================
config_postProcessing.tmpl config_postProcessing.tmpl

13
gui/slick/interfaces/default/cast_person.tmpl

@ -152,15 +152,15 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
</style> </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.akas
#set $akas = ' <i>*</i> '.join(['<span%s>%s</span>' % (('', ' class="grey-text"')[bool(idx % 2)], $aka) for (idx, $aka) in $enumerate($person.akas)])
<div><span class="details-title">Akas</span><span class="details-info akas">$akas</span></div>
#end if
#if $person.real_name #if $person.real_name
<div><span class="details-title">Real name</span><span class="details-info">$person.real_name</span></div> <div><span class="details-title">Real name</span><span class="details-info">$person.real_name</span></div>
#end if #end if
#if $person.akas
#set $akas = ' <i>*</i> '.join(['<span%s>%s</span>' % (('', ' class="grey-text"')[bool(idx % 2)], $aka) for (idx, $aka) in $enumerate($person.lang_ordered_akas)])
<div><span class="details-title">Akas</span><span class="details-info akas">$akas</span></div>
#end if
#if $person.nicknames #if $person.nicknames
<div><span class="details-title">Nicknames</span><span class="details-info">#echo', '.join($person.nicknames)#</span></div> <div><span class="details-title">Nicknames</span><span class="details-info">#echo', '.join($person.lang_ordered_nicknames)#</span></div>
#end if #end if
#if $person.height #if $person.height
#set $inches = str(round(($person.height / float(2.54)), 1)).rstrip('.0') #set $inches = str(round(($person.height / float(2.54)), 1)).rstrip('.0')
@ -208,7 +208,8 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
<div> <div>
<h3>Bio</h3> <h3>Bio</h3>
<div style="max-height:250px; overflow:auto; word-break:normal"> <div style="max-height:250px; overflow:auto; word-break:normal">
<p style="padding-right:5px">$person.biography.replace('\n', '<br>')</p> ## cleanup bio
<p style="padding-right:5px">$re.sub(r'\s+.*?CC-BY-SA.*?$', '', $person.biography).replace('\n', '<br>')</p>
</div> </div>
</div> </div>
#end if #end if

3
gui/slick/interfaces/default/cast_role.tmpl

@ -156,7 +156,8 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
<div> <div>
<h3>Bio</h3> <h3>Bio</h3>
<div style="max-height:250px; overflow:auto; word-break:normal"> <div style="max-height:250px; overflow:auto; word-break:normal">
<p>$character.biography.replace('\n', '<br>')</p> ## cleanup bio
<p>$re.sub(r'\s+.*?CC-BY-SA.*?$', '', $character.biography).replace('\n', '<br>')</p>
</div> </div>
</div> </div>
#end if #end if

10
gui/slick/interfaces/default/config_general.tmpl

@ -577,14 +577,14 @@
<span id="generate-result">&nbsp;</span> <span id="generate-result">&nbsp;</span>
<p class="clear-left">apps using SickGear API calls gain full access, legacy SickBeard endpoints are limited to thetvdb.com shows</p> <p class="clear-left">apps using SickGear API calls gain full access, legacy SickBeard endpoints are limited to thetvdb.com shows</p>
<div id="api-keys" class="clear-left"> <div id="api-keys" class="clear-left">
<div class="new-key highlight-text" style="display:none"><span class="qr-btn"><a rel="" name="qr" title="API key QR code"><span class="glyphicon glyphicon-qrcode"></span></a></span><span class="api-key"></span><span class="app-name"></span><input value="Revoke" type="button" class="revoke btn btn-inline"></div> <div class="new-key highlight-text" style="display:none"><span class="qr-btn"><a data-src="#qr-body" rel="" name="qr" title="API key QR code"><span class="glyphicon glyphicon-qrcode"></span></a></span><span class="api-key"></span><span class="app-name"></span><input value="Revoke" type="button" class="revoke btn btn-inline"></div>
<div id="qr-body"></div>
#set $tip_addkeys = '' #set $tip_addkeys = ''
#for $appname, $uid in $api_keys #for $appname, $uid in $api_keys
#if not ($appname and $uid) #if $appname and $uid
#continue <div class="grey-text"><span class="qr-btn"><a data-src="#qr-body" data-api-name="$appname" data-api-key="$uid" rel="qr" name="qr" title="API key QR code"><span class="glyphicon glyphicon-qrcode"></span></a></span><span class="api-key">$uid</span><span class="app-name">$appname</span><input value="Revoke" type="button" class="revoke btn btn-inline"></div>
#set $tip_addkeys = ' style="display:none"'
#end if #end if
<div class="grey-text"><span class="qr-btn"><a rel="qr" name="qr" title="API key QR code"><span class="glyphicon glyphicon-qrcode"></span></a></span><span class="api-key">$uid</span><span class="app-name">$appname</span><input value="Revoke" type="button" class="revoke btn btn-inline"></div>
#set $tip_addkeys = ' style="display:none"'
#end for #end for
<div id="tip-addkeys"$tip_addkeys>Keys used by 3rd party programs to access SickGear will list here when generated</div> <div id="tip-addkeys"$tip_addkeys>Keys used by 3rd party programs to access SickGear will list here when generated</div>
</div> </div>

5
gui/slick/interfaces/default/displayShow.tmpl

@ -233,6 +233,9 @@
#set $flags = 'country_codes' in $show_obj.imdb_info and '' != $show_obj.imdb_info['country_codes'] #set $flags = 'country_codes' in $show_obj.imdb_info and '' != $show_obj.imdb_info['country_codes']
#if 'runtimes' in $show_obj.imdb_info #if 'runtimes' in $show_obj.imdb_info
#set $runtime = $show_obj.imdb_info['runtimes'] #set $runtime = $show_obj.imdb_info['runtimes']
#if $show_obj.imdb_info['is_mini_series'] and $runtime
#set $runtime = '%s (av) of %s' % (int($runtime/$show_obj.imdb_info['episode_count']), $runtime)
#end if
#end if #end if
#end if #end if
#if None is $startyear and $show_obj.startyear #if None is $startyear and $show_obj.startyear
@ -274,7 +277,7 @@
#if None is not $runtime #if None is not $runtime
<div> <div>
<span class="details-title">Runtime</span> <span class="details-title">Runtime</span>
<span class="details-info">$runtime minutes</span> <span class="details-info">$runtime mins.</span>
</div> </div>
#end if #end if
#if $show_obj.status #if $show_obj.status

2
gui/slick/interfaces/default/manage.tmpl

@ -36,7 +36,7 @@
type: 'numeric' type: 'numeric'
}); });
## ##
#set $enable_tvinfo = False #set $enable_tvinfo = True
#set $column_headers = [ #set $column_headers = [
('!1', '!1', False), ('!1', '!1', False),
("'showNames'", '!0', False), ("'showNames'", '!0', False),

3
gui/slick/interfaces/default/manage_showProcesses.tmpl

@ -256,7 +256,8 @@
#if $cur_show['show_obj'] #if $cur_show['show_obj']
<a class="whitelink" href="$sbRoot/home/view-show?tvid_prodid=$cur_show['show_obj'].tvid_prodid">$cur_show['show_obj'].name</a> <a class="whitelink" href="$sbRoot/home/view-show?tvid_prodid=$cur_show['show_obj'].tvid_prodid">$cur_show['show_obj'].name</a>
#else #else
<span>Unknown Show: $cur_show['old_tvid']:$cur_show['old_prodid']</span> ## <span>Unknown Show: $cur_show['old_tvid']:$cur_show['old_prodid']</span>
<span>Unknown</span>
#end if #end if
</td> </td>
<td class="text-center">$sickbeard.TVInfoAPI($cur_show['new_tvid']).name</td> <td class="text-center">$sickbeard.TVInfoAPI($cur_show['new_tvid']).name</td>

14
gui/slick/js/cast.js

@ -30,13 +30,19 @@ function removeImageBackground(oImage){
} else if (sizeImage.h < oImage.height){ } else if (sizeImage.h < oImage.height){
var heightDiff = oImage.height - sizeImage.h, var heightDiff = oImage.height - sizeImage.h,
margin = Math.floor(heightDiff / 2), marginBottom = Math.floor(heightDiff / 2),
roundError = Math.floor(oImage.height - sizeImage.h - (margin * 2)), roundError = Math.floor(oImage.height - sizeImage.h - (marginBottom * 2)),
marginTop = margin + ((0 < roundError) ? roundError : 0); marginTop = marginBottom + ((0 < roundError) ? roundError : 0);
/* if img is lower than header name text, align it to top */
if(35 < marginTop){
marginBottom += marginTop;
marginTop = 0;
}
$(this).css('height', 'auto') $(this).css('height', 'auto')
.css('marginTop', marginTop) .css('marginTop', marginTop)
.css('marginBottom', margin); .css('marginBottom', marginBottom);
} }
} }
} }

56
gui/slick/js/config.js

File diff suppressed because one or more lines are too long

15
gui/slick/js/displayShow.js

@ -72,12 +72,16 @@ $(document).ready(function() {
gap: slideGap, gap: slideGap,
startAt: -1 === startAt ? 0 : startAt, startAt: -1 === startAt ? 0 : startAt,
peek: 0, peek: 0,
perSwipe: '|',
perView: initGlideVars.perView, perView: initGlideVars.perView,
autoplay: initGlideVars.isNotEnd && slideTime() autoplay: initGlideVars.isNotEnd && slideTime()
}); });
$.glide.on('resize', function(){ $.glide.on('resize', function(){
$.calcSlideCount(!1); $.calcSlideCount(!1);
$('#display-show .cast-bg').each(function (i, oImage){
scaleImage(oImage);
});
}); });
$.glide.on('run.after', function(){ $.glide.on('run.after', function(){
@ -124,13 +128,16 @@ $(document).ready(function() {
$('li[data-rid="' + $(this).data('rid') + '"]:not(.glide__slide--clone) a[rel="glide"]')[0].click(); $('li[data-rid="' + $(this).data('rid') + '"]:not(.glide__slide--clone) a[rel="glide"]')[0].click();
return !1; return !1;
}); });
$('#display-show .cast-bg').each(function (i, oImage){
scaleImage(oImage);
});
}); });
window.onload = function(){ window.onload = function(){
$.glide.mount(); $.glide.mount();
}; };
function saveGlide(){ function saveGlide(saveTime){
if (!$.SickGear.glideFancyBoxOpen){ if (!$.SickGear.glideFancyBoxOpen){
var params = {}; var params = {};
if (!slideTime()){ if (!slideTime()){
@ -139,7 +146,9 @@ $(document).ready(function() {
start_at: $('.cast.glide__slide--active').data('rid') start_at: $('.cast.glide__slide--active').data('rid')
}; };
} }
params.slidetime = $.SickGear.config.glideSlideTime; if (saveTime){
params.slidetime = $.SickGear.config.glideSlideTime;
}
$.get($.SickGear.Root + '/home/set-display-show-glide', params); $.get($.SickGear.Root + '/home/set-display-show-glide', params);
} }
} }
@ -176,7 +185,7 @@ $(document).ready(function() {
} }
pinState($(this)); pinState($(this));
$.calcSlideCount(!1); $.calcSlideCount(!1);
saveGlide(); saveGlide(!0);
}); });
} }

895
gui/slick/js/fancybox/jquery.fancybox.css

@ -1,895 +0,0 @@
body.compensate-for-scrollbar {
overflow: hidden;
}
.fancybox-active {
height: auto;
}
.fancybox-is-hidden {
left: -9999px;
margin: 0;
position: absolute !important;
top: -9999px;
visibility: hidden;
}
.fancybox-container {
-webkit-backface-visibility: hidden;
height: 100%;
left: 0;
outline: none;
position: fixed;
-webkit-tap-highlight-color: transparent;
top: 0;
-ms-touch-action: manipulation;
touch-action: manipulation;
transform: translateZ(0);
width: 100%;
z-index: 99992;
}
.fancybox-container * {
box-sizing: border-box;
}
.fancybox-outer,
.fancybox-inner,
.fancybox-bg,
.fancybox-stage {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.fancybox-outer {
-webkit-overflow-scrolling: touch;
overflow-y: auto;
}
.fancybox-bg {
background: rgb(30, 30, 30);
opacity: 0;
transition-duration: inherit;
transition-property: opacity;
transition-timing-function: cubic-bezier(.47, 0, .74, .71);
}
.fancybox-is-open .fancybox-bg {
opacity: .9;
transition-timing-function: cubic-bezier(.22, .61, .36, 1);
}
.fancybox-infobar,
.fancybox-toolbar,
.fancybox-caption,
.fancybox-navigation .fancybox-button {
direction: ltr;
opacity: 0;
position: absolute;
transition: opacity .25s ease, visibility 0s ease .25s;
visibility: hidden;
z-index: 99997;
}
.fancybox-show-infobar .fancybox-infobar,
.fancybox-show-toolbar .fancybox-toolbar,
.fancybox-show-caption .fancybox-caption,
.fancybox-show-nav .fancybox-navigation .fancybox-button {
opacity: 1;
transition: opacity .25s ease 0s, visibility 0s ease 0s;
visibility: visible;
}
.fancybox-infobar {
color: #ccc;
font-size: 13px;
-webkit-font-smoothing: subpixel-antialiased;
height: 44px;
left: 0;
line-height: 44px;
min-width: 44px;
mix-blend-mode: difference;
padding: 0 10px;
pointer-events: none;
top: 0;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.fancybox-toolbar {
right: 0;
top: 0;
}
.fancybox-stage {
direction: ltr;
overflow: visible;
transform: translateZ(0);
z-index: 99994;
}
.fancybox-is-open .fancybox-stage {
overflow: hidden;
}
.fancybox-slide {
-webkit-backface-visibility: hidden;
/* Using without prefix would break IE11 */
display: none;
height: 100%;
left: 0;
outline: none;
overflow: auto;
-webkit-overflow-scrolling: touch;
padding: 44px;
position: absolute;
text-align: center;
top: 0;
transition-property: transform, opacity;
white-space: normal;
width: 100%;
z-index: 99994;
}
.fancybox-slide::before {
content: '';
display: inline-block;
font-size: 0;
height: 100%;
vertical-align: middle;
width: 0;
}
.fancybox-is-sliding .fancybox-slide,
.fancybox-slide--previous,
.fancybox-slide--current,
.fancybox-slide--next {
display: block;
}
.fancybox-slide--image {
overflow: hidden;
padding: 44px 0;
}
.fancybox-slide--image::before {
display: none;
}
.fancybox-slide--html {
padding: 6px;
}
.fancybox-content {
background: #fff;
display: inline-block;
margin: 0;
max-width: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
padding: 44px;
position: relative;
text-align: left;
vertical-align: middle;
}
.fancybox-slide--image .fancybox-content {
animation-timing-function: cubic-bezier(.5, 0, .14, 1);
-webkit-backface-visibility: hidden;
background: transparent;
background-repeat: no-repeat;
background-size: 100% 100%;
left: 0;
max-width: none;
overflow: visible;
padding: 0;
position: absolute;
top: 0;
-ms-transform-origin: top left;
transform-origin: top left;
transition-property: transform, opacity;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
z-index: 99995;
}
.fancybox-can-zoomOut .fancybox-content {
cursor: zoom-out;
}
.fancybox-can-zoomIn .fancybox-content {
cursor: zoom-in;
}
.fancybox-can-swipe .fancybox-content,
.fancybox-can-pan .fancybox-content {
cursor: -webkit-grab;
cursor: grab;
}
.fancybox-is-grabbing .fancybox-content {
cursor: -webkit-grabbing;
cursor: grabbing;
}
.fancybox-container [data-selectable='true'] {
cursor: text;
}
.fancybox-image,
.fancybox-spaceball {
background: transparent;
border: 0;
height: 100%;
left: 0;
margin: 0;
max-height: none;
max-width: none;
padding: 0;
position: absolute;
top: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width: 100%;
}
.fancybox-spaceball {
z-index: 1;
}
.fancybox-slide--video .fancybox-content,
.fancybox-slide--map .fancybox-content,
.fancybox-slide--pdf .fancybox-content,
.fancybox-slide--iframe .fancybox-content {
height: 100%;
overflow: visible;
padding: 0;
width: 100%;
}
.fancybox-slide--video .fancybox-content {
background: #000;
}
.fancybox-slide--map .fancybox-content {
background: #e5e3df;
}
.fancybox-slide--iframe .fancybox-content {
background: #fff;
}
.fancybox-video,
.fancybox-iframe {
background: transparent;
border: 0;
display: block;
height: 100%;
margin: 0;
overflow: hidden;
padding: 0;
width: 100%;
}
/* Fix iOS */
.fancybox-iframe {
left: 0;
position: absolute;
top: 0;
}
.fancybox-error {
background: #fff;
cursor: default;
max-width: 400px;
padding: 40px;
width: 100%;
}
.fancybox-error p {
color: #444;
font-size: 16px;
line-height: 20px;
margin: 0;
padding: 0;
}
/* Buttons */
.fancybox-button {
background: rgba(30, 30, 30, .6);
border: 0;
border-radius: 0;
box-shadow: none;
cursor: pointer;
display: inline-block;
height: 44px;
margin: 0;
padding: 10px;
position: relative;
transition: color .2s;
vertical-align: top;
visibility: inherit;
width: 44px;
}
.fancybox-button,
.fancybox-button:visited,
.fancybox-button:link {
color: #ccc;
}
.fancybox-button:hover {
color: #fff;
}
.fancybox-button:focus {
outline: none;
}
.fancybox-button.fancybox-focus {
outline: 1px dotted;
}
.fancybox-button[disabled],
.fancybox-button[disabled]:hover {
color: #888;
cursor: default;
outline: none;
}
/* Fix IE11 */
.fancybox-button div {
height: 100%;
}
.fancybox-button svg {
display: block;
height: 100%;
overflow: visible;
position: relative;
width: 100%;
}
.fancybox-button svg path {
fill: currentColor;
stroke-width: 0;
}
.fancybox-button--play svg:nth-child(2),
.fancybox-button--fsenter svg:nth-child(2) {
display: none;
}
.fancybox-button--pause svg:nth-child(1),
.fancybox-button--fsexit svg:nth-child(1) {
display: none;
}
.fancybox-progress {
background: #ff5268;
height: 2px;
left: 0;
position: absolute;
right: 0;
top: 0;
-ms-transform: scaleX(0);
transform: scaleX(0);
-ms-transform-origin: 0;
transform-origin: 0;
transition-property: transform;
transition-timing-function: linear;
z-index: 99998;
}
/* Close button on the top right corner of html content */
.fancybox-close-small {
background: transparent;
border: 0;
border-radius: 0;
color: #ccc;
cursor: pointer;
opacity: .8;
padding: 8px;
position: absolute;
right: -12px;
top: -44px;
z-index: 401;
}
.fancybox-close-small:hover {
color: #fff;
opacity: 1;
}
.fancybox-slide--html .fancybox-close-small {
color: currentColor;
padding: 10px;
right: 0;
top: 0;
}
.fancybox-slide--image.fancybox-is-scaling .fancybox-content {
overflow: hidden;
}
.fancybox-is-scaling .fancybox-close-small,
.fancybox-is-zoomable.fancybox-can-pan .fancybox-close-small {
display: none;
}
/* Navigation arrows */
.fancybox-navigation .fancybox-button {
background-clip: content-box;
height: 100px;
opacity: 0;
position: absolute;
top: calc(50% - 50px);
width: 70px;
}
.fancybox-navigation .fancybox-button div {
padding: 7px;
}
.fancybox-navigation .fancybox-button--arrow_left {
left: 0;
left: env(safe-area-inset-left);
padding: 31px 26px 31px 6px;
}
.fancybox-navigation .fancybox-button--arrow_right {
padding: 31px 6px 31px 26px;
right: 0;
right: env(safe-area-inset-right);
}
/* Caption */
.fancybox-caption {
background: linear-gradient(to top,
rgba(0, 0, 0, .85) 0%,
rgba(0, 0, 0, .3) 50%,
rgba(0, 0, 0, .15) 65%,
rgba(0, 0, 0, .075) 75.5%,
rgba(0, 0, 0, .037) 82.85%,
rgba(0, 0, 0, .019) 88%,
rgba(0, 0, 0, 0) 100%);
bottom: 0;
color: #eee;
font-size: 14px;
font-weight: 400;
left: 0;
line-height: 1.5;
padding: 75px 44px 25px 44px;
pointer-events: none;
right: 0;
text-align: center;
z-index: 99996;
}
@supports (padding: max(0px)) {
.fancybox-caption {
padding: 75px max(44px, env(safe-area-inset-right)) max(25px, env(safe-area-inset-bottom)) max(44px, env(safe-area-inset-left));
}
}
.fancybox-caption--separate {
margin-top: -50px;
}
.fancybox-caption__body {
max-height: 50vh;
overflow: auto;
pointer-events: all;
}
.fancybox-caption a,
.fancybox-caption a:link,
.fancybox-caption a:visited {
color: #ccc;
text-decoration: none;
}
.fancybox-caption a:hover {
color: #fff;
text-decoration: underline;
}
/* Loading indicator */
.fancybox-loading {
animation: fancybox-rotate 1s linear infinite;
background: transparent;
border: 4px solid #888;
border-bottom-color: #fff;
border-radius: 50%;
height: 50px;
left: 50%;
margin: -25px 0 0 -25px;
opacity: .7;
padding: 0;
position: absolute;
top: 50%;
width: 50px;
z-index: 99999;
}
@keyframes fancybox-rotate {
100% {
transform: rotate(360deg);
}
}
/* Transition effects */
.fancybox-animated {
transition-timing-function: cubic-bezier(0, 0, .25, 1);
}
/* transitionEffect: slide */
.fancybox-fx-slide.fancybox-slide--previous {
opacity: 0;
transform: translate3d(-100%, 0, 0);
}
.fancybox-fx-slide.fancybox-slide--next {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
.fancybox-fx-slide.fancybox-slide--current {
opacity: 1;
transform: translate3d(0, 0, 0);
}
/* transitionEffect: fade */
.fancybox-fx-fade.fancybox-slide--previous,
.fancybox-fx-fade.fancybox-slide--next {
opacity: 0;
transition-timing-function: cubic-bezier(.19, 1, .22, 1);
}
.fancybox-fx-fade.fancybox-slide--current {
opacity: 1;
}
/* transitionEffect: zoom-in-out */
.fancybox-fx-zoom-in-out.fancybox-slide--previous {
opacity: 0;
transform: scale3d(1.5, 1.5, 1.5);
}
.fancybox-fx-zoom-in-out.fancybox-slide--next {
opacity: 0;
transform: scale3d(.5, .5, .5);
}
.fancybox-fx-zoom-in-out.fancybox-slide--current {
opacity: 1;
transform: scale3d(1, 1, 1);
}
/* transitionEffect: rotate */
.fancybox-fx-rotate.fancybox-slide--previous {
opacity: 0;
-ms-transform: rotate(-360deg);
transform: rotate(-360deg);
}
.fancybox-fx-rotate.fancybox-slide--next {
opacity: 0;
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
.fancybox-fx-rotate.fancybox-slide--current {
opacity: 1;
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
/* transitionEffect: circular */
.fancybox-fx-circular.fancybox-slide--previous {
opacity: 0;
transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0);
}
.fancybox-fx-circular.fancybox-slide--next {
opacity: 0;
transform: scale3d(0, 0, 0) translate3d(100%, 0, 0);
}
.fancybox-fx-circular.fancybox-slide--current {
opacity: 1;
transform: scale3d(1, 1, 1) translate3d(0, 0, 0);
}
/* transitionEffect: tube */
.fancybox-fx-tube.fancybox-slide--previous {
transform: translate3d(-100%, 0, 0) scale(.1) skew(-10deg);
}
.fancybox-fx-tube.fancybox-slide--next {
transform: translate3d(100%, 0, 0) scale(.1) skew(10deg);
}
.fancybox-fx-tube.fancybox-slide--current {
transform: translate3d(0, 0, 0) scale(1);
}
/* Styling for Small-Screen Devices */
@media all and (max-height: 576px) {
.fancybox-slide {
padding-left: 6px;
padding-right: 6px;
}
.fancybox-slide--image {
padding: 6px 0;
}
.fancybox-close-small {
right: -6px;
}
.fancybox-slide--image .fancybox-close-small {
background: #4e4e4e;
color: #f2f4f6;
height: 36px;
opacity: 1;
padding: 6px;
right: 0;
top: 0;
width: 36px;
}
.fancybox-caption {
padding-left: 12px;
padding-right: 12px;
}
@supports (padding: max(0px)) {
.fancybox-caption {
padding-left: max(12px, env(safe-area-inset-left));
padding-right: max(12px, env(safe-area-inset-right));
}
}
}
/* Share */
.fancybox-share {
background: #f4f4f4;
border-radius: 3px;
max-width: 90%;
padding: 30px;
text-align: center;
}
.fancybox-share h1 {
color: #222;
font-size: 35px;
font-weight: 700;
margin: 0 0 20px 0;
}
.fancybox-share p {
margin: 0;
padding: 0;
}
.fancybox-share__button {
border: 0;
border-radius: 3px;
display: inline-block;
font-size: 14px;
font-weight: 700;
line-height: 40px;
margin: 0 5px 10px 5px;
min-width: 130px;
padding: 0 15px;
text-decoration: none;
transition: all .2s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
white-space: nowrap;
}
.fancybox-share__button:visited,
.fancybox-share__button:link {
color: #fff;
}
.fancybox-share__button:hover {
text-decoration: none;
}
.fancybox-share__button--fb {
background: #3b5998;
}
.fancybox-share__button--fb:hover {
background: #344e86;
}
.fancybox-share__button--pt {
background: #bd081d;
}
.fancybox-share__button--pt:hover {
background: #aa0719;
}
.fancybox-share__button--tw {
background: #1da1f2;
}
.fancybox-share__button--tw:hover {
background: #0d95e8;
}
.fancybox-share__button svg {
height: 25px;
margin-right: 7px;
position: relative;
top: -1px;
vertical-align: middle;
width: 25px;
}
.fancybox-share__button svg path {
fill: #fff;
}
.fancybox-share__input {
background: transparent;
border: 0;
border-bottom: 1px solid #d7d7d7;
border-radius: 0;
color: #5d5b5b;
font-size: 14px;
margin: 10px 0 0 0;
outline: none;
padding: 10px 15px;
width: 100%;
}
/* Thumbs */
.fancybox-thumbs {
background: #ddd;
bottom: 0;
display: none;
margin: 0;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
padding: 2px 2px 4px 2px;
position: absolute;
right: 0;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
top: 0;
width: 212px;
z-index: 99995;
}
.fancybox-thumbs-x {
overflow-x: auto;
overflow-y: hidden;
}
.fancybox-show-thumbs .fancybox-thumbs {
display: block;
}
.fancybox-show-thumbs .fancybox-inner {
right: 212px;
}
.fancybox-thumbs__list {
font-size: 0;
height: 100%;
list-style: none;
margin: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 0;
position: absolute;
position: relative;
white-space: nowrap;
width: 100%;
}
.fancybox-thumbs-x .fancybox-thumbs__list {
overflow: hidden;
}
.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar {
width: 7px;
}
.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-track {
background: #fff;
border-radius: 10px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
}
.fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-thumb {
background: #2a2a2a;
border-radius: 10px;
}
.fancybox-thumbs__list a {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
background-color: rgba(0, 0, 0, .1);
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
cursor: pointer;
float: left;
height: 75px;
margin: 2px;
max-height: calc(100% - 8px);
max-width: calc(50% - 4px);
outline: none;
overflow: hidden;
padding: 0;
position: relative;
-webkit-tap-highlight-color: transparent;
width: 100px;
}
.fancybox-thumbs__list a::before {
border: 6px solid #ff5268;
bottom: 0;
content: '';
left: 0;
opacity: 0;
position: absolute;
right: 0;
top: 0;
transition: all .2s cubic-bezier(.25, .46, .45, .94);
z-index: 99991;
}
.fancybox-thumbs__list a:focus::before {
opacity: .5;
}
.fancybox-thumbs__list a.fancybox-thumbs-active::before {
opacity: 1;
}
/* Styling for Small-Screen Devices */
@media all and (max-width: 576px) {
.fancybox-thumbs {
width: 110px;
}
.fancybox-show-thumbs .fancybox-inner {
right: 110px;
}
.fancybox-thumbs__list a {
max-width: calc(100% - 10px);
}
}

5632
gui/slick/js/fancybox/jquery.fancybox.js

File diff suppressed because it is too large

1
gui/slick/js/fancybox/jquery.fancybox.min.css

File diff suppressed because one or more lines are too long

13
gui/slick/js/fancybox/jquery.fancybox.min.js

File diff suppressed because one or more lines are too long

6
gui/slick/js/glide/glide.min.js

File diff suppressed because one or more lines are too long

2
gui/slick/js/newShow.js

@ -128,7 +128,7 @@ $(document).ready(function () {
+ '" data-sort-az="' + item[result.AzSort] + '" data-sort-az-combined="' + item[result.AzCombined] + '" data-sort-az="' + item[result.AzSort] + '" data-sort-az-combined="' + item[result.AzCombined]
+ '" data-sort-za="' + item[result.ZaSort] + '" data-sort-za-combined="' + item[result.ZaCombined] + '">' + '" data-sort-za="' + item[result.ZaSort] + '" data-sort-za-combined="' + item[result.ZaCombined] + '">'
+ '<label><i></i>' + '<label><i></i>'
+ '<input id="which_series" type="radio"' + '<input type="radio"'
+ ' class="stepone-result-radio"' + ' class="stepone-result-radio"'
+ (!1 === item[result.isInDB] + (!1 === item[result.isInDB]
? ' title="Add show <span style=\'color: rgb(66, 139, 202)\'>' + displayShowName + '</span>"' ? ' title="Add show <span style=\'color: rgb(66, 139, 202)\'>' + displayShowName + '</span>"'

5
gui/slick/js/script.js

@ -69,8 +69,9 @@ function preventDefault() {
function initFancybox() { function initFancybox() {
if (!!$('a[rel="dialog"]').length) { if (!!$('a[rel="dialog"]').length) {
$.getScript(sbRoot + '/js/fancybox/jquery.fancybox.js', function () { var fancy = sbRoot + '/js/fancybox/jquery.fancybox.min';
$('head').append('<link rel="stylesheet" href="' + sbRoot + '/js/fancybox/jquery.fancybox.css">'); $.getScript(fancy + '.js', function () {
$('head').append('<link rel="stylesheet" href="' + fancy + '.css">');
$.sgFancyBoxOptions = { $.sgFancyBoxOptions = {
loop: !0, loop: !0,

2
lib/tvdb_api/tvdb_api.py

@ -762,7 +762,7 @@ class Tvdb(TVInfoBase):
'highlightPreTag=__ais-highlight__', 'highlightPostTag=__/ais-highlight__' 'highlightPreTag=__ais-highlight__', 'highlightPostTag=__/ais-highlight__'
]) ])
}]}, }]},
language=language, parse_json=True) language=language, parse_json=True, failure_monitor=False)
return src return src
except (KeyError, IndexError, Exception): except (KeyError, IndexError, Exception):
pass pass

4
sickbeard/databases/mainDB.py

@ -1715,6 +1715,10 @@ class ChangeShowData(db.SchemaUpgrade):
('network_country_code', 'TEXT', ''), ('network_id', 'NUMERIC'), ('network_country_code', 'TEXT', ''), ('network_id', 'NUMERIC'),
('network_is_stream', 'INTEGER')]) ('network_is_stream', 'INTEGER')])
if not self.hasColumn('imdb_info', 'is_mini_series'):
self.upgrade_log('Adding new data columns to imdb_info')
self.addColumns('imdb_info', [('is_mini_series', 'INTEGER', 0), ('episode_count', 'NUMERIC')])
self.upgrade_log('Adding Character and Persons tables') self.upgrade_log('Adding Character and Persons tables')
table_create_sql = { table_create_sql = {

28
sickbeard/db.py

@ -209,33 +209,29 @@ class DBConnection(object):
attempt = 0 attempt = 0
sql_result = {'affected': 0, 'data': []} sql_result = []
affected = 0
while 5 > attempt: while 5 > attempt:
try: try:
def sql_action(query, result): cursor = self.connection.cursor()
cursor = self.connection.cursor()
if 'SELECT' == query[0].strip()[0:6].upper():
result['data'].append(cursor.execute(*tuple(query)).fetchall())
else:
cursor.execute(*tuple(query)).fetchall()
result['affected'] += abs(cursor.rowcount)
if not log_transaction: if not log_transaction:
for cur_query in queries: for cur_query in queries:
sql_action(cur_query, sql_result) sql_result.append(cursor.execute(*tuple(cur_query)).fetchall())
affected += abs(cursor.rowcount)
else: else:
for cur_query in queries: for cur_query in queries:
logger.log(cur_query[0] if 1 == len(cur_query) logger.log(cur_query[0] if 1 == len(cur_query)
else '%s with args %s' % tuple(cur_query), logger.DB) else '%s with args %s' % tuple(cur_query), logger.DB)
sql_action(cur_query, sql_result) sql_result.append(cursor.execute(*tuple(cur_query)).fetchall())
affected += abs(cursor.rowcount)
self.connection.commit() self.connection.commit()
if 0 < sql_result['affected']: if 0 < affected:
logger.debug(u'Transaction with %s queries executed affected at least %i row%s' % ( logger.debug(u'Transaction with %s queries executed affected at least %i row%s' % (
len(queries), sql_result['affected'], helpers.maybe_plural(sql_result['affected']))) len(queries), affected, helpers.maybe_plural(affected)))
return sql_result['data'] return sql_result
except sqlite3.OperationalError as e: except sqlite3.OperationalError as e:
sql_result['data'] = [] sql_result = []
if self.connection: if self.connection:
self.connection.rollback() self.connection.rollback()
if not self.action_error(e): if not self.action_error(e):
@ -247,7 +243,7 @@ class DBConnection(object):
logger.error(u'Fatal error executing query: ' + ex(e)) logger.error(u'Fatal error executing query: ' + ex(e))
raise raise
return sql_result['data'] return sql_result
@staticmethod @staticmethod
def action_error(e): def action_error(e):

25
sickbeard/show_queue.py

@ -116,19 +116,18 @@ class ShowQueue(generic_queue.GenericQueue):
self.download_subtitles(add_to_db=False, show_obj=show_obj, uid=cur_row['uid']) self.download_subtitles(add_to_db=False, show_obj=show_obj, uid=cur_row['uid'])
elif ShowQueueActions.ADD == cur_row['action_id']: elif ShowQueueActions.ADD == cur_row['action_id']:
self.addShow(tvid=cur_row['tvid'], prodid=cur_row['prodid'], self.add_show(
add_to_db=False, allowlist=cur_row['allowlist'], tvid=cur_row['tvid'], prodid=cur_row['prodid'], show_dir=cur_row['show_dir'],
anime=bool_none(cur_row['anime']), quality=cur_row['quality'], upgrade_once=bool(cur_row['upgrade_once']),
blocklist=cur_row['blocklist'], default_status=cur_row['default_status'], wanted_begin=cur_row['wanted_begin'], wanted_latest=cur_row['wanted_latest'],
flatten_folders=bool_none(cur_row['flatten_folders']), lang=cur_row['lang'], tag=cur_row['tag'],
new_show=bool(cur_row['new_show']), paused=bool_none(cur_row['paused']), paused=bool_none(cur_row['paused']), prune=cur_row['prune'],
prune=cur_row['prune'], quality=cur_row['quality'], default_status=cur_row['default_status'], scene=bool_none(cur_row['scene']),
scene=bool_none(cur_row['scene']), show_dir=cur_row['show_dir'], subtitles=cur_row['subtitles'],
show_name=cur_row['show_name'], subtitles=cur_row['subtitles'], flatten_folders=bool_none(cur_row['flatten_folders']), anime=bool_none(cur_row['anime']),
tag=cur_row['tag'], uid=cur_row['uid'], blocklist=cur_row['blocklist'], allowlist=cur_row['allowlist'],
upgrade_once=bool(cur_row['upgrade_once']), show_name=cur_row['show_name'], new_show=bool(cur_row['new_show']),
wanted_begin=cur_row['wanted_begin'], lang=cur_row['lang'], uid=cur_row['uid'], add_to_db=False)
wanted_latest=cur_row['wanted_latest'])
except (BaseException, Exception) as e: except (BaseException, Exception) as e:
logger.log('Exception loading queue %s: %s' % (self.__class__.__name__, ex(e)), logger.ERROR) logger.log('Exception loading queue %s: %s' % (self.__class__.__name__, ex(e)), logger.ERROR)

33
sickbeard/tv.py

@ -415,6 +415,31 @@ class Person(Referential):
elif not self.name: elif not self.name:
self.load_from_db() self.load_from_db()
def _order_names(self, name_set):
# type: (Set[AnyStr]) -> List[AnyStr]
rc_aka = re.compile(r'[\x00-\x7f]+')
aka_all_ascii = []
aka_non_ascii = []
for cur_aka in name_set or []:
if rc_aka.match(cur_aka):
aka_all_ascii += [cur_aka]
else:
aka_non_ascii += [cur_aka]
return aka_all_ascii + aka_non_ascii
@property
def lang_ordered_akas(self):
# type: (...) -> List[AnyStr]
return self._order_names(self.akas)
@property
def lang_ordered_nicknames(self):
# type: (...) -> List[AnyStr]
return self._order_names(self.nicknames)
def _remember_properties(self): def _remember_properties(self):
# type: (...) -> Dict # type: (...) -> Dict
return {k: self.__dict__[k] for k in return {k: self.__dict__[k] for k in
@ -2644,6 +2669,8 @@ class TVShow(TVShowBase):
self._imdb_info = sql_result[0] self._imdb_info = sql_result[0]
else: else:
self._imdb_info = dict(zip(sql_result[0].keys(), [(r, '')[None is r] for r in sql_result[0]])) self._imdb_info = dict(zip(sql_result[0].keys(), [(r, '')[None is r] for r in sql_result[0]]))
if 'is_mini_series' in self._imdb_info:
self._imdb_info['is_mini_series'] = bool(self._imdb_info['is_mini_series'])
elif sickbeard.USE_IMDB_INFO: elif sickbeard.USE_IMDB_INFO:
logger.log('%s: The next show update will attempt to find IMDb info for [%s]' % logger.log('%s: The next show update will attempt to find IMDb info for [%s]' %
(self.tvid_prodid, self.name), logger.DEBUG) (self.tvid_prodid, self.name), logger.DEBUG)
@ -2952,6 +2979,8 @@ class TVShow(TVShowBase):
'year': '', 'year': '',
'akas': '', 'akas': '',
'runtimes': self._runtime, 'runtimes': self._runtime,
'is_mini_series': False,
'episode_count': None,
'genres': '', 'genres': '',
'countries': '', 'countries': '',
'country_codes': '', 'country_codes': '',
@ -3032,6 +3061,10 @@ class TVShow(TVShowBase):
imdb_info['year'] = try_int(imdb_tv.get('year'), '') imdb_info['year'] = try_int(imdb_tv.get('year'), '')
if isinstance(imdb_tv.get('runningTimeInMinutes'), (int, string_types)): if isinstance(imdb_tv.get('runningTimeInMinutes'), (int, string_types)):
imdb_info['runtimes'] = try_int(imdb_tv.get('runningTimeInMinutes'), '') imdb_info['runtimes'] = try_int(imdb_tv.get('runningTimeInMinutes'), '')
if isinstance(imdb_tv.get('titleType'), string_types):
imdb_info['is_mini_series'] = 'mini' in imdb_tv.get('titleType').lower()
if isinstance(imdb_tv.get('numberOfEpisodes'), (int, string_types)):
imdb_info['episode_count'] = try_int(imdb_tv.get('numberOfEpisodes'), 1)
if isinstance(imdb_tv.get('genres'), (list, tuple)): if isinstance(imdb_tv.get('genres'), (list, tuple)):
imdb_info['genres'] = '|'.join(filter_iter(lambda _v: _v, imdb_tv.get('genres'))) imdb_info['genres'] = '|'.join(filter_iter(lambda _v: _v, imdb_tv.get('genres')))
if isinstance(imdb_tv.get('origins'), list): if isinstance(imdb_tv.get('origins'), list):

25
sickbeard/webapi.py

@ -59,7 +59,7 @@ from .sgdatetime import SGDatetime
from .tv import TVEpisode, TVShow, TVidProdid from .tv import TVEpisode, TVShow, TVidProdid
from .webserve import AddShows from .webserve import AddShows
from _23 import decode_str, unquote_plus from _23 import decode_str, list_keys, unquote_plus
from six import integer_types, iteritems, iterkeys, PY2, string_types, text_type from six import integer_types, iteritems, iterkeys, PY2, string_types, text_type
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
@ -1023,15 +1023,22 @@ class CMD_SickGearComingEpisodes(ApiCall):
ep['network'] = ep['episode_network'] or ep['network'] ep['network'] = ep['episode_network'] or ep['network']
ep['timezone'] = ep['ep_timezone'] or ep['show_timezone'] or ep['timezone'] or ( ep['timezone'] = ep['ep_timezone'] or ep['show_timezone'] or ep['timezone'] or (
ep['network'] and network_timezones.get_network_timezone(ep['network'], return_name=True)[1]) ep['network'] and network_timezones.get_network_timezone(ep['network'], return_name=True)[1])
# remove all field we don't want for api response # remove all field we don't want for api response
for f in ('localtime', 'ep_airtime', 'airtime', 'timestamp', 'show_airtime', 'network_id', for cur_f in list_keys(ep):
'network_is_stream', 'notify_list', 'src_update_timestamp', 'subtitles_lastsearch', 'subtitles', if cur_f not in [ # fields to preserve
'subtitles_searchcount', 'name', 'description', 'hasnfo', 'hastbn', 'last_update_indexer', 'absolute_number', 'air_by_date', 'airdate', 'airs', 'archive_firstmatch',
'file_size', 'flatten_folders', 'is_proper', 'prune', 'episode_network', 'network_country', 'classification', 'data_network', 'data_show_name',
'network_country_code', 'show_timezone', 'release_group', 'release_name', 'ep_name', 'ep_plot', 'episode', 'episode_id', 'genre',
'rls_global_exclude_ignore', 'rls_global_exclude_require', 'rls_ignore_words', 'imdb_id', 'imdb_url', 'indexer', 'indexer_id', 'indexerid',
'rls_require_words', 'location', 'ep_timezone', 'dvdorder', 'anime', 'sports'): 'lang', 'local_datetime', 'network', 'overview', 'parsed_datetime', 'paused', 'prod_id',
del ep[f] 'quality', 'runtime', 'scene', 'scene_absolute_number', 'scene_episode', 'scene_season',
'season', 'show_id', 'show_name', 'show_network', 'show_status', 'showid', 'startyear',
'status', 'status_str', 'tag', 'timezone', 'trakt_watched', 'tv_id', 'tvid_prodid',
'version', 'weekday'
]:
del ep[cur_f]
# Add tvdbid for backward compatibility # Add tvdbid for backward compatibility
try: try:
show_obj = helpers.find_show_by_id({ep['tv_id']: ep['prod_id']}) show_obj = helpers.find_show_by_id({ep['tv_id']: ep['prod_id']})

200
sickbeard/webserve.py

@ -103,7 +103,7 @@ from six import binary_type, integer_types, iteritems, iterkeys, itervalues, mov
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
if False: if False:
from typing import AnyStr, Dict, List, Optional, Set, Tuple from typing import Any, AnyStr, Dict, List, Optional, Set, Tuple
from sickbeard.providers.generic import TorrentProvider from sickbeard.providers.generic import TorrentProvider
@ -918,12 +918,13 @@ class MainHandler(WebHandler):
self.redirect('/view-shows/') self.redirect('/view-shows/')
@staticmethod @staticmethod
def set_display_show_glide(slidetime, tvid_prodid=None, start_at=None): def set_display_show_glide(slidetime=None, tvid_prodid=None, start_at=None):
if tvid_prodid and start_at: if tvid_prodid and start_at:
sickbeard.DISPLAY_SHOW_GLIDE.setdefault(tvid_prodid, {}).update({'start_at': start_at}) sickbeard.DISPLAY_SHOW_GLIDE.setdefault(tvid_prodid, {}).update({'start_at': start_at})
sickbeard.DISPLAY_SHOW_GLIDE_SLIDETIME = sg_helpers.try_int(slidetime, 3000) if slidetime:
sickbeard.DISPLAY_SHOW_GLIDE_SLIDETIME = sg_helpers.try_int(slidetime, 3000)
sickbeard.save_config() sickbeard.save_config()
@staticmethod @staticmethod
@ -3842,7 +3843,8 @@ class AddShows(Home):
total, slug, id_str = cur_match.groups() total, slug, id_str = cur_match.groups()
for cur_tvid in sickbeard.TVInfoAPI().all_sources: for cur_tvid in sickbeard.TVInfoAPI().all_sources:
if sickbeard.TVInfoAPI(cur_tvid).config.get('slug') \ if sickbeard.TVInfoAPI(cur_tvid).config.get('slug') \
and sickbeard.TVInfoAPI(cur_tvid).config['slug'] == slug.lower(): and (slug.lower() == sickbeard.TVInfoAPI(cur_tvid).config['slug']
or cur_tvid == sg_helpers.try_int(slug, None)):
try: try:
ids_to_search[cur_tvid] = int(id_str.strip().replace('tt', '')) ids_to_search[cur_tvid] = int(id_str.strip().replace('tt', ''))
except (BaseException, Exception): except (BaseException, Exception):
@ -4271,7 +4273,8 @@ class AddShows(Home):
slug = sickbeard.TVInfoAPI(cur_tvid).config['slug'] slug = sickbeard.TVInfoAPI(cur_tvid).config['slug']
try_id = has_shows and cur_try and sickbeard.showList[-1].ids[cur_tvid].get('id') try_id = has_shows and cur_try and sickbeard.showList[-1].ids[cur_tvid].get('id')
if not cur_idx: if not cur_idx:
t.try_name = [{'showname': 'Game of Thrones' if not try_id else sickbeard.showList[-1].name}] t.try_name = [{
'showname': 'Game of Thrones' if not try_id else sickbeard.showList[-1].name.replace("'", "\\'")}]
if cur_try: if cur_try:
id_key = '%s:id%s' % (slug, ('', ' (GoT)')[not try_id]) id_key = '%s:id%s' % (slug, ('', ' (GoT)')[not try_id])
@ -9296,60 +9299,57 @@ class CachedImages(MainHandler):
file_name = ek.ek(os.path.basename, source) file_name = ek.ek(os.path.basename, source)
elif filename not in [None, 0, '0']: elif filename not in [None, 0, '0']:
file_name = filename file_name = filename
static_image_path = ek.ek(os.path.join, sickbeard.CACHE_DIR, 'images', path, file_name) image_file = ek.ek(os.path.join, sickbeard.CACHE_DIR, 'images', path, file_name)
static_image_path = ek.ek(os.path.abspath, static_image_path.replace('\\', '/')) image_file = ek.ek(os.path.abspath, image_file.replace('\\', '/'))
if not ek.ek(os.path.isfile, static_image_path) and has_image_ext(file_name): if not ek.ek(os.path.isfile, image_file) and has_image_ext(file_name):
basepath = ek.ek(os.path.dirname, static_image_path) basepath = ek.ek(os.path.dirname, image_file)
helpers.make_dirs(basepath) helpers.make_dirs(basepath)
s = '' poster_url = ''
tmdbimage = False tmdb_image = False
if None is not source and source in sickbeard.CACHE_IMAGE_URL_LIST: if None is not source and source in sickbeard.CACHE_IMAGE_URL_LIST:
s = source poster_url = source
if None is source and tmdbid not in [None, 'None', 0, '0'] \ if None is source and tmdbid not in [None, 'None', 0, '0'] \
and self.should_try_image(static_image_path, 'tmdb'): and self.should_try_image(image_file, 'tmdb'):
tmdbimage = True tmdb_image = True
try: try:
tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TMDB).api_params.copy() tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TMDB).api_params.copy()
t = sickbeard.TVInfoAPI(TVINFO_TMDB).setup(**tvinfo_config) t = sickbeard.TVInfoAPI(TVINFO_TMDB).setup(**tvinfo_config)
tv_s = t.get_show(tmdbid, load_episodes=False, posters=True) show_obj = t.get_show(tmdbid, load_episodes=False, posters=True)
if tv_s and tv_s.poster: if show_obj and show_obj.poster:
s = tv_s.poster poster_url = show_obj.poster
except (BaseException, Exception): except (BaseException, Exception):
s = '' poster_url = ''
if s and not sg_helpers.download_file(s, static_image_path) and s.find('trakt.us'): if poster_url and not sg_helpers.download_file(poster_url, image_file) and poster_url.find('trakt.us'):
sg_helpers.download_file(s.replace('trakt.us', 'trakt.tv'), static_image_path) sg_helpers.download_file(poster_url.replace('trakt.us', 'trakt.tv'), image_file)
if tmdbimage and not ek.ek(os.path.isfile, static_image_path): if tmdb_image and not ek.ek(os.path.isfile, image_file):
self.create_dummy_image(static_image_path, 'tmdb') self.create_dummy_image(image_file, 'tmdb')
if None is source and tvdbid not in [None, 'None', 0, '0'] \ if None is source and tvdbid not in [None, 'None', 0, '0'] \
and not ek.ek(os.path.isfile, static_image_path) \ and not ek.ek(os.path.isfile, image_file) \
and self.should_try_image(static_image_path, 'tvdb'): and self.should_try_image(image_file, 'tvdb'):
try: try:
tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TVDB).api_params.copy() tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TVDB).api_params.copy()
tvinfo_config['posters'] = True tvinfo_config['posters'] = True
r = sickbeard.TVInfoAPI(TVINFO_TVDB).setup(**tvinfo_config)[helpers.try_int(tvdbid), False] t = sickbeard.TVInfoAPI(TVINFO_TVDB).setup(**tvinfo_config)[helpers.try_int(tvdbid), False]
if hasattr(r, 'data') and 'poster' in r.data: if hasattr(t, 'data') and 'poster' in t.data:
s = r.data['poster'] poster_url = t.data['poster']
except (BaseException, Exception): except (BaseException, Exception):
s = '' poster_url = ''
if s: if poster_url:
sg_helpers.download_file(s, static_image_path) sg_helpers.download_file(poster_url, image_file)
if not ek.ek(os.path.isfile, static_image_path): if not ek.ek(os.path.isfile, image_file):
self.create_dummy_image(static_image_path, 'tvdb') self.create_dummy_image(image_file, 'tvdb')
if ek.ek(os.path.isfile, static_image_path): if ek.ek(os.path.isfile, image_file):
self.delete_all_dummy_images(static_image_path) self.delete_all_dummy_images(image_file)
if not ek.ek(os.path.isfile, static_image_path): if not ek.ek(os.path.isfile, image_file):
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', image_file = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick',
'images', ('image-light.png', 'trans.png')[bool(int(trans))]) 'images', ('image-light.png', 'trans.png')[bool(int(trans))])
else: else:
helpers.set_file_timestamp(static_image_path, min_age=3, new_time=None) helpers.set_file_timestamp(image_file, min_age=3, new_time=None)
mime_type, encoding = MimeTypes().guess_type(static_image_path) return self.image_data(image_file)
self.set_header('Content-Type', mime_type)
with open(static_image_path, 'rb') as img:
return img.read()
@staticmethod @staticmethod
def should_load_image(filename, days=7): def should_load_image(filename, days=7):
@ -9401,45 +9401,28 @@ class CachedImages(MainHandler):
prefer_person = prefer_person in (True, '1', 'true', 'True') and char_obj.person and 1 < len(char_obj.person) \ prefer_person = prefer_person in (True, '1', 'true', 'True') and char_obj.person and 1 < len(char_obj.person) \
and bool(person_obj) and bool(person_obj)
image_file = None
if not prefer_person and (char_obj.thumb_url or char_obj.image_url): if not prefer_person and (char_obj.thumb_url or char_obj.image_url):
img_cache_obj = image_cache.ImageCache() image_cache_obj = image_cache.ImageCache()
filename_image_path, thumb_image_path = img_cache_obj.character_both_path(char_obj, show_obj, image_normal, image_thumb = image_cache_obj.character_both_path(char_obj, show_obj, person_obj=person_obj)
person_obj=person_obj) sg_helpers.make_dirs(image_cache_obj.characters_dir)
sg_helpers.make_dirs(img_cache_obj.characters_dir) if self.should_load_image(image_normal) and char_obj.image_url:
if self.should_load_image(filename_image_path) and char_obj.image_url: sg_helpers.download_file(char_obj.image_url, image_normal)
sg_helpers.download_file(char_obj.image_url, filename_image_path) if self.should_load_image(image_thumb) and char_obj.thumb_url:
if self.should_load_image(thumb_image_path) and char_obj.thumb_url: sg_helpers.download_file(char_obj.thumb_url, image_thumb)
sg_helpers.download_file(char_obj.thumb_url, thumb_image_path)
if thumb: primary, fallback = ((image_normal, image_thumb), (image_thumb, image_normal))[thumb]
if ek.ek(os.path.isfile, thumb_image_path): if ek.ek(os.path.isfile, primary):
static_image_path = thumb_image_path image_file = primary
elif ek.ek(os.path.isfile, filename_image_path): elif ek.ek(os.path.isfile, fallback):
# use normal image as fallback for thumb image_file = fallback
static_image_path = filename_image_path
else:
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images',
'poster-person.jpg')
else:
if ek.ek(os.path.isfile, filename_image_path):
static_image_path = filename_image_path
elif ek.ek(os.path.isfile, thumb_image_path):
# use thumb image as fallback
static_image_path = thumb_image_path
else:
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images',
'poster-person.jpg')
elif person_id: elif person_id:
return self.person(rid=char_id, pid=person_id, show_obj=show_obj, thumb=thumb) return self.person(rid=char_id, pid=person_id, show_obj=show_obj, thumb=thumb)
elif char_obj.person and (char_obj.person[0].thumb_url or char_obj.person[0].image_url): elif char_obj.person and (char_obj.person[0].thumb_url or char_obj.person[0].image_url):
return self.person(rid=char_id, pid=char_obj.person[0].id, show_obj=show_obj, return self.person(rid=char_id, pid=char_obj.person[0].id, show_obj=show_obj, thumb=thumb)
thumb=thumb)
else:
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'poster-person.jpg')
mime_type, encoding = MimeTypes().guess_type(static_image_path) return self.image_data(image_file, cast_default=True)
self.set_header('Content-Type', mime_type)
with open(static_image_path, 'rb') as img:
return img.read()
def person(self, rid=None, pid=None, tvid_prodid=None, show_obj=None, thumb=True, **kwargs): def person(self, rid=None, pid=None, tvid_prodid=None, show_obj=None, thumb=True, **kwargs):
_ = kwargs.get('oid') # suppress pyc non used var highlight, oid (original id) is a visual ui key _ = kwargs.get('oid') # suppress pyc non used var highlight, oid (original id) is a visual ui key
@ -9456,36 +9439,37 @@ class CachedImages(MainHandler):
return return
thumb = thumb in (True, '1', 'true', 'True') thumb = thumb in (True, '1', 'true', 'True')
image_file = None
if person_obj.thumb_url or person_obj.image_url: if person_obj.thumb_url or person_obj.image_url:
img_cache_obj = image_cache.ImageCache() image_cache_obj = image_cache.ImageCache()
filename_image_path, thumb_image_path = img_cache_obj.person_both_paths(person_obj) image_normal, image_thumb = image_cache_obj.person_both_paths(person_obj)
sg_helpers.make_dirs(img_cache_obj.characters_dir) sg_helpers.make_dirs(image_cache_obj.characters_dir)
if self.should_load_image(filename_image_path) and person_obj.image_url: if self.should_load_image(image_normal) and person_obj.image_url:
sg_helpers.download_file(person_obj.image_url, filename_image_path) sg_helpers.download_file(person_obj.image_url, image_normal)
if self.should_load_image(thumb_image_path) and person_obj.thumb_url: if self.should_load_image(image_thumb) and person_obj.thumb_url:
sg_helpers.download_file(person_obj.thumb_url, thumb_image_path) sg_helpers.download_file(person_obj.thumb_url, image_thumb)
if thumb:
if ek.ek(os.path.isfile, thumb_image_path): primary, fallback = ((image_normal, image_thumb), (image_thumb, image_normal))[thumb]
static_image_path = thumb_image_path if ek.ek(os.path.isfile, primary):
elif ek.ek(os.path.isfile, filename_image_path): image_file = primary
# use normal image as fallback for thumb elif ek.ek(os.path.isfile, fallback):
static_image_path = filename_image_path image_file = fallback
else:
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', return self.image_data(image_file, cast_default=True)
'poster-person.jpg')
else: def image_data(self, image_file, cast_default=False):
if ek.ek(os.path.isfile, filename_image_path): # type: (Optional[AnyStr], bool) -> Optional[Any]
static_image_path = filename_image_path """
elif ek.ek(os.path.isfile, thumb_image_path): return image file binary data
# use thumb image as fallback
static_image_path = thumb_image_path :param image_file: file path
else: :param cast_default: if required, use default cast file path if None is image_file
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', :return: binary image data or None
'poster-person.jpg') """
else: if cast_default and None is image_file:
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'poster-person.jpg') image_file = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'poster-person.jpg')
mime_type, encoding = MimeTypes().guess_type(static_image_path) mime_type, encoding = MimeTypes().guess_type(image_file)
self.set_header('Content-Type', mime_type) self.set_header('Content-Type', mime_type)
with open(static_image_path, 'rb') as img: with open(image_file, 'rb') as io_stream:
return img.read() return io_stream.read()

1
sickgear.py

@ -709,6 +709,7 @@ class SickGear(object):
ii.indexer AS ii_indexer, ii.indexer_id AS ii_indexer_id, ii.indexer AS ii_indexer, ii.indexer_id AS ii_indexer_id,
ii.last_update AS ii_ii_last_update, ii.last_update AS ii_ii_last_update,
ii.rating AS ii_rating, ii.runtimes AS ii_runtimes, ii.rating AS ii_rating, ii.runtimes AS ii_runtimes,
ii.is_mini_series AS ii_is_mini_series, ii.episode_count AS ii_episode_count,
ii.title AS ii_title, ii.votes AS ii_votes, ii.year AS ii_year, ii.title AS ii_title, ii.votes AS ii_votes, ii.year AS ii_year,
tsnf.fail_count AS tsnf_fail_count, tsnf.indexer AS tsnf_indexer, tsnf.fail_count AS tsnf_fail_count, tsnf.indexer AS tsnf_indexer,
tsnf.indexer_id AS tsnf_indexer_id, tsnf.last_check AS tsnf_last_check, tsnf.indexer_id AS tsnf_indexer_id, tsnf.last_check AS tsnf_last_check,

Loading…
Cancel
Save