diff options
15 files changed, 680 insertions, 273 deletions
diff --git a/addons/metadata.tvshows.themoviedb.org.python/addon.xml b/addons/metadata.tvshows.themoviedb.org.python/addon.xml index 39f0815558..4cc91b1698 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/addon.xml +++ b/addons/metadata.tvshows.themoviedb.org.python/addon.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <addon id="metadata.tvshows.themoviedb.org.python" name="TMDb TV Shows" - version="1.3.14" + version="1.6.0" provider-name="Team Kodi"> <requires> <import addon="xbmc.python" version="3.0.0"/> @@ -9,11 +9,10 @@ </requires> <extension point="xbmc.metadata.scraper.tvshows" library="main.py" cachepersistence="00:15"/> <extension point="xbmc.addon.metadata"> - <news>1.3.14 -fix for named seasons in NFO files being ignored + <news>1.6.0 +change to use new JSON based episodeguide format to handle multiple providers +fixes for last minute changes to new Nexus Python bindings </news> - <summary lang="en_GB">Fetch TV Show metadata from themoviedb.org</summary> - <description lang="en_GB">The Movie Database (TMDb) is a community built movie and TV database. Every piece of data has been added by our amazing community dating back to 2008. TMDb's strong international focus and breadth of data is largely unmatched and something we're incredibly proud of. Put simply, we live and breathe community and that's precisely what makes us different.</description> <platform>all</platform> <license>GPL-3.0-or-later</license> <assets> @@ -22,6 +21,50 @@ fix for named seasons in NFO files being ignored <forum>https://forum.kodi.tv/showthread.php?tid=357232</forum> <website>https://www.themoviedb.org</website> <source>https://github.com/xbmc/metadata.tvshows.themoviedb.org.python</source> + <summary lang="be_BY">Атрымліваць метаданыя серыялаў з themoviedb.org</summary> + <summary lang="ca_ES">Agafa les metadades de les sèries de themoviedb.org</summary> + <summary lang="cs_CZ">Získat metadata pro seriály z themoviedb.org</summary> + <summary lang="da_DK">Hent metadata til tv-shows fra themoviedb.org</summary> + <summary lang="de_DE">TV-Show-Metadaten von themoviedb.org abrufen</summary> + <summary lang="en_GB">Fetch TV Show metadata from themoviedb.org</summary> + <summary lang="es_ES">Obtener metadatos de Serie de TV de themoviedb.org</summary> + <summary lang="es_MX">Obtener metadatos de series de themoviedb.org</summary> + <summary lang="et_EE">Hangi seriaali metaandmed allikast themoviedb.org</summary> + <summary lang="fi_FI">The Movie Database (TMDB) -tietolähde televisiosarjoille</summary> + <summary lang="fr_FR">Récupérer les métadonnées d'une série depuis themoviedb.org</summary> + <summary lang="id_ID">Ambil metadata Acara TV dari themoviedb.org</summary> + <summary lang="is_IS">Sækja lýsigögn sjónvarpsþátta frá themoviedb.org</summary> + <summary lang="it_IT">Recupera i metadati dei programmi TV da themovedb.org</summary> + <summary lang="ko_KR">themoviedb.org에서 TV쇼 메타데이터를 가져옵니다</summary> + <summary lang="nl_NL">Haal metadata van tv-programma's op van themoviedb.org</summary> + <summary lang="pl_PL">Pobieraj metadane programów telewizyjnch z themoviedb.org</summary> + <summary lang="pt_BR">Obter metadados de Séries em themoviedb.org</summary> + <summary lang="ru_RU">Получение метаданных о сериалах с сайта themoviedb.org</summary> + <summary lang="sv_SE">Hämta metadata för TV-serier från themoviedb.org</summary> + <summary lang="tr_TR">TV Programları meta verilerini themoviedb.org sitesinden al</summary> + <summary lang="zh_CN">从 themoviedb.org 获取剧集信息</summary> + <description lang="be_BY">The Movie Database (TMDb) - база даных фільмаў і серыялаў, створаная супольнасцю. Усе даныя дадавалі ўдзельнікі гэтай цудоўнай супольнасці, пачынаючы з 2008. Шматнацыянасць і разнастайнасць даных - тое, чым мы вельмі ганарымся. Калі сказаць прасцей, то мы жывем супольнасцю, і гэта адрознівае нас ад іншых.</description> + <description lang="ca_ES">The Movie Database (TMDb) és una base de dades de pel·lícules i sèries creada per la comunitat. Cada dada ha estat afegida per la nostra increïble comunitat que data de 2008. El fort enfoc internacional i la quantitat de dades de TMDb és en gran mesura inigualable i una cosa de la qual estem increïblement orgullosos. En poques paraules, vivim i respirem comunitat i això és precisament el que ens fa diferents.</description> + <description lang="cs_CZ">The Movie Database (TMDb) je komunitní databáze filmů a seriálů. Každý kousek dat byl přidán naší úžasnou komunitou sahající až do roku 2008. Silné mezinárodní zaměření a rozsah dat TMDb jsou do značné míry bezkonkurenční a jsme na ně nesmírně hrdí. Jednoduše řečeno, žijeme a dýcháme komunitou, a to je přesně to, co nás odlišuje.</description> + <description lang="da_DK">The Movie Database (TMDb) er en community-bygget film- og tv-database. Hvert stykke data er blevet tilføjet af vores fantastiske community, der går tilbage til 2008. TMDbs stærke internationale fokus og bredde af data er stort set uovertruffen, og noget vi er utroligt stolte af. Enkelt sagt, vi lever og ånder community, og det er netop det, der gør os forskellige.</description> + <description lang="de_DE">The Movie Database (TMDb) ist eine Film- und TV-Datenbank. Seit 2008 wurde jeder einzelne Datensatz von einer tollen Community beigesteuert. TMDb's starker internationaler Fokus und die Vielfalt der Daten ist weitgehend unübertroffen. Die Betreiber von TMDb sind darauf sehr stolz. Vereinfacht gesagt, lebt und atmet TMDb den Community-Gedanken und das genau macht den Unterschied.</description> + <description lang="en_GB">The Movie Database (TMDb) is a community built movie and TV database. Every piece of data has been added by our amazing community dating back to 2008. TMDb's strong international focus and breadth of data is largely unmatched and something we're incredibly proud of. Put simply, we live and breathe community and that's precisely what makes us different.</description> + <description lang="es_ES">The Movie Database (TMDb) es una base de datos contruida comunitariamente sobre películas y TV. Cada pedazo de información ha sido añadida por nuestra increible comunidad desde 2008. El sólido enfoque internacional y la amplitud de datos de TMDb son en gran medida incomparables y es algo de lo que estamos increíblemente orgullosos. En pocas palabras, vivimos y respiramos en comunidad y eso es precisamente lo que nos hace diferentes.</description> + <description lang="es_MX">The Movie Database (TMDb) es una base de datos de películas y series creada por la comunidad. Cada pedazo de información ha sido agregada por nuestra increíble comunidad desde 2008. El fuerte enfoque internacional de TMDb y la variedad de información son en gran medida incomparables y algo de lo que estamos increíblemente orgullosos. En pocas palabras, vivimos y respiramos comunidad y eso es precisamente lo que nos hace diferentes.</description> + <description lang="et_EE">The Movie Database (TMDb) on kogukonna loodud filmide ja sarjade andmebaas. Meie imeline kogukond on lisanud kõik need andmed alates aastast 2008. TMDb tugev rahvusvaheline fookus ja andmete laius on suures osas võrreldamatud ja selle üle oleme ääretult uhked. Lihtsamalt öeldes elame ja hingame kogukonnas ning just see teebki meid erinevaks.</description> + <description lang="fi_FI">The Movie Database (TMDB) on yhteisön ylläpitämä, ilmainen ja avoin tietokanta elokuville ja televisiosarjoille. Palvelulla on miljoonia kuukausittaisia käyttäjiä ja sen tehokkaan rajapinnan avulla Kodi ja monet muut mediasovellukset voivat hyödyntää palvelun metatietoja ja mediakuvituksia käyttökokemuksen parantamiseen. Tietokannan vahva painotus kansaivälisyyteen ja kattava tietomäärä on suurelta osin ylivertainen ja olemme tästä erittäin ylpeitä. Yksinkertaisesti sanoen, elämme ja hengitämme yhteisönä ja juuri se tekee meistä erilaisia.</description> + <description lang="fr_FR">La base de données The Movie Database (TMDb) est un projet communautaire visant à référencer les informations de films et séries télé. Chaque information a été ajoutée par notre incroyable communauté, et ce depuis 2008. La force internationale du projet TMBd, ainsi que la diversité et l'étendue de ces données l'ont rendu leader dans ce domaine et nous en sommes extrêmement fière. Décrit simplement, nous vivons et respirons communauté et c'est précisément ce qui nous rend différent.</description> + <description lang="id_ID">The Movie Database (TMDb) adalah database film dan TV yang dibangun oleh komunitas. Setiap bagian data telah ditambahkan oleh komunitas kami yang luar biasa sejak tahun 2008. Fokus internasional TMDb yang kuat dan luasnya data sebagian besar tak tertandingi dan sesuatu yang sangat kami banggakan. Sederhananya, kita hidup dan bernafas dalam komunitas dan justru itulah yang membuat kita berbeda.</description> + <description lang="is_IS">The Movie Database (TMDb) er samfélagslegur gagnagrunnur kvikmynda- og sjónvarpsefnis. Hver einasti gagnabútur hefur verið settur inn af frábærum liðsmönnum okkar allar götur síðan 2008. Mikil alþjóðleg áhersla TMDb ásamt breiðu sviði gagna sést óvíða og er eitthvað sem við erum ótrúlega stolt af. Sett fram á einfaldan máta, við lifum og hrærumst í þessu samfélagi og það er nákvæmlega þess vegna sem við erum öðruvísi.</description> + <description lang="it_IT">Il Movie Database (TMDb) è un database di film e TV creato dalla comunità. Ogni dato è stato aggiunto dalla nostra straordinaria comunità che risale al 2008. La forte attenzione internazionale e l'ampiezza dei dati di TMDb sono in gran parte impareggiabili e qualcosa di cui siamo incredibilmente orgogliosi. In parole povere, viviamo e respiriamo in comunità ed è proprio questo che ci rende diversi.</description> + <description lang="ko_KR">The Movie Database (TMDb)는 영화와 TV 데이터베이스를 구축한 커뮤니티입니다. 모든 자료들은 우리 놀라운 커뮤니티에서 2008년부터 추가돼 온 것들입니다. TMDb의 강력한 국제적 초점과 광범위한 데이터는 타의 추종을 불허하며 우리가 자랑스러워하는 점입니다. 간단히 말해, 우리는 커뮤니티와 살고 호흡하며 그것이 우리가 다른 점입니다.</description> + <description lang="nl_NL">De Movie Database (TMDb) is een door de gemeenschap gebouwde film- en tv-database. Elk stukje data is toegevoegd door onze geweldige community die teruggaat tot 2008. TMDb's sterke internationale focus en brede waaier aan data is grotendeels ongeëvenaard en iets waar we ongelooflijk trots op zijn. Simpel gezegd, we leven en ademen gemeenschap en dat is precies wat ons anders maakt.</description> + <description lang="pl_PL">The Movie Database (TMDb) to baza filmów i programów telewizyjnych zbudowana przez społeczność. Każdy element danych został dodany przez naszą niesamowitą społeczność od 2008 roku. Silna międzynarodowa koncentracja TMDb i zakres danych są w dużej mierze niezrównane i jesteśmy z tego niesamowicie dumni. Mówiąc prościej, żyjemy i oddychamy wspólnotą i właśnie to nas wyróżnia.</description> + <description lang="pt_BR">The Movie Database (TMDb) é um banco de dados de filmes e séries criado pela comunidade. Cada um dos pedaços de dados foram adicionados por nossa incrível comunidade desde 2008. O forte foco internacional e a amplitude de dados do TMDb são incomparáveis, algo do qual temos muito orgulho. Resumindo, vivemos e respiramos comunidade e é precisamente isso que nos torna diferentes.</description> + <description lang="ru_RU">The Movie Database (TMDb) - это база данных фильмов и сериалов, созданная сообществом. Все данные были добавлены нашим замечательным сообществом, начиная с 2008 года. Международная направленность и широта данных TMDb во многом не имеют себе равных, и это то, чем мы невероятно гордимся. Проще говоря, мы живём и дышим сообществом, и именно это отличает нас от других.</description> + <description lang="sv_SE">The Movie Database (TMDb) är en communitybyggd film- och TV-databas. All data har lagts till av våra fantastiska medlemmar ända sen 2008. TMDbs starka internationella fokus och bredd är i stort sett oöverträffad och något vi är väldigt stolta över. Communityn är hjärtat i det hela och det är det som skiljer oss från mängden.</description> + <description lang="tr_TR">The Movie Database (TMDb), topluluk tarafından oluşturulmuş bir film ve TV veritabanıdır. Her bir veri parçası, 2008 yılına dayanan muhteşem topluluğumuz tarafından eklenmiştir. TMDb'nin güçlü uluslararası odağı ve veri genişliği büyük ölçüde benzersizdir ve inanılmaz derecede gurur duyduğumuz bir şeydir. Basitçe söylemek gerekirse, topluluk içinde yaşıyor ve nefes alıyoruz ve bizi farklı kılan da tam olarak bu.</description> + <description lang="zh_CN">TMDb 是一个由网络社区创建的影视数据库。每一条数据都是由我们令人惊叹的社区添加的,可以追溯到2008年。TMDb 强大的国际关注度和数据广度在很大程度上是无与伦比的,我们对此感到无比自豪。简单地说,我们生活在社区中,呼吸着社区的气息,这正是我们与众不同的原因。</description> </extension> </addon> diff --git a/addons/metadata.tvshows.themoviedb.org.python/changelog.txt b/addons/metadata.tvshows.themoviedb.org.python/changelog.txt index 8b8f394dec..9fd18138ef 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/changelog.txt +++ b/addons/metadata.tvshows.themoviedb.org.python/changelog.txt @@ -1,3 +1,77 @@ +1.6.0 +change to use new JSON based episodeguide format to handle multiple providers +fixes for last minute changes to new Nexus Python bindings + +1.5.6 +fix for scraper ignoring NFO files with TVDB artwork URL + +1.5.5 +fix for cast order in episode lists +remove use of depreciated logos categorization +filter out SVG artwork +updated language files + +1.5.4 +unreleased + +1.5.3 +fix to properly parse NFO file for show ID + +1.5.2 +added episode runtime from TMDb API +updated language files + +1.5.1 +added two language options to the settings (kn-IN and te-IN) +updated language files +added additional error catching for bad JSON from API +added typing for all calls + +1.5.0 +Nexus only updates to use new VideoInfoTag setters +general code cleanup +updated language files + +1.4.10 +updates to image reduction algorithm to better deal with shows with lots of seasons + +1.4.9 +fix to fallback to English trailer if no trailer found in selected language + +1.4.8 +changed IMDB parsing to use JSON from application/ld+json section instead of HTML +updated language files + +1.4.7 +fix for shows with large number of images causing SQL error when using mySQL/MariaDB + +1.4.6 +fix IMDB HTML scrape to work with new IMDB site +language updates + +1.4.5 +fix settings file to show correct default for studio and country storage + +1.4.4 +fix so studios get added even if there is no country +change the default setting so that studio and country are stored separately + +1.4.3 +fix for crash if episode group has a season with no episodes + +1.4.2 +fix for crash if Kodi calls the scraper without passing the source settings +change so that old TVDB XML scraper episodeguide URL will work to get new episodeguide from TMDb + +1.4.1 +fix for crash if TMDb returns no TV show results when using external ID in parsing nfo file + +1.4.0 +TV show trailers from YouTube now included in metadata (with option to turn it off) +option to select which YouTube addon is used to play trailers +scraper now parses IDs from TVDB and IMDB URLs for identifying shows +(thanks to tielis for these contributions) + 1.3.14 fix for named seasons in NFO files being ignored diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/__init__.py b/addons/metadata.tvshows.themoviedb.org.python/libs/__init__.py deleted file mode 100644 index 2cbe995660..0000000000 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# coding: utf-8 -# Author:Team Kodi diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/actions.py b/addons/metadata.tvshows.themoviedb.org.python/libs/actions.py index d50558e007..100476a6bc 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/actions.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/actions.py @@ -22,8 +22,11 @@ from __future__ import absolute_import, unicode_literals -import sys, urllib.parse -import xbmcgui, xbmcplugin +import sys +import json +import urllib.parse +import xbmcgui +import xbmcplugin from . import tmdb, data_utils from .utils import logger, safe_get try: @@ -47,7 +50,8 @@ def find_show(title, year=None): show_name += ' ({})'.format(search_result['first_air_date'][:4]) list_item = xbmcgui.ListItem(show_name, offscreen=True) show_info = search_result - list_item = data_utils.add_main_show_info(list_item, show_info, full_info=False) + list_item = data_utils.add_main_show_info( + list_item, show_info, full_info=False) # Below "url" is some unique ID string (may be an actual URL to a show page) # that is used to get information about a specific TV show. xbmcplugin.addDirectoryItem( @@ -74,7 +78,8 @@ def get_show_id_from_nfo(nfo): parse_result, named_seasons = data_utils.parse_nfo_url(nfo) if parse_result: if parse_result.provider == 'themoviedb': - show_info = tmdb.load_show_info(parse_result.show_id, ep_grouping=parse_result.ep_grouping, named_seasons=named_seasons) + show_info = tmdb.load_show_info( + parse_result.show_id, ep_grouping=parse_result.ep_grouping, named_seasons=named_seasons) else: show_info = None if show_info is not None: @@ -96,34 +101,55 @@ def get_details(show_id): show_info = tmdb.load_show_info(show_id) if show_info is not None: list_item = xbmcgui.ListItem(show_info['name'], offscreen=True) - list_item = data_utils.add_main_show_info(list_item, show_info, full_info=True) + list_item = data_utils.add_main_show_info( + list_item, show_info, full_info=True) xbmcplugin.setResolvedUrl(HANDLE, True, list_item) else: - xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem(offscreen=True)) + xbmcplugin.setResolvedUrl( + HANDLE, False, xbmcgui.ListItem(offscreen=True)) -def get_episode_list(show_id): # pylint: disable=missing-docstring +def get_episode_list(show_ids): # pylint: disable=missing-docstring # type: (Text) -> None - logger.debug('Getting episode list for show id {}'.format(show_id)) - if not show_id.isdigit(): - # Kodi has a bug: when a show directory contains an XML NFO file with - # episodeguide URL, that URL is always passed here regardless of - # the actual parsing result in get_show_from_nfo() + # Kodi has a bug: when a show directory contains an XML NFO file with + # episodeguide URL, that URL is always passed here regardless of + # the actual parsing result in get_show_from_nfo() + # so much of this weird logic is to deal with that + try: + all_ids = json.loads(show_ids) + show_id = all_ids.get('tmdb') + if not show_id: + for key, value in all_ids.items(): + show_id = str(data_utils._convert_ext_id(key, value)) + if show_id: + break + if not show_id: + show_id = str(show_ids) + except (ValueError, AttributeError): + show_id = str(show_ids) + if show_id.isdigit(): + logger.error( + 'using deprecated episodeguide format, this show should be refreshed or rescraped') + if not show_id: + raise RuntimeError( + 'No TMDb TV show id found in episode guide, this show should be refreshed or rescraped') + elif not show_id.isdigit(): parse_result, named_seasons = data_utils.parse_nfo_url(show_id) - if not parse_result: - return - if parse_result.provider == 'themoviedb' or parse_result.provider == 'tmdb': - show_info = tmdb.load_show_info(parse_result.show_id) + if parse_result: + show_id = parse_result.show_id else: - return - else: - show_info = tmdb.load_show_info(show_id) + raise RuntimeError( + 'No TMDb TV show id found in episode guide, this show should be refreshed or rescraped') + logger.debug('Getting episode list for show id {}'.format(show_id)) + show_info = tmdb.load_show_info(show_id) if show_info is not None: theindex = 0 for episode in show_info['episodes']: - epname = episode.get('name', 'Episode ' + str(episode['episode_number'])) + epname = episode.get('name', 'Episode ' + + str(episode['episode_number'])) list_item = xbmcgui.ListItem(epname, offscreen=True) - list_item = data_utils.add_episode_info(list_item, episode, full_info=False) + list_item = data_utils.add_episode_info( + list_item, episode, full_info=False) encoded_ids = urllib.parse.urlencode( {'show_id': str(show_info['id']), 'episode_id': str(theindex)} ) @@ -137,6 +163,10 @@ def get_episode_list(show_id): # pylint: disable=missing-docstring listitem=list_item, isFolder=True ) + else: + logger.error( + 'unable to get show information using show id {}'.format(show_id)) + logger.error('you may need to refresh the show to get a valid show id') def get_episode_details(encoded_ids): # pylint: disable=missing-docstring @@ -149,10 +179,12 @@ def get_episode_details(encoded_ids): # pylint: disable=missing-docstring ) if episode_info: list_item = xbmcgui.ListItem(episode_info['name'], offscreen=True) - list_item = data_utils.add_episode_info(list_item, episode_info, full_info=True) + list_item = data_utils.add_episode_info( + list_item, episode_info, full_info=True) xbmcplugin.setResolvedUrl(HANDLE, True, list_item) else: - xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem(offscreen=True)) + xbmcplugin.setResolvedUrl( + HANDLE, False, xbmcgui.ListItem(offscreen=True)) def get_artwork(show_id): @@ -163,7 +195,7 @@ def get_artwork(show_id): :param show_id: default unique ID set by setUniqueIDs() method """ if not show_id: - return + return logger.debug('Getting artwork for show ID {}'.format(show_id)) show_info = tmdb.load_show_info(show_id) if show_info is not None: @@ -171,7 +203,8 @@ def get_artwork(show_id): list_item = data_utils.set_show_artwork(show_info, list_item) xbmcplugin.setResolvedUrl(HANDLE, True, list_item) else: - xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem(offscreen=True)) + xbmcplugin.setResolvedUrl( + HANDLE, False, xbmcgui.ListItem(offscreen=True)) def router(paramstring): diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/api_utils.py b/addons/metadata.tvshows.themoviedb.org.python/libs/api_utils.py index 71ad98732f..ce65fa32b0 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/api_utils.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/api_utils.py @@ -35,11 +35,12 @@ HEADERS = {} def set_headers(headers): + # type: (Dict) -> None HEADERS.update(headers) -def load_info(url, params=None, default=None, resp_type = 'json', verboselog=False): - # type: (Text, Optional[Dict[Text, Union[Text, List[Text]]]]) -> Union[dict, list] +def load_info(url, params=None, default=None, resp_type='json', verboselog=False): + # type: (Text, Dict, Text, Text, bool) -> Optional[Text] """ Load info from external api @@ -57,14 +58,20 @@ def load_info(url, params=None, default=None, resp_type = 'json', verboselog=Fal response = urlopen(req) except URLError as e: if hasattr(e, 'reason'): - logger.debug('failed to reach the remote site\nReason: {}'.format(e.reason)) + logger.debug( + 'failed to reach the remote site\nReason: {}'.format(e.reason)) elif hasattr(e, 'code'): - logger.debug('remote site unable to fulfill the request\nError code: {}'.format(e.code)) + logger.debug( + 'remote site unable to fulfill the request\nError code: {}'.format(e.code)) response = None if response is None: resp = default elif resp_type.lower() == 'json': - resp = json.loads(response.read().decode('utf-8')) + try: + resp = json.loads(response.read().decode('utf-8')) + except json.decoder.JSONDecodeError: + logger.debug('remote site sent back bad JSON') + resp = default else: resp = response.read().decode('utf-8') if verboselog: diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/cache.py b/addons/metadata.tvshows.themoviedb.org.python/libs/cache.py index a19853b9a8..272f339a61 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/cache.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/cache.py @@ -22,8 +22,10 @@ from __future__ import absolute_import, unicode_literals -import os, pickle -import xbmc, xbmcvfs +import os +import pickle +import xbmc +import xbmcvfs from .utils import ADDON, logger @@ -33,7 +35,6 @@ except ImportError: pass - def _get_cache_directory(): # pylint: disable=missing-docstring # type: () -> Text temp_dir = xbmcvfs.translatePath('special://temp') diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/data_utils.py b/addons/metadata.tvshows.themoviedb.org.python/libs/data_utils.py index 0090837691..779789ac65 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/data_utils.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/data_utils.py @@ -22,29 +22,42 @@ from __future__ import absolute_import, unicode_literals -import re, json -from collections import OrderedDict, namedtuple +import re +import json +from xbmc import Actor, VideoStreamDetail +from collections import namedtuple from .utils import safe_get, logger -from . import settings +from . import settings, api_utils try: - from typing import Optional, Text, Dict, List, Any # pylint: disable=unused-import + from typing import Optional, Tuple, Text, Dict, List, Any # pylint: disable=unused-import from xbmcgui import ListItem # pylint: disable=unused-import InfoType = Dict[Text, Any] # pylint: disable=invalid-name except ImportError: pass +TMDB_PARAMS = {'api_key': settings.TMDB_CLOWNCAR, 'language': settings.LANG} +BASE_URL = 'https://api.themoviedb.org/3/{}' +FIND_URL = BASE_URL.format('find/{}') TAG_RE = re.compile(r'<[^>]+>') + +# Regular expressions are listed in order of priority. +# "TMDB" provider is preferred than other providers (IMDB and TheTVDB), +# because external providers IDs need to be converted to TMDB_ID. SHOW_ID_REGEXPS = ( - r'(tvmaze)\.com/shows/(\d+)/[\w\-]', - r'(thetvdb)\.com/.*?series/(\d+)', - r'(thetvdb)\.com[\w=&\?/]+id=(\d+)', - r'(imdb)\.com/[\w/\-]+/(tt\d+)', - r'(themoviedb)\.org/tv/(\d+).*/episode_group/(.*)', - r'(themoviedb)\.org/tv/(\d+)', - r'(themoviedb)\.org/./tv/(\d+)', - r'(tmdb)\.org/./tv/(\d+)' + r'(themoviedb)\.org/tv/(\d+).*/episode_group/(.*)', # TMDB_http_link + r'(themoviedb)\.org/tv/(\d+)', # TMDB_http_link + r'(themoviedb)\.org/./tv/(\d+)', # TMDB_http_link + r'(tmdb)\.org/./tv/(\d+)', # TMDB_http_link + r'(imdb)\.com/.+/(tt\d+)', # IMDB_http_link + r'(thetvdb)\.com.+&id=(\d+)', # TheTVDB_http_link + r'(thetvdb)\.com/series/(\d+)', # TheTVDB_http_link + r'(thetvdb)\.com/api/.*series/(\d+)', # TheTVDB_http_link + r'(thetvdb)\.com/.*?"id":(\d+)', # TheTVDB_http_link + r'<uniqueid.+?type="(tvdb|imdb)".*?>([t\d]+?)</uniqueid>' ) + + SUPPORTED_ARTWORK_TYPES = {'poster', 'banner'} IMAGE_SIZES = ('large', 'original', 'medium') CLEAN_PLOT_REPLACEMENTS = ( @@ -56,8 +69,8 @@ CLEAN_PLOT_REPLACEMENTS = ( ) VALIDEXTIDS = ['tmdb_id', 'imdb_id', 'tvdb_id'] -UrlParseResult = namedtuple('UrlParseResult', ['provider', 'show_id', 'ep_grouping']) - +UrlParseResult = namedtuple( + 'UrlParseResult', ['provider', 'show_id', 'ep_grouping']) def _clean_plot(plot): @@ -69,12 +82,12 @@ def _clean_plot(plot): return plot -def _set_cast(cast_info, list_item): +def _set_cast(cast_info, vtag): # type: (InfoType, ListItem) -> ListItem """Save cast info to list item""" cast = [] for item in cast_info: - data = { + actor = { 'name': item['name'], 'role': item.get('character', item.get('character_name', '')), 'order': item['order'], @@ -82,11 +95,8 @@ def _set_cast(cast_info, list_item): thumb = None if safe_get(item, 'profile_path') is not None: thumb = settings.IMAGEROOTURL + item['profile_path'] - if thumb: - data['thumbnail'] = thumb - cast.append(data) - list_item.setCast(cast) - return list_item + cast.append(Actor(actor['name'], actor['role'], actor['order'], thumb)) + vtag.setCast(cast) def _get_credits(show_info): @@ -96,7 +106,8 @@ def _get_credits(show_info): for item in show_info.get('created_by', []): credits.append(item['name']) for item in show_info.get('credits', {}).get('crew', []): - isWriter = item.get('job', '').lower() == 'writer' or item.get('department', '').lower() == 'writing' + isWriter = item.get('job', '').lower() == 'writer' or item.get( + 'department', '').lower() == 'writing' if isWriter and item.get('name') not in credits: credits.append(item['name']) return credits @@ -112,59 +123,70 @@ def _get_directors(episode_info): return directors_ -def _set_unique_ids(ext_ids, list_item): - # type: (InfoType, ListItem) -> ListItem +def _set_unique_ids(ext_ids, vtag): + # type: (Dict, ListItem) -> ListItem """Extract unique ID in various online databases""" - unique_ids = {} + return_ids = {} for key, value in ext_ids.items(): if key in VALIDEXTIDS and value: - key = key[:-3] - unique_ids[key] = str(value) - list_item.setUniqueIDs(unique_ids, 'tmdb') - return list_item + if key == 'tmdb_id': + isTMDB = True + else: + isTMDB = False + shortkey = key[:-3] + str_value = str(value) + vtag.setUniqueID(str_value, type=shortkey, isdefault=isTMDB) + return_ids[shortkey] = str_value + return return_ids -def _set_rating(the_info, list_item, episode=False): - # type: (InfoType, ListItem) -> ListItem +def _set_rating(the_info, vtag): + # type: (InfoType, ListItem) -> None """Set show/episode rating""" first = True for rating_type in settings.RATING_TYPES: logger.debug('adding rating type of %s' % rating_type) - rating = float(the_info.get('ratings', {}).get(rating_type, {}).get('rating', '0')) - votes = int(the_info.get('ratings', {}).get(rating_type, {}).get('votes', '0')) - logger.debug("adding rating of %s and votes of %s" % (str(rating), str(votes))) + rating = float(the_info.get('ratings', {}).get( + rating_type, {}).get('rating', '0')) + votes = int(the_info.get('ratings', {}).get( + rating_type, {}).get('votes', '0')) + logger.debug("adding rating of %s and votes of %s" % + (str(rating), str(votes))) if rating > 0: - list_item.setRating(rating_type, rating, votes=votes, defaultt=first) + vtag.setRating(rating, votes=votes, + type=rating_type, isdefault=first) first = False - return list_item -def _add_season_info(show_info, list_item): - # type: (InfoType, ListItem) -> ListItem +def _add_season_info(show_info, vtag): + # type: (InfoType, ListItem) -> None """Add info for show seasons""" for season in show_info['seasons']: - logger.debug('adding information for season %s to list item' % season['season_number']) - list_item.addSeason(season['season_number'], safe_get(season, 'name', '')) + logger.debug('adding information for season %s to list item' % + season['season_number']) + vtag.addSeason(season['season_number'], + safe_get(season, 'name', '')) for image_type, image_list in season.get('images', {}).items(): if image_type == 'posters': destination = 'poster' else: destination = image_type for image in image_list: - if image.get('type') == 'fanarttv': - theurl = image['file_path'] - previewurl = theurl.replace('.fanart.tv/fanart/', '.fanart.tv/preview/') - else: - theurl = settings.IMAGEROOTURL + image['file_path'] - previewurl = settings.PREVIEWROOTURL + image['file_path'] - list_item.addAvailableArtwork(theurl, art_type=destination, preview=previewurl, season=season['season_number']) - return list_item + theurl, previewurl = get_image_urls(image) + if theurl: + vtag.addAvailableArtwork( + theurl, arttype=destination, preview=previewurl, season=season['season_number']) -def get_image_urls( image ): +def get_image_urls(image): + # type: (Dict) -> Tuple[Text, Text] + """Get image URLs from image information""" + if image.get('file_path', '').endswith('.svg'): + return None, None if image.get('type') == 'fanarttv': theurl = image['file_path'] - previewurl = theurl.replace('.fanart.tv/fanart/', '.fanart.tv/preview/') + previewurl = theurl.replace( + '.fanart.tv/fanart/', '.fanart.tv/preview/') else: theurl = settings.IMAGEROOTURL + image['file_path'] previewurl = settings.PREVIEWROOTURL + image['file_path'] @@ -174,61 +196,64 @@ def get_image_urls( image ): def set_show_artwork(show_info, list_item): # type: (InfoType, ListItem) -> ListItem """Set available images for a show""" + vtag = list_item.getVideoInfoTag() for image_type, image_list in show_info.get('images', {}).items(): if image_type == 'backdrops': fanart_list = [] for image in image_list: - if image.get('type') == 'fanarttv': - theurl = image['file_path'] - else: - theurl = settings.IMAGEROOTURL + image['file_path'] - if image.get('iso_639_1') != None and settings.CATLANDSCAPE: - theurl, previewurl = get_image_urls( image ) - list_item.addAvailableArtwork(theurl, art_type="landscape", preview=previewurl) - else: + theurl, previewurl = get_image_urls(image) + if image.get('iso_639_1') != None and settings.CATLANDSCAPE and theurl: + vtag.addAvailableArtwork( + theurl, arttype="landscape", preview=previewurl) + elif theurl: fanart_list.append({'image': theurl}) if fanart_list: list_item.setAvailableFanart(fanart_list) else: if image_type == 'posters': destination = 'poster' + elif image_type == 'logos': + destination = 'clearlogo' else: destination = image_type for image in image_list: - theurl, previewurl = get_image_urls( image ) - list_item.addAvailableArtwork(theurl, art_type=destination, preview=previewurl) + theurl, previewurl = get_image_urls(image) + if theurl: + vtag.addAvailableArtwork( + theurl, arttype=destination, preview=previewurl) return list_item def add_main_show_info(list_item, show_info, full_info=True): # type: (ListItem, InfoType, bool) -> ListItem """Add main show info to a list item""" - plot = _clean_plot(safe_get(show_info, 'overview', '')) + vtag = list_item.getVideoInfoTag() original_name = show_info.get('original_name') if settings.KEEPTITLE and original_name: showname = original_name else: showname = show_info['name'] - video = { - 'plot': plot, - 'plotoutline': plot, - 'title': showname, - 'originaltitle': original_name, - 'tvshowtitle': showname, - 'mediatype': 'tvshow', - # This property is passed as "url" parameter to getepisodelist call - 'episodeguide': str(show_info['id']), - } + plot = _clean_plot(safe_get(show_info, 'overview', '')) + vtag.setTitle(showname) + vtag.setOriginalTitle(original_name) + vtag.setTvShowTitle(showname) + vtag.setPlot(plot) + vtag.setPlotOutline(plot) + vtag.setMediaType('tvshow') + ext_ids = {'tmdb_id': show_info['id']} + ext_ids.update(show_info.get('external_ids', {})) + epguide_ids = _set_unique_ids(ext_ids, vtag) + vtag.setEpisodeGuide(json.dumps(epguide_ids)) if show_info.get('first_air_date'): - video['year'] = int(show_info['first_air_date'][:4]) - video['premiered'] = show_info['first_air_date'] + vtag.setYear(int(show_info['first_air_date'][:4])) + vtag.setPremiered(show_info['first_air_date']) if full_info: - video['status'] = safe_get(show_info, 'status', '') + vtag.setTvShowStatus(safe_get(show_info, 'status', '')) genre_list = safe_get(show_info, 'genres', {}) genres = [] for genre in genre_list: genres.append(genre['name']) - video['genre'] = genres + vtag.setGenres(genres) networks = show_info.get('networks', []) if networks: network = networks[0] @@ -236,10 +261,14 @@ def add_main_show_info(list_item, show_info, full_info=True): else: network = None country = None - if network and country: - video['studio'] = '{0} ({1})'.format(network['name'], country) - video['country'] = country - content_ratings = show_info.get('content_ratings', {}).get('results', {}) + if network and country and settings.STUDIOCOUNTRY: + vtag.setStudios(['{0} ({1})'.format(network['name'], country)]) + elif network: + vtag.setStudios([network['name']]) + if country: + vtag.setCountries([country]) + content_ratings = show_info.get( + 'content_ratings', {}).get('results', {}) if content_ratings: mpaa = '' mpaa_backup = '' @@ -252,60 +281,67 @@ def add_main_show_info(list_item, show_info, full_info=True): if not mpaa: mpaa = mpaa_backup if mpaa: - video['Mpaa'] = settings.CERT_PREFIX + mpaa - video['credits'] = video['writer'] = _get_credits(show_info) + vtag.setMpaa(settings.CERT_PREFIX + mpaa) + vtag.setWriters(_get_credits(show_info)) + if settings.ENABTRAILER: + trailer = _parse_trailer(show_info.get( + 'videos', {}).get('results', {})) + if trailer: + vtag.setTrailer(trailer) list_item = set_show_artwork(show_info, list_item) - list_item = _add_season_info(show_info, list_item) - list_item = _set_cast(show_info['credits']['cast'], list_item) - list_item = _set_rating(show_info, list_item) - ext_ids = {'tmdb_id': show_info['id']} - ext_ids.update(show_info.get('external_ids', {})) - list_item = _set_unique_ids(ext_ids, list_item) + _add_season_info(show_info, vtag) + _set_cast(show_info['credits']['cast'], vtag) + _set_rating(show_info, vtag) else: - image = safe_get(show_info, 'poster_path', '') - if image: + image = show_info.get('poster_path', '') + if image and not image.endswith('.svg'): theurl = settings.IMAGEROOTURL + image previewurl = settings.PREVIEWROOTURL + image - list_item.addAvailableArtwork(theurl, art_type='poster', preview=previewurl) - logger.debug('adding tv show information for %s to list item' % video['tvshowtitle']) - list_item.setInfo('video', video) - # This is needed for getting artwork - list_item = _set_unique_ids(show_info, list_item) + vtag.addAvailableArtwork( + theurl, arttype='poster', preview=previewurl) + logger.debug('adding tv show information for %s to list item' % showname) return list_item def add_episode_info(list_item, episode_info, full_info=True): # type: (ListItem, InfoType, bool) -> ListItem """Add episode info to a list item""" - video = { - 'title': episode_info.get('name', 'Episode ' + str(episode_info['episode_number'])), - 'season': episode_info['season_number'], - 'episode': episode_info['episode_number'], - 'mediatype': 'episode', - } + title = episode_info.get('name', 'Episode ' + + str(episode_info['episode_number'])) + vtag = list_item.getVideoInfoTag() + vtag.setTitle(title) + vtag.setSeason(episode_info['season_number']) + vtag.setEpisode(episode_info['episode_number']) + vtag.setMediaType('episode') if safe_get(episode_info, 'air_date') is not None: - video['aired'] = episode_info['air_date'] + vtag.setFirstAired(episode_info['air_date']) if full_info: summary = safe_get(episode_info, 'overview') if summary is not None: - video['plot'] = video['plotoutline'] = _clean_plot(summary) + plot = _clean_plot(summary) + vtag.setPlot(plot) + vtag.setPlotOutline(plot) if safe_get(episode_info, 'air_date') is not None: - video['premiered'] = episode_info['air_date'] - list_item = _set_cast(episode_info['credits']['guest_stars'], list_item) + vtag.setPremiered(episode_info['air_date']) + duration = episode_info.get('runtime') + if duration: + videostream = VideoStreamDetail(duration=int(duration)*60) + vtag.addVideoStream(videostream) + _set_cast( + episode_info['season_cast'] + episode_info['credits']['guest_stars'], vtag) ext_ids = {'tmdb_id': episode_info['id']} ext_ids.update(episode_info.get('external_ids', {})) - list_item = _set_unique_ids(ext_ids, list_item) - list_item = _set_rating(episode_info, list_item, episode=True) + _set_unique_ids(ext_ids, vtag) + _set_rating(episode_info, vtag) for image in episode_info.get('images', {}).get('stills', []): - img_path = image.get('file_path') - if img_path: - theurl = settings.IMAGEROOTURL + img_path - previewurl = settings.PREVIEWROOTURL + img_path - list_item.addAvailableArtwork(theurl, art_type='thumb', preview=previewurl) - video['credits'] = video['writer'] = _get_credits(episode_info) - video['director'] = _get_directors(episode_info) - logger.debug('adding episode information for S%sE%s - %s to list item' % (video['season'], video['episode'], video['title'])) - list_item.setInfo('video', video) + theurl, previewurl = get_image_urls(image) + if theurl: + vtag.addAvailableArtwork( + theurl, arttype='thumb', preview=previewurl) + vtag.setWriters(_get_credits(episode_info)) + vtag.setDirectors(_get_directors(episode_info)) + logger.debug('adding episode information for S%sE%s - %s to list item' % + (episode_info['season_number'], episode_info['episode_number'], title)) return list_item @@ -316,6 +352,7 @@ def parse_nfo_url(nfo): ns_regex = r'<namedseason number="(.*)">(.*)</namedseason>' ns_match = re.findall(ns_regex, nfo, re.I) sid_match = None + ep_grouping = None for regexp in SHOW_ID_REGEXPS: logger.debug('trying regex to match service from parsing nfo:') logger.debug(regexp) @@ -323,27 +360,92 @@ def parse_nfo_url(nfo): if show_id_match: logger.debug('match group 1: ' + show_id_match.group(1)) logger.debug('match group 2: ' + show_id_match.group(2)) - try: - ep_grouping = show_id_match.group(3) - except IndexError: - ep_grouping = None - if ep_grouping is not None: - logger.debug('match group 3: ' + ep_grouping) + if show_id_match.group(1) == "themoviedb" or show_id_match.group(1) == "tmdb": + try: + ep_grouping = show_id_match.group(3) + except IndexError: + pass + tmdb_id = show_id_match.group(2) else: - logger.debug('match group 3: None') - sid_match = UrlParseResult(show_id_match.group(1), show_id_match.group(2), ep_grouping) - break + tmdb_id = _convert_ext_id( + show_id_match.group(1), show_id_match.group(2)) + if tmdb_id: + logger.debug('match group 3: ' + str(ep_grouping)) + sid_match = UrlParseResult('tmdb', tmdb_id, ep_grouping) + break return sid_match, ns_match +def _convert_ext_id(ext_provider, ext_id): + # type: (Text, Text) -> Text + """get a TMDb ID from an external ID""" + providers_dict = {'imdb': 'imdb_id', + 'thetvdb': 'tvdb_id', + 'tvdb': 'tvdb_id'} + show_url = FIND_URL.format(ext_id) + params = TMDB_PARAMS.copy() + provider = providers_dict.get(ext_provider) + if provider: + params['external_source'] = provider + show_info = api_utils.load_info(show_url, params=params) + else: + show_info = None + if show_info: + tv_results = show_info.get('tv_results') + if tv_results: + return tv_results[0].get('id') + return None + + def parse_media_id(title): + # type: (Text) -> Dict + """get the ID from a title and return with the type""" title = title.lower() if title.startswith('tt') and title[2:].isdigit(): - return {'type': 'imdb_id', 'title': title} # IMDB ID works alone because it is clear - elif title.startswith('imdb/tt') and title[7:].isdigit(): # IMDB ID with prefix to match - return {'type': 'imdb_id', 'title': title[5:]} # IMDB ID works alone because it is clear - elif title.startswith('tmdb/') and title[5:].isdigit(): # TVDB ID + # IMDB ID works alone because it is clear + return {'type': 'imdb_id', 'title': title} + # IMDB ID with prefix to match + elif title.startswith('imdb/tt') and title[7:].isdigit(): + # IMDB ID works alone because it is clear + return {'type': 'imdb_id', 'title': title[5:]} + elif title.startswith('tmdb/') and title[5:].isdigit(): # TMDB ID return {'type': 'tmdb_id', 'title': title[5:]} - elif title.startswith('tvdb/') and title[5:].isdigit(): # TVDB ID + elif title.startswith('tvdb/') and title[5:].isdigit(): # TVDB ID return {'type': 'tvdb_id', 'title': title[5:]} return None + + +def _parse_trailer(results): + # type: (Text) -> Text + """create a valid Tubed or YouTube plugin trailer URL""" + if results: + if settings.PLAYERSOPT == 'tubed': + addon_player = 'plugin://plugin.video.tubed/?mode=play&video_id=' + elif settings.PLAYERSOPT == 'youtube': + addon_player = 'plugin://plugin.video.youtube/?action=play_video&videoid=' + backup_keys = [] + for video_lang in [settings.LANG[0:2], 'en']: + for result in results: + if result.get('site') == 'YouTube' and result.get('iso_639_1') == video_lang: + key = result.get('key') + if result.get('type') == 'Trailer': + if _check_youtube(key): + # video is available and is defined as "Trailer" by TMDB. Perfect link! + return addon_player+key + else: + # video is available, but NOT defined as "Trailer" by TMDB. Saving it as backup in case it doesn't find any perfect link. + backup_keys.append(key) + for keybackup in backup_keys: + if _check_youtube(keybackup): + return addon_player+keybackup + return None + + +def _check_youtube(key): + # type: (Text) -> bool + """check to see if the YouTube key returns a valid link""" + chk_link = "https://www.youtube.com/watch?v="+key + check = api_utils.load_info(chk_link, resp_type='not_json') + if not check or "Video unavailable" in check: # video not available + return False + return True diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/debugger.py b/addons/metadata.tvshows.themoviedb.org.python/libs/debugger.py index fd63eff084..6414722e6b 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/debugger.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/debugger.py @@ -90,10 +90,12 @@ def debug_exception(logger_func=logger.error): yield except Exception as exc: frame_info = inspect.trace(5)[-1] - logger_func('*** Unhandled exception detected: {} {} ***'.format(type(exc), exc)) + logger_func( + '*** Unhandled exception detected: {} {} ***'.format(type(exc), exc)) logger_func('*** Start diagnostic info ***') logger_func('System info: {0}'.format(uname())) - logger_func('OS info: {0}'.format(xbmc.getInfoLabel('System.OSVersionInfo'))) + logger_func('OS info: {0}'.format( + xbmc.getInfoLabel('System.OSVersionInfo'))) logger_func('Kodi version: {0}'.format( xbmc.getInfoLabel('System.BuildVersion'))) logger_func('File: {0}'.format(frame_info[1])) @@ -105,7 +107,9 @@ def debug_exception(logger_func=logger.error): else: context += '{0}: {1}'.format(str(i).rjust(5), line) logger_func('Code context:\n' + context) - logger_func('Global variables:\n' + _format_vars(frame_info[0].f_globals)) - logger_func('Local variables:\n' + _format_vars(frame_info[0].f_locals)) + logger_func('Global variables:\n' + + _format_vars(frame_info[0].f_globals)) + logger_func('Local variables:\n' + + _format_vars(frame_info[0].f_locals)) logger_func('**** End diagnostic info ****') raise exc diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/imdbratings.py b/addons/metadata.tvshows.themoviedb.org.python/libs/imdbratings.py index 331e55bae1..f74f0a4559 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/imdbratings.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/imdbratings.py @@ -20,43 +20,53 @@ import re +import json from . import api_utils from . import settings +try: + from typing import Optional, Tuple, Text, Dict, List, Any # pylint: disable=unused-import +except ImportError: + pass IMDB_RATINGS_URL = 'https://www.imdb.com/title/{}/' -IMDB_RATING_REGEX = re.compile(r'itemprop="ratingValue".*?>.*?([\d.]+).*?<') -IMDB_VOTES_REGEX = re.compile(r'itemprop="ratingCount".*?>.*?([\d,]+).*?<') +IMDB_JSON_REGEX = re.compile( + r'<script type="application\/ld\+json">(.*?)<\/script>') def get_details(imdb_id): + # type: (Text) -> Dict + """get the IMDB ratings details""" if not imdb_id: return {} votes, rating = _get_ratinginfo(imdb_id) return _assemble_imdb_result(votes, rating) + def _get_ratinginfo(imdb_id): - response = api_utils.load_info(IMDB_RATINGS_URL.format(imdb_id), default = '', resp_type='text', verboselog=settings.VERBOSELOG) + # type: (Text) -> Tuple[Text, Text] + """get the IMDB ratings details""" + response = api_utils.load_info(IMDB_RATINGS_URL.format( + imdb_id), default='', resp_type='text', verboselog=settings.VERBOSELOG) return _parse_imdb_result(response) + def _assemble_imdb_result(votes, rating): + # type: (Text, Text) -> Dict + """assemble to IMDB ratings into a Dict""" result = {} if votes and rating: result['ratings'] = {'imdb': {'votes': votes, 'rating': rating}} return result + def _parse_imdb_result(input_html): - rating = _parse_imdb_rating(input_html) - votes = _parse_imdb_votes(input_html) + # type: (Text) -> Tuple[Text, Text] + """parse the IMDB ratings from the JSON in the raw HTML""" + match = re.search(IMDB_JSON_REGEX, input_html) + if not match: + return None, None + imdb_json = json.loads(match.group(1)) + imdb_ratings = imdb_json.get("aggregateRating", {}) + rating = imdb_ratings.get("ratingValue", None) + votes = imdb_ratings.get("ratingCount", None) return votes, rating - -def _parse_imdb_rating(input_html): - match = re.search(IMDB_RATING_REGEX, input_html) - if (match): - return float(match.group(1)) - return None - -def _parse_imdb_votes(input_html): - match = re.search(IMDB_VOTES_REGEX, input_html) - if (match): - return int(match.group(1).replace(',', '')) - return None diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/settings.py b/addons/metadata.tvshows.themoviedb.org.python/libs/settings.py index 21c4e5b329..3d6cf7d367 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/settings.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/settings.py @@ -16,7 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. # pylint: disable=missing-docstring -import json, sys, urllib.parse +import json +import sys +import urllib.parse from .utils import logger from . import api_utils from xbmcaddon import Addon @@ -44,7 +46,8 @@ def _load_base_urls(): preview_root_url = conf['images']['secure_base_url'] + 'w780' ADDON.setSetting('originalUrl', image_root_url) ADDON.setSetting('previewUrl', preview_root_url) - ADDON.setSetting('lastUpdated', str(_get_date_numeric(datetime.now()))) + ADDON.setSetting('lastUpdated', str( + _get_date_numeric(datetime.now()))) return image_root_url, preview_root_url @@ -52,39 +55,49 @@ ADDON = Addon() TMDB_CLOWNCAR = 'af3a53eb387d57fc935e9128468b1899' FANARTTV_CLOWNCAR = 'b018086af0e1478479adfc55634db97d' TRAKT_CLOWNCAR = '90901c6be3b2de5a4fa0edf9ab5c75e9a5a0fef2b4ee7373d8b63dcf61f95697' -MAXIMAGES = 350 -FANARTTV_MAPPING = { 'showbackground': 'backdrops', - 'tvposter': 'posters', - 'tvbanner': 'banner', - 'hdtvlogo': 'clearlogo', - 'clearlogo': 'clearlogo', - 'hdclearart': 'clearart', - 'clearart': 'clearart', - 'tvthumb': 'landscape', - 'characterart': 'characterart', - 'seasonposter':'seasonposters', - 'seasonbanner':'seasonbanner', - 'seasonthumb': 'seasonlandscape' - } +MAXIMAGES = 200 +FANARTTV_MAPPING = {'showbackground': 'backdrops', + 'tvposter': 'posters', + 'tvbanner': 'banner', + 'hdtvlogo': 'clearlogo', + 'clearlogo': 'clearlogo', + 'hdclearart': 'clearart', + 'clearart': 'clearart', + 'tvthumb': 'landscape', + 'characterart': 'characterart', + 'seasonposter': 'seasonposters', + 'seasonbanner': 'seasonbanner', + 'seasonthumb': 'seasonlandscape' + } try: source_params = dict(urllib.parse.parse_qsl(sys.argv[2])) except IndexError: source_params = {} -source_settings = json.loads(source_params.get('pathSettings', {})) +source_settings = json.loads(source_params.get('pathSettings', '{}')) -KEEPTITLE =source_settings.get('keeporiginaltitle', ADDON.getSettingBool('keeporiginaltitle')) +KEEPTITLE = source_settings.get( + 'keeporiginaltitle', ADDON.getSettingBool('keeporiginaltitle')) CATLANDSCAPE = source_settings.get('cat_landscape', True) -VERBOSELOG = source_settings.get('verboselog', ADDON.getSettingBool('verboselog')) +STUDIOCOUNTRY = source_settings.get('studio_country', False) +ENABTRAILER = source_settings.get( + 'enab_trailer', ADDON.getSettingBool('enab_trailer')) +PLAYERSOPT = source_settings.get( + 'players_opt', ADDON.getSettingString('players_opt')).lower() +VERBOSELOG = source_settings.get( + 'verboselog', ADDON.getSettingBool('verboselog')) LANG = source_settings.get('language', ADDON.getSettingString('language')) -CERT_COUNTRY = source_settings.get('tmdbcertcountry', ADDON.getSettingString('tmdbcertcountry')).lower() +CERT_COUNTRY = source_settings.get( + 'tmdbcertcountry', ADDON.getSettingString('tmdbcertcountry')).lower() IMAGEROOTURL, PREVIEWROOTURL = _load_base_urls() if source_settings.get('usecertprefix', ADDON.getSettingBool('usecertprefix')): - CERT_PREFIX = source_settings.get('certprefix', ADDON.getSettingString('certprefix')) + CERT_PREFIX = source_settings.get( + 'certprefix', ADDON.getSettingString('certprefix')) else: CERT_PREFIX = '' -primary_rating = source_settings.get('ratings', ADDON.getSettingString('ratings')).lower() +primary_rating = source_settings.get( + 'ratings', ADDON.getSettingString('ratings')).lower() RATING_TYPES = [primary_rating] if source_settings.get('imdbanyway', ADDON.getSettingBool('imdbanyway')) and primary_rating != 'imdb': RATING_TYPES.append('imdb') @@ -92,5 +105,7 @@ if source_settings.get('traktanyway', ADDON.getSettingBool('traktanyway')) and p RATING_TYPES.append('trakt') if source_settings.get('tmdbanyway', ADDON.getSettingBool('tmdbanyway')) and primary_rating != 'tmdb': RATING_TYPES.append('tmdb') -FANARTTV_ENABLE = source_settings.get('enable_fanarttv', ADDON.getSettingBool('enable_fanarttv')) -FANARTTV_CLIENTKEY = source_settings.get('fanarttv_clientkey', ADDON.getSettingString('fanarttv_clientkey')) +FANARTTV_ENABLE = source_settings.get( + 'enable_fanarttv', ADDON.getSettingBool('enable_fanarttv')) +FANARTTV_CLIENTKEY = source_settings.get( + 'fanarttv_clientkey', ADDON.getSettingString('fanarttv_clientkey')) diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/tmdb.py b/addons/metadata.tvshows.themoviedb.org.python/libs/tmdb.py index 07211db568..3b5019fc3b 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/tmdb.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/tmdb.py @@ -52,7 +52,7 @@ if settings.FANARTTV_CLIENTKEY: def search_show(title, year=None): - # type: (Text) -> List[InfoType] + # type: (Text, Text) -> List """ Search for a single TV show @@ -64,7 +64,8 @@ def search_show(title, year=None): results = [] ext_media_id = data_utils.parse_media_id(title) if ext_media_id: - logger.debug('using %s of %s to find show' % (ext_media_id['type'], ext_media_id['title'])) + logger.debug('using %s of %s to find show' % + (ext_media_id['type'], ext_media_id['title'])) if ext_media_id['type'] == 'tmdb_id': search_url = SHOW_URL.format(ext_media_id['title']) else: @@ -76,7 +77,8 @@ def search_show(title, year=None): params['query'] = unicodedata.normalize('NFKC', title) if year: params['first_air_date_year'] = str(year) - resp = api_utils.load_info(search_url, params=params, verboselog=settings.VERBOSELOG) + resp = api_utils.load_info( + search_url, params=params, verboselog=settings.VERBOSELOG) if resp is not None: if ext_media_id: if ext_media_id['type'] == 'tmdb_id': @@ -92,18 +94,25 @@ def search_show(title, year=None): def load_episode_list(show_info, season_map, ep_grouping): - # type: (Text) -> List[InfoType] + # type: (InfoType, Dict, Text) -> Optional[InfoType] + """get the IMDB ratings details""" """Load episode list from themoviedb.org API""" episode_list = [] if ep_grouping is not None: - logger.debug('Getting episodes with episode grouping of ' + ep_grouping) + logger.debug( + 'Getting episodes with episode grouping of ' + ep_grouping) episode_group_url = EPISODE_GROUP_URL.format(ep_grouping) - custom_order = api_utils.load_info(episode_group_url, params=TMDB_PARAMS, verboselog=settings.VERBOSELOG) + custom_order = api_utils.load_info( + episode_group_url, params=TMDB_PARAMS, verboselog=settings.VERBOSELOG) if custom_order is not None: show_info['seasons'] = [] for custom_season in custom_order.get('groups', []): season_episodes = [] - current_season = season_map.get(str(custom_season['episodes'][0]['season_number']), {}).copy() + try: + current_season = season_map.get( + str(custom_season['episodes'][0]['season_number']), {}).copy() + except IndexError: + continue current_season['name'] = custom_season['name'] current_season['season_number'] = custom_season['order'] for episode in custom_season['episodes']: @@ -130,11 +139,13 @@ def load_episode_list(show_info, season_map, ep_grouping): def load_show_info(show_id, ep_grouping=None, named_seasons=None): - # type: (Text) -> Optional[InfoType] + # type: (Text, Text, Dict) -> Optional[InfoType] """ Get full info for a single show :param show_id: themoviedb.org show ID + :param ep_grouping: the episode group from TMDb + :param named_seasons: the named seasons from the NFO file :return: show info or None """ if named_seasons == None: @@ -144,40 +155,49 @@ def load_show_info(show_id, ep_grouping=None, named_seasons=None): logger.debug('no cache file found, loading from scratch') show_url = SHOW_URL.format(show_id) params = TMDB_PARAMS.copy() - params['append_to_response'] = 'credits,content_ratings,external_ids,images' + params['append_to_response'] = 'credits,content_ratings,external_ids,images,videos' params['include_image_language'] = '%s,en,null' % settings.LANG[0:2] - show_info = api_utils.load_info(show_url, params=params, verboselog=settings.VERBOSELOG) + params['include_video_language'] = '%s,en,null' % settings.LANG[0:2] + show_info = api_utils.load_info( + show_url, params=params, verboselog=settings.VERBOSELOG) if show_info is None: return None if show_info['overview'] == '' and settings.LANG != 'en-US': params['language'] = 'en-US' del params['append_to_response'] - show_info_backup = api_utils.load_info(show_url, params=params, verboselog=settings.VERBOSELOG) + show_info_backup = api_utils.load_info( + show_url, params=params, verboselog=settings.VERBOSELOG) if show_info_backup is not None: show_info['overview'] = show_info_backup.get('overview', '') params['language'] = settings.LANG season_map = {} params['append_to_response'] = 'credits,images' for season in show_info.get('seasons', []): - season_url = SEASON_URL.format(show_id, season['season_number']) - season_info = api_utils.load_info(season_url, params=params, default={}, verboselog=settings.VERBOSELOG) - if (season_info['overview'] == '' or season_info['name'].lower().startswith('season')) and settings.LANG != 'en-US': + season_url = SEASON_URL.format( + show_id, season.get('season_number', 0)) + season_info = api_utils.load_info( + season_url, params=params, default={}, verboselog=settings.VERBOSELOG) + if (season_info.get('overview', '') == '' or season_info.get('name', '').lower().startswith('season')) and settings.LANG != 'en-US': params['language'] = 'en-US' - season_info_backup = api_utils.load_info(season_url, params=params, default={}, verboselog=settings.VERBOSELOG) + season_info_backup = api_utils.load_info( + season_url, params=params, default={}, verboselog=settings.VERBOSELOG) params['language'] = settings.LANG - if season_info['overview'] == '': - season_info['overview'] = season_info_backup['overview'] - if season_info['name'].lower().startswith('season'): - season_info['name'] = season_info_backup['name'] + if season_info.get('overview', '') == '': + season_info['overview'] = season_info_backup.get( + 'overview', '') + if season_info.get('name', '').lower().startswith('season'): + season_info['name'] = season_info_backup.get('name', '') # this is part of a work around for xbmcgui.ListItem.addSeasons() not respecting NFO file information for named_season in named_seasons: - if str(named_season[0]) == str(season['season_number']): - logger.debug('adding season name of %s from named seasons in NFO for season %s' % (named_season[1], season['season_number'])) + if str(named_season[0]) == str(season.get('season_number')): + logger.debug('adding season name of %s from named seasons in NFO for season %s' % ( + named_season[1], season['season_number'])) season_info['name'] = named_season[1] break # end work around - season_info['images'] = _sort_image_types(season_info.get('images', {})) - season_map[str(season['season_number'])] = season_info + season_info['images'] = _sort_image_types( + season_info.get('images', {})) + season_map[str(season.get('season_number', 0))] = season_info show_info = load_episode_list(show_info, season_map, ep_grouping) show_info['ratings'] = load_ratings(show_info) show_info = load_fanarttv_art(show_info) @@ -187,9 +207,9 @@ def load_show_info(show_id, ep_grouping=None, named_seasons=None): cast = [] for season in reversed(show_info.get('seasons', [])): for cast_member in season.get('credits', {}).get('cast', []): - if cast_member['name'] not in cast_check: + if cast_member.get('name', '') not in cast_check: cast.append(cast_member) - cast_check.append(cast_member['name']) + cast_check.append(cast_member.get('name', '')) show_info['credits']['cast'] = cast logger.debug('saving show info to the cache') if settings.VERBOSELOG: @@ -216,11 +236,13 @@ def load_episode_info(show_id, episode_id): except KeyError: return None # this ensures we are using the season/ep from the episode grouping if provided - ep_url = EPISODE_URL.format(show_info['id'], episode_info['org_seasonnum'], episode_info['org_epnum']) + ep_url = EPISODE_URL.format( + show_info['id'], episode_info['org_seasonnum'], episode_info['org_epnum']) params = TMDB_PARAMS.copy() params['append_to_response'] = 'credits,external_ids,images' params['include_image_language'] = '%s,en,null' % settings.LANG[0:2] - ep_return = api_utils.load_info(ep_url, params=params, verboselog=settings.VERBOSELOG) + ep_return = api_utils.load_info( + ep_url, params=params, verboselog=settings.VERBOSELOG) if ep_return is None: return None bad_return_name = False @@ -228,7 +250,8 @@ def load_episode_info(show_id, episode_id): check_name = ep_return.get('name') if check_name == None: bad_return_name = True - ep_return['name'] = 'Episode ' + str(episode_info['episode_number']) + ep_return['name'] = 'Episode ' + \ + str(episode_info['episode_number']) elif check_name.lower().startswith('episode') or check_name == '': bad_return_name = True if ep_return.get('overview', '') == '': @@ -236,18 +259,27 @@ def load_episode_info(show_id, episode_id): if (bad_return_overview or bad_return_name) and settings.LANG != 'en-US': params['language'] = 'en-US' del params['append_to_response'] - ep_return_backup = api_utils.load_info(ep_url, params=params, verboselog=settings.VERBOSELOG) + ep_return_backup = api_utils.load_info( + ep_url, params=params, verboselog=settings.VERBOSELOG) if ep_return_backup is not None: if bad_return_overview: - ep_return['overview'] = ep_return_backup.get('overview', '') + ep_return['overview'] = ep_return_backup.get( + 'overview', '') if bad_return_name: - ep_return['name'] = ep_return_backup.get('name', 'Episode ' + str(episode_info['episode_number'])) + ep_return['name'] = ep_return_backup.get( + 'name', 'Episode ' + str(episode_info['episode_number'])) ep_return['images'] = _sort_image_types(ep_return.get('images', {})) ep_return['season_number'] = episode_info['season_number'] ep_return['episode_number'] = episode_info['episode_number'] ep_return['org_seasonnum'] = episode_info['org_seasonnum'] ep_return['org_epnum'] = episode_info['org_epnum'] - ep_return['ratings'] = load_ratings(ep_return, show_imdb_id=show_info.get('external_ids', {}).get('imdb_id')) + ep_return['ratings'] = load_ratings( + ep_return, show_imdb_id=show_info.get('external_ids', {}).get('imdb_id')) + for season in show_info.get('seasons', []): + if season.get('season_number') == episode_info['season_number']: + ep_return['season_cast'] = season.get( + 'credits', {}).get('cast', []) + break show_info['episodes'][int(episode_id)] = ep_return cache.cache_show_info(show_info) return ep_return @@ -255,12 +287,21 @@ def load_episode_info(show_id, episode_id): def load_ratings(the_info, show_imdb_id=''): + # type: (InfoType, Text) -> Dict + """ + Load the ratings for the show/episode + + :param the_info: show or episode info + :param show_imdb_id: show IMDB + :return: ratings or empty dict + """ ratings = {} imdb_id = the_info.get('external_ids', {}).get('imdb_id') for rating_type in settings.RATING_TYPES: logger.debug('setting rating using %s' % rating_type) if rating_type == 'tmdb': - ratings['tmdb'] = {'votes': the_info['vote_count'], 'rating': the_info['vote_average']} + ratings['tmdb'] = {'votes': the_info['vote_count'], + 'rating': the_info['vote_average']} elif rating_type == 'imdb' and imdb_id: imdb_rating = imdbratings.get_details(imdb_id).get('ratings') if imdb_rating: @@ -269,7 +310,8 @@ def load_ratings(the_info, show_imdb_id=''): if show_imdb_id: season = the_info['org_seasonnum'] episode = the_info['org_epnum'] - resp = traktratings.get_details(show_imdb_id, season=season, episode=episode) + resp = traktratings.get_details( + show_imdb_id, season=season, episode=episode) else: resp = traktratings.get_details(imdb_id) trakt_rating = resp.get('ratings') @@ -278,8 +320,9 @@ def load_ratings(the_info, show_imdb_id=''): logger.debug('returning ratings of\n{}'.format(pformat(ratings))) return ratings + def load_fanarttv_art(show_info): - # type: (Text) -> Optional[InfoType] + # type: (InfoType) -> Optional[InfoType] """ Add fanart.tv images for a show @@ -289,7 +332,8 @@ def load_fanarttv_art(show_info): tvdb_id = show_info.get('external_ids', {}).get('tvdb_id') if tvdb_id and settings.FANARTTV_ENABLE: fanarttv_url = FANARTTV_URL.format(tvdb_id) - artwork = api_utils.load_info(fanarttv_url, params=FANARTTV_PARAMS, verboselog=settings.VERBOSELOG) + artwork = api_utils.load_info( + fanarttv_url, params=FANARTTV_PARAMS, verboselog=settings.VERBOSELOG) if artwork is None: return show_info for fanarttv_type, tmdb_type in settings.FANARTTV_MAPPING.items(): @@ -313,14 +357,16 @@ def load_fanarttv_art(show_info): if not show_info['seasons'][s]['images'].get(image_type): show_info['seasons'][s]['images'][image_type] = [] if artseason == '' or artseason == str(season_num): - show_info['seasons'][s]['images'][image_type].append({'file_path':filepath, 'type':'fanarttv', 'iso_639_1': lang}) + show_info['seasons'][s]['images'][image_type].append( + {'file_path': filepath, 'type': 'fanarttv', 'iso_639_1': lang}) else: - show_info['images'][tmdb_type].append({'file_path':filepath, 'type':'fanarttv', 'iso_639_1': lang}) + show_info['images'][tmdb_type].append( + {'file_path': filepath, 'type': 'fanarttv', 'iso_639_1': lang}) return show_info def trim_artwork(show_info): - # type: (Text) -> Optional[InfoType] + # type: (InfoType) -> Optional[InfoType] """ Trim artwork to keep the text blob below 65K characters @@ -335,44 +381,41 @@ def trim_artwork(show_info): if image_type == 'backdrops': backdrops_total = backdrops_total + total else: - image_counts[image_type] = {'total':total} + image_counts[image_type] = {'total': total} image_total = image_total + total for season in show_info.get('seasons', []): for image_type, image_list in season.get('images', {}).items(): total = len(image_list) thetype = '%s_%s' % (str(season['season_number']), image_type) - image_counts[thetype] = {'total':total} + image_counts[thetype] = {'total': total} image_total = image_total + total if image_total <= settings.MAXIMAGES and backdrops_total <= settings.MAXIMAGES: return show_info if backdrops_total > settings.MAXIMAGES: logger.error('there are %s fanart images' % str(backdrops_total)) - logger.error('that is more than the max of %s, image results will be trimmed to the max' % str(settings.MAXIMAGES)) - reduce = -1 * (backdrops_total - settings.MAXIMAGES ) + logger.error('that is more than the max of %s, image results will be trimmed to the max' % str( + settings.MAXIMAGES)) + reduce = -1 * (backdrops_total - settings.MAXIMAGES) del show_info['images']['backdrops'][reduce:] if image_total > settings.MAXIMAGES: reduction = (image_total - settings.MAXIMAGES)/image_total logger.error('there are %s non-fanart images' % str(image_total)) - logger.error('that is more than the max of %s, image results will be trimmed by %s' % (str(settings.MAXIMAGES), str(reduction))) + logger.error('that is more than the max of %s, image results will be trimmed by %s' % ( + str(settings.MAXIMAGES), str(reduction))) for key, value in image_counts.items(): - total = value['total'] - reduce = int(floor(total * reduction)) - target = total - reduce - if target < 5: - reduce = 0 - else: - reduce = -1 * reduce - image_counts[key]['reduce'] = reduce + image_counts[key]['reduce'] = -1 * \ + int(floor(value['total'] * reduction)) logger.debug('%s: %s' % (key, pformat(image_counts[key]))) for image_type, image_list in show_info.get('images', {}).items(): if image_type == 'backdrops': - continue # already handled backdrops above + continue # already handled backdrops above reduce = image_counts[image_type]['reduce'] if reduce != 0: del show_info['images'][image_type][reduce:] for s in range(len(show_info.get('seasons', []))): for image_type, image_list in show_info['seasons'][s].get('images', {}).items(): - thetype = '%s_%s' % (str(show_info['seasons'][s]['season_number']), image_type) + thetype = '%s_%s' % ( + str(show_info['seasons'][s]['season_number']), image_type) reduce = image_counts[thetype]['reduce'] if reduce != 0: del show_info['seasons'][s]['images'][image_type][reduce:] @@ -380,12 +423,27 @@ def trim_artwork(show_info): def _sort_image_types(imagelist): + # type: (Dict) -> Dict + """ + sort the images by language + + :param imagelist: + :return: imagelist + """ for image_type, images in imagelist.items(): imagelist[image_type] = _image_sort(images, image_type) return imagelist def _image_sort(images, image_type): + # type: (List, Text) -> List + """ + sort the images by language + + :param images: + :param image_type: + :return: list of images + """ lang_pref = [] lang_null = [] lang_en = [] diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/traktratings.py b/addons/metadata.tvshows.themoviedb.org.python/libs/traktratings.py index e442b56455..48091b5550 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/traktratings.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/traktratings.py @@ -21,10 +21,8 @@ from __future__ import absolute_import, unicode_literals from . import api_utils, settings -from .utils import logger try: from typing import Text, Optional, Union, List, Dict, Any # pylint: disable=unused-import - InfoType = Dict[Text, Any] # pylint: disable=invalid-name except ImportError: pass @@ -43,6 +41,15 @@ EP_URL = SHOW_URL + '/seasons/{}/episodes/{}/ratings' def get_details(imdb_id, season=None, episode=None): + # type: (Text, Text, Text) -> Dict + """ + get the Trakt ratings + + :param imdb_id: + :param season: + :param episode: + :return: trackt ratings + """ result = {} if season and episode: url = EP_URL.format(imdb_id, season, episode) @@ -50,8 +57,9 @@ def get_details(imdb_id, season=None, episode=None): else: url = SHOW_URL.format(imdb_id) params = {'extended': 'full'} - resp = api_utils.load_info(url, params=params, default={}, verboselog=settings.VERBOSELOG) - rating =resp.get('rating') + resp = api_utils.load_info( + url, params=params, default={}, verboselog=settings.VERBOSELOG) + rating = resp.get('rating') votes = resp.get('votes') if votes and rating: result['ratings'] = {'trakt': {'votes': votes, 'rating': rating}} diff --git a/addons/metadata.tvshows.themoviedb.org.python/libs/utils.py b/addons/metadata.tvshows.themoviedb.org.python/libs/utils.py index 48961b47c5..a9dbfbbb1c 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/libs/utils.py +++ b/addons/metadata.tvshows.themoviedb.org.python/libs/utils.py @@ -33,7 +33,8 @@ ADDON = Addon() class logger: - log_message_prefix = '[{} ({})]: '.format(ADDON_ID, ADDON.getAddonInfo('version')) + log_message_prefix = '[{} ({})]: '.format( + ADDON_ID, ADDON.getAddonInfo('version')) @staticmethod def log(message, level=xbmc.LOGDEBUG): diff --git a/addons/metadata.tvshows.themoviedb.org.python/resources/language/resource.language.en_gb/strings.po b/addons/metadata.tvshows.themoviedb.org.python/resources/language/resource.language.en_gb/strings.po index b3153b254f..7ece84d5d0 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/resources/language/resource.language.en_gb/strings.po +++ b/addons/metadata.tvshows.themoviedb.org.python/resources/language/resource.language.en_gb/strings.po @@ -16,6 +16,14 @@ msgstr "" "Language: en\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +msgctxt "Addon Summary" +msgid "Fetch TV Show metadata from themoviedb.org" +msgstr "" + +msgctxt "Addon Description" +msgid "The Movie Database (TMDb) is a community built movie and TV database. Every piece of data has been added by our amazing community dating back to 2008. TMDb's strong international focus and breadth of data is largely unmatched and something we're incredibly proud of. Put simply, we live and breathe community and that's precisely what makes us different." +msgstr "" + msgctxt "#30000" msgid "Preferred language" msgstr "" @@ -40,6 +48,10 @@ msgctxt "#30005" msgid "Categorize fanart with text as landscape art" msgstr "" +msgctxt "#30006" +msgid "Include country code with studio" +msgstr "" + # empty strings from 30006 to 30099 msgctxt "#30100" @@ -128,3 +140,18 @@ msgctxt "#30304" msgid "Also add TMDb ratings" msgstr "" +msgctxt "#30305" +msgid "Enable Trailer" +msgstr "" + +msgctxt "#30306" +msgid "Addon Player" +msgstr "" + +msgctxt "#30307" +msgid "YouTube" +msgstr "" + +msgctxt "#30308" +msgid "Tubed" +msgstr "" diff --git a/addons/metadata.tvshows.themoviedb.org.python/resources/settings.xml b/addons/metadata.tvshows.themoviedb.org.python/resources/settings.xml index 626e01bead..df5de249f7 100644 --- a/addons/metadata.tvshows.themoviedb.org.python/resources/settings.xml +++ b/addons/metadata.tvshows.themoviedb.org.python/resources/settings.xml @@ -38,6 +38,7 @@ <option>it-IT</option> <option>ja-JP</option> <option>ka-GE</option> + <option>kn-IN</option> <option>ko-KR</option> <option>lt-LT</option> <option>lv-LV</option> @@ -55,6 +56,7 @@ <option>sr-RS</option> <option>sv-SE</option> <option>ta-IN</option> + <option>te-IN</option> <option>th-TH</option> <option>tr-TR</option> <option>uk-UA</option> @@ -146,6 +148,30 @@ <default>true</default> <control type="toggle"/> </setting> + <setting id="studio_country" type="boolean" label="30006" help=""> + <level>0</level> + <default>false</default> + <control type="toggle"/> + </setting> + <setting id="enab_trailer" type="boolean" label="30305" help=""> + <level>0</level> + <default>true</default> + <control type="toggle"/> + </setting> + <setting id="players_opt" type="string" parent="enab_trailer" label="30306" help=""> + <level>0</level> + <default>Tubed</default> + <constraints> + <options> + <option label="30307">YouTube</option> + <option label="30308">Tubed</option> + </options> + </constraints> + <dependencies> + <dependency type="enable" setting="enab_trailer" operator="is">true</dependency> + </dependencies> + <control type="spinner" format="string"/> + </setting> </group> </category> <category id="ratings" label="30300" help=""> |