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']),