diff --git a/CHANGES.md b/CHANGES.md
index 8e7b4f9..a2f45ce 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -79,6 +79,9 @@
* Add fetch extra data fallback from TMDB for persons
* Change fanart icon
* 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]
diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css
index 175be1f..b2fc538 100644
--- a/gui/slick/css/style.css
+++ b/gui/slick/css/style.css
@@ -707,7 +707,8 @@ inc_top.tmpl
}
.sgicon-tvmaze:before{
- content:"\e89a"
+ content:"\e89a";
+ margin-right:14px
}
.sgicon-emby:before{
diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl
index bec0627..8ae6865 100644
--- a/gui/slick/interfaces/default/home_browseShows.tmpl
+++ b/gui/slick/interfaces/default/home_browseShows.tmpl
@@ -8,6 +8,8 @@
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp#
##
#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_ratings = $kwargs and $kwargs.get('use_ratings', True)
##
@@ -20,7 +22,11 @@
##
#import os.path
#include $os.path.join($sg_str('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl')
-
+
+ #end if
+
+
+
+#end if
diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl
index 97cbeb5..8e1518e 100644
--- a/gui/slick/interfaces/default/inc_top.tmpl
+++ b/gui/slick/interfaces/default/inc_top.tmpl
@@ -188,6 +188,10 @@
#set $tvc_mode = $tvc_modes.get($sg_var('TVC_MRU'), 'new shows')
TV Calendar Cards
+#set $tvm_modes = dict(tvm_premieres='new shows', tvm_returning='returning')
+#set $tvm_mode = $tvm_modes.get($sg_var('TVM_MRU'), 'new shows')
+ TVmaze Cards
+
#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')
Next Episode Cards
diff --git a/lib/imdb_api/imdb_api.py b/lib/imdb_api/imdb_api.py
index 8c86514..52f45c4 100644
--- a/lib/imdb_api/imdb_api.py
+++ b/lib/imdb_api/imdb_api.py
@@ -80,7 +80,7 @@ class IMDbIndexer(TVInfoBase):
is_none, shows = self._get_cache_entry(cache_id_key)
if not self.config.get('cache_search') or (None is shows and not is_none):
try:
- show = imdbpie.Imdb().get_title_auxiliary('tt%07d' % p)
+ show = imdbpie.Imdb().get_title_auxiliary('tt%08d' % p)
except (BaseException, Exception):
continue
self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire)
diff --git a/lib/libtrakt/indexerapiinterface.py b/lib/libtrakt/indexerapiinterface.py
index b854131..1132356 100644
--- a/lib/libtrakt/indexerapiinterface.py
+++ b/lib/libtrakt/indexerapiinterface.py
@@ -185,7 +185,7 @@ class TraktIndexer(TVInfoBase):
if TraktSearchTypes.trakt_slug == search_type:
url = '/shows/%s?extended=full' % series
elif TraktSearchTypes.text != search_type:
- url = '/search/%s/%s?type=%s&extended=full&limit=100' % (search_type, (series, 'tt%07d' % series)[
+ url = '/search/%s/%s?type=%s&extended=full&limit=100' % (search_type, (series, 'tt%08d' % series)[
TraktSearchTypes.imdb_id == search_type and not str(series).startswith('tt')],
','.join(self.config['result_types']))
else:
diff --git a/lib/tmdb_api/tmdb_api.py b/lib/tmdb_api/tmdb_api.py
index ec7b78f..635ce80 100644
--- a/lib/tmdb_api/tmdb_api.py
+++ b/lib/tmdb_api/tmdb_api.py
@@ -210,7 +210,7 @@ class TmdbIndexer(TVInfoBase):
is_none, shows = self._get_cache_entry(cache_id_key)
if not self.config.get('cache_search') or (None is shows and not is_none):
try:
- show = tmdbsimple.Find(id=(p, 'tt%07d' % p)[t == TVINFO_IMDB]).info(
+ show = tmdbsimple.Find(id=(p, 'tt%08d' % p)[t == TVINFO_IMDB]).info(
external_source=id_map[t])
if show.get('tv_results') and 1 == len(show['tv_results']):
show = tmdbsimple.TV(id=show['tv_results'][0]['id']).info(
diff --git a/lib/tvinfo_base/base.py b/lib/tvinfo_base/base.py
index 2bffd94..c4806f9 100644
--- a/lib/tvinfo_base/base.py
+++ b/lib/tvinfo_base/base.py
@@ -126,6 +126,9 @@ class TVInfoIDs(object):
return {TVINFO_TVDB: self.tvdb, TVINFO_TMDB: self.tmdb, TVINFO_TVMAZE: self.tvmaze,
TVINFO_IMDB: self.imdb, TVINFO_TRAKT: self.trakt, TVINFO_TVRAGE: self.rage}.get(key)
+ def get(self, key):
+ return self.__getitem__(key)
+
def __iter__(self):
for s, v in [(TVINFO_TVDB, self.tvdb), (TVINFO_TMDB, self.tmdb), (TVINFO_TVMAZE, self.tvmaze),
(TVINFO_IMDB, self.imdb), (TVINFO_TRAKT, self.trakt), (TVINFO_TVRAGE, self.rage)]:
@@ -502,14 +505,16 @@ class TVInfoEpisode(dict):
self.thumbadded = None # type: Optional[AnyStr]
self.rating = None # type: Union[integer_types, float]
self.siteratingcount = None # type: integer_types
+ self.show = None # type: Optional[TVInfoShow]
def __str__(self):
+ show_name = self.show and self.show.seriesname and ' - ' % self.show.seriesname
seasno, epno = int(getattr(self, 'seasonnumber', 0)), int(getattr(self, 'episodenumber', 0))
epname = getattr(self, 'episodename', '')
if None is not epname:
- return '' % (seasno, epno, epname)
+ return '%s' % (show_name, seasno, epno, epname)
else:
- return '' % (seasno, epno)
+ return '%s' % (show_name, seasno, epno)
def __getattr__(self, key):
if key in self:
@@ -554,6 +559,7 @@ class TVInfoEpisode(dict):
if cur_value.find(text_type(term).lower()) > -1:
return self
+ __unicode__ = __str__
__repr__ = __str__
__nonzero__ = __bool__
@@ -1146,8 +1152,22 @@ class TVInfoBase(object):
"""
return []
- def discover(self, result_count=100, **kwargs):
+ def discover(self, result_count=100, get_extra_images=False, **kwargs):
+ # type: (...) -> List[TVInfoEpisode]
+ return []
+
+ def get_premieres(self, result_count=100, **kwargs):
+ # type: (...) -> List[TVInfoEpisode]
+ """
+ get all premiering shows
+ """
+ return []
+
+ def get_returning(self, result_count=100, get_extra_images=False, **kwargs):
# type: (...) -> List[TVInfoShow]
+ """
+ get all returning shows
+ """
return []
def __getitem__(self, item):
diff --git a/lib/tvmaze_api/tvmaze_api.py b/lib/tvmaze_api/tvmaze_api.py
index c1614c7..3ddab81 100644
--- a/lib/tvmaze_api/tvmaze_api.py
+++ b/lib/tvmaze_api/tvmaze_api.py
@@ -6,18 +6,21 @@ __author__ = 'Prinz23'
__version__ = '1.0'
__api_version__ = '1.0.0'
-import logging
import datetime
+import logging
+import re
+
import requests
-from requests.packages.urllib3.util.retry import Retry
+from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
-from six import iteritems
+from six import integer_types, iteritems
from sg_helpers import get_url, try_int
from lib.dateutil.parser import parser
+# noinspection PyProtectedMember
from lib.dateutil.tz.tz import _datetime_to_timestamp
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, \
crew_type_names, Person, RoleTypes, TVInfoShow, TVInfoEpisode, TVInfoIDs, TVInfoSeason, PersonGenders, \
TVINFO_TVMAZE, TVINFO_TVDB, TVINFO_IMDB
@@ -25,8 +28,8 @@ from lib.pytvmaze import tvmaze
# noinspection PyUnreachableCode
if False:
- from typing import Any, AnyStr, Dict, List, Optional, Union
- from six import integer_types
+ from typing import Any, AnyStr, Dict, List, Optional
+ from lib.pytvmaze.tvmaze import Episode as TVMazeEpisode, Show as TVMazeShow
log = logging.getLogger('tvmaze.api')
log.addHandler(logging.NullHandler())
@@ -38,8 +41,10 @@ def tvmaze_endpoint_standard_get(url):
retries = Retry(total=5,
backoff_factor=0.1,
status_forcelist=[429])
+ # noinspection HttpUrlsUsage
s.mount('http://', 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)
@@ -139,10 +144,10 @@ class TvMaze(TVInfoBase):
return {'seriesname': s.name, 'id': s.id, 'firstaired': s.premiered,
'network': s.network and s.network.name,
'genres': s.genres, 'overview': s.summary,
- '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,
- imdb=s.externals.get('imdb') and try_int(s.externals.get('imdb').replace('tt', ''),
- None))}
+ '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,
+ imdb=s.externals.get('imdb') and try_int(s.externals.get('imdb').replace('tt', ''), None))}
results = []
if ids:
for t, p in iteritems(ids):
@@ -161,7 +166,7 @@ class TvMaze(TVInfoBase):
elif t == TVINFO_IMDB:
if not self.config.get('cache_search') or (None is shows and not is_none):
try:
- show = tvmaze.lookup_imdb((p, 'tt%07d' % p)[not str(p).startswith('tt')])
+ show = tvmaze.lookup_imdb((p, 'tt%08d' % p)[not str(p).startswith('tt')])
self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire)
except (BaseException, Exception):
continue
@@ -201,11 +206,11 @@ class TvMaze(TVInfoBase):
return results
def _set_episode(self, sid, ep_obj):
- for _k, _s in [('seasonnumber', 'season_number'), ('episodenumber', 'episode_number'),
- ('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'),
- ('airtime', 'airtime'), ('runtime', 'runtime'),
- ('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'),
- ('filename', 'image')]:
+ for _k, _s in (
+ ('seasonnumber', 'season_number'), ('episodenumber', 'episode_number'),
+ ('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'),
+ ('airtime', 'airtime'), ('runtime', 'runtime'),
+ ('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'), ('filename', 'image')):
if 'filename' == _k:
image = getattr(ep_obj, _s, {}) or {}
image = image.get('original') or image.get('medium')
@@ -221,7 +226,8 @@ class TvMaze(TVInfoBase):
except (BaseException, Exception):
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_timezone'] = network.timezone
show_obj['network_country'] = network.country
@@ -229,17 +235,21 @@ class TvMaze(TVInfoBase):
show_obj['network_id'] = network.maze_id
show_obj['network_is_stream'] = is_stream
- 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)
+ def _get_tvm_show(self, show_id, get_ep_info):
try:
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:
self.show_not_found = True
- return False
- except (BaseException, Exception) as e:
- log.debug('Error getting data for tvmaze show id: %s' % sid)
+ except (BaseException, Exception):
+ log.debug('Error getting data for tvmaze show id: %s' % show_id)
+
+ 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
show_obj = self.shows[sid].__dict__
@@ -330,19 +340,20 @@ class TvMaze(TVInfoBase):
except (BaseException, Exception):
print('error')
pass
- existing_person.p_id, existing_person.name, existing_person.image, existing_person.gender, \
- existing_person.birthdate, existing_person.deathdate, existing_person.country, \
- existing_person.country_code, existing_person.country_timezone, existing_person.thumb_url, \
- existing_person.url, existing_person.ids = \
- ch.person.id, ch.person.name, ch.person.image and ch.person.image.get('original'), \
- PersonGenders.named.get(ch.person.gender and ch.person.gender.lower(),
- PersonGenders.unknown),\
- person.birthdate, person.deathdate,\
- ch.person.country and ch.person.country.get('name'),\
- ch.person.country and ch.person.country.get('code'),\
- ch.person.country and ch.person.country.get('timezone'),\
- ch.person.image and ch.person.image.get('medium'),\
- ch.person.url, {TVINFO_TVMAZE: ch.person.id}
+ (existing_person.p_id, existing_person.name, existing_person.image, existing_person.gender,
+ existing_person.birthdate, existing_person.deathdate, existing_person.country,
+ existing_person.country_code, existing_person.country_timezone, existing_person.thumb_url,
+ existing_person.url, existing_person.ids) = \
+ (ch.person.id, ch.person.name,
+ ch.person.image and ch.person.image.get('original'),
+ PersonGenders.named.get(
+ ch.person.gender and ch.person.gender.lower(), PersonGenders.unknown),
+ person.birthdate, person.deathdate,
+ ch.person.country and ch.person.country.get('name'),
+ ch.person.country and ch.person.country.get('code'),
+ ch.person.country and ch.person.country.get('timezone'),
+ ch.person.image and ch.person.image.get('medium'),
+ ch.person.url, {TVINFO_TVMAZE: ch.person.id})
else:
existing_character.person.append(person)
else:
@@ -396,7 +407,7 @@ class TvMaze(TVInfoBase):
show_obj['ids'] = TVInfoIDs(tvdb=show_data.externals.get('thetvdb'),
rage=show_data.externals.get('tvrage'),
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:
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):
log.debug('Getting all episodes of %s' % sid)
if None is show_data:
- try:
- self.show_not_found = False
- 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)
+ show_data = self._get_tvm_show(sid, get_ep_info)
+ if not show_data:
return False
if show_data.episodes:
@@ -543,3 +547,120 @@ class TvMaze(TVInfoBase):
p = None
if p:
return self._convert_person(p)
+
+ def get_premieres(self, result_count=100, get_extra_images=False, **kwargs):
+ # type: (...) -> List[TVInfoEpisode]
+ return self._filtered_schedule(lambda e: all([1 == e.season_number, 1 == e.episode_number]),
+ get_images=get_extra_images)
+
+ def get_returning(self, result_count=100, get_extra_images=False, **kwargs):
+ # type: (...) -> List[TVInfoEpisode]
+ return self._filtered_schedule(lambda e: all([1 != e.season_number, 1 == e.episode_number]),
+ get_images=get_extra_images)
+
+ def _make_episode(self, episode_data, show_data=None, get_images=False):
+ # type: (TVMazeEpisode, TVMazeShow, bool) -> TVInfoEpisode
+ """
+ make out of TVMazeEpisode object and optionally TVMazeShow a TVInfoEpisode
+ """
+ ti_show = TVInfoShow()
+ ti_show.seriesname = show_data.name
+ ti_show.id = show_data.maze_id
+ ti_show.seriesid = ti_show.id
+ ti_show.language = show_data.language
+ ti_show.overview = show_data.summary
+ ti_show.firstaired = show_data.premiered
+ ti_show.runtime = show_data.average_runtime or show_data.runtime
+ ti_show.vote_average = show_data.rating and show_data.rating.get('average')
+ ti_show.popularity = show_data.weight
+ ti_show.genre_list = show_data.genres or []
+ ti_show.genre = ', '.join(ti_show.genre_list)
+ ti_show.official_site = show_data.official_site
+ ti_show.status = show_data.status
+ ti_show.show_type = show_data.type
+ ti_show.lastupdated = show_data.updated
+ ti_show.poster = show_data.image and show_data.image.get('original')
+ ti_show.aliases = [a.name for a in show_data.akas]
+ if 'days' in show_data.schedule:
+ ti_show.airs_dayofweek = ', '.join(show_data.schedule['days'])
+ network = show_data.network or show_data.web_channel
+ if network:
+ ti_show.network_is_stream = None is not show_data.web_channel
+ ti_show.network = network.name
+ ti_show.network_id = network.maze_id
+ ti_show.network_country = network.country
+ ti_show.network_country_code = network.code
+ ti_show.network_timezone = network.timezone
+ if get_images and show_data.images:
+ b_set, f_set, p_set = False, False, False
+ for cur_img in show_data.images:
+ img_type = img_type_map.get(cur_img.type, TVInfoImageType.other)
+ img_width, img_height = cur_img.resolutions['original'].get('width'), \
+ cur_img.resolutions['original'].get('height')
+ img_ar = img_width and img_height and float(img_width) / float(img_height)
+ img_ar_type = self._which_type(img_width, img_ar)
+ if TVInfoImageType.poster == img_type and img_ar and img_ar_type != img_type and \
+ ti_show.poster == cur_img.resolutions.get('original')['url']:
+ p_set = False
+ ti_show.poster = None
+ ti_show.poster_thumb = None
+ img_type = (TVInfoImageType.other, img_type)[
+ not img_ar or img_ar_type == img_type or
+ img_type not in (TVInfoImageType.banner, TVInfoImageType.poster, TVInfoImageType.fanart)]
+ img_src = {}
+ for cur_res, cur_img_url in iteritems(cur_img.resolutions):
+ img_size = img_size_map.get(cur_res)
+ if img_size:
+ img_src[img_size] = cur_img_url.get('url')
+ ti_show.images.setdefault(img_type, []).append(
+ TVInfoImage(
+ image_type=img_type, sizes=img_src, img_id=cur_img.id, main_image=cur_img.main,
+ type_str=cur_img.type, width=img_width, height=img_height, aspect_ratio=img_ar))
+ if not p_set and TVInfoImageType.poster == img_type:
+ p_set = True
+ ti_show.poster = cur_img.resolutions.get('original')['url']
+ ti_show.poster_thumb = cur_img.resolutions.get('original')['url']
+ elif not b_set and 'banner' == cur_img.type and TVInfoImageType.banner == img_type:
+ b_set = True
+ ti_show.banner = cur_img.resolutions.get('original')['url']
+ ti_show.banner_thumb = cur_img.resolutions.get('medium')['url']
+ elif not f_set and 'background' == cur_img.type and TVInfoImageType.fanart == img_type:
+ f_set = True
+ ti_show.fanart = cur_img.resolutions.get('original')['url']
+ ti_show.ids = TVInfoIDs(
+ tvdb=show_data.externals.get('thetvdb'), rage=show_data.externals.get('tvrage'), tvmaze=show_data.id,
+ imdb=show_data.externals.get('imdb') and try_int(show_data.externals.get('imdb').replace('tt', ''), None))
+ ti_show.imdb_id = show_data.externals.get('imdb')
+ if isinstance(ti_show.imdb_id, integer_types):
+ ti_show.imdb_id = 'tt%08d' % ti_show.imdb_id
+
+ ti_episode = TVInfoEpisode()
+ ti_episode.id = episode_data.maze_id
+ ti_episode.seasonnumber = episode_data.season_number
+ ti_episode.episodenumber = episode_data.episode_number
+ ti_episode.episodename = episode_data.title
+ ti_episode.airtime = episode_data.airtime
+ ti_episode.firstaired = episode_data.airdate
+ if episode_data.airstamp:
+ try:
+ at = _datetime_to_timestamp(tz_p.parse(episode_data.airstamp))
+ ti_episode.timestamp = at
+ except (BaseException, Exception):
+ pass
+ ti_episode.filename = episode_data.image and (episode_data.image.get('original') or
+ episode_data.image.get('medium'))
+ ti_episode.is_special = episode_data.is_special()
+ ti_episode.overview = episode_data.summary
+ ti_episode.runtime = episode_data.runtime
+ ti_episode.show = ti_show
+ return ti_episode
+
+ def _filtered_schedule(self, condition, get_images=False):
+ try:
+ result = 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)
+ return [self._make_episode(r, r.show, get_images) for r in result]
+ except(BaseException, Exception):
+ return []
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index 7d6aa38..f0f20b3 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -618,6 +618,7 @@ else:
MC_MRU = ''
TVC_MRU = ''
+TVM_MRU = ''
NE_MRU = ''
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, \
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, \
- 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, \
SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \
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', '')
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', '')
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),
threadName='PLEXWATCHEDSTATE')
- MEMCACHE['history_tab_limit'] = 10
+ MEMCACHE['history_tab_limit'] = 11
MEMCACHE['history_tab'] = History.menu_tab(MEMCACHE['history_tab_limit'])
try:
@@ -2212,6 +2214,9 @@ def save_config():
('TVCalendar', [
('mru', TVC_MRU)
]),
+ ('TVmaze', [
+ ('mru', TVM_MRU)
+ ]),
('NextEpisode', [
('mru', NE_MRU)
]),
diff --git a/sickbeard/indexers/indexer_config.py b/sickbeard/indexers/indexer_config.py
index d55d83b..1aa4ad3 100644
--- a/sickbeard/indexers/indexer_config.py
+++ b/sickbeard/indexers/indexer_config.py
@@ -176,7 +176,7 @@ tvinfo_config[src].update(dict(
src = TVINFO_IMDB
tvinfo_config[src].update(dict(
base_url=tvinfo_config[src]['main_url'],
- show_url='%stitle/tt%%07d' % tvinfo_config[src]['main_url'],
+ show_url='%stitle/tt%%08d' % tvinfo_config[src]['main_url'],
finder='%sfind?q=%s&s=tt&ttype=tv&ref_=fn_tv' % (tvinfo_config[src]['main_url'], '%s'),
))
diff --git a/sickbeard/tv.py b/sickbeard/tv.py
index e787a69..492093b 100644
--- a/sickbeard/tv.py
+++ b/sickbeard/tv.py
@@ -2757,7 +2757,7 @@ class TVShow(TVShowBase):
old_imdb = self.imdbid
if show_info.ids.imdb:
- self.imdbid = 'tt%07d' % show_info.ids.imdb
+ self.imdbid = 'tt%08d' % show_info.ids.imdb
else:
self.imdbid = self.dict_prevent_nonetype(show_info, 'imdb_id')
if old_imdb != self.imdbid:
@@ -2977,7 +2977,7 @@ class TVShow(TVShowBase):
if not self._imdbid and 0 >= self.ids.get(indexermapper.TVINFO_IMDB, {'id': 0}).get('id', 0):
return
- imdb_info = {'imdb_id': self._imdbid or 'tt%07d' % self.ids[indexermapper.TVINFO_IMDB]['id'],
+ imdb_info = {'imdb_id': self._imdbid or 'tt%08d' % self.ids[indexermapper.TVINFO_IMDB]['id'],
'title': '',
'year': '',
'akas': '',
@@ -2995,7 +2995,7 @@ class TVShow(TVShowBase):
imdb_id = None
imdb_certificates = None
try:
- imdb_id = str(self._imdbid or 'tt%07d' % self.ids[indexermapper.TVINFO_IMDB]['id'])
+ imdb_id = str(self._imdbid or 'tt%08d' % self.ids[indexermapper.TVINFO_IMDB]['id'])
redirect_check = self.check_imdb_redirect(imdb_id)
if redirect_check:
self._imdbid = redirect_check
@@ -3016,15 +3016,15 @@ class TVShow(TVShowBase):
})
imdb_certificates = i.get_title_certificates(imdb_id=imdb_id)
except LookupError as e:
- if 'Title was an episode' in ex(e) and imdb_id == 'tt%07d' % self.ids[indexermapper.TVINFO_IMDB]['id']:
+ if 'Title was an episode' in ex(e) and imdb_id == 'tt%08d' % self.ids[indexermapper.TVINFO_IMDB]['id']:
self.ids[indexermapper.TVINFO_IMDB]['id'] = 0
self.ids[indexermapper.TVINFO_IMDB]['status'] = MapStatus.NOT_FOUND
if datetime.date.today() != self.ids[indexermapper.TVINFO_IMDB]['date']:
indexermapper.map_indexers_to_show(self, force=True)
- if not retry and imdb_id != 'tt%07d' % self.ids[indexermapper.TVINFO_IMDB]['id']:
+ if not retry and imdb_id != 'tt%08d' % self.ids[indexermapper.TVINFO_IMDB]['id']:
# add retry arg to prevent endless loops
logger.log('imdbid: %s not found. retrying with newly found id: %s' %
- (imdb_id, 'tt%07d' % self.ids[indexermapper.TVINFO_IMDB]['id']), logger.DEBUG)
+ (imdb_id, 'tt%08d' % self.ids[indexermapper.TVINFO_IMDB]['id']), logger.DEBUG)
self._get_imdb_info(retry=True)
return
logger.log('imdbid: %s not found. Error: %s' % (imdb_id, ex(e)), logger.WARNING)
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index f5d3018..526b4bf 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -5168,6 +5168,159 @@ class AddShows(Home):
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
+
+ now = int(_datetime_to_timestamp(datetime.datetime.now()))
+ if (sickbeard.MEMCACHE.get(mem_key, {}).get('data')
+ and (now < sickbeard.MEMCACHE.get(mem_key, {}).get('expire', 0))):
+ return sickbeard.MEMCACHE.get(mem_key).get('data')
+
+ tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TVMAZE).api_params.copy()
+ t = sickbeard.TVInfoAPI(TVINFO_TVMAZE).setup(**tvinfo_config)
+ if 'prem' in mem_key:
+ data = t.get_premieres()
+ else:
+ data = t.get_returning()
+ sickbeard.MEMCACHE[mem_key] = dict(expire=(30*60) + 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)
+ base_url = sickbeard.TVInfoAPI(TVINFO_TVMAZE).config['show_url']
+ for cur_episode_info in episodes:
+ if cur_episode_info.show.id in dedupe:
+ continue
+ dedupe += [cur_episode_info.show.id]
+
+ try:
+ if cur_episode_info.airtime:
+ airtime = dateutil.parser.parse(cur_episode_info.airtime).time()
+ else:
+ airtime = cur_episode_info.timestamp \
+ and SGDatetime.from_timestamp(cur_episode_info.timestamp).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.firstaired or cur_episode_info.firstaired), 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.firstaired:
+ returning = cur_episode_info.firstaired
+ 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 = cur_episode_info.show.poster
+ 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.id)
+ imdb_id = cur_episode_info.show.imdb_id
+ if imdb_id:
+ ids['imdb'] = imdb_id
+ tvdb_id = cur_episode_info.show.ids.get(TVINFO_TVDB)
+ if tvdb_id:
+ ids['tvdb'] = tvdb_id
+
+ network_name = cur_episode_info.show.network
+ cc = 'US'
+ if network_name:
+ use_networks = True
+ cc = cur_episode_info.show.network_country_code or cc
+
+ 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.episodenumber,
+ episode_season=cur_episode_info.seasonnumber,
+ episode_overview='' if not cur_episode_info.overview else cur_episode_info.overview.strip(),
+ genres=(', '.join(['%s' % v for v in cur_episode_info.show.genre_list])
+ or cur_episode_info.show.show_type or ''),
+ images=images,
+ overview=('No overview yet' if not cur_episode_info.show.overview
+ else helpers.xhtml_escape(cur_episode_info.show.overview.strip()[:250:])
+ .strip('*').strip()),
+ title=cur_episode_info.show.seriesname,
+ 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.rating or cur_episode_info.show.popularity or 0,
+ url_src_db=base_url % cur_episode_info.show.id,
+ ))
+ 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):
return self.redirect('/add-shows/%s' % ('tvc_newshows', sickbeard.TVC_MRU)[any(sickbeard.TVC_MRU)])
@@ -5525,7 +5678,7 @@ class AddShows(Home):
@staticmethod
def browse_mru(browse_type, **kwargs):
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
sickbeard.BROWSELIST_MRU[browse_type] = dict(
showfilter=kwargs.get('showfilter', ''), showsort=kwargs.get('showsort', ''))
@@ -5547,7 +5700,8 @@ class AddShows(Home):
showsort = t.saved_showsort.split(',')
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)
- 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.saved_showsort_view = 1 <= len(showsort) and showsort[0] or '*'
t.all_shows = []
@@ -5635,6 +5789,7 @@ class AddShows(Home):
('order', lambda _x: _x['order']),
('name', lambda _x: _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']))),
('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']),