From: E.Smith <31170571+azlm8t@users.noreply.github.com> Date: Tue, 2 Oct 2018 18:00:42 +0000 (+0100) Subject: fanart: Add basic tvdb lookup. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8464e469bbfa4f4d76f095e414c35c4ec4cd834e;p=thirdparty%2Ftvheadend.git fanart: Add basic tvdb lookup. The lookup is by title+year (+language) only (episode-specific fanart is not yet retrieved). To use with Tvheadend, the extra arguments in the grabber need to include: --tvdb-key XX And an optional two character languages as csv: --tvdb-languages en,it The key is from registering at TheTVDB.com. --- diff --git a/lib/py/tvh/tv_meta_tvdb.py b/lib/py/tvh/tv_meta_tvdb.py new file mode 100755 index 000000000..2b7e5d941 --- /dev/null +++ b/lib/py/tvh/tv_meta_tvdb.py @@ -0,0 +1,218 @@ +#! /usr/bin/env python +# Retrieve details for a series from tvdb. +# +# Required options: +# --tvdb-key XX +# Option important options: +# --tvdb-languages - a csv of 2-character languages to use such as en,nl +# +# TV information and images are provided by TheTVDB.com, but we are +# not endorsed or certified by TheTVDB.com or its affiliates +# +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os,sys +import json +import logging +import requests + + + +def get_capabilities(): + return { + "name": "tv_meta_tvdb", + "version": "0.1", + "description": "Grab movie details from TVDB.", + "supports_tv": True, + "supports_movie": False, + "required_config" : { + "tvdb-key" : "string" + } + } + + + +class Tvdb(object): + """Basic tvdb wrapper. + +The class assumes you pass a key (from registering at tvdb.com). +Exceptions are thrown to indicate data could not be retrieved. +""" + def __init__(self, args): + logging.info(args) + for arg in (["key"]): + if args is None or arg not in args or args[arg] is None or args[arg] == "": + logging.critical("Need a tvdb-" + arg) + raise RuntimeError("Need a tvdb-" + arg); + + self.languages = "en" + # 2 character language code. At time of writing, valid languages on the server are: + # ['da', 'fi', 'nl', 'de', 'it', 'es', 'fr', 'pl', 'hu', 'el', 'tr', 'ru', 'he', 'ja', 'pt', 'zh', 'cs', 'sl', 'hr', 'ko', 'en', 'sv', 'no'] + # We accept a csv of languages. + # + # In general, it seems best to include "en" otherwise you only + # get fanart that is specifically tagged for your language (there + # does not seem to be a "all languages" option on the images). + if 'languages' in args and args["languages"] is not None: + self.languages = args["languages"] + + self.auth = None + self.session = requests.Session() + self.session.headers = self._get_headers() + self.timeout = 10 # Timeout in seconds + self.apikey = args["key"] + self.base_url = "https://api.thetvdb.com/" + self.auth = self._get_auth() + + def _get_headers(self): + headers = { 'Content-Type': 'application/json', + 'Accept-Language': self.languages } + if self.auth: + headers['Authorization'] = 'Bearer ' + self.auth + return headers + + def _get_auth(self): + r = self.session.post( + self.base_url + 'login', + timeout = self.timeout, + data=json.dumps({'apikey' : self.apikey})) + token = r.json()['token'] + self.session.headers.update({'Authorization' : 'Bearer ' + token}) + return token + + def get_tvdbid(self, title): + """Return episode tvdb id""" + r=self.session.get( + self.base_url + 'search/series', + timeout = self.timeout, + params={'name': title}) + return r.json()['data'][0]['id'] + + def _get_art(self, title = None, tvdbid = None, artworkType = 'fanart'): + if tvdbid is None: + tvdbid = self.get_tvdbid(title) + + logging.debug("%s type %s" % (tvdbid, type(tvdbid))) + url = self.base_url + 'series/' + str(tvdbid) + '/images/query' + logging.debug("Searching %s with id %s and keytype %s" % (url, tvdbid, artworkType)) + r=self.session.get( + url, + timeout = self.timeout, + params={'keyType': artworkType}) + r.raise_for_status() + filename = r.json()['data'][0]['fileName'] + if not filename.startswith("http"): + filename = "https://thetvdb.com/banners/" + filename + return filename + + def get_fanart(self, title = None, tvdbid = None): + return self._get_art(title = title, tvdbid = tvdbid, artworkType = 'fanart') + + def get_poster(self, title = None, tvdbid = None): + return self._get_art(title = title, tvdbid = tvdbid, artworkType = 'poster') + + +class Tv_meta_tvdb(object): + + def __init__(self, args): + self.tvdb = Tvdb(args) + + def fetch_details(self, args): + logging.debug("Fetching with details %s " % args); + title = args["title"] + year = args["year"] + + if title is None: + logging.critical("Need a title"); + raise RuntimeError("Need a title"); + + tvdbid = None + query = title + + # try with "title (year)". + if year is not None: + query = query + " (%s)" % year + try: + tvdbid = self.tvdb.get_tvdbid(query) + except: + pass + + if tvdbid is None: + try: + tvdbid = self.tvdb.get_tvdbid(title) + except: + logging.error("Could not find any matching episode"); + raise LookupError("Could not find match for " + title); + + poster = fanart = None + # We don't want failure to process one image to affect the other. + try: + poster = self.tvdb.get_poster(tvdbid = tvdbid) + except Exception: + pass + + try: + fanart = self.tvdb.get_fanart(tvdbid = tvdbid) + except: + pass + + logging.debug("poster=%s fanart=%s title=%s year=%s" % (poster, fanart, title, year)) + return {"poster": poster, "fanart": fanart} + +if __name__ == '__main__': + def process(argv): + from optparse import OptionParser + optp = OptionParser() + optp.add_option('--tvdb-key', default=None, + help='Specify authorization key.') + optp.add_option('--tvdb-languages', default=None, + help='Specify tvdb language codes separated by commas, such as "en,sv".') + optp.add_option('--title', default=None, + help='Title to search for.') + optp.add_option('--year', default=None, type="int", + help='Year to search for.') + optp.add_option('--capabilities', default=None, action="store_true", + help='Display program capabilities (for PVR grabber)') + optp.add_option('--debug', default=None, action="store_true", + help='Enable debug.') + (opts, args) = optp.parse_args(argv) + if (opts.debug): + logging.root.setLevel(logging.DEBUG) + + if opts.capabilities: + # Output a program-parseable format. + print(json.dumps(get_capabilities())) + return 0 + + if opts.title is None or opts.tvdb_key is None: + print("Need a title to search for and a tvdb-key.") + sys.exit(1) + + grabber = Tv_meta_tvdb({ + "key" : opts.tvdb_key, + "languages": opts.tvdb_languages, + }) + print(json.dumps(grabber.fetch_details({ + "title": opts.title, + "year" : opts.year, + }))) + + try: + logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(module)s:%(message)s') + sys.exit(process(sys.argv)) + except KeyboardInterrupt: pass + except (RuntimeError,LookupError) as err: + logging.info("Got exception: " + str(err)) + sys.exit(1)