From 01eb8c0129f3e89da010960d36854cea1a702283 Mon Sep 17 00:00:00 2001 From: echel0n Date: Thu, 13 Mar 2014 05:20:45 -0700 Subject: [PATCH] Added in TMDB lookups for metadata fanart and posters using there TV api, works good for TVRage shows that don't supply that content. Few bugfixes to the code. --- lib/tmdb_api/__init__.py | 1 + lib/tmdb_api/test_tmdb_api.py | 183 +++++++ lib/tmdb_api/tmdb_api.py | 828 ++++++++++++++++++++++++++++++ sickbeard/__init__.py | 4 +- sickbeard/indexers/generic.py | 3 +- sickbeard/indexers/indexer_api.py | 16 +- sickbeard/metadata/generic.py | 85 ++- sickbeard/metadata/xbmc_12plus.py | 2 +- sickbeard/webserve.py | 2 +- 9 files changed, 1106 insertions(+), 18 deletions(-) create mode 100644 lib/tmdb_api/__init__.py create mode 100644 lib/tmdb_api/test_tmdb_api.py create mode 100644 lib/tmdb_api/tmdb_api.py diff --git a/lib/tmdb_api/__init__.py b/lib/tmdb_api/__init__.py new file mode 100644 index 00000000..b576a14b --- /dev/null +++ b/lib/tmdb_api/__init__.py @@ -0,0 +1 @@ +from .tmdb_api import TMDB diff --git a/lib/tmdb_api/test_tmdb_api.py b/lib/tmdb_api/test_tmdb_api.py new file mode 100644 index 00000000..a192a4f7 --- /dev/null +++ b/lib/tmdb_api/test_tmdb_api.py @@ -0,0 +1,183 @@ +""" +test.py contains unit tests for tmdbsimple.py + +Fill in Global Variables below before running tests. + +Created by Celia Oakley on 2013-11-05 +""" + +import unittest +import sys + +from tmdb_api import TMDB + +# +# Global Variables (fill in or put in keys.py) +# +TMDB_API_KEY = 'edc5f123313769de83a71e157758030b' + +try: + from keys import * +except ImportError: + pass + +class TVCheck(unittest.TestCase): + def testTVInfo(self): + id = 1396 + name = 'UFC' + tmdb = TMDB(TMDB_API_KEY) + tv = tmdb.TV(id) + response = tv.images() + + def testTVSearch(self): + id = 1396 + name = 'UFC' + tmdb = TMDB(TMDB_API_KEY) + + # get TMDB configuration info + config = tmdb.Configuration() + response = config.info() + base_url = response['images']['base_url'] + sizes = response['images']['poster_sizes'] + + def size_str_to_int(x): + return float("inf") if x == 'original' else int(x[1:]) + max_size = max(sizes, key=size_str_to_int) + + # get show ID on TMDB + search = tmdb.Search() + response = search.collection({'query': name}) + for result in response['results']: + id = result['id'] + + # get show images + collection = tmdb.Collections(id) + response = collection.images() + rel_path = response['posters'][0]['file_path'] + url = "{0}{1}{2}".format(base_url, max_size, rel_path) + self.assertTrue(hasattr(response, name)) + + def testTVCredits(self): + id = 1396 + tmdb = TMDB(TMDB_API_KEY) + tv = tmdb.TV(id) + response = tv.credits() + self.assertTrue(hasattr(tv, 'cast')) + + def testTVExternalIds(self): + id = 1396 + imdb_id = 'tt0903747' + tmdb = TMDB(TMDB_API_KEY) + tv = tmdb.TV(id) + response = tv.external_ids() + self.assertEqual(tv.imdb_id, imdb_id) + + def testTVImages(self): + id = 1396 + tmdb = TMDB(TMDB_API_KEY) + tv = tmdb.TV(id) + response = tv.images() + self.assertTrue(hasattr(tv, 'backdrops')) + + def testTVTranslations(self): + id = 1396 + tmdb = TMDB(TMDB_API_KEY) + tv = tmdb.TV(id) + response = tv.translations() + self.assertTrue(hasattr(tv, 'translations')) + + def testTVTopRated(self): + tmdb = TMDB(TMDB_API_KEY) + tv = tmdb.TV() + response = tv.top_rated() + self.assertTrue(hasattr(tv, 'results')) + + def testTVPopular(self): + tmdb = TMDB(TMDB_API_KEY) + tv = tmdb.TV() + response = tv.popular() + self.assertTrue(hasattr(tv, 'results')) + +class TVSeasonsCheck(unittest.TestCase): + def testTVSeasonsInfo(self): + id = 3572 + season_number = 1 + name = 'Season 1' + tmdb = TMDB(TMDB_API_KEY) + tv_seasons = tmdb.TV_Seasons(id, season_number) + response = tv_seasons.info() + self.assertEqual(tv_seasons.name, name) + + def testTVSeasonsCredits(self): + id = 3572 + season_number = 1 + tmdb = TMDB(TMDB_API_KEY) + tv_seasons = tmdb.TV_Seasons(id, season_number) + response = tv_seasons.credits() + self.assertTrue(hasattr(tv_seasons, 'crew')) + + def testTVSeasonsExternalIds(self): + id = 3572 + season_number = 1 + tvdb_id = 2547 + tmdb = TMDB(TMDB_API_KEY) + tv_seasons = tmdb.TV_Seasons(id, season_number) + response = tv_seasons.external_ids() + self.assertEqual(tv_seasons.tvdb_id, tvdb_id) + + def testTVSeasonsImages(self): + id = 3572 + season_number = 1 + tmdb = TMDB(TMDB_API_KEY) + tv_seasons = tmdb.TV_Seasons(id, season_number) + response = tv_seasons.images() + self.assertTrue(hasattr(tv_seasons, 'posters')) + +class TVEpisodesCheck(unittest.TestCase): + def testTVEpisodesInfo(self): + id = 1396 + season_number = 1 + episode_number = 1 + name = 'Pilot' + tmdb = TMDB(TMDB_API_KEY) + tv_episodes = tmdb.TV_Episodes(id, season_number, episode_number) + response = tv_episodes.info() + self.assertEqual(tv_episodes.name, name) + + def testTVEpisodesCredits(self): + id = 1396 + season_number = 1 + episode_number = 1 + tmdb = TMDB(TMDB_API_KEY) + tv_episodes = tmdb.TV_Episodes(id, season_number, episode_number) + response = tv_episodes.credits() + self.assertTrue(hasattr(tv_episodes, 'guest_stars')) + + def testTVEpisodesExternalIds(self): + id = 1396 + season_number = 1 + episode_number = 1 + imdb_id = 'tt0959621' + tmdb = TMDB(TMDB_API_KEY) + tv_episodes = tmdb.TV_Episodes(id, season_number, episode_number) + response = tv_episodes.external_ids() + self.assertEqual(tv_episodes.imdb_id, imdb_id) + + def testTVEpisodesImages(self): + id = 1396 + season_number = 1 + episode_number = 1 + tmdb = TMDB(TMDB_API_KEY) + tv_episodes = tmdb.TV_Episodes(id, season_number, episode_number) + response = tv_episodes.images() + self.assertTrue(hasattr(tv_episodes, 'stills')) + +if __name__ == "__main__": + unittest.main() + +# Run with: +# python3 test_tmdbsimple.py ConfigurationCheck -v +# python3 test_tmdbsimple.py ConfigurationCheck +# ... or other Check classes +# python3 test_tmdbsimple.py -v +# python3 test_tmdbsimple.py diff --git a/lib/tmdb_api/tmdb_api.py b/lib/tmdb_api/tmdb_api.py new file mode 100644 index 00000000..d0ef11fe --- /dev/null +++ b/lib/tmdb_api/tmdb_api.py @@ -0,0 +1,828 @@ +""" +tmdbsimple.py is a wrapper for The Movie Database API. +Refer to the official API documentation for more information. +http://docs.themoviedb.apiary.io/ + +Created by Celia Oakley on 2013-10-31. +""" + +import json +import lib.requests as requests + +class TMDB: + def __init__(self, api_key, version=3): + TMDB.api_key = str(api_key) + TMDB.url = 'https://api.themoviedb.org' + '/' + str(version) + + @staticmethod + def _request(method, path, params={}, json_body={}): + url = TMDB.url + '/' + path + '?api_key=' + TMDB.api_key + if method == 'GET': + headers = {'Accept': 'application/json'} + content = requests.get(url, params=params, headers=headers).content + elif method == 'POST': + for key in params.keys(): + url += '&' + key + '=' + params[key] + headers = {'Content-Type': 'application/json', \ + 'Accept': 'application/json'} + content = requests.post(url, data=json.dumps(json_body), \ + headers=headers).content + elif method == 'DELETE': + for key in params.keys(): + url += '&' + key + '=' + params[key] + headers = {'Content-Type': 'application/json', \ + 'Accept': 'application/json'} + content = requests.delete(url, data=json.dumps(json_body), \ + headers=headers).content + else: + raise Exception('method: ' + method + ' not supported.') + response = json.loads(content.decode('utf-8')) + return response + + # + # Set attributes to dictionary values. + # - e.g. + # >>> tmdb = TMDB() + # >>> movie = tmdb.Movie(103332) + # >>> response = movie.info() + # >>> movie.title # instead of response['title'] + # + @staticmethod + def _set_attrs_to_values(object, response={}): + for key in response.keys(): + setattr(object, key, response[key]) + + # + # Configuration + # http://docs.themoviedb.apiary.io/#configuration + # + class Configuration: + def __init__(self): + pass + + def info(self): + path = 'configuration' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Account + # http://docs.themoviedb.apiary.io/#account + # + class Account: + def __init__(self, session_id): + self.session_id = session_id + + # need to call this first to set account id + def info(self): + path = 'account' + params = {'session_id': self.session_id} + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def lists(self, params={}): + path = 'account' + '/' + str(self.session_id) + '/lists' + params['session_id'] = self.session_id + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, sort_by, sort_order, language + def favorite_movies(self, params={}): + path = 'account' + '/' + str(self.session_id) + '/favorite_movies' + params['session_id'] = self.session_id + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required JSON body: movie_id, favorite + def favorite(self, json_body): + path = 'account' + '/' + str(json_body['movie_id']) + '/favorite' + params = {'session_id': self.session_id} + response = TMDB._request('POST', path, params, json_body) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, sort_by, sort_order, language + def rated_movies(self, params={}): + path = 'account' + '/' + str(self.session_id) + '/rated_movies' + params['session_id'] = self.session_id + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, sort_by, sort_order, language + def movie_watchlist(self, params={}): + path = 'account' + '/' + str(self.session_id) + '/movie_watchlist' + params['session_id'] = self.session_id + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required JSON body: movie_id, movie_watchlist + def movie_watchlist_post(self, json_body): + path = 'account' + '/' + str(json_body['movie_id']) + \ + '/movie_watchlist' + params = {'session_id': self.session_id} + response = TMDB._request('POST', path, params, json_body) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Authentication + # http://docs.themoviedb.apiary.io/#authentication + # + # Note: to use authentication to access a user account, see: + # https://www.themoviedb.org/documentation/api/sessions + # + class Authentication: + def __init__(self): + pass + + def token_new(self): + path = 'authentication/token/new' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: request_token + def session_new(self, params): + path = 'authentication/session/new' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def guest_session_new(self): + path = 'authentication/guest_session/new' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Changes + # http://docs.themoviedb.apiary.io/#changes + # + class Changes: + def __init__(self): + pass + + # optional parameters: page, start_date, end_date + def movie(self, params={}): + path = 'movie/changes' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, start_date, end_date + def person(self, params={}): + path = 'person/changes' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Collections + # http://docs.themoviedb.apiary.io/#collections + # + class Collections: + def __init__(self, id): + self.id = id + + # optional parameter: language + def info(self, params={}): + path = 'collection' + '/' + str(self.id) + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: language, include_image_language + def images(self, params={}): + path = 'collection' + '/' + str(self.id) + '/images' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Companies + # http://docs.themoviedb.apiary.io/#companies + # + class Companies: + def __init__(self, id=0): + self.id = id + + def info(self): + path = 'company' + '/' + str(self.id) + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def movies(self, params={}): + path = 'company' + '/' + str(self.id) + '/movies' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Credits + # http://docs.themoviedb.apiary.io/#credits + # + class Credits: + def __init__(self, credit_id): + self.credit_id = credit_id + + # optional parameters: language + def info(self, params={}): + path = 'credit' + '/' + str(self.credit_id) + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Discover + # http://docs.themoviedb.apiary.io/#discover + # + class Discover: + def __init__(self): + pass + + # optional parameters: page, language, sort_by, include_adult, year, + # primary_release_year, vote_count.gte, vote_average.gte, with_genres, + # release_date.gte, release_date.lte, certification_country, + # certification.lte, with_companies + def movie(self, params): + path = 'discover/movie' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language, sort_by, first_air_date_year, + # vote_count.gte, vote_average.gte, with_genres, with_networks, + # first_air_date.gte, first_air_date.lte + def tv(self, params): + path = 'discover/tv' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Find + # http://docs.themoviedb.apiary.io/#find + # + class Find: + def __init__(self, id=0): + self.id = id + + # required parameters: external_source + def info(self, params={}): + path = 'find' + '/' + str(self.id) + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Genres + # http://docs.themoviedb.apiary.io/#genres + # + class Genres: + def __init__(self, id=0): + self.id = id + + # optional parameters: language + def list(self, params={}): + path = 'genre/list' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language, include_all_movies, include_adult + def movies(self, params={}): + path = 'genre' + '/' + str(self.id) + '/movies' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Jobs + # http://docs.themoviedb.apiary.io/#jobs + # + class Jobs: + def __init__(self): + pass + + def list(self): + path = 'job/list' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Keywords + # http://docs.themoviedb.apiary.io/#keywords + # + class Keywords: + def __init__(self, id): + self.id = id + + def info(self): + path = 'keyword' + '/' + str(self.id) + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def movies(self, params={}): + path = 'keyword' + '/' + str(self.id) + '/movies' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Lists + # http://docs.themoviedb.apiary.io/#lists + # + class Lists: + def __init__(self, id=0, session_id=0): + self.id = id + self.session_id = session_id + + def info(self): + path = 'list' + '/' + str(self.id) + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: movie_id + def item_status(self, params): + path = 'list' + '/' + str(self.id) + '/item_status' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required JSON body: name, description + # optional JSON body: language + def create_list(self, json_body): + path = 'list' + params = {'session_id': self.session_id} + response = TMDB._request('POST', path, params, json_body) + TMDB._set_attrs_to_values(self, response) + return response + + # required JSON body: media_id + def add_item(self, json_body): + path = 'list' + '/' + str(self.id) + '/add_item' + params = {'session_id': self.session_id} + response = TMDB._request('POST', path, params, json_body) + TMDB._set_attrs_to_values(self, response) + return response + + # required JSON body: media_id + def remove_item(self, json_body): + path = 'list' + '/' + str(self.id) + '/remove_item' + params = {'session_id': self.session_id} + response = TMDB._request('POST', path, params, json_body) + TMDB._set_attrs_to_values(self, response) + return response + + def delete_list(self): + path = 'list' + '/' + str(self.id) + params = {'session_id': self.session_id} + response = TMDB._request('DELETE', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Movies + # http://docs.themoviedb.apiary.io/#movies + # + class Movies: + """ """ + def __init__(self, id=0): + self.id = id + + # optional parameters: language + def info(self, params={}): + path = 'movie' + '/' + str(self.id) + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: country + def alternative_titles(self, params={}): + path = 'movie' + '/' + str(self.id) + '/alternative_titles' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def credits(self): + path = 'movie' + '/' + str(self.id) + '/credits' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: language, include_image_language + def images(self, params={}): + path = 'movie' + '/' + str(self.id) + '/images' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def keywords(self): + path = 'movie' + '/' + str(self.id) + '/keywords' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + def releases(self): + path = 'movie' + '/' + str(self.id) + '/releases' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + def trailers(self): + path = 'movie' + '/' + str(self.id) + '/trailers' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + def translations(self): + path = 'movie' + '/' + str(self.id) + '/translations' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def similar_movies(self, params={}): + path = 'movie' + '/' + str(self.id) + '/similar_movies' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def reviews(self, params={}): + path = 'movie' + '/' + str(self.id) + '/reviews' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def lists(self, params={}): + path = 'movie' + '/' + str(self.id) + '/lists' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: start_date, end_date + def changes(self, params={}): + path = 'movie' + '/' + str(self.id) + '/changes' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def latest(self): + path = 'movie/latest' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def upcoming(self, params={}): + path = 'movie/upcoming' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def now_playing(self, params={}): + path = 'movie/now_playing' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def popular(self, params={}): + path = 'movie/popular' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page, language + def top_rated(self, params={}): + path = 'movie/top_rated' + response = TMDB._request('GET', 'movie' + '/top_rated', params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: session_id + def account_states(self, params): + path = 'movie' + '/' + str(self.id) + '/account_states' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: session_id or guest_session_id + # required JSON body: value + def rating(self, params, json_body): + path = 'movie' + '/' + str(self.id) + '/rating' + response = TMDB._request('POST', path, params, json_body) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Networks + # http://docs.themoviedb.apiary.io/#networks + # + class Networks: + def __init__(self, id): + self.id = id + + def info(self): + path = 'network' + '/' + str(self.id) + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # + # People + # http://docs.themoviedb.apiary.io/#people + # + class People: + def __init__(self, id=0): + self.id = id + + def info(self): + path = 'person' + '/' + str(self.id) + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: language + def movie_credits(self, params={}): + path = 'person' + '/' + str(self.id) + '/movie_credits' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: language + def tv_credits(self, params={}): + path = 'person' + '/' + str(self.id) + '/tv_credits' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: language + def combined_credits(self, params={}): + path = 'person' + '/' + str(self.id) + '/combined_credits' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def images(self): + path = 'person' + '/' + str(self.id) + '/images' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: start_date, end_date + def changes(self, params={}): + path = 'person' + '/' + str(self.id) + '/changes' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: page + def popular(self, params={}): + path = 'person/popular' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def latest(self): + path = 'person/latest' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Reviews + # http://docs.themoviedb.apiary.io/#reviews + # + class Reviews: + def __init__(self, id): + self.id = id + + def info(self): + path = 'review' + '/' + str(self.id) + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # + # Search + # http://docs.themoviedb.apiary.io/#search + # + class Search: + def __init__(self): + pass + + # required parameters: query + # optional parameters: page, language, include_adult, year, + # primary_release_year, search_type + def movie(self, params): + path = 'search/movie' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: query + # optional parameters: page, language + def collection(self, params): + path = 'search/collection' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: query + # optional parameters: page, language, first_air_date_year, search_type + def tv(self, params): + path = 'search/tv' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: query + # optional parameters: page, include_adult, search_type + def person(self, params): + path = 'search/person' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: query + # optional parameters: page, include_adult + def list(self, params): + path = 'search/list' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: query + # optional parameters: page + def company(self, params): + path = 'search/company' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # required parameters: query + # optional parameters: page + def keyword(self, params): + path = 'search/keyword' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # TV + # http://docs.themoviedb.apiary.io/#tv + # + class TV: + def __init__(self, id=0): + self.id = id + + # optional parameter: language + def info(self, params={}): + path = 'tv' + '/' + str(self.id) + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: language + def credits(self, params={}): + path = 'tv' + '/' + str(self.id) + '/credits' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: language + def external_ids(self, params={}): + path = 'tv' + '/' + str(self.id) + '/external_ids' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameters: language, include_image_language + def images(self, params={}): + path = 'tv' + '/' + str(self.id) + '/images' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def translations(self): + path = 'tv' + '/' + str(self.id) + '/translations' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: page, language + def top_rated(self, params={}): + path = 'tv/top_rated' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: page, language + def popular(self, params={}): + path = 'tv/popular' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # TV Seasons + # http://docs.themoviedb.apiary.io/#tvseasons + # + class TV_Seasons: + def __init__(self, id, season_number): + self.id = id + self.season_number = season_number + + # optional parameter: language + def info(self, params={}): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def credits(self): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + '/credits' + response = TMDB._request('GET', path) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: language + def external_ids(self, params={}): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + '/external_ids' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: language + def images(self, params={}): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + '/images' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # + # TV Episodes + # http://docs.themoviedb.apiary.io/#tvepisodes + # + class TV_Episodes: + def __init__(self, id, season_number, episode_number): + self.id = id + self.season_number = season_number + self.episode_number = episode_number + + # optional parameter: language + def info(self, params={}): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + '/episode' + \ + '/' + str(self.episode_number) + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + def credits(self, params={}): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + '/episode' + \ + '/' + str(self.episode_number) + '/credits' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: language + def external_ids(self, params={}): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + '/episode' + \ + '/' + str(self.episode_number) + '/external_ids' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + + # optional parameter: language + def images(self, params={}): + path = 'tv' + '/' + str(self.id) + '/season' + \ + '/' + str(self.season_number) + '/episode' + \ + '/' + str(self.episode_number) + '/images' + response = TMDB._request('GET', path, params) + TMDB._set_attrs_to_values(self, response) + return response + diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index e7770c62..411cd261 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -429,6 +429,8 @@ IGNORE_WORDS = "german,french,core2hd,dutch,swedish,reenc,MrLss" CALENDAR_UNPROTECTED = False +TMDB_API_KEY = 'edc5f123313769de83a71e157758030b' + __INITIALIZED__ = False def get_backlog_cycle_time(): @@ -479,7 +481,7 @@ def initialize(consoleLogging=True): GUI_NAME, HOME_LAYOUT, HISTORY_LAYOUT, DISPLAY_SHOW_SPECIALS, COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, COMING_EPS_MISSED_RANGE, DATE_PRESET, TIME_PRESET, TIME_PRESET_W_SECONDS, \ METADATA_WDTV, METADATA_TIVO, IGNORE_WORDS, CALENDAR_UNPROTECTED, CREATE_MISSING_SHOW_DIRS, \ ADD_SHOWS_WO_DIR, USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, subtitlesFinderScheduler, \ - USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP + USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, TMDB_API_KEY if __INITIALIZED__: return False diff --git a/sickbeard/indexers/generic.py b/sickbeard/indexers/generic.py index 1069f3cc..531d625a 100644 --- a/sickbeard/indexers/generic.py +++ b/sickbeard/indexers/generic.py @@ -35,7 +35,7 @@ class GenericIndexer(object): INDEXER_BASEURL = {} INDEXER_BASEURL[INDEXER_NONE] = '' - INDEXER_BASEURL[INDEXER_TVDB] = 'http://thetvdb.com/api/' + INDEXER_API_KEY[INDEXER_TVDB] + INDEXER_BASEURL[INDEXER_TVDB] = 'http://thetvdb.com/api/' + INDEXER_API_KEY[INDEXER_TVDB] + '/series/' INDEXER_BASEURL[INDEXER_TVRAGE] = 'http://tvrage.com/showinfo?key=' + INDEXER_API_KEY[INDEXER_TVRAGE] + 'sid=' INDEXER_API_PARMS = {} @@ -57,5 +57,6 @@ class GenericIndexer(object): 'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31, 'de': 14, 'da': 10, 'fi': 11, 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32, 'sv': 8, 'sl': 30} + self.config['base_url'] = INDEXER_BASEURL[indexer] self.config['api_parms'] = INDEXER_API_PARMS[indexer] self.config['name'] = INDEXER_NAME[indexer] \ No newline at end of file diff --git a/sickbeard/indexers/indexer_api.py b/sickbeard/indexers/indexer_api.py index 86b67d21..21e0d23e 100644 --- a/sickbeard/indexers/indexer_api.py +++ b/sickbeard/indexers/indexer_api.py @@ -20,11 +20,13 @@ import os import sickbeard import generic +from indexer_exceptions import indexer_attributenotfound from lib.tvdb_api.tvdb_api import Tvdb from lib.tvrage_api.tvrage_api import TVRage class indexerApi(generic.GenericIndexer): - def __init__(self, indexer=None, *args, **kwargs): + def __init__(self, *args, **kwargs): + indexer = kwargs.pop('indexer',None) super(indexerApi, self).__init__(indexer) self.name = self.config['name'] @@ -35,10 +37,16 @@ class indexerApi(generic.GenericIndexer): self.config['api_parms']['cache'] = os.path.join(sickbeard.CACHE_DIR, indexer) # wrap the indexer API object and return it back - self._wrapped = eval(indexer)(*args, **self.config['api_parms']) + self._wrapped = eval(indexer)(forceConnect=True, *args, **self.config['api_parms']) def __getattr__(self, attr): - return getattr(self._wrapped, attr) + try: + return getattr(self._wrapped, attr) + except KeyError: + raise indexer_attributenotfound def __getitem__(self, attr): - return self._wrapped.__getitem__(attr) \ No newline at end of file + try: + return self._wrapped.__getitem__(attr) + except KeyError: + raise indexer_attributenotfound \ No newline at end of file diff --git a/sickbeard/metadata/generic.py b/sickbeard/metadata/generic.py index 08afb4c9..2814a64f 100644 --- a/sickbeard/metadata/generic.py +++ b/sickbeard/metadata/generic.py @@ -32,6 +32,7 @@ from sickbeard import logger from sickbeard import encodingKludge as ek from sickbeard.exceptions import ex +from lib.tmdb_api.tmdb_api import TMDB from sickbeard.indexers import indexer_api, indexer_exceptions class GenericMetadata(): @@ -704,7 +705,7 @@ class GenericMetadata(): def _retrieve_show_image(self, image_type, show_obj, which=None): """ - Gets an image URL from theTVDB.com and TVRage.com, downloads it and returns the data. + Gets an image URL from theTVDB.com and TMDB.com, downloads it and returns the data. image_type: type of image to retrieve (currently supported: fanart, poster, banner) show_obj: a TVShow object to use when searching for the image @@ -712,7 +713,7 @@ class GenericMetadata(): Returns: the binary image data if available, or else None """ - + image_url = None indexer_lang = show_obj.lang try: @@ -738,19 +739,32 @@ class GenericMetadata(): logger.log(u"Invalid image type " + str(image_type) + ", couldn't find it in the " + show_obj.indexer + " object", logger.ERROR) return None - try: - if image_type == 'poster_thumb': + if image_type == 'poster_thumb': + if getattr(indexer_show_obj, 'poster', None) is not None: image_url = re.sub('posters', '_cache/posters', indexer_show_obj['poster']) - elif image_type == 'banner_thumb': + elif image_type == 'banner_thumb': + if getattr(indexer_show_obj, 'banner', None) is not None: image_url = re.sub('graphical', '_cache/graphical', indexer_show_obj['banner']) - else: + else: + if getattr(indexer_show_obj, 'banner', None) is not None: image_url = indexer_show_obj[image_type] - except: - return None - image_data = metadata_helpers.getShowImage(image_url, which) + # Try and get posters and fanart from TMDB + if image_url is None: + for showname in show_obj.name, show_obj.exceptions: + if image_type in ('poster', 'poster_thumb'): + image_url = self._retrieve_show_images_from_tmdb(showname, poster=True) + elif image_type == 'fanart': + image_url = self._retrieve_show_images_from_tmdb(showname, backdrop=True) - return image_data + if image_url: + break + + if image_url: + image_data = metadata_helpers.getShowImage(image_url, which) + return image_data + + return None def _season_posters_dict(self, show_obj, season): """ @@ -900,3 +914,54 @@ class GenericMetadata(): return empty_return return (indexer_id, name, indexer) + + def _retrieve_show_images_from_tmdb(self, name, id=None, backdrop=False, poster=False): + tmdb = TMDB(sickbeard.TMDB_API_KEY) + result = None + + # get TMDB configuration info + config = tmdb.Configuration() + response = config.info() + base_url = response['images']['base_url'] + sizes = response['images']['poster_sizes'] + + def size_str_to_int(x): + return float("inf") if x == 'original' else int(x[1:]) + max_size = max(sizes, key=size_str_to_int) + + try: + if id is None: + search = tmdb.Search() + response = search.collection({'query': name}) + id = response['results'][0]['id'] + + result = tmdb.Collections(id) + except: + try: + if id is None: + search = tmdb.Search() + response = search.tv({'query': name}) + id = response['results'][0]['id'] + + result = tmdb.TV(id) + except: + return None + return None + + if result is None: + return None + + images = result.images() + # get backdrop urls + if backdrop: + rel_path = images['backdrops'][0]['file_path'] + url = "{0}{1}{2}".format(base_url, max_size, rel_path) + return url + + # get poster urls + if poster: + rel_path = images['posters'][0]['file_path'] + url = "{0}{1}{2}".format(base_url, max_size, rel_path) + return url + + return None \ No newline at end of file diff --git a/sickbeard/metadata/xbmc_12plus.py b/sickbeard/metadata/xbmc_12plus.py index 14dc8c43..941f65e9 100644 --- a/sickbeard/metadata/xbmc_12plus.py +++ b/sickbeard/metadata/xbmc_12plus.py @@ -153,7 +153,7 @@ class XBMC_12PlusMetadata(generic.GenericMetadata): episodeguideurl = etree.SubElement(episodeguide, "url") episodeguideurl2 = etree.SubElement(tv_node, "episodeguideurl") if getattr(myShow, 'id', None) is not None: - showurl = indexer_api.indexerApi(show_obj.indexer) + '/series/' + myShow["id"] + '/all/en.zip' + showurl = indexer_api.indexerApi(show_obj.indexer).config['base_url'] + myShow["id"] + '/all/en.zip' episodeguideurl.text = showurl episodeguideurl2.text = showurl diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 6bd20697..ae630ec3 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -2052,7 +2052,7 @@ class NewHomeAddShows: break # default to TVDB if indexer was not detected - if indexer is None: + if indexer is None and show_name: found_info = helpers.searchIndexersForShow(show_name) if found_info is not None: indexer = found_info