From d879a9d23c6428aced52611f11b275534bf6cb95 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Fri, 29 Apr 2016 12:44:56 +0200 Subject: [PATCH] Additional artwork download from FanartTV! --- resources/language/English/strings.xml | 2 + resources/language/German/strings.xml | 5 +- resources/lib/PlexAPI.py | 382 +++++++++++++++---------- resources/lib/downloadutils.py | 4 +- resources/lib/initialsetup.py | 6 + resources/lib/utils.py | 12 - resources/settings.xml | 2 +- 7 files changed, 252 insertions(+), 161 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 08f0d98d..7d7feca9 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -302,6 +302,7 @@ Users must log in every time Kodi restarts RESTART KODI IF YOU MAKE ANY CHANGES Complete Re-Sync necessary + Download additional art from FanArtTV (slower!) @@ -404,6 +405,7 @@ Extend Plex TV Series "On Deck" view to all shows Recently Added: Append show title to episode Recently Added: Append season- and episode-number SxxExx + Would you like to download additional artwork from FanArtTV? Sync will be slower! Log-out Plex Home User diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml index 4283d477..1b6b57ba 100644 --- a/resources/language/German/strings.xml +++ b/resources/language/German/strings.xml @@ -28,6 +28,8 @@ Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden BEI ÄNDERUNGEN KODI NEU STARTEN Komplette Neusynchronisierung nötig + Zusätzliche Bilder von FanArtTV herunterladen (langsamer!) + @@ -342,8 +344,7 @@ Standard Plex Ansicht "Aktuell" auf alle TV Shows erweitern "Zuletzt hinzugefügt": Serien- an Episoden-Titel anfügen "Zuletzt hinzugefügt": Staffel und Episode anfügen, SxxExx - - + Zusätzliche Bilder von FanArtTV herunterladen? Die Synchronisierung wird länger dauern! Plex Home Benutzer abmelden: diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 8466ba29..20b57a8c 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -1825,16 +1825,14 @@ class API(): Output: { - 'Primary' : xml key 'thumb' - 'Art' : always '' - 'Banner' : xml key 'banner' - 'Logo' : always '' - 'Thumb' : xml key 'grandparentThumb' - 'Disc' : always '' - 'Backdrop' : LIST with ONE xml key "art" + 'Primary' + 'Art' + 'Banner' + 'Logo' + 'Thumb' + 'Disc' + 'Backdrop' : LIST with the first entry xml key "art" } - - """ item = self.item.attrib @@ -1865,161 +1863,255 @@ class API(): self.__getOneArtwork('parentArt')) if not allartworks['Primary']: allartworks['Primary'] = self.__getOneArtwork('parentThumb') + + # Plex does not get much artwork - go ahead and get the rest from + # fanart tv only for movie or tv show + if utils.settings('FanartTV') == 'true': + if item.get('type') in ('movie', 'show'): + externalId = self.getExternalItemId() + if externalId is not None: + allartworks = self.getFanartTVArt(externalId, allartworks) return allartworks - # TO BE DONE - # Plex does not get much artwork - go ahead and get the rest from fanart tv only for movie or tv show - type = item.get('type') - if type=='movie' or type=='show': - allartworks = self.getfanartTVimages(allartworks) + def getExternalItemId(self): + """ + Returns the item's IMDB id for movies or tvdb id for TV shows - if allartworks == None: - self.logMsg('No artwork found for title%s' %str(item.get('title'))) - return {} - else: - return allartworks - - def getfanartTVimages(self,allartworks): + If not found in item's Plex metadata, check themovidedb.org + """ item = self.item.attrib - tmdb_apiKey = "ae06df54334aa653354e9a010f4b81cb" + media_type = item.get('type') + externalId = None + if media_type == 'movie': + externalId = self.getProvider('imdb') + elif media_type == 'show': + externalId = self.getProvider('tvdb') + if externalId is not None: + return externalId + + self.logMsg('Plex did not provide ID for IMDB or TVDB. Start lookup ' + 'process', 1) KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1) - media_id = None - media_type = None - type = item.get('type') - if type == 'show': - type = 'tv' - title = item.get('title') - # if the title has the year in remove it as tmdb cannot deal with it...making an assumption it is something like The Americans (2015) - if title.endswith(")"): title = title[:-6] - year = item.get('year') - if not type: type="multi" + apiKey = "ae06df54334aa653354e9a010f4b81cb" + if media_type == 'show': + media_type = 'tv' + title = item.get('title', '') + # if the title has the year in remove it as tmdb cannot deal with it... + # replace e.g. 'The Americans (2015)' with 'The Americans' + title = re.sub(r'\s*\(\d{4}\)$', '', title, count=1) + url = 'http://api.themoviedb.org/3/search/%s' % media_type + parameters = { + 'api_key': apiKey, + 'language': KODILANGUAGE, + 'query': title.encode('utf-8', errors='ignore') + } + data = downloadutils.DownloadUtils().downloadUrl( + url, + authenticate=False, + parameters=parameters, + timeout=7) try: - url = 'http://api.themoviedb.org/3/search/%s?api_key=%s&language=%s&query=%s' %(type,tmdb_apiKey,KODILANGUAGE,utils.try_encode(title)) - response = requests.get(url, timeout=5) - if response.status_code == 200: - data = json.loads(response.content.decode('utf-8','replace')) - #find year match - if data and year and data.get("results"): - for item in data["results"]: - if item.get("first_air_date") and year in item.get("first_air_date"): - matchFound = item - break - elif item.get("release_date") and year in item.get("release_date"): - matchFound = item - break - #find exact match based on title - if not matchFound and data and data.get("results",None): - for item in data["results"]: - name = item.get("name") - if not name: name = item.get("title") - original_name = item.get("original_name","") - title_alt = title.lower().replace(" ","").replace("-","").replace(":","").replace("&","").replace(",","") - name_alt = name.lower().replace(" ","").replace("-","").replace(":","").replace("&","").replace(",","") - org_name_alt = original_name.lower().replace(" ","").replace("-","").replace(":","").replace("&","").replace(",","") - if name == title or original_name == title: - #match found for exact title name - matchFound = item - break - elif name.split(" (")[0] == title or title_alt == name_alt or title_alt == org_name_alt: - #match found with substituting some stuff - matchFound = item - break + data.get('test') + except: + self.logMsg('Could not download data from FanartTV', -1) + return + if data.get('results') is None: + self.logMsg('No match found on themoviedb for type: %s, title: %s' + % (media_type, title), 1) + return - #if a match was not found, we accept the closest match from TMDB - if not matchFound and len(data.get("results")) > 0 and not len(data.get("results")) > 5: - matchFound = item = data.get("results")[0] + year = item.get('year') + matchFound = None + # find year match + if year is not None: + for entry in data["results"]: + if year in entry.get("first_air_date", ""): + matchFound = entry + break + elif year in entry.get("release_date", ""): + matchFound = entry + break + # find exact match based on title, if we haven't found a year match + if matchFound is None: + self.logMsg('No themoviedb match found using year %s' % year, 1) + replacements = ( + ' ', + '-', + '&', + ',', + ':', + ';' + ) + for entry in data["results"]: + name = entry.get("name", entry.get("title", "")) + original_name = entry.get("original_name", "") + title_alt = title.lower() + name_alt = name.lower() + org_name_alt = original_name.lower() + for replaceString in replacements: + title_alt = title_alt.replace(replaceString, '') + name_alt = name_alt.replace(replaceString, '') + org_name_alt = org_name_alt.replace(replaceString, '') + if name == title or original_name == title: + # match found for exact title name + matchFound = entry + break + elif (name.split(" (")[0] == title or title_alt == name_alt + or title_alt == org_name_alt): + # match found with substituting some stuff + matchFound = entry + break - if matchFound: - coverUrl = matchFound.get("poster_path","") - fanartUrl = matchFound.get("backdrop_path","") - id = str(matchFound.get("id","")) - media_type = type - if media_type == "multi" and matchFound.get("media_type"): - media_type = matchFound.get("media_type","") - name = item.get("name") - if not name: name = item.get("title") - #lookup external tmdb_id and perform artwork lookup on fanart.tv - if id: - languages = [KODILANGUAGE,"en"] - for language in languages: - if media_type == "movie": - url = 'http://api.themoviedb.org/3/movie/%s?api_key=%s&language=%s&append_to_response=videos' %(id,tmdb_apiKey,language) - elif media_type == "tv": - url = 'http://api.themoviedb.org/3/tv/%s?api_key=%s&append_to_response=external_ids,videos&language=%s' %(id,tmdb_apiKey,language) - response = requests.get(url) - data = json.loads(response.content.decode('utf-8','replace')) - if data: - if not media_id and data.get("imdb_id"): - media_id = str(data.get("imdb_id")) - if not media_id and data.get("external_ids"): - media_id = str(data["external_ids"].get("tvdb_id")) + # if a match was not found, we accept the closest match from TMDB + if matchFound is None and len(data.get("results")) > 0: + self.logMsg('Using very first match from themoviedb', 1) + matchFound = entry = data.get("results")[0] - #lookup artwork on fanart.tv - if media_id and media_type: - #gets fanart.tv images for given id - api_key = "639191cb0774661597f28a47e7e2bad5" + if matchFound is None: + self.logMsg('Still no themoviedb match for type: %s, title: %s, ' + 'year: %s' % (media_type, title, year), 1) + self.logMsg('themoviedb answer was %s' % data['results'], 1) + return - if type == "movie": - url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' %(media_id,api_key) - else: - url = 'http://webservice.fanart.tv/v3/tv/%s?api_key=%s' %(media_id,api_key) - try: - response = requests.get(url, timeout=15) - except Exception as e: - return allartworks - if response and response.content and response.status_code == 200: - data = json.loads(response.content.decode('utf-8','replace')) - else: - #not found - return allartworks - if data: - #we need to use a little mapping between fanart.tv arttypes and kodi artttypes - fanartTVTypes = [ ("logo","Logo"),("musiclogo","clearlogo"),("disc","Disc"),("clearart","Art"),("banner","Banner"),("clearlogo","Logo"),("background","fanart"),("showbackground","fanart"),("characterart","characterart")] - if type != "artist": fanartTVTypes.append( ("thumb","Thumb") ) - if type == "artist": fanartTVTypes.append( ("thumb","folder") ) - prefixes = ["",type,"hd","hd"+type] - for fanarttype in fanartTVTypes: - for prefix in prefixes: - fanarttvimage = prefix+fanarttype[0] - if data.has_key(fanarttvimage): - for item in data[fanarttvimage]: - if item.get("lang","") == KODILANGUAGE: - #select image in preferred language - if xbmcvfs.exists(item.get("url")): - allartworks[fanarttype[1]] = item.get("url") - break - if not allartworks.get(fanarttype[1]) or (not "http:" in allartworks.get(fanarttype[1])): - #just grab the first english one as fallback - for item in data[fanarttvimage]: - if item.get("lang","") == "en" or not item.get("lang"): - if xbmcvfs.exists(item.get("url")): - allartworks[fanarttype[1]] = item.get("url") - break - #grab extrafanarts in list - maxfanarts = 10 - if "background" in fanarttvimage: - fanartcount = 0 - for item in data[fanarttvimage]: - if fanartcount < maxfanarts: - if xbmcvfs.exists(item.get("url")): - allartworks['Backdrop'].append(item.get("url")) - fanartcount += 1 - #save extrafanarts as string + self.logMsg('Found themoviedb match for %s: %s' + % (item.get('title'), matchFound), 1) - return allartworks + tmdbId = str(entry.get("id", "")) + if tmdbId == '': + self.logMsg('No themoviedb ID found, aborting', -1) + return - except Exception as e: - #no artwork - self.logMsg('No extra artwork found') + if media_type == "multi" and entry.get("media_type"): + media_type = entry.get("media_type") + name = entry.get("name", entry.get("title")) + # lookup external tmdbId and perform artwork lookup on fanart.tv + parameters = { + 'api_key': apiKey + } + mediaId = None + for language in [KODILANGUAGE, "en"]: + parameters['language'] = language + if media_type == "movie": + url = 'http://api.themoviedb.org/3/movie/%s' % tmdbId + parameters['append_to_response'] = 'videos' + elif media_type == "tv": + url = 'http://api.themoviedb.org/3/tv/%s' % tmdbId + parameters['append_to_response'] = 'external_ids,videos' + data = downloadutils.DownloadUtils().downloadUrl( + url, + authenticate=False, + parameters=parameters, + timeout=7) + try: + data.get('test') + except: + self.logMsg('Could not download %s with parameters %s' + % (url, parameters), -1) + continue + if data.get("imdb_id") is not None: + mediaId = str(data.get("imdb_id")) + break + if data.get("external_ids") is not None: + mediaId = str(data["external_ids"].get("tvdb_id")) + break + return mediaId + + def getFanartTVArt(self, mediaId, allartworks): + """ + perform artwork lookup on fanart.tv + + mediaId: IMDB id for movies, tvdb id for TV shows + """ + item = self.item.attrib + KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1) + api_key = "639191cb0774661597f28a47e7e2bad5" + typus = item.get('type') + if typus == 'show': + typus = 'tv' + + if typus == "movie": + url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' \ + % (mediaId, api_key) + elif typus == 'tv': + url = 'http://webservice.fanart.tv/v3/tv/%s?api_key=%s' \ + % (mediaId, api_key) + else: + # Not supported artwork return allartworks + data = downloadutils.DownloadUtils().downloadUrl( + url, + authenticate=False, + timeout=15) + try: + data.get('test') + except: + self.logMsg('Could not download data from FanartTV', -1) + return allartworks + + # we need to use a little mapping between fanart.tv arttypes and kodi + # artttypes + fanartTVTypes = [ + ("logo", "Logo"), + ("musiclogo", "clearlogo"), + ("disc", "Disc"), + ("clearart", "Art"), + ("banner", "Banner"), + ("clearlogo", "Logo"), + ("background", "fanart"), + ("showbackground", "fanart"), + ("characterart", "characterart") + ] + if typus == "artist": + fanartTVTypes.append(("thumb", "folder")) + else: + fanartTVTypes.append(("thumb", "Thumb")) + prefixes = ( + "hd" + typus, + "hd", + typus, + "", + ) + for fanarttype in fanartTVTypes: + # Skip the ones we already have + if allartworks.get(fanarttype[1]): + continue + for prefix in prefixes: + fanarttvimage = prefix + fanarttype[0] + if fanarttvimage not in data: + continue + # select image in preferred language + for entry in data[fanarttvimage]: + if entry.get("lang") == KODILANGUAGE: + allartworks[fanarttype[1]] = entry.get("url") + break + # just grab the first english OR undefinded one as fallback + if allartworks.get(fanarttype[1]) is None: + for entry in data[fanarttvimage]: + if entry.get("lang") in ("en", "00"): + allartworks[fanarttype[1]] = entry.get("url") + break + + # grab extrafanarts in list + maxfanarts = 10 + fanartcount = 0 + for prefix in prefixes: + fanarttvimage = prefix + 'background' + if fanarttvimage not in data: + continue + for entry in data[fanarttvimage]: + if fanartcount < maxfanarts: + if xbmcvfs.exists(entry.get("url")): + allartworks['Backdrop'].append(entry.get("url")) + fanartcount += 1 + + return allartworks def shouldStream(self): """ Returns True if the item's 'optimizedForStreaming' is set, False other- wise """ - return (True if self.item[0].attrib.get('optimizedForStreaming') == '1' - else False) + return self.item[0].attrib.get('optimizedForStreaming') == '1' def getTranscodeVideoPath(self, action, quality={}): """ diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 5a0d1fc9..9aba8f18 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -157,7 +157,7 @@ class DownloadUtils(): def downloadUrl(self, url, action_type="GET", postBody=None, parameters=None, authenticate=True, headerOptions=None, - verifySSL=True): + verifySSL=True, timeout=None): """ Override SSL check with verifySSL=False @@ -203,6 +203,8 @@ class DownloadUtils(): kwargs['data'] = postBody if parameters is not None: kwargs['params'] = parameters + if timeout is not None: + kwargs['timeout'] = timeout # ACTUAL DOWNLOAD HAPPENING HERE try: diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index d80b8c29..7fcf8517 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -284,6 +284,12 @@ class InitialSetup(): else: utils.advancedSettingsXML() + # Download additional art from FanArtTV + if dialog.yesno(heading=self.addonName, + line1=string(39016)): + self.logMsg("User opted to use FanArtTV", 1) + utils.settings('FanartTV', value="true") + if goToSettings is False: # Open Settings page now? You will need to restart! goToSettings = dialog.yesno(heading=self.addonName, diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 16daac41..445f3c39 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -928,15 +928,3 @@ def deleteNodes(): xbmcvfs.delete(("%s%s" % (path, file.decode('utf-8'))).encode('utf-8')) except: logMsg("PLEX", "Failed to file: %s" % file.decode('utf-8')) - -def try_encode(text, encoding="utf-8"): - try: - return text.encode(encoding,"ignore") - except: - return text - -def try_decode(text, encoding="utf-8"): - try: - return text.decode(encoding,"ignore") - except: - return text diff --git a/resources/settings.xml b/resources/settings.xml index 5dc36709..1ba17adf 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -56,9 +56,9 @@ + -