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 8c0b468..59b579c 100644 --- a/lib/tvmaze_api/tvmaze_api.py +++ b/lib/tvmaze_api/tvmaze_api.py @@ -30,6 +30,7 @@ from lib.pytvmaze import tvmaze if False: from typing import Any, AnyStr, Dict, List, Optional from six import integer_types + from lib.pytvmaze.tvmaze import Episode as TVMazeEpisode, Show as TVMazeShow log = logging.getLogger('tvmaze.api') log.addHandler(logging.NullHandler()) @@ -548,20 +549,116 @@ class TvMaze(TVInfoBase): if 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): + 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 + """ + tv_s = TVInfoShow() + tv_s.seriesname = show_data.name + tv_s.id = show_data.maze_id + tv_s.seriesid = tv_s.id + tv_s.language = show_data.language + tv_s.overview = show_data.summary + tv_s.firstaired = show_data.premiered + tv_s.runtime = show_data.average_runtime or show_data.runtime + tv_s.vote_average = show_data.rating and show_data.rating.get('average') + tv_s.popularity = show_data.weight + tv_s.genre_list = show_data.genres or [] + tv_s.genre = ', '.join(tv_s.genre_list) + tv_s.official_site = show_data.official_site + tv_s.status = show_data.status + tv_s.show_type = show_data.type + tv_s.lastupdated = show_data.updated + tv_s.poster = show_data.image and show_data.image.get('original') + tv_s.aliases = [a.name for a in show_data.akas] + if 'days' in show_data.schedule: + tv_s.airs_dayofweek = ', '.join(show_data.schedule['days']) + network = show_data.network or show_data.web_channel + if network: + tv_s.network_is_stream = None is not show_data.web_channel + tv_s.network = network.name + tv_s.network_id = network.maze_id + tv_s.network_country = network.country + tv_s.network_country_code = network.code + tv_s.network_timezone = network.timezone + if get_images and show_data.images: + b_set, f_set, p_set = False, False, False + for img in show_data.images: + img_type = img_type_map.get(img.type, TVInfoImageType.other) + img_width, img_height = img.resolutions['original'].get('width'), \ + 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 \ + tv_s.poster == img.resolutions.get('original')['url']: + p_set = False + tv_s.poster = None + tv_s.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 res, img_url in iteritems(img.resolutions): + img_size = img_size_map.get(res) + if img_size: + img_src[img_size] = img_url.get('url') + tv_s.images.setdefault(img_type, []).append( + TVInfoImage( + image_type=img_type, sizes=img_src, img_id=img.id, main_image=img.main, + type_str=img.type, width=img_width, height=img_height, aspect_ratio=img_ar)) + if not p_set and TVInfoImageType.poster == img_type: + p_set = True + tv_s.poster = img.resolutions.get('original')['url'] + tv_s.poster_thumb = img.resolutions.get('original')['url'] + elif not b_set and 'banner' == img.type and TVInfoImageType.banner == img_type: + b_set = True + tv_s.banner = img.resolutions.get('original')['url'] + tv_s.banner_thumb = img.resolutions.get('medium')['url'] + elif not f_set and 'background' == img.type and TVInfoImageType.fanart == img_type: + f_set = True + tv_s.fanart = img.resolutions.get('original')['url'] + tv_s.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)) + tv_s.imdb_id = show_data.externals.get('imdb') and try_int(show_data.externals.get('imdb').replace('tt', '')) + ep_obj = TVInfoEpisode() + ep_obj.id = episode_data.maze_id + ep_obj.seasonnumber = episode_data.season_number + ep_obj.episodenumber = episode_data.episode_number + ep_obj.episodename = episode_data.title + ep_obj.airtime = episode_data.airtime + ep_obj.firstaired = episode_data.airdate + if episode_data.airstamp: + try: + at = _datetime_to_timestamp(tz_p.parse(episode_data.airstamp)) + ep_obj.timestamp = at + except (BaseException, Exception): + pass + ep_obj.filename = episode_data.image and (episode_data.image.get('original') or + episode_data.image.get('medium')) + ep_obj.is_special = episode_data.is_special() + ep_obj.overview = episode_data.summary + ep_obj.runtime = episode_data.runtime + ep_obj.show = tv_s + return ep_obj + + def _filtered_schedule(self, condition, get_images=False): try: - return sorted([ + 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) - except(BaseException, Exception): + return [self._make_episode(r, r.show, get_images) for r in result] + except(BaseException, Exception) as e: return [] diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 5162452..16d0f21 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -93,7 +93,6 @@ from lib.dateutil.relativedelta import relativedelta from lib.fuzzywuzzy import fuzz from lib.libtrakt import TraktAPI from lib.libtrakt.exceptions import TraktException, TraktAuthException -from lib.tvmaze_api.tvmaze_api import TvMaze import lib.rarfile.rarfile as rarfile @@ -5195,10 +5194,13 @@ class AddShows(Home): 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') + + tvinfo_config = sickbeard.TVInfoAPI(TVINFO_TVMAZE).api_params.copy() + t = sickbeard.TVInfoAPI(TVINFO_TVMAZE).setup(**tvinfo_config) if 'prem' in mem_key: - data = TvMaze().get_premieres() + data = t.get_premieres() else: - data = TvMaze().get_returning() + data = t.get_returning() sickbeard.MEMCACHE[mem_key] = dict( last_update=(30*60) + int(_datetime_to_timestamp(datetime.datetime.now())), data=data) return data @@ -5211,21 +5213,23 @@ class AddShows(Home): 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.maze_id in dedupe: + if cur_episode_info.show.id in dedupe: continue - dedupe += [cur_episode_info.show.maze_id] + 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.airstamp and dateutil.parser.parse(cur_episode_info.airstamp).time() + 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.premiered or cur_episode_info.airdate), parseinfo).date(), airtime) + (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 @@ -5242,36 +5246,36 @@ class AddShows(Home): if 'Return' in browse_title: returning = '9' returning_str = 'TBC' - if cur_episode_info.airdate: - returning = cur_episode_info.airdate + 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 = next(i for i in cur_episode_info.show.images - if i.main and 'poster' == i.type).resolutions['original']['url'] + 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.maze_id) - imdb_id = cur_episode_info.show.externals.get('imdb') + ids = dict(tvmaze=cur_episode_info.id) + imdb_id = cur_episode_info.show.imdb_id if imdb_id: - ids.update(dict(imdb=imdb_id)) - tvdb_id = cur_episode_info.show.externals.get('thetvdb') + if isinstance(imdb_id, string_types) and 'tt' in imdb_id: + ids.update(dict(imdb=imdb_id)) + elif isinstance(imdb_id, integer_types): + ids.update(dict(imdb='tt%08d' % imdb_id)) + tvdb_id = cur_episode_info.show.ids.get(TVINFO_TVDB) 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 '') + network_name = cur_episode_info.show.network 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') + 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') @@ -5282,23 +5286,23 @@ class AddShows(Home): 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 ''), + 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.summary - else helpers.xhtml_escape(cur_episode_info.show.summary.strip()[:250:]) + 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.name, + 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.weight or 0, - url_src_db=cur_episode_info.show.url, + 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