Browse Source

Add menu Shows/"TVmaze Cards".

Add show name/networks card user input filter.
Change only auto refresh card view if a recoverable error occurs.
tags/release_0.25.1
JackDandy 4 years ago
parent
commit
042e1da903
  1. 3
      CHANGES.md
  2. 3
      gui/slick/css/style.css
  3. 85
      gui/slick/interfaces/default/home_browseShows.tmpl
  4. 4
      gui/slick/interfaces/default/inc_top.tmpl
  5. 108
      lib/tvmaze_api/tvmaze_api.py
  6. 9
      sickbeard/__init__.py
  7. 158
      sickbeard/webserve.py

3
CHANGES.md

@ -79,6 +79,9 @@
* Add fetch extra data fallback from TMDB for persons * Add fetch extra data fallback from TMDB for persons
* Change fanart icon * Change fanart icon
* Add provider TorrentDB * Add provider TorrentDB
* Add menu Shows/"TVmaze Cards"
* Add show name/networks card user input filter
* Change only auto refresh card view if a recoverable error occurs
[develop changelog] [develop changelog]

3
gui/slick/css/style.css

@ -707,7 +707,8 @@ inc_top.tmpl
} }
.sgicon-tvmaze:before{ .sgicon-tvmaze:before{
content:"\e89a" content:"\e89a";
margin-right:14px
} }
.sgicon-emby:before{ .sgicon-emby:before{

85
gui/slick/interfaces/default/home_browseShows.tmpl

@ -8,6 +8,8 @@
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp# <% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp#
## ##
#set $mode = $kwargs and $kwargs.get('mode', '') #set $mode = $kwargs and $kwargs.get('mode', '')
#set $use_network = $kwargs.get('use_networks', False)
#set $use_returning = 'returning' == mode
#set $use_votes = $kwargs and $kwargs.get('use_votes', True) #set $use_votes = $kwargs and $kwargs.get('use_votes', True)
#set $use_ratings = $kwargs and $kwargs.get('use_ratings', True) #set $use_ratings = $kwargs and $kwargs.get('use_ratings', True)
## ##
@ -20,7 +22,11 @@
## ##
#import os.path #import os.path
#include $os.path.join($sg_str('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sg_str('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl')
<script>
var config = {
homeSearchFocus: #echo ['!1','!0'][$sg_var('HOME_SEARCH_FOCUS', True)]#,
};
</script>
<script type="text/javascript" src="$sg_root/js/plotTooltip.js?v=$sbPID"></script> <script type="text/javascript" src="$sg_root/js/plotTooltip.js?v=$sbPID"></script>
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">
@ -78,6 +84,13 @@ $(document).ready(function(){
#end if #end if
return name.toLowerCase(); return name.toLowerCase();
}, },
#if $use_returning
#raw
returning: function( itemElem ) {
return $( itemElem ).attr('data-returning') || '';
},
#end raw
#end if
#if $use_ratings #if $use_ratings
rating: '[data-rating] parseFloat', rating: '[data-rating] parseFloat',
#end if #end if
@ -98,6 +111,9 @@ $(document).ready(function(){
#end raw #end raw
case 'order': case 'order':
case 'premiered': case 'premiered':
#if $use_returning
case 'returning':
#end if
#if $use_votes #if $use_votes
case 'votes': case 'votes':
#end if #end if
@ -222,7 +238,8 @@ $(document).ready(function(){
$('#showfilter').on('change', function(){ $('#showfilter').on('change', function(){
var filterValue = this.value; var filterValue = this.value;
if (-1 == filterValue.indexOf('trakt') && -1 == filterValue.indexOf('imdb') && -1 == filterValue.indexOf('mc_') if (-1 == filterValue.indexOf('trakt') && -1 == filterValue.indexOf('imdb') && -1 == filterValue.indexOf('mc_')
&& -1 == filterValue.indexOf('tvc_') && -1 == filterValue.indexOf('ne_') && -1 == filterValue.indexOf('_ne') && -1 == filterValue.indexOf('tvc_') && -1 == filterValue.indexOf('tvm_')
&& -1 == filterValue.indexOf('ne_') && -1 == filterValue.indexOf('_ne')
&& -1 == filterValue.indexOf('default')) { && -1 == filterValue.indexOf('default')) {
var el$ = $('#container') var el$ = $('#container')
el$.on('layoutComplete', llUpdate); el$.on('layoutComplete', llUpdate);
@ -241,6 +258,34 @@ $(document).ready(function(){
}); });
$('.service, .browse-image').each(addQTip); $('.service, .browse-image').each(addQTip);
if (config.homeSearchFocus) {
$('#search_show_name').focus();
}
llUpdate = (function(){
$.ll.handleScroll();
});
$('#search_show_name').on('input', function() {
var obj = $('#container');
obj.one('layoutComplete', llUpdate);
obj.isotope({
filter: function () {
var reSearch = RegExp($('#search_show_name').val(), 'i');
return reSearch.test($(this).attr('data-name'))#end raw##slurp#
#if $use_network#
|| reSearch.test(\$(this).attr('data-network'))#end if#;
#raw
}
});
});
$('.resetshows').click(function() {
var input = $('#search_show_name');
if ('' !== input.val()){
input.val('').trigger('input').change();
if (config.homeSearchFocus)
input.focus();
}
});
}); });
#end raw #end raw
@ -279,8 +324,14 @@ $(document).ready(function(){
</optgroup> </optgroup>
<optgroup label="Sort by"> <optgroup label="Sort by">
<option value="by_name"#if 'by_name' in $saved_showsort_sortby and not $reset_showsort_sortby#$selected>>&nbsp;#else#>#end if#Name</option> <option value="by_name"#if 'by_name' in $saved_showsort_sortby and not $reset_showsort_sortby#$selected>>&nbsp;#else#>#end if#Name</option>
# omit for TVMaze as Original == First Aired
#if 'TVmaze' not in $browse_type
<option value="by_order"#if 'by_order' in $saved_showsort_sortby or $reset_showsort_sortby#$selected>>&nbsp;#else#>#end if#Original</option> <option value="by_order"#if 'by_order' in $saved_showsort_sortby or $reset_showsort_sortby#$selected>>&nbsp;#else#>#end if#Original</option>
#end if
<option value="by_premiered"#if 'by_premiered' in $saved_showsort_sortby and not $reset_showsort_sortby#$selected>>&nbsp;#else#>#end if#First aired</option> <option value="by_premiered"#if 'by_premiered' in $saved_showsort_sortby and not $reset_showsort_sortby#$selected>>&nbsp;#else#>#end if#First aired</option>
#if $use_returning
<option value="by_returning"#if 'by_returning' in $saved_showsort_sortby and not $reset_showsort_sortby#$selected>>&nbsp;#else#>#end if#Returning</option>
#end if
#if $use_votes #if $use_votes
<option value="by_votes"#if 'by_votes' in $saved_showsort_sortby#$selected>>&nbsp;#else#>#end if#Votes</option> <option value="by_votes"#if 'by_votes' in $saved_showsort_sortby#$selected>>&nbsp;#else#>#end if#Votes</option>
#end if #end if
@ -381,6 +432,11 @@ $(document).ready(function(){
#end for #end for
<option value="tvc_latest"#echo ('', selected)['latest' == $mode]#>Latest additions</option> <option value="tvc_latest"#echo ('', selected)['latest' == $mode]#>Latest additions</option>
</optgroup> </optgroup>
#elif 'TVmaze' == $browse_type
<optgroup label="TVmaze">
<option value="tvm_premieres"#echo ('', selected)['premieres' == $mode]#>Premieres</option>
<option value="tvm_returning"#echo ('', selected)['returning' == $mode]#>Returning</option>
</optgroup>
#elif 'Nextepisode' == $browse_type #elif 'Nextepisode' == $browse_type
<optgroup label="Nextepisode"> <optgroup label="Nextepisode">
<option value="ne_newpop"#echo ('', selected)['newpop' == $mode]#>Popular premiered</option> <option value="ne_newpop"#echo ('', selected)['newpop' == $mode]#>Popular premiered</option>
@ -393,9 +449,13 @@ $(document).ready(function(){
</select> </select>
#end if #end if
</div> </div>
<div class="pull-right" style="clear:right">
<input id="search_show_name" class="search form-control form-control-inline input-sm input200" type="search" placeholder="Filter Show Name#if $use_network#/Network#end if#">
&nbsp;<button type="button" class="resetshows btn btn-inline">Reset Filter</button>
</div>
<h4 style="float:left;margin:0 0 0 2px">$browse_title</h4> <h4 style="float:left;margin:0 0 0 2px">$browse_title</h4>
#if $kwargs and $kwargs.get('oldest') #if $kwargs and $kwargs.get('oldest')
<div class="grey-text" style="clear:both;margin-left:2px;font-size:0.85em"> <div class="grey-text" style="clear:left;margin-left:2px;font-size:0.85em">
First aired from $kwargs['oldest'] until $kwargs['newest'] First aired from $kwargs['oldest'] until $kwargs['newest']
</div> </div>
#end if #end if
@ -421,11 +481,14 @@ $(document).ready(function(){
#set $hide = ('', '%shide ' % ('', 'to-')['.hide' in $saved_showsort_view])[bool($this_show.get('hide'))] #set $hide = ('', '%shide ' % ('', 'to-')['.hide' in $saved_showsort_view])[bool($this_show.get('hide'))]
#set $data_rating = $try_float($this_show['rating']) #set $data_rating = $try_float($this_show['rating'])
<div class="show-card ${hide}${known}inlibrary" data-name="#echo re.sub(r'([\'\"])', r'', $this_show['title'])#" data_id="$show_id"#if $use_ratings# data-rating="$data_rating"#end if##if $use_votes# data-votes="$this_show['votes']"#end if# data-premiered="$this_show['premiered']" data-order="$this_show['order']"> <div class="show-card ${hide}${known}inlibrary" data-name="#echo re.sub(r'([\'\"])', r'', $this_show['title'])#" data_id="$show_id"#if $use_ratings# data-rating="$data_rating"#end if##if $use_votes# data-votes="$this_show['votes']"#end if# data-premiered="$this_show['premiered']"#if $use_returning# data-returning="$this_show['returning']"#end if# data-order="$this_show['order']"#if $use_network# data-network="$this_show['network']"#end if#>
<div class="show-card-inner"> <div class="show-card-inner">
<div class="browse-image"> <div class="browse-image">
<a class="browse-image" href="<%= anon_url(this_show['url_src_db']) %>" target="_blank" <a class="browse-image" href="<%= anon_url(this_show['url_src_db']) %>" target="_blank"
title="<span style='color: rgb(66, 139, 202)'>$re.sub(r'(?m)\s+\((?:19|20)\d\d\)\s*$', '', $title_html)</span>#if $this_show['genres']#<br /><div style='font-weight:bold'>(<em>$this_show['genres']</em>)</div>#end if# title="<span style='color: rgb(66, 139, 202)'>$re.sub(r'(?m)\s+\((?:19|20)\d\d\)\s*$', '', $title_html)</span>
#if $this_show['genres']#<br><div style='font-weight:bold'>(<em>$this_show['genres']</em>)</div>#end if#
#if $kwargs and $use_returning#<span style='font-weight:bold;font-size:0.9em;color:#888'><em>Season $this_show['episode_season'] returns $this_show['returning_str']</em></span>#end if#
#if $this_show.get('country') or $this_show.get('language') #if $this_show.get('country') or $this_show.get('language')
<p style='line-height:15px;margin-bottom:2px'> <p style='line-height:15px;margin-bottom:2px'>
#if $this_show.get('country') #if $this_show.get('country')
@ -496,18 +559,18 @@ $(document).ready(function(){
$kwargs['error_msg'] $kwargs['error_msg']
#else #else
$browse_type did not return results, this can happen from time to time. $browse_type did not return results, this can happen from time to time.
<br /><br />This view should auto refresh every 10 mins. <br><br>This view should auto refresh every 10 mins.
#end if
</p>
</div>
</div>
#end if
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">
<!-- <!--
window.setInterval('location.reload(true)', 600000); // Refresh every 10 minutes window.setInterval('location.reload(true)', 600000); // Refresh every 10 minutes
//--> //-->
</script> </script>
#end if
</p>
</div>
</div>
#end if
<script type="text/javascript" src="$sg_root/js/lazyload/lazyload.min.js?v=$sbPID"></script> <script type="text/javascript" src="$sg_root/js/lazyload/lazyload.min.js?v=$sbPID"></script>
<script type="text/javascript" src="$sg_root/js/inc_bottom.js?v=$sbPID"></script> <script type="text/javascript" src="$sg_root/js/inc_bottom.js?v=$sbPID"></script>

4
gui/slick/interfaces/default/inc_top.tmpl

@ -188,6 +188,10 @@
#set $tvc_mode = $tvc_modes.get($sg_var('TVC_MRU'), 'new shows') #set $tvc_mode = $tvc_modes.get($sg_var('TVC_MRU'), 'new shows')
<li><a href="$sbRoot/add-shows/tvc-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-tvc"></i>TV Calendar Cards <li><a href="$sbRoot/add-shows/tvc-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-tvc"></i>TV Calendar Cards
<div class="menu-item-desc opacity60">$tvc_mode...</div></a></li> <div class="menu-item-desc opacity60">$tvc_mode...</div></a></li>
#set $tvm_modes = dict(tvm_premieres='new shows', tvm_returning='returning')
#set $tvm_mode = $tvm_modes.get($sg_var('TVM_MRU'), 'new shows')
<li><a href="$sbRoot/add-shows/tvm-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-tvmaze"></i>TVmaze Cards
<div class="menu-item-desc opacity60">$tvm_mode...</div></a></li>
#set $ne_modes = dict(ne_newpop='new popular', ne_newtop='new top rated', ne_upcoming='upcoming', ne_trending='trending') #set $ne_modes = dict(ne_newpop='new popular', ne_newtop='new top rated', ne_upcoming='upcoming', ne_trending='trending')
#set $ne_mode = $ne_modes.get($sg_var('NE_MRU'), 'new popular') #set $ne_mode = $ne_modes.get($sg_var('NE_MRU'), 'new popular')
<li><a href="$sbRoot/add-shows/ne-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-ne"></i>Next Episode Cards <li><a href="$sbRoot/add-shows/ne-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-ne"></i>Next Episode Cards

108
lib/tvmaze_api/tvmaze_api.py

@ -6,18 +6,21 @@ __author__ = 'Prinz23'
__version__ = '1.0' __version__ = '1.0'
__api_version__ = '1.0.0' __api_version__ = '1.0.0'
import logging
import datetime import datetime
import logging
import re
import requests import requests
from requests.packages.urllib3.util.retry import Retry from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter from requests.adapters import HTTPAdapter
from six import iteritems from six import iteritems
from sg_helpers import get_url, try_int from sg_helpers import get_url, try_int
from lib.dateutil.parser import parser from lib.dateutil.parser import parser
# noinspection PyProtectedMember
from lib.dateutil.tz.tz import _datetime_to_timestamp from lib.dateutil.tz.tz import _datetime_to_timestamp
from lib.exceptions_helper import ConnectionSkipException, ex from lib.exceptions_helper import ConnectionSkipException, ex
from .tvmaze_exceptions import * # from .tvmaze_exceptions import *
from lib.tvinfo_base import TVInfoBase, TVInfoImage, TVInfoImageSize, TVInfoImageType, Character, Crew, \ from lib.tvinfo_base import TVInfoBase, TVInfoImage, TVInfoImageSize, TVInfoImageType, Character, Crew, \
crew_type_names, Person, RoleTypes, TVInfoShow, TVInfoEpisode, TVInfoIDs, TVInfoSeason, PersonGenders, \ crew_type_names, Person, RoleTypes, TVInfoShow, TVInfoEpisode, TVInfoIDs, TVInfoSeason, PersonGenders, \
TVINFO_TVMAZE, TVINFO_TVDB, TVINFO_IMDB TVINFO_TVMAZE, TVINFO_TVDB, TVINFO_IMDB
@ -25,7 +28,7 @@ from lib.pytvmaze import tvmaze
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
if False: if False:
from typing import Any, AnyStr, Dict, List, Optional, Union from typing import Any, AnyStr, Dict, List, Optional
from six import integer_types from six import integer_types
log = logging.getLogger('tvmaze.api') log = logging.getLogger('tvmaze.api')
@ -38,8 +41,10 @@ def tvmaze_endpoint_standard_get(url):
retries = Retry(total=5, retries = Retry(total=5,
backoff_factor=0.1, backoff_factor=0.1,
status_forcelist=[429]) status_forcelist=[429])
# noinspection HttpUrlsUsage
s.mount('http://', HTTPAdapter(max_retries=retries)) s.mount('http://', HTTPAdapter(max_retries=retries))
s.mount('https://', HTTPAdapter(max_retries=retries)) s.mount('https://', HTTPAdapter(max_retries=retries))
# noinspection PyProtectedMember
return get_url(url, json=True, session=s, hooks={'response': tvmaze._record_hook}, raise_skip_exception=True) return get_url(url, json=True, session=s, hooks={'response': tvmaze._record_hook}, raise_skip_exception=True)
@ -140,9 +145,9 @@ class TvMaze(TVInfoBase):
'network': s.network and s.network.name, 'network': s.network and s.network.name,
'genres': s.genres, 'overview': s.summary, 'genres': s.genres, 'overview': s.summary,
'aliases': [a.name for a in s.akas], 'image': s.image and s.image.get('original'), 'aliases': [a.name for a in s.akas], 'image': s.image and s.image.get('original'),
'ids': TVInfoIDs(tvdb=s.externals.get('thetvdb'), rage=s.externals.get('tvrage'), tvmaze=s.id, 'ids': TVInfoIDs(
imdb=s.externals.get('imdb') and try_int(s.externals.get('imdb').replace('tt', ''), tvdb=s.externals.get('thetvdb'), rage=s.externals.get('tvrage'), tvmaze=s.id,
None))} imdb=s.externals.get('imdb') and try_int(s.externals.get('imdb').replace('tt', ''), None))}
results = [] results = []
if ids: if ids:
for t, p in iteritems(ids): for t, p in iteritems(ids):
@ -201,11 +206,11 @@ class TvMaze(TVInfoBase):
return results return results
def _set_episode(self, sid, ep_obj): def _set_episode(self, sid, ep_obj):
for _k, _s in [('seasonnumber', 'season_number'), ('episodenumber', 'episode_number'), for _k, _s in (
('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'), ('seasonnumber', 'season_number'), ('episodenumber', 'episode_number'),
('airtime', 'airtime'), ('runtime', 'runtime'), ('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'),
('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'), ('airtime', 'airtime'), ('runtime', 'runtime'),
('filename', 'image')]: ('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'), ('filename', 'image')):
if 'filename' == _k: if 'filename' == _k:
image = getattr(ep_obj, _s, {}) or {} image = getattr(ep_obj, _s, {}) or {}
image = image.get('original') or image.get('medium') image = image.get('original') or image.get('medium')
@ -221,7 +226,8 @@ class TvMaze(TVInfoBase):
except (BaseException, Exception): except (BaseException, Exception):
pass pass
def _set_network(self, show_obj, network, is_stream): @staticmethod
def _set_network(show_obj, network, is_stream):
show_obj['network'] = network.name show_obj['network'] = network.name
show_obj['network_timezone'] = network.timezone show_obj['network_timezone'] = network.timezone
show_obj['network_country'] = network.country show_obj['network_country'] = network.country
@ -229,17 +235,21 @@ class TvMaze(TVInfoBase):
show_obj['network_id'] = network.maze_id show_obj['network_id'] = network.maze_id
show_obj['network_is_stream'] = is_stream show_obj['network_is_stream'] = is_stream
def _get_show_data(self, sid, language, get_ep_info=False, banners=False, posters=False, seasons=False, def _get_tvm_show(self, show_id, get_ep_info):
seasonwides=False, fanart=False, actors=False, **kwargs):
log.debug('Getting all series data for %s' % sid)
try: try:
self.show_not_found = False self.show_not_found = False
show_data = tvm_obj.get_show(maze_id=sid, embed='cast%s' % ('', ',episodeswithspecials')[get_ep_info]) return tvm_obj.get_show(maze_id=show_id, embed='cast%s' % ('', ',episodeswithspecials')[get_ep_info])
except tvmaze.ShowNotFound: except tvmaze.ShowNotFound:
self.show_not_found = True self.show_not_found = True
return False except (BaseException, Exception):
except (BaseException, Exception) as e: log.debug('Error getting data for tvmaze show id: %s' % show_id)
log.debug('Error getting data for tvmaze show id: %s' % sid)
def _get_show_data(self, sid, language, get_ep_info=False, banners=False, posters=False, seasons=False,
seasonwides=False, fanart=False, actors=False, **kwargs):
log.debug('Getting all series data for %s' % sid)
show_data = self._get_tvm_show(sid, get_ep_info)
if not show_data:
return False return False
show_obj = self.shows[sid].__dict__ show_obj = self.shows[sid].__dict__
@ -330,19 +340,20 @@ class TvMaze(TVInfoBase):
except (BaseException, Exception): except (BaseException, Exception):
print('error') print('error')
pass pass
existing_person.p_id, existing_person.name, existing_person.image, existing_person.gender, \ (existing_person.p_id, existing_person.name, existing_person.image, existing_person.gender,
existing_person.birthdate, existing_person.deathdate, existing_person.country, \ existing_person.birthdate, existing_person.deathdate, existing_person.country,
existing_person.country_code, existing_person.country_timezone, existing_person.thumb_url, \ existing_person.country_code, existing_person.country_timezone, existing_person.thumb_url,
existing_person.url, existing_person.ids = \ existing_person.url, existing_person.ids) = \
ch.person.id, ch.person.name, ch.person.image and ch.person.image.get('original'), \ (ch.person.id, ch.person.name,
PersonGenders.named.get(ch.person.gender and ch.person.gender.lower(), ch.person.image and ch.person.image.get('original'),
PersonGenders.unknown),\ PersonGenders.named.get(
person.birthdate, person.deathdate,\ ch.person.gender and ch.person.gender.lower(), PersonGenders.unknown),
ch.person.country and ch.person.country.get('name'),\ person.birthdate, person.deathdate,
ch.person.country and ch.person.country.get('code'),\ ch.person.country and ch.person.country.get('name'),
ch.person.country and ch.person.country.get('timezone'),\ ch.person.country and ch.person.country.get('code'),
ch.person.image and ch.person.image.get('medium'),\ ch.person.country and ch.person.country.get('timezone'),
ch.person.url, {TVINFO_TVMAZE: ch.person.id} ch.person.image and ch.person.image.get('medium'),
ch.person.url, {TVINFO_TVMAZE: ch.person.id})
else: else:
existing_character.person.append(person) existing_character.person.append(person)
else: else:
@ -396,7 +407,7 @@ class TvMaze(TVInfoBase):
show_obj['ids'] = TVInfoIDs(tvdb=show_data.externals.get('thetvdb'), show_obj['ids'] = TVInfoIDs(tvdb=show_data.externals.get('thetvdb'),
rage=show_data.externals.get('tvrage'), rage=show_data.externals.get('tvrage'),
imdb=show_data.externals.get('imdb') and imdb=show_data.externals.get('imdb') and
try_int(show_data.externals.get('imdb').replace('tt', ''), None)) try_int(show_data.externals.get('imdb').replace('tt', ''), None))
if show_data.network: if show_data.network:
self._set_network(show_obj, show_data.network, False) self._set_network(show_obj, show_data.network, False)
@ -406,15 +417,8 @@ class TvMaze(TVInfoBase):
if get_ep_info and not getattr(self.shows.get(sid), 'ep_loaded', False): if get_ep_info and not getattr(self.shows.get(sid), 'ep_loaded', False):
log.debug('Getting all episodes of %s' % sid) log.debug('Getting all episodes of %s' % sid)
if None is show_data: if None is show_data:
try: show_data = self._get_tvm_show(sid, get_ep_info)
self.show_not_found = False if not show_data:
show_data = tvm_obj.get_show(maze_id=sid, embed='cast%s' % ('', ',episodeswithspecials')[
get_ep_info])
except tvmaze.ShowNotFound:
self.show_not_found = True
return False
except (BaseException, Exception) as e:
log.debug('Error getting data for tvmaze show id: %s' % sid)
return False return False
if show_data.episodes: if show_data.episodes:
@ -543,3 +547,21 @@ class TvMaze(TVInfoBase):
p = None p = None
if p: if p:
return self._convert_person(p) return self._convert_person(p)
def get_premieres(self):
# type: (...) -> List[tvmaze.Episode]
return self.filtered_schedule(lambda e: all([1 == e.season_number, 1 == e.episode_number]))
def get_returning(self):
# type: (...) -> List[tvmaze.Episode]
return self.filtered_schedule(lambda e: all([1 != e.season_number, 1 == e.episode_number]))
@staticmethod
def filtered_schedule(condition):
try:
return sorted([
e for e in tvmaze.get_full_schedule()
if condition(e) and (None is e.show.language or re.search('(?i)eng|jap', e.show.language))],
key=lambda x: x.show.premiered or x.airstamp)
except(BaseException, Exception):
return []

9
sickbeard/__init__.py

@ -618,6 +618,7 @@ else:
MC_MRU = '' MC_MRU = ''
TVC_MRU = '' TVC_MRU = ''
TVM_MRU = ''
NE_MRU = '' NE_MRU = ''
COOKIE_SECRET = b64encodestring(uuid.uuid4().bytes + uuid.uuid4().bytes) COOKIE_SECRET = b64encodestring(uuid.uuid4().bytes + uuid.uuid4().bytes)
@ -763,7 +764,7 @@ def init_stage_1(console_logging):
global USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, \ global USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, \
TRAKT_USE_WATCHLIST, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, \ TRAKT_USE_WATCHLIST, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, \
TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \ TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \
MC_MRU, TVC_MRU, NE_MRU, \ MC_MRU, TVC_MRU, TVM_MRU, NE_MRU, \
USE_SLACK, SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD, \ USE_SLACK, SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD, \
SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \ SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \
USE_DISCORD, DISCORD_NOTIFY_ONSNATCH, DISCORD_NOTIFY_ONDOWNLOAD, \ USE_DISCORD, DISCORD_NOTIFY_ONSNATCH, DISCORD_NOTIFY_ONDOWNLOAD, \
@ -1207,6 +1208,7 @@ def init_stage_1(console_logging):
MC_MRU = check_setting_str(CFG, 'Metacritic', 'mc_mru', '') MC_MRU = check_setting_str(CFG, 'Metacritic', 'mc_mru', '')
TVC_MRU = check_setting_str(CFG, 'TVCalendar', 'tvc_mru', '') TVC_MRU = check_setting_str(CFG, 'TVCalendar', 'tvc_mru', '')
TVM_MRU = check_setting_str(CFG, 'TVmaze', 'tvm_mru', '')
NE_MRU = check_setting_str(CFG, 'NextEpisode', 'ne_mru', '') NE_MRU = check_setting_str(CFG, 'NextEpisode', 'ne_mru', '')
USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0)) USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0))
@ -1692,7 +1694,7 @@ def init_stage_2():
run_delay=datetime.timedelta(minutes=5), run_delay=datetime.timedelta(minutes=5),
threadName='PLEXWATCHEDSTATE') threadName='PLEXWATCHEDSTATE')
MEMCACHE['history_tab_limit'] = 10 MEMCACHE['history_tab_limit'] = 11
MEMCACHE['history_tab'] = History.menu_tab(MEMCACHE['history_tab_limit']) MEMCACHE['history_tab'] = History.menu_tab(MEMCACHE['history_tab_limit'])
try: try:
@ -2212,6 +2214,9 @@ def save_config():
('TVCalendar', [ ('TVCalendar', [
('mru', TVC_MRU) ('mru', TVC_MRU)
]), ]),
('TVmaze', [
('mru', TVM_MRU)
]),
('NextEpisode', [ ('NextEpisode', [
('mru', NE_MRU) ('mru', NE_MRU)
]), ]),

158
sickbeard/webserve.py

@ -93,6 +93,7 @@ from lib.dateutil.relativedelta import relativedelta
from lib.fuzzywuzzy import fuzz from lib.fuzzywuzzy import fuzz
from lib.libtrakt import TraktAPI from lib.libtrakt import TraktAPI
from lib.libtrakt.exceptions import TraktException, TraktAuthException from lib.libtrakt.exceptions import TraktException, TraktAuthException
from lib.tvmaze_api.tvmaze_api import TvMaze
import lib.rarfile.rarfile as rarfile import lib.rarfile.rarfile as rarfile
@ -5168,6 +5169,157 @@ class AddShows(Home):
return self.new_show('|'.join(['', '', '', show_name]), use_show_name=True) return self.new_show('|'.join(['', '', '', show_name]), use_show_name=True)
def tvm_default(self):
return self.redirect('/add-shows/%s' % ('tvm_premieres', sickbeard.TVM_MRU)[any(sickbeard.TVM_MRU)])
def tvm_premieres(self, **kwargs):
return self.browse_tvm(
'New Shows at TVmaze', mode='premieres', **kwargs)
def tvm_returning(self, **kwargs):
return self.browse_tvm(
'Returning Shows at TVmaze', mode='returning', **kwargs)
def browse_tvm(self, browse_title, **kwargs):
browse_type = 'TVmaze'
footnote = None
filtered = []
def card_cache(mem_key):
# noinspection PyProtectedMember
from lib.dateutil.tz.tz import _datetime_to_timestamp
if (int(_datetime_to_timestamp(datetime.datetime.now()))
< sickbeard.MEMCACHE.get(mem_key, {}).get('last_update', 0)):
return sickbeard.MEMCACHE.get(mem_key).get('data')
if 'prem' in mem_key:
data = TvMaze().get_premieres()
else:
data = TvMaze().get_returning()
sickbeard.MEMCACHE[mem_key] = dict(
last_update=(30*60) + int(_datetime_to_timestamp(datetime.datetime.now())), data=data)
return data
if 'New' in browse_title:
episodes = card_cache('tvmaze_premiere')
else:
episodes = card_cache('tvmaze_returning')
oldest, newest, oldest_dt, newest_dt, use_networks = None, None, 9999999, 0, False
dedupe = []
parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True)
for cur_episode_info in episodes:
if cur_episode_info.show.maze_id in dedupe:
continue
dedupe += [cur_episode_info.show.maze_id]
try:
if cur_episode_info.airtime:
airtime = dateutil.parser.parse(cur_episode_info.airtime).time()
else:
airtime = cur_episode_info.airstamp and dateutil.parser.parse(cur_episode_info.airstamp).time()
if (0, 0) == (airtime.hour, airtime.minute):
airtime = dateutil.parser.parse('23:59').time()
dt = datetime.datetime.combine(
dateutil.parser.parse(
(cur_episode_info.show.premiered or cur_episode_info.airdate), parseinfo).date(), airtime)
dt_ordinal = dt.toordinal()
now_ordinal = datetime.datetime.now().toordinal()
when_past = dt_ordinal < now_ordinal
dt_string = SGDatetime.sbfdate(dt)
if dt_ordinal < oldest_dt:
oldest_dt = dt_ordinal
oldest = dt_string
if dt_ordinal > newest_dt:
newest_dt = dt_ordinal
newest = dt_string
returning = returning_str = None
if 'Return' in browse_title:
returning = '9'
returning_str = 'TBC'
if cur_episode_info.airdate:
returning = cur_episode_info.airdate
dt_returning = datetime.datetime.combine(
dateutil.parser.parse(returning, parseinfo).date(), airtime)
when_past = dt_returning.toordinal() < now_ordinal
returning_str = SGDatetime.sbfdate(dt_returning)
try:
img_uri = next(i for i in cur_episode_info.show.images
if i.main and 'poster' == i.type).resolutions['original']['url']
images = dict(poster=dict(thumb='imagecache?path=browse/thumb/tvmaze&source=%s' % img_uri))
sickbeard.CACHE_IMAGE_URL_LIST.add_url(img_uri)
except(BaseException, Exception):
images = {}
ids = dict(tvmaze=cur_episode_info.maze_id)
imdb_id = cur_episode_info.show.externals.get('imdb')
if imdb_id:
ids.update(dict(imdb=imdb_id))
tvdb_id = cur_episode_info.show.externals.get('thetvdb')
if tvdb_id:
ids.update(dict(tvdb=tvdb_id))
network_name = (getattr(cur_episode_info.show.network, 'name', None)
or getattr(cur_episode_info.show.web_channel, 'name', None) or '')
cc = 'US'
if network_name:
use_networks = True
cc = (getattr(cur_episode_info.show.network, 'code', None)
or getattr(cur_episode_info.show.web_channel, 'code', None) or 'US')
language = ((cur_episode_info.show.language and 'jap' in cur_episode_info.show.language.lower())
and 'jp' or 'en')
filtered.append(dict(
ids=ids,
premiered=dt_ordinal,
premiered_str=dt_string,
returning=returning,
returning_str=returning_str,
when_past=when_past,
episode_number=cur_episode_info.episode_number,
episode_season=cur_episode_info.season_number,
episode_overview='' if not cur_episode_info.summary else cur_episode_info.summary.strip(),
genres=(', '.join(['%s' % v for v in cur_episode_info.show.genres])
or cur_episode_info.show.type or ''),
images=images,
overview=('No overview yet' if not cur_episode_info.show.summary
else helpers.xhtml_escape(cur_episode_info.show.summary.strip()[:250:])
.strip('*').strip()),
title=cur_episode_info.show.name,
language=language,
language_img=sickbeard.MEMCACHE_FLAG_IMAGES.get(language, False),
country=cc,
country_img=sickbeard.MEMCACHE_FLAG_IMAGES.get(cc.lower(), False),
network=network_name,
rating=cur_episode_info.show.weight or 0,
url_src_db=cur_episode_info.show.url,
))
except (BaseException, Exception):
pass
kwargs.update(dict(oldest=oldest, newest=newest))
kwargs.update(dict(footnote=footnote, use_votes=False, use_networks=use_networks))
mode = kwargs.get('mode', '')
if mode:
func = 'tvm_%s' % mode
if callable(getattr(self, func, None)):
sickbeard.TVM_MRU = func
sickbeard.save_config()
return self.browse_shows(browse_type, browse_title, filtered, **kwargs)
# noinspection PyUnusedLocal
def info_tvmaze(self, ids, show_name):
if not filter_list(lambda tvid_prodid: helpers.find_show_by_id(tvid_prodid), ids.split(' ')):
return self.new_show('|'.join(['', '', '', ' '.join([ids, show_name])]), use_show_name=True)
def tvc_default(self): def tvc_default(self):
return self.redirect('/add-shows/%s' % ('tvc_newshows', sickbeard.TVC_MRU)[any(sickbeard.TVC_MRU)]) return self.redirect('/add-shows/%s' % ('tvc_newshows', sickbeard.TVC_MRU)[any(sickbeard.TVC_MRU)])
@ -5525,7 +5677,7 @@ class AddShows(Home):
@staticmethod @staticmethod
def browse_mru(browse_type, **kwargs): def browse_mru(browse_type, **kwargs):
save_config = False save_config = False
if browse_type in ('AniDB', 'IMDb', 'Metacritic', 'Trakt', 'TVCalendar', 'Nextepisode'): if browse_type in ('AniDB', 'IMDb', 'Metacritic', 'Trakt', 'TVCalendar', 'TVmaze', 'Nextepisode'):
save_config = True save_config = True
sickbeard.BROWSELIST_MRU[browse_type] = dict( sickbeard.BROWSELIST_MRU[browse_type] = dict(
showfilter=kwargs.get('showfilter', ''), showsort=kwargs.get('showsort', '')) showfilter=kwargs.get('showfilter', ''), showsort=kwargs.get('showsort', ''))
@ -5547,7 +5699,8 @@ class AddShows(Home):
showsort = t.saved_showsort.split(',') showsort = t.saved_showsort.split(',')
t.saved_showsort_sortby = 3 == len(showsort) and showsort[2] or 'by_order' t.saved_showsort_sortby = 3 == len(showsort) and showsort[2] or 'by_order'
t.reset_showsort_sortby = ('votes' in t.saved_showsort_sortby and not kwargs.get('use_votes', True) t.reset_showsort_sortby = ('votes' in t.saved_showsort_sortby and not kwargs.get('use_votes', True)
or 'rating' in t.saved_showsort_sortby and not kwargs.get('use_ratings', True)) or 'rating' in t.saved_showsort_sortby and not kwargs.get('use_ratings', True)
or 'returning' in t.saved_showsort_sortby and 'Returning' not in browse_title)
t.is_showsort_desc = ('desc' == (2 <= len(showsort) and showsort[1] or 'asc')) and not t.reset_showsort_sortby t.is_showsort_desc = ('desc' == (2 <= len(showsort) and showsort[1] or 'asc')) and not t.reset_showsort_sortby
t.saved_showsort_view = 1 <= len(showsort) and showsort[0] or '*' t.saved_showsort_view = 1 <= len(showsort) and showsort[0] or '*'
t.all_shows = [] t.all_shows = []
@ -5635,6 +5788,7 @@ class AddShows(Home):
('order', lambda _x: _x['order']), ('order', lambda _x: _x['order']),
('name', lambda _x: _title(_x['title'])), ('name', lambda _x: _title(_x['title'])),
('premiered', lambda _x: (_x['premiered'], _title(_x['title']))), ('premiered', lambda _x: (_x['premiered'], _title(_x['title']))),
('returning', lambda _x: (_x['returning'], _title(_x['title']))),
('votes', lambda _x: (helpers.try_int(_x['votes']), _title(_x['title']))), ('votes', lambda _x: (helpers.try_int(_x['votes']), _title(_x['title']))),
('rating', lambda _x: (helpers.try_float(_x['rating']), _title(_x['title']))), ('rating', lambda _x: (helpers.try_float(_x['rating']), _title(_x['title']))),
('rating_votes', lambda _x: (helpers.try_float(_x['rating']), helpers.try_int(_x['votes']), ('rating_votes', lambda _x: (helpers.try_float(_x['rating']), helpers.try_int(_x['votes']),

Loading…
Cancel
Save