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. 192
      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-header{margin-bottom:3px; font-weight:bolder}
#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-show .cast .thumb .cast-bg,
@ -1959,19 +1958,19 @@ a.addQTip{
#pin-glide{-moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px}
#display-show .cast .thumb{display:block}
#display-show .cast .thumb,
#display-show .cast .thumb .cast-bg{height:150px}
#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{height:150px; min-width:102px}
#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 .role,
#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 svg{padding:1px}
.glide-next{right:0.7rem}
.glide-prev{left:0.7rem}
#pin-glide,
#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}
.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)}
@ -3334,6 +3333,7 @@ select .selected:before{
background-position:-104px 0
}
#api-keys > #qr-body{display:none}
#api-keys > div{display:inline-block}
#api-keys span{float:left}
#api-keys span, #generate-result{line-height:22px}
@ -3341,10 +3341,11 @@ select .selected:before{
#api-keys .app-name{width:135px}
.qr-btn{margin-right:6px}
.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 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

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>
<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
<div><span class="details-title">Real name</span><span class="details-info">$person.real_name</span></div>
#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
<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
#if $person.height
#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>
<h3>Bio</h3>
<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>
#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>
<h3>Bio</h3>
<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>
#end if

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

@ -577,14 +577,14 @@
<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>
<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 = ''
#for $appname, $uid in $api_keys
#if not ($appname and $uid)
#continue
#if $appname and $uid
<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
<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
<div id="tip-addkeys"$tip_addkeys>Keys used by 3rd party programs to access SickGear will list here when generated</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']
#if 'runtimes' in $show_obj.imdb_info
#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
#if None is $startyear and $show_obj.startyear
@ -274,7 +277,7 @@
#if None is not $runtime
<div>
<span class="details-title">Runtime</span>
<span class="details-info">$runtime minutes</span>
<span class="details-info">$runtime mins.</span>
</div>
#end if
#if $show_obj.status

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

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

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

@ -256,7 +256,8 @@
#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>
#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
</td>
<td class="text-center">$sickbeard.TVInfoAPI($cur_show['new_tvid']).name</td>

192
gui/slick/js/cast.js

@ -1,93 +1,99 @@
function getContainedSize(img){
var width, height, ratio;
try {
ratio = img.naturalWidth / img.naturalHeight;
width = height = img.height;
width *= ratio;
if (width > img.width) {
height = width = img.width;
height /= ratio;
}
} catch (error) {
width = height = 0;
}
return {w:width, h:height}
}
function removeImageBackground(oImage){
var image$ = $(oImage).load(function(){
// swap out placeholder
if (-1 !== $(this).css('background-image').indexOf('poster-person.jpg')){
$(this).css('background', 'transparent');
var sizeImage = getContainedSize(oImage);
if (0 < sizeImage.w){
if (sizeImage.w < oImage.width){
$(this).css('width', 'auto');
} else if (sizeImage.h < oImage.height){
var heightDiff = oImage.height - sizeImage.h,
margin = Math.floor(heightDiff / 2),
roundError = Math.floor(oImage.height - sizeImage.h - (margin * 2)),
marginTop = margin + ((0 < roundError) ? roundError : 0);
$(this).css('height', 'auto')
.css('marginTop', marginTop)
.css('marginBottom', margin);
}
}
}
});
// trigger the load handler if the image is already loaded
if (image$[0].complete){
image$.trigger('load');
}
}
function scaleImage(oImage) {
var image$ = $(oImage).load(function() {
var sizeImage = getContainedSize(oImage),
containerWidth = $(oImage).parent().width()
if (sizeImage.w > containerWidth) {
// image width is wider than its container so reduce image height to down scale
var ratio = (containerWidth - 4) / sizeImage.w,
height = Math.floor(oImage.height * ratio);
$(this).css('height', height);
}
});
// trigger the load handler if the image is already loaded
if (image$[0].complete){
image$.trigger('load');
}
}
var addQTip = (function(){
$(this).css('cursor', 'help');
$(this).qtip({
show: {solo:true},
position: {viewport:$(window), my:'right center', at: 'left center', adjust:{y: 0, x: 2}},
style: {tip: {corner:true, method:'polygon'}, classes:'qtip-dark qtip-rounded qtip-shadow'}
});
});
$(function() {
objectFitImages();
$('#person .cast-bg, #character .cast-bg').each(function(i, oImage){
removeImageBackground(oImage);
});
$('#display-show .cast-bg').each(function (i, oImage){
scaleImage(oImage);
});
$('.addQTip').each(addQTip);
});
function getContainedSize(img){
var width, height, ratio;
try {
ratio = img.naturalWidth / img.naturalHeight;
width = height = img.height;
width *= ratio;
if (width > img.width) {
height = width = img.width;
height /= ratio;
}
} catch (error) {
width = height = 0;
}
return {w:width, h:height}
}
function removeImageBackground(oImage){
var image$ = $(oImage).load(function(){
// swap out placeholder
if (-1 !== $(this).css('background-image').indexOf('poster-person.jpg')){
$(this).css('background', 'transparent');
var sizeImage = getContainedSize(oImage);
if (0 < sizeImage.w){
if (sizeImage.w < oImage.width){
$(this).css('width', 'auto');
} else if (sizeImage.h < oImage.height){
var heightDiff = oImage.height - sizeImage.h,
marginBottom = Math.floor(heightDiff / 2),
roundError = Math.floor(oImage.height - sizeImage.h - (marginBottom * 2)),
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')
.css('marginTop', marginTop)
.css('marginBottom', marginBottom);
}
}
}
});
// trigger the load handler if the image is already loaded
if (image$[0].complete){
image$.trigger('load');
}
}
function scaleImage(oImage) {
var image$ = $(oImage).load(function() {
var sizeImage = getContainedSize(oImage),
containerWidth = $(oImage).parent().width()
if (sizeImage.w > containerWidth) {
// image width is wider than its container so reduce image height to down scale
var ratio = (containerWidth - 4) / sizeImage.w,
height = Math.floor(oImage.height * ratio);
$(this).css('height', height);
}
});
// trigger the load handler if the image is already loaded
if (image$[0].complete){
image$.trigger('load');
}
}
var addQTip = (function(){
$(this).css('cursor', 'help');
$(this).qtip({
show: {solo:true},
position: {viewport:$(window), my:'right center', at: 'left center', adjust:{y: 0, x: 2}},
style: {tip: {corner:true, method:'polygon'}, classes:'qtip-dark qtip-rounded qtip-shadow'}
});
});
$(function() {
objectFitImages();
$('#person .cast-bg, #character .cast-bg').each(function(i, oImage){
removeImageBackground(oImage);
});
$('#display-show .cast-bg').each(function (i, oImage){
scaleImage(oImage);
});
$('.addQTip').each(addQTip);
});

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,
startAt: -1 === startAt ? 0 : startAt,
peek: 0,
perSwipe: '|',
perView: initGlideVars.perView,
autoplay: initGlideVars.isNotEnd && slideTime()
});
$.glide.on('resize', function(){
$.calcSlideCount(!1);
$('#display-show .cast-bg').each(function (i, oImage){
scaleImage(oImage);
});
});
$.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();
return !1;
});
$('#display-show .cast-bg').each(function (i, oImage){
scaleImage(oImage);
});
});
window.onload = function(){
$.glide.mount();
};
function saveGlide(){
function saveGlide(saveTime){
if (!$.SickGear.glideFancyBoxOpen){
var params = {};
if (!slideTime()){
@ -139,7 +146,9 @@ $(document).ready(function() {
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);
}
}
@ -176,7 +185,7 @@ $(document).ready(function() {
}
pinState($(this));
$.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-za="' + item[result.ZaSort] + '" data-sort-za-combined="' + item[result.ZaCombined] + '">'
+ '<label><i></i>'
+ '<input id="which_series" type="radio"'
+ '<input type="radio"'
+ ' class="stepone-result-radio"'
+ (!1 === item[result.isInDB]
? ' 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() {
if (!!$('a[rel="dialog"]').length) {
$.getScript(sbRoot + '/js/fancybox/jquery.fancybox.js', function () {
$('head').append('<link rel="stylesheet" href="' + sbRoot + '/js/fancybox/jquery.fancybox.css">');
var fancy = sbRoot + '/js/fancybox/jquery.fancybox.min';
$.getScript(fancy + '.js', function () {
$('head').append('<link rel="stylesheet" href="' + fancy + '.css">');
$.sgFancyBoxOptions = {
loop: !0,

2
lib/tvdb_api/tvdb_api.py

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

4
sickbeard/databases/mainDB.py

@ -1715,6 +1715,10 @@ class ChangeShowData(db.SchemaUpgrade):
('network_country_code', 'TEXT', ''), ('network_id', 'NUMERIC'),
('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')
table_create_sql = {

28
sickbeard/db.py

@ -209,33 +209,29 @@ class DBConnection(object):
attempt = 0
sql_result = {'affected': 0, 'data': []}
sql_result = []
affected = 0
while 5 > attempt:
try:
def sql_action(query, result):
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)
cursor = self.connection.cursor()
if not log_transaction:
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:
for cur_query in queries:
logger.log(cur_query[0] if 1 == len(cur_query)
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()
if 0 < sql_result['affected']:
if 0 < affected:
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'])))
return sql_result['data']
len(queries), affected, helpers.maybe_plural(affected)))
return sql_result
except sqlite3.OperationalError as e:
sql_result['data'] = []
sql_result = []
if self.connection:
self.connection.rollback()
if not self.action_error(e):
@ -247,7 +243,7 @@ class DBConnection(object):
logger.error(u'Fatal error executing query: ' + ex(e))
raise
return sql_result['data']
return sql_result
@staticmethod
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'])
elif ShowQueueActions.ADD == cur_row['action_id']:
self.addShow(tvid=cur_row['tvid'], prodid=cur_row['prodid'],
add_to_db=False, allowlist=cur_row['allowlist'],
anime=bool_none(cur_row['anime']),
blocklist=cur_row['blocklist'], default_status=cur_row['default_status'],
flatten_folders=bool_none(cur_row['flatten_folders']), lang=cur_row['lang'],
new_show=bool(cur_row['new_show']), paused=bool_none(cur_row['paused']),
prune=cur_row['prune'], quality=cur_row['quality'],
scene=bool_none(cur_row['scene']), show_dir=cur_row['show_dir'],
show_name=cur_row['show_name'], subtitles=cur_row['subtitles'],
tag=cur_row['tag'], uid=cur_row['uid'],
upgrade_once=bool(cur_row['upgrade_once']),
wanted_begin=cur_row['wanted_begin'],
wanted_latest=cur_row['wanted_latest'])
self.add_show(
tvid=cur_row['tvid'], prodid=cur_row['prodid'], show_dir=cur_row['show_dir'],
quality=cur_row['quality'], upgrade_once=bool(cur_row['upgrade_once']),
wanted_begin=cur_row['wanted_begin'], wanted_latest=cur_row['wanted_latest'],
tag=cur_row['tag'],
paused=bool_none(cur_row['paused']), prune=cur_row['prune'],
default_status=cur_row['default_status'], scene=bool_none(cur_row['scene']),
subtitles=cur_row['subtitles'],
flatten_folders=bool_none(cur_row['flatten_folders']), anime=bool_none(cur_row['anime']),
blocklist=cur_row['blocklist'], allowlist=cur_row['allowlist'],
show_name=cur_row['show_name'], new_show=bool(cur_row['new_show']),
lang=cur_row['lang'], uid=cur_row['uid'], add_to_db=False)
except (BaseException, Exception) as e:
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:
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):
# type: (...) -> Dict
return {k: self.__dict__[k] for k in
@ -2644,6 +2669,8 @@ class TVShow(TVShowBase):
self._imdb_info = sql_result[0]
else:
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:
logger.log('%s: The next show update will attempt to find IMDb info for [%s]' %
(self.tvid_prodid, self.name), logger.DEBUG)
@ -2952,6 +2979,8 @@ class TVShow(TVShowBase):
'year': '',
'akas': '',
'runtimes': self._runtime,
'is_mini_series': False,
'episode_count': None,
'genres': '',
'countries': '',
'country_codes': '',
@ -3032,6 +3061,10 @@ class TVShow(TVShowBase):
imdb_info['year'] = try_int(imdb_tv.get('year'), '')
if isinstance(imdb_tv.get('runningTimeInMinutes'), (int, string_types)):
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)):
imdb_info['genres'] = '|'.join(filter_iter(lambda _v: _v, imdb_tv.get('genres')))
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 .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
# noinspection PyUnreachableCode
@ -1023,15 +1023,22 @@ class CMD_SickGearComingEpisodes(ApiCall):
ep['network'] = ep['episode_network'] or ep['network']
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])
# remove all field we don't want for api response
for f in ('localtime', 'ep_airtime', 'airtime', 'timestamp', 'show_airtime', 'network_id',
'network_is_stream', 'notify_list', 'src_update_timestamp', 'subtitles_lastsearch', 'subtitles',
'subtitles_searchcount', 'name', 'description', 'hasnfo', 'hastbn', 'last_update_indexer',
'file_size', 'flatten_folders', 'is_proper', 'prune', 'episode_network', 'network_country',
'network_country_code', 'show_timezone', 'release_group', 'release_name',
'rls_global_exclude_ignore', 'rls_global_exclude_require', 'rls_ignore_words',
'rls_require_words', 'location', 'ep_timezone', 'dvdorder', 'anime', 'sports'):
del ep[f]
for cur_f in list_keys(ep):
if cur_f not in [ # fields to preserve
'absolute_number', 'air_by_date', 'airdate', 'airs', 'archive_firstmatch',
'classification', 'data_network', 'data_show_name',
'ep_name', 'ep_plot', 'episode', 'episode_id', 'genre',
'imdb_id', 'imdb_url', 'indexer', 'indexer_id', 'indexerid',
'lang', 'local_datetime', 'network', 'overview', 'parsed_datetime', 'paused', 'prod_id',
'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
try:
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
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
@ -918,12 +918,13 @@ class MainHandler(WebHandler):
self.redirect('/view-shows/')
@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:
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()
@staticmethod
@ -3842,7 +3843,8 @@ class AddShows(Home):
total, slug, id_str = cur_match.groups()
for cur_tvid in sickbeard.TVInfoAPI().all_sources:
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:
ids_to_search[cur_tvid] = int(id_str.strip().replace('tt', ''))
except (BaseException, Exception):
@ -4271,7 +4273,8 @@ class AddShows(Home):
slug = sickbeard.TVInfoAPI(cur_tvid).config['slug']
try_id = has_shows and cur_try and sickbeard.showList[-1].ids[cur_tvid].get('id')
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:
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)
elif filename not in [None, 0, '0']:
file_name = filename
static_image_path = ek.ek(os.path.join, sickbeard.CACHE_DIR, 'images', path, file_name)
static_image_path = ek.ek(os.path.abspath, static_image_path.replace('\\', '/'))
if not ek.ek(os.path.isfile, static_image_path) and has_image_ext(file_name):
basepath = ek.ek(os.path.dirname, static_image_path)
image_file = ek.ek(os.path.join, sickbeard.CACHE_DIR, 'images', path, file_name)
image_file = ek.ek(os.path.abspath, image_file.replace('\\', '/'))
if not ek.ek(os.path.isfile, image_file) and has_image_ext(file_name):
basepath = ek.ek(os.path.dirname, image_file)
helpers.make_dirs(basepath)
s = ''
tmdbimage = False
poster_url = ''
tmdb_image = False
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'] \
and self.should_try_image(static_image_path, 'tmdb'):
tmdbimage = True
and self.should_try_image(image_file, 'tmdb'):
tmdb_image = True
try:
tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TMDB).api_params.copy()
t = sickbeard.TVInfoAPI(TVINFO_TMDB).setup(**tvinfo_config)
tv_s = t.get_show(tmdbid, load_episodes=False, posters=True)
if tv_s and tv_s.poster:
s = tv_s.poster
show_obj = t.get_show(tmdbid, load_episodes=False, posters=True)
if show_obj and show_obj.poster:
poster_url = show_obj.poster
except (BaseException, Exception):
s = ''
if s and not sg_helpers.download_file(s, static_image_path) and s.find('trakt.us'):
sg_helpers.download_file(s.replace('trakt.us', 'trakt.tv'), static_image_path)
if tmdbimage and not ek.ek(os.path.isfile, static_image_path):
self.create_dummy_image(static_image_path, 'tmdb')
poster_url = ''
if poster_url and not sg_helpers.download_file(poster_url, image_file) and poster_url.find('trakt.us'):
sg_helpers.download_file(poster_url.replace('trakt.us', 'trakt.tv'), image_file)
if tmdb_image and not ek.ek(os.path.isfile, image_file):
self.create_dummy_image(image_file, 'tmdb')
if None is source and tvdbid not in [None, 'None', 0, '0'] \
and not ek.ek(os.path.isfile, static_image_path) \
and self.should_try_image(static_image_path, 'tvdb'):
and not ek.ek(os.path.isfile, image_file) \
and self.should_try_image(image_file, 'tvdb'):
try:
tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TVDB).api_params.copy()
tvinfo_config['posters'] = True
r = sickbeard.TVInfoAPI(TVINFO_TVDB).setup(**tvinfo_config)[helpers.try_int(tvdbid), False]
if hasattr(r, 'data') and 'poster' in r.data:
s = r.data['poster']
t = sickbeard.TVInfoAPI(TVINFO_TVDB).setup(**tvinfo_config)[helpers.try_int(tvdbid), False]
if hasattr(t, 'data') and 'poster' in t.data:
poster_url = t.data['poster']
except (BaseException, Exception):
s = ''
if s:
sg_helpers.download_file(s, static_image_path)
if not ek.ek(os.path.isfile, static_image_path):
self.create_dummy_image(static_image_path, 'tvdb')
if ek.ek(os.path.isfile, static_image_path):
self.delete_all_dummy_images(static_image_path)
if not ek.ek(os.path.isfile, static_image_path):
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick',
'images', ('image-light.png', 'trans.png')[bool(int(trans))])
poster_url = ''
if poster_url:
sg_helpers.download_file(poster_url, image_file)
if not ek.ek(os.path.isfile, image_file):
self.create_dummy_image(image_file, 'tvdb')
if ek.ek(os.path.isfile, image_file):
self.delete_all_dummy_images(image_file)
if not ek.ek(os.path.isfile, image_file):
image_file = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick',
'images', ('image-light.png', 'trans.png')[bool(int(trans))])
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)
self.set_header('Content-Type', mime_type)
with open(static_image_path, 'rb') as img:
return img.read()
return self.image_data(image_file)
@staticmethod
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) \
and bool(person_obj)
image_file = None
if not prefer_person and (char_obj.thumb_url or char_obj.image_url):
img_cache_obj = image_cache.ImageCache()
filename_image_path, thumb_image_path = img_cache_obj.character_both_path(char_obj, show_obj,
person_obj=person_obj)
sg_helpers.make_dirs(img_cache_obj.characters_dir)
if self.should_load_image(filename_image_path) and char_obj.image_url:
sg_helpers.download_file(char_obj.image_url, filename_image_path)
if self.should_load_image(thumb_image_path) and char_obj.thumb_url:
sg_helpers.download_file(char_obj.thumb_url, thumb_image_path)
if thumb:
if ek.ek(os.path.isfile, thumb_image_path):
static_image_path = thumb_image_path
elif ek.ek(os.path.isfile, filename_image_path):
# use normal image as fallback for thumb
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')
image_cache_obj = image_cache.ImageCache()
image_normal, image_thumb = image_cache_obj.character_both_path(char_obj, show_obj, person_obj=person_obj)
sg_helpers.make_dirs(image_cache_obj.characters_dir)
if self.should_load_image(image_normal) and char_obj.image_url:
sg_helpers.download_file(char_obj.image_url, image_normal)
if self.should_load_image(image_thumb) and char_obj.thumb_url:
sg_helpers.download_file(char_obj.thumb_url, image_thumb)
primary, fallback = ((image_normal, image_thumb), (image_thumb, image_normal))[thumb]
if ek.ek(os.path.isfile, primary):
image_file = primary
elif ek.ek(os.path.isfile, fallback):
image_file = fallback
elif person_id:
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):
return self.person(rid=char_id, pid=char_obj.person[0].id, show_obj=show_obj,
thumb=thumb)
else:
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'poster-person.jpg')
return self.person(rid=char_id, pid=char_obj.person[0].id, show_obj=show_obj, thumb=thumb)
mime_type, encoding = MimeTypes().guess_type(static_image_path)
self.set_header('Content-Type', mime_type)
with open(static_image_path, 'rb') as img:
return img.read()
return self.image_data(image_file, cast_default=True)
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
@ -9456,36 +9439,37 @@ class CachedImages(MainHandler):
return
thumb = thumb in (True, '1', 'true', 'True')
image_file = None
if person_obj.thumb_url or person_obj.image_url:
img_cache_obj = image_cache.ImageCache()
filename_image_path, thumb_image_path = img_cache_obj.person_both_paths(person_obj)
sg_helpers.make_dirs(img_cache_obj.characters_dir)
if self.should_load_image(filename_image_path) and person_obj.image_url:
sg_helpers.download_file(person_obj.image_url, filename_image_path)
if self.should_load_image(thumb_image_path) and person_obj.thumb_url:
sg_helpers.download_file(person_obj.thumb_url, thumb_image_path)
if thumb:
if ek.ek(os.path.isfile, thumb_image_path):
static_image_path = thumb_image_path
elif ek.ek(os.path.isfile, filename_image_path):
# use normal image as fallback for thumb
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')
else:
static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'poster-person.jpg')
image_cache_obj = image_cache.ImageCache()
image_normal, image_thumb = image_cache_obj.person_both_paths(person_obj)
sg_helpers.make_dirs(image_cache_obj.characters_dir)
if self.should_load_image(image_normal) and person_obj.image_url:
sg_helpers.download_file(person_obj.image_url, image_normal)
if self.should_load_image(image_thumb) and person_obj.thumb_url:
sg_helpers.download_file(person_obj.thumb_url, image_thumb)
primary, fallback = ((image_normal, image_thumb), (image_thumb, image_normal))[thumb]
if ek.ek(os.path.isfile, primary):
image_file = primary
elif ek.ek(os.path.isfile, fallback):
image_file = fallback
return self.image_data(image_file, cast_default=True)
def image_data(self, image_file, cast_default=False):
# type: (Optional[AnyStr], bool) -> Optional[Any]
"""
return image file binary data
:param image_file: file path
:param cast_default: if required, use default cast file path if None is image_file
:return: binary image data or None
"""
if cast_default and None is image_file:
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)
with open(static_image_path, 'rb') as img:
return img.read()
with open(image_file, 'rb') as io_stream:
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.last_update AS ii_ii_last_update,
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,
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,

Loading…
Cancel
Save