# -*- coding: utf-8 -*- ### HTTP Anidb Metadata Agent (HAMA) By ZeroQI (Forked from Atomicstrawberry's v0.4 - AniDB, TVDB, AniDB mod agent for XBMC XML's, and Plex URL and path variable definition ### ANIDB_TITLES = 'http://anidb.net/api/anime-titles.xml.gz' # AniDB title database file contain all ids, all languages #http://bakabt.info/anidb/animetitles.xml ANIDB_TVDB_MAPPING = 'http://rawgit.com/ScudLee/anime-lists/master/anime-list-master.xml' # ScudLee mapping file url ANIDB_TVDB_MAPPING_CUSTOM = 'anime-list-custom.xml' # custom local correction for ScudLee mapping file url ANIDB_COLLECTION = 'http://rawgit.com/ScudLee/anime-lists/master/anime-movieset-list.xml' # ScudLee collection mapping file ANIDB_HTTP_API_URL = 'http://api.anidb.net:9001/httpapi?request=anime&client=hama&clientver=1&protover=1&aid=' # ANIDB_PIC_BASE_URL = 'http://img7.anidb.net/pics/anime/' # AniDB picture directory ANIDB_SERIE_URL = 'http://anidb.net/perl-bin/animedb.pl?show=anime&aid=%s' # AniDB link to the anime ANIDB_TVDB_MAPPING_FEEDBACK = 'http://github.com/ScudLee/anime-lists/issues/new?title=%s&body=%s' # ScudLee mapping file git feedback url TVDB_HTTP_API_URL = 'http://thetvdb.com/api/A27AD9BE0DA63333/series/%s/all/%s.xml' # TVDB Serie XML for episodes sumaries for now TVDB_BANNERS_URL = 'http://thetvdb.com/api/A27AD9BE0DA63333/series/%s/banners.xml' # TVDB Serie pictures xml: fanarts, posters, banners TVDB_SERIE_SEARCH = 'http://thetvdb.com/api/GetSeries.php?seriesname=' # TVDB_IMAGES_URL = 'http://thetvdb.com/banners/' # TVDB picture directory TVDB_SERIE_URL = 'http://thetvdb.com/?tab=series&id=%s' # TMDB_CONFIG_URL = 'http://api.tmdb.org/3/configuration?api_key=7f4a0bd0bd3315bb832e17feda70b5cd' # TMDB_MOVIE_SEARCH = 'http://api.tmdb.org/3/search/movie?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&query=%s&year=&language=en&include_adult=true' TMDB_MOVIE_SEARCH_BY_TMDBID = 'http://api.tmdb.org/3/movie/%s?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&append_to_response=releases,credits&language=en' TMDB_SEARCH_URL_BY_IMDBID = 'http://api.tmdb.org/3/find/%s?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&external_source=imdb_id' # TMDB_IMAGES_URL = 'http://api.tmdb.org/3/movie/%s/images?api_key=7f4a0bd0bd3315bb832e17feda70b5cd' # TMDB_SERIE_SEARCH_BY_TMDBID = 'http://api.tmdb.org/3/tv/%s?api_key=7f4a0bd0bd3315bb832e17feda70b5cd&append_to_response=releases,credits&language=en' # TMDB_SERIE_IMAGES_URL = 'https://api.tmdb.org/3/tv/%s/images?api_key=7f4a0bd0bd3315bb832e17feda70b5cd' # OMDB_HTTP_API_URL = "http://www.omdbapi.com/?i=" # THEME_URL = 'http://tvthemes.plexapp.com/%s.mp3' # Plex TV Theme url ASS_MAPPING_URL = 'http://rawgit.com/ZeroQI/Absolute-Series-Scanner/master/tvdb4.mapping.xml' # ASS_POSTERS_URL = 'http://rawgit.com/ZeroQI/Absolute-Series-Scanner/master/tvdb4.posters.xml' # FANART_TV_TV_URL = 'http://webservice.fanart.tv/v3/tv/{tvdbid}?api_key={api_key}' # Fanart TV URL for TV FANART_TV_MOVIES_URL = 'http://webservice.fanart.tv/v3/movies/{tmdbid}?api_key={api_key}' # Fanart TV URL for Movies FANART_TV_API_KEY = 'cfa9dc054d221b8d107f8411cd20b13f' # API key for Hama Dev RESTRICTED_GENRE = {'X': ["18 restricted", "pornography"], 'TV-MA': ["tv censoring", "borderline porn"]} MOVIE_RATING_MAP = {'TV-Y': 'G', 'TV-Y7': 'G', 'TV-G': 'G', 'TV-PG': 'PG', 'TV-14': 'PG-13', 'TV-MA': 'NC-17', 'X': 'X'} FILTER_CHARS = "\\/:*?<>|~-; " SPLIT_CHARS = [';', ':', '*', '?', ',', '.', '~', '-', '\\', '/' ] #Space is implied, characters forbidden by os filename limitations WEB_LINK = "%s" FILTER_SEARCH_WORDS = [ ### These are words which cause extra noise due to being uninteresting for doing searches on, Lowercase only ############################################################# 'to', 'wa', 'ga', 'no', 'age', 'da', 'chou', 'super', 'yo', 'de', 'chan', 'hime', 'ni', 'sekai', # Jp 'a', 'of', 'an', 'the', 'motion', 'picture', 'special', 'oav', 'ova', 'tv', 'special', 'eternal', 'final', 'last', 'one', 'movie', 'me', 'princess', 'theater', # En Continued 'le', 'la', 'un', 'les', 'nos', 'vos', 'des', 'ses', # Fr 'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix', 'x', 'xi', 'xii', 'xiii', 'xiv', 'xv', 'xvi'] # Roman digits import os, re, time, datetime, string, thread, threading, urllib, copy # Functions used per module: os (read), re (sub, match), time (sleep), datetim (datetime). AniDB_title_tree, AniDB_collection_tree, AniDB_TVDB_mapping_tree = None, None, None #ValueError if in Start() SERIE_LANGUAGE_PRIORITY = [ Prefs['SerieLanguage1' ].encode('utf-8'), Prefs['SerieLanguage2' ].encode('utf-8'), Prefs['SerieLanguage3'].encode('utf-8') ] #override default language EPISODE_LANGUAGE_PRIORITY = [ Prefs['EpisodeLanguage1'].encode('utf-8'), Prefs['EpisodeLanguage2'].encode('utf-8') ] #override default language error_log_locked, error_log_lock_sleep = {}, 10 import logging hama_logger, formatter = logging.getLogger('com.plexapp.agents.hama'), logging.Formatter('%(asctime)-15s - %(name)s (%(thread)x) : %(levelname)s (%(module)s/%(funcName)s:%(lineno)d) - %(message)s') #Log("Loggers: %s" % logging.Logger.manager.loggerDict) #Log("Logger->Handlers: 'com.plexapp.agents.hama': %s" % hama_logger.handlers) for handler in hama_logger.handlers: handler.setFormatter(formatter) ### Pre-Defined ValidatePrefs function Values in "DefaultPrefs.json", accessible in Settings>Tab:Plex Media Server>Sidebar:Agents>Tab:Movies/TV Shows>Tab:HamaTV ####### def ValidatePrefs(): # a = sum(getattr(t, name, 0) for name in "xyz") DefaultPrefs = ("GetTvdbFanart", "GetTvdbPosters", "GetTvdbBanners", "GetAnidbPoster", "GetTmdbFanart", "GetTmdbPoster", "GetOmdbPoster", "GetFanartTVBackground", "GetFanartTVPoster", "GetFanartTVBanner", "GetASSPosters", "localart", "adult", "GetPlexThemes", "MinimumWeight", "SerieLanguage1", "SerieLanguage2", "SerieLanguage3", "EpisodeLanguage1", "EpisodeLanguage2") try: for key in DefaultPrefs: Log.Info("Prefs[%s] = %s" % (key, Prefs[key])) if [Prefs[key] == None for key in DefaultPrefs].count(True) > 0: Log.Error("Some Pref values do not exist. Edit and save your preferences.") except: err_str = "Value '%s' missing from 'DefaultPrefs.json', update it." % key; Log.Error("DefaultPrefs.json invalid. " + err_str); return MessageContainer ('Error', err_str) else: ok_str = 'HAMA - Provided preference values are ok'; Log.Info( "DefaultPrefs.json is valid." + ok_str ); return MessageContainer ('Success', ok_str ) ### Pre-Defined Start function ######################################################################################################################################### def Start(): msgContainer = ValidatePrefs(); if msgContainer.header == 'Error': return Log.Info('### HTTP Anidb Metadata Agent (HAMA) Started ##############################################################################################################') global AniDB_title_tree, AniDB_TVDB_mapping_tree, AniDB_collection_tree # only this one to make search after start faster AniDB_title_tree = HamaCommonAgent().xmlElementFromFile(ANIDB_TITLES, os.path.splitext(os.path.basename(ANIDB_TITLES))[0] , True, CACHE_1HOUR * 24 * 2) if not AniDB_title_tree: Log.Critical("Failed to load core file '%s'" % os.path.splitext(os.path.basename(ANIDB_TITLES))[0]); raise Exception("HAMA Fatal Error Hit") #; AniDB_title_tree = XML.ElementFromString("") AniDB_TVDB_mapping_tree = HamaCommonAgent().xmlElementFromFile(ANIDB_TVDB_MAPPING, os.path.basename(ANIDB_TVDB_MAPPING), False, CACHE_1HOUR * 24 * 2) if not AniDB_TVDB_mapping_tree: Log.Critical("Failed to load core file '%s'" % os.path.basename(ANIDB_TVDB_MAPPING)); raise Exception("HAMA Fatal Error Hit") #; AniDB_TVDB_mapping_tree = XML.ElementFromString("") AniDB_collection_tree = HamaCommonAgent().xmlElementFromFile(ANIDB_COLLECTION, os.path.basename(ANIDB_COLLECTION ), False, CACHE_1HOUR * 24 * 2) if not AniDB_collection_tree: AniDB_collection_tree = XML.ElementFromString(""); Log.Error("Failed to load core file '%s'" % os.path.basename(ANIDB_COLLECTION )) HTTP.CacheTime = CACHE_1HOUR * 24 class HamaCommonAgent: ### Serie search ###################################################################################################################################################### def Search(self, results, media, lang, manual, movie): Log.Info("=== Search - Begin - ================================================================================================") orig_title = ( media.title if movie else media.show ) try: orig_title = orig_title.encode('utf-8') # NEEDS UTF-8 except Exception as e: Log.Error("UTF-8 encode issue, Exception: '%s'" % e) if not orig_title: return if orig_title.startswith("clear-cache"): HTTP.ClearCache() ### Clear Plex http cache manually by searching a serie named "clear-cache" ### Log.Info("Title: '%s', name: '%s', filename: '%s', manual:'%s'" % (orig_title, media.name, media.filename, str(manual))) #if media.filename is not None: filename = String.Unquote(media.filename) #auto match only ### Check if a guid is specified "Show name [anidb-id]" ### global SERIE_LANGUAGE_PRIORITY match = re.search("(?P.*?) ?\[(?P(anidb|tvdb|tvdb2|tvdb3|tvdb4|tmdb|imdb))-(tt)?(?P[0-9]{1,7})\]", orig_title, re.IGNORECASE) if match: ###metadata id provided source, guid, show = match.group('source').lower(), match.group('guid'), match.group('show') if source=="anidb": show, mainTitle = self.getAniDBTitle(AniDB_title_tree.xpath("/animetitles/anime[@aid='%s']/*" % guid), SERIE_LANGUAGE_PRIORITY) #global AniDB_title_tree, SERIE_LANGUAGE_PRIORITY; Log.Info("source: '%s', id: '%s', show from id: '%s' provided in foldername: '%s'" % (source, guid, show, orig_title) ) results.Append(MetadataSearchResult(id="%s-%s" % (source, guid), name=show, year=media.year, lang=lang, score=100)) return ### AniDB Local exact search ### cleansedTitle = self.cleanse_title (orig_title).encode('utf-8') if media.year is not None: orig_title = orig_title + " (" + str(media.year) + ")" ### Year - if present (manual search or from scanner but not mine), include in title ### parent_element, show , score, maxi = None, "", 0, 0 AniDB_title_tree_elements = list(AniDB_title_tree.iterdescendants()) if AniDB_title_tree else [] for element in AniDB_title_tree_elements: if element.get('aid'): if score: #only when match found and it skipped to next serie in file, then add if score>maxi: maxi=score Log.Info("AniDB - score: '%3d', id: '%6s', title: '%s' " % (score, aid, show)) langTitle, mainTitle = self.getAniDBTitle(parent_element, SERIE_LANGUAGE_PRIORITY) results.Append(MetadataSearchResult(id="%s-%s" % ("anidb", aid), name="%s [%s-%s]" % (langTitle, "anidb", aid), year=media.year, lang=lang, score=score)) parent_element, show, score = None, "", 0 aid = element.get('aid') elif element.get('type') in ('main', 'official', 'syn', 'short'): title = element.text if title.lower() == orig_title.lower() and 100 > score: parent_element, show , score = element.getparent(), title, 100; Log.Info("AniDB - temp score: '%3d', id: '%6s', title: '%s' " % (100, aid, show)) #match = [element.getparent(), show, 100] elif self.cleanse_title (title) == cleansedTitle and 99 > score: parent_element, show , score = element.getparent(), cleansedTitle, 99 #match = [element.getparent(), cleansedTitle, 99] elif orig_title in title and 100*len(orig_title)/len(title) > score: parent_element, show , score = element.getparent(), orig_title, 100*len(orig_title)/len(title) #match = [element.getparent(), show, 100*len(orig_title)/len(element.text)] else: continue #no match if score: #last serie detected, added on next serie OR here Log.Info("AniDB - score: '%3d', id: '%6s', title: '%s' " % (score, aid, show)) langTitle, mainTitle = self.getAniDBTitle(parent_element, SERIE_LANGUAGE_PRIORITY) results.Append(MetadataSearchResult(id="%s-%s" % ("anidb", aid), name="%s [%s-%s]" % (langTitle, "anidb", aid), year=media.year, lang=lang, score=score)) if len(results)>=1: return #results.Sort('score', descending=True) ### AniDB local keyword search ### matchedTitles, matchedWords, words = [ ], { }, [ ] log_string = "Keyword search - Matching '%s' with: " % orig_title for word in self.splitByChars(orig_title, SPLIT_CHARS): word = self.cleanse_title (word) if word and word not in FILTER_SEARCH_WORDS and len(word) > 1: words.append (word.encode('utf-8')); log_string += "'%s', " % word Log.Info(log_string[:-2]) #remove last 2 chars #if len(words)==0: for title in AniDB_title_tree_elements: if title.get('aid'): aid = title.get('aid') elif title.get('{http://www.w3.org/XML/1998/namespace}lang') in SERIE_LANGUAGE_PRIORITY or title.get('type')=='main': sample = self.cleanse_title (title.text).encode('utf-8') for word in words: if word in sample: index = len(matchedTitles)-1 if index >=0 and matchedTitles[index][0] == aid: if title.get('type') == 'main': matchedTitles[index][1] = title.text if not title.text in matchedTitles[index][2]: matchedTitles[index][2].append(title.text) else: matchedTitles.append([aid, title.text, [title.text] ]) if word in matchedWords: matchedWords[word].append(sample) ## a[len(a):] = [x] else: matchedWords[word]=[sample] ## Log.Info(", ".join( key+"(%d)" % len(value) for key, value in matchedWords.iteritems() )) #list comprehention log_string = "similarity with '%s': " % orig_title for match in matchedTitles: ### calculate scores + Buid results ### scores = [] for title in match[2]: # Calculate distance without space and characters a, b = self.cleanse_title(title), cleansedTitle scores.append( int(100 - (100*float(Util.LevenshteinDistance(a,b)) / float(max(len(a),len(b))) )) ) #To-Do: LongestCommonSubstring(first, second). use that? bestScore = max(scores) log_string = log_string + match[1] + " (%s%%), " % '{:>2}'.format(str(bestScore)) results.Append(MetadataSearchResult(id="anidb-"+match[0], name=match[1]+" [anidb-%s]" % match[0], year=media.year, lang=lang, score=bestScore)) Log.Info(log_string) #results.Sort('score', descending=True) return ### TVDB serie search ### Log.Info("maxi: '%d'" % maxi) if maxi<50: try: TVDBsearchXml = XML.ElementFromURL( TVDB_SERIE_SEARCH + orig_title.replace(" ", "%20"), cacheTime=CACHE_1HOUR * 24) except Exception as e: Log.Error("TVDB Loading search XML failed, Exception: '%s'" % e) else: for serie in TVDBsearchXml.xpath('Series'): a, b = orig_title, serie.xpath('SeriesName')[0].text.encode('utf-8') #a, b = cleansedTitle, self.cleanse_title (serie.xpath('SeriesName')[0].text) score = 100 - 100*Util.LevenshteinDistance(a,b) / max(len(a),len(b)) if a!=b else 100 Log.Info("TVDB - score: '%3d', id: '%6s', title: '%s'" % (score, serie.xpath('seriesid')[0].text, serie.xpath('SeriesName')[0].text) ) results.Append(MetadataSearchResult(id="%s-%s" % ("tvdb", serie.xpath('seriesid')[0].text), name="%s [%s-%s]" % (serie.xpath('SeriesName')[0].text, "tvdb", serie.xpath('seriesid')[0].text), year=None, lang=lang, score=score) ) if len(results)>=1: return ### TMDB movie search ### Log.Info("TMDB - url: " + TMDB_MOVIE_SEARCH % orig_title) #config_dict = self.get_json(TMDB_CONFIG_URL, cache_time=CACHE_1WEEK * 2) try: tmdb_json = JSON.ObjectFromURL(TMDB_MOVIE_SEARCH % orig_title.replace(" ", "%20"), sleep=2.0, headers={'Accept': 'application/json'}, cacheTime=CACHE_1WEEK * 2) except Exception as e: Log.Error("get_json - Error fetching JSON page '%s', Exception: '%s'" %( TMDB_MOVIE_SEARCH % orig_title, e)) # tmdb_json = self.get_json(TMDB_MOVIE_SEARCH % orig_title, cache_time=CACHE_1WEEK * 2) else: if isinstance(tmdb_json, dict) and 'results' in tmdb_json: for i, movie in enumerate(tmdb_json['results']): a, b = orig_title, movie['title'].encode('utf-8') score = 100 - 100*Util.LevenshteinDistance(a,b) / max(len(a),len(b)) if a!=b else 100 id = movie['id'] Log.Info("TMDB - score: '%3d', id: '%6s', title: '%s'" % (score, movie['id'], movie['title']) ) results.Append(MetadataSearchResult(id="%s-%s" % ("tmdb", movie['id']), name="%s [%s-%s]" % (movie['title'], "tmdb", movie['id']), year=None, lang=lang, score=score) ) if '' in movie and movie['adult']!="null": Log.Info("adult: '%s'" % movie['adult']) # genre_ids, original_language, id, original_language, original_title, overview, release_date, poster_path, popularity, video, vote_average, vote_count, adult, backdrop_path ### Parse the AniDB anime title XML ################################################################################################################################## def Update(self, metadata, media, lang, force, movie): global SERIE_LANGUAGE_PRIORITY, EPISODE_LANGUAGE_PRIORITY, error_log_locked error_log = { 'anime-list anidbid missing': [], 'anime-list tvdbid missing' : [], 'anime-list studio logos': [], 'AniDB summaries missing' : [], 'AniDB posters missing' : [], 'TVDB posters missing' : [], 'TVDB season posters missing': [], 'Plex themes missing' : [], 'Missing Episodes' : [], 'Missing Episode Summaries' : [], 'Missing Specials' : [], 'Missing Special Summaries' : [] } for key in error_log.keys(): if key not in error_log_locked.keys(): error_log_locked[key] = [False, 0] Log.Info('--- Update Begin -------------------------------------------------------------------------------------------') if not "-" in metadata.id: metadata.id = "anidb-" + metadata.id # Old metadata from when the id was only the anidbid metadata_id_source, metadata_id_number = metadata.id.split('-', 1); metadata_id_source_core = metadata_id_source.rstrip("0123456789") current_date = int(time.strftime("%Y%m%d")) Log.Info("metadata source: '%s', id: '%s', Title: '%s', lang: '%s', (%s)" % (metadata_id_source, metadata_id_number, metadata.title, lang, force) ) getElementText = lambda el, xp: el.xpath(xp)[0].text if el is not None and el.xpath(xp) and el.xpath(xp)[0].text else "" # helper for getting text from XML element ### Get tvdbid, tmdbid, imdbid (+etc...) through mapping file ### anidbid, tvdbid, tmdbid, imdbid, defaulttvdbseason, mapping_studio, poster_id, mappingList, anidbid_table = "", "", "", "", "", "", "", {}, [] tvdbposternumber, tvdb_table, tvdbtitle, tvdbOverview, tvdbNetwork, tvdbFirstAired, tvdbRating, tvdbContentRating, tvdbgenre = 0, {}, "", "", "", "", None, None, () if metadata_id_source_core == "tvdb": tvdbid = metadata_id_number elif metadata_id_source == "anidb": anidbid = metadata_id_number tvdbid, tmdbid, imdbid, defaulttvdbseason, mappingList, mapping_studio, anidbid_table, poster_id = self.anidbTvdbMapping(metadata, anidbid, error_log) elif metadata_id_source in ["tmdb", "tsdb"]: tmdbid = metadata_id_number Log.Info("TMDB - url: " + TMDB_MOVIE_SEARCH_BY_TMDBID % tmdbid) try: tmdb_json = JSON.ObjectFromURL((TMDB_MOVIE_SEARCH_BY_TMDBID if metadata_id_source.startswith("tmdb") else TMDB_SERIE_SEARCH_BY_TMDBID)% tmdbid , sleep=2.0, headers={'Accept': 'application/json'}, cacheTime=CACHE_1DAY) except Exception as e: Log.Error("get_json - Error fetching JSON page '%s', Exception: '%s'" %(TMDB_MOVIE_SEARCH_BY_TMDBID % tmdbid, e)) else: Log('Update() - get_json - worked: ' + TMDB_MOVIE_SEARCH_BY_TMDBID % tmdbid) if 'vote_average' in tmdb_json and isinstance(tmdb_json['vote_average'], float): metadata.rating = tmdb_json['vote_average'] # if not ep.isdigit() and "." in ep and ep.split(".", 1)[0].isdigit() and ep.split(".")[1].isdigit(): if 'runtime' in tmdb_json and isinstance(tmdb_json['runtime' ], int): metadata.duration = int(tmdb_json['runtime']) * 60 * 1000 if 'title' in tmdb_json and tmdb_json['title']: metadata.title = tmdb_json['title'] if 'overview' in tmdb_json and tmdb_json['overview']: metadata.summary = tmdb_json['overview'] if 'release_date' in tmdb_json and tmdb_json['release_date']: metadata.originally_available_at = Datetime.ParseDate(tmdb_json['release_date']).date() if 'imdb_id' in tmdb_json and tmdb_json['imdb_id'] and not imdbid: imdbid = tmdb_json['imdb_id'] if 'vote_average' in tmdb_json and tmdb_json['vote_average'] and 'vote_count' in tmdb_json and tmdb_json['vote_count'] > 3: metadata.rating = tmdb_json['vote_average'] if 'genres' in tmdb_json and tmdb_json['genres']!=[]: metadata.genres.clear() for genre in tmdb_json['genres']: metadata.genres.add(genre['name'].strip()) #metadata.genres = tmdb_json['genres'] ??? if 'production_companies' in tmdb_json and len(tmdb_json['production_companies']) > 0: # Studio. index, company = tmdb_json['production_companies'][0]['id'],"" for studio in tmdb_json['production_companies']: if studio['id'] <= index: index, company = studio['id'], studio['name'].strip() metadata.studio = company if 'belongs_to_collection' in tmdb_json and tmdb_json['belongs_to_collection']: metadata.collections.clear() metadata.collections.add(tmdb_json['belongs_to_collection']['name'].replace(' Collection','')) if movie: if tmdb_json['tagline']: metadata.tagline = tmdb_json['tagline'] metadata.year = metadata.originally_available_at.year if tvdbid.isdigit(): ### TVDB ID exists #### ### Plex - Plex Theme song - https://plexapp.zendesk.com/hc/en-us/articles/201178657-Current-TV-Themes ### if THEME_URL % tvdbid in metadata.themes: Log.Info("Theme song - already added") elif Prefs['GetPlexThemes']: self.metadata_download (metadata.themes, THEME_URL % tvdbid, 1, "Plex/%s.mp3" % tvdbid) if not THEME_URL % tvdbid in metadata.themes: error_log['Plex themes missing'].append("tvdbid: %s | Title: '%s' | %s" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid), tvdbtitle, WEB_LINK % ("mailto:themes@plexapp.com?cc=&subject=Missing%%20theme%%20song%%20-%%20'%s%%20-%%20%s.mp3'" % (tvdbtitle, tvdbid), 'Upload'))) ### TVDB - Load serie XML ### tvdbanime, tvdb_episode_missing, tvdb_special_missing, special_summary_missing, summary_missing, summary_present = None, [], [], [], [], [] Log.Info("TVDB - tvdbid: '%s', url: '%s'" %(tvdbid, TVDB_HTTP_API_URL % (tvdbid, lang))) tvdbanime=self.xmlElementFromFile ( TVDB_HTTP_API_URL % (tvdbid, lang), "TVDB/"+tvdbid+".xml", False, CACHE_1HOUR * 24) if tvdbanime: tvdbanime = tvdbanime.xpath('/Data')[0] tvdbtitle, tvdbOverview, tvdbFirstAired = getElementText(tvdbanime, 'Series/SeriesName'), getElementText(tvdbanime, 'Series/Overview' ), getElementText(tvdbanime, 'Series/FirstAired') tvdbContentRating, tvdbNetwork, tvdbGenre = getElementText(tvdbanime, 'Series/ContentRating'), getElementText(tvdbanime, 'Series/Network'), filter(None, getElementText(tvdbanime, 'Series/Genre').split("|")) if '.' in getElementText(tvdbanime, 'Series/Rating'): ###tvdbRating # isinstance(tmdb_json['vote_average'], float) try: tvdbRating = float(getElementText(tvdbanime, 'Series/Rating')) except: tvdbRating = None else: tvdbRating = None if imdbid is None or imdbid =="" and getElementText(tvdbanime, 'Series/IMDB_ID'): imdbid = getElementText(tvdbanime, 'Series/IMDB_ID'); Log.Warn("IMDB ID was empty, loaded through tvdb serie xml, IMDBID: '%s'" % imdbid) ### TVDB - Build 'tvdb_table' ### abs_manual_placement_worked = True if defaulttvdbseason != "0" and max(map(int, media.seasons.keys()))==1 or metadata_id_source in ["tvdb3", "tvdb4"]: ep_count, abs_manual_placement_info, number_set = 0, [], False for episode in tvdbanime.xpath('Episode'): if episode.xpath('SeasonNumber')[0].text != '0': ep_count = ep_count + 1 if not episode.xpath('absolute_number')[0].text: episode.xpath('absolute_number')[0].text, number_set = str(ep_count), True if episode.xpath('EpisodeName')[0].text: episode.xpath('EpisodeName')[0].text = "(Guessed) " + episode.xpath('EpisodeName')[0].text if episode.xpath('Overview')[0].text: episode.xpath('Overview')[0].text = "(Guessed mapping as TVDB absolute numbering is missing)\n" + episode.xpath('Overview')[0].text abs_manual_placement_info.append("s%se%s = abs %s" % (episode.xpath('SeasonNumber')[0].text, episode.xpath('EpisodeNumber')[0].text, episode.xpath('absolute_number')[0].text)) elif not number_set: ep_count = int(episode.xpath('absolute_number')[0].text) else: Log.Error("An abs number has been found on ep (s%se%s) after starting to manually place our own abs numbers" % (episode.xpath('SeasonNumber')[0].text, episode.xpath('EpisodeNumber')[0].text) ) abs_manual_placement_worked = False break Log.Info("abs_manual_placement_worked: '%s', abs_manual_placement_info: '%s'" % (str(abs_manual_placement_worked), str(abs_manual_placement_info))) if abs_manual_placement_worked: for episode in tvdbanime.xpath('Episode'): # Combined_episodenumber, Combined_season, DVD(_chapter, _discid, _episodenumber, _season), Director, EpImgFlag, EpisodeName, EpisodeNumber, FirstAired, GuestStars, IMDB_ID #seasonid, imdbd currentSeasonNum = getElementText(episode, 'SeasonNumber') currentEpNum = getElementText(episode, 'EpisodeNumber') currentAbsNum = getElementText(episode, 'absolute_number') currentAirDate = getElementText(episode, 'FirstAired').replace('-','') currentAirDate = int(currentAirDate) if currentAirDate.isdigit() and int(currentAirDate) > 10000000 else 99999999 numbering, alt = (currentAbsNum, "s" + currentSeasonNum + "e" + currentEpNum) if defaulttvdbseason=="a" or metadata_id_source in ["tvdb3", "tvdb4"] and currentSeasonNum != '0' else ("s" + currentSeasonNum + "e" + currentEpNum, currentAbsNum) ##numbering = currentAbsNum if defaulttvdbseason=="a" or metadata_id_source in ["tvdb3", "tvdb4"] and currentSeasonNum != '0' else "s" + currentSeasonNum + "e" + currentEpNum tvdb_table [numbering] = { 'EpisodeName': getElementText(episode, 'EpisodeName'), 'FirstAired': getElementText(episode, 'FirstAired' ), 'filename': getElementText(episode, 'filename' ), 'Overview': getElementText(episode, 'Overview' ), 'Director': getElementText(episode, 'Director' ), 'Writer': getElementText(episode, 'Writer' ), 'Rating': getElementText(episode, 'Rating' ) if '.' in getElementText(episode, 'Rating') else None } tvdb_table [alt] = tvdb_table [numbering] ### Check for Missing Summaries ### if getElementText(episode, 'Overview'): summary_present.append (numbering) elif currentSeasonNum == '0': special_summary_missing.append(numbering) else: summary_missing.append (numbering) ### Check for Missing Episodes ### if not movie and (len(media.seasons)>2 or max(map(int, media.seasons.keys()))>1 or metadata_id_source_core == "tvdb"): if current_date <= (currentAirDate+1): Log.Warn("Episode '%s' is missing in Plex but air date '%s+1' is either missing (99999999) or in the future" % (numbering, currentAirDate)) elif currentSeasonNum and not ( ((not metadata_id_source in ["tvdb3","tvdb4"] or currentSeasonNum==0) and currentSeasonNum in media.seasons and currentEpNum in media.seasons[currentSeasonNum].episodes) or (metadata_id_source in ["tvdb3","tvdb4"] and [currentAbsNum in media.seasons[season].episodes for season in media.seasons].count(True) > 0) ): if currentSeasonNum == '0': tvdb_special_missing.append(numbering) else: tvdb_episode_missing.append(numbering) if summary_missing: error_log['Missing Episode Summaries'].append("tvdbid: %s | Title: '%s' | Missing Episode Summaries: %s" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid), tvdbtitle, str(summary_missing))) if special_summary_missing: error_log['Missing Special Summaries'].append("tvdbid: %s | Title: '%s' | Missing Special Summaries: %s" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid), tvdbtitle, str(special_summary_missing))) if tvdb_episode_missing: error_log['Missing Episodes' ].append("tvdbid: %s | Title: '%s' | Missing Episodes: %s" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid), tvdbtitle, str(tvdb_episode_missing))) if tvdb_special_missing: error_log['Missing Specials' ].append("tvdbid: %s | Title: '%s' | Missing Specials: %s" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid), tvdbtitle, str(tvdb_special_missing))) else: Log.Warn("'anime-list tvdbid missing.htm' log added as tvdb serie deleted: '%s', modify in custom mapping file to circumvent but please submit feedback to ScumLee's mapping file using html log link" % (TVDB_HTTP_API_URL % (tvdbid, lang))) error_log['anime-list tvdbid missing'].append("anidbid: %s | tvdbid: %s | " % (WEB_LINK % (ANIDB_SERIE_URL % anidbid, anidbid), WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid)) + TVDB_HTTP_API_URL % (tvdbid, lang) + " | Not downloadable so serie deleted from thetvdb") Log.Debug("TVDB - Episodes with Summary: " + str(sorted(summary_present))) Log.Debug("TVDB - Episodes without Summary: " + str(sorted(summary_missing))) if metadata_id_source == "tvdb4" and Prefs['GetASSPosters']: self.getImagesFromASS(metadata, media, tvdbid, movie, 0) ### TVDB - Fanart, Poster and Banner ### if Prefs['GetTvdbPosters'] or Prefs['GetTvdbFanart' ] or Prefs['GetTvdbBanners']: tvdbposternumber, tvdbseasonposter = self.getImagesFromTVDB(metadata, media, tvdbid, movie, poster_id, force, defaulttvdbseason, 1) if tvdbposternumber == 0: error_log['TVDB posters missing'].append("tvdbid: %s | Title: '%s'" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid), tvdbtitle)) if tvdbseasonposter == 0: error_log['TVDB season posters missing'].append("tvdbid: %s | Title: '%s'" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid), tvdbtitle)) if tvdbposternumber * tvdbseasonposter == 0: Log.Warn("TVDB - No poster, check logs in ../../Plug-in Support/Data/com.plexapp.agents.hama/DataItems/TVDB posters missing.htm to update Metadata Source") ### Movie posters including imdb from TVDB ### if Prefs["GetTmdbFanart"] or Prefs["GetTmdbPoster"]: self.getImagesFromTMDB(metadata, imdbid if imdbid.isalnum() else tmdbid, 97) #The Movie Database is least prefered by the mapping file, only when imdbid missing ### Movie posters including imdb from OMDB ### if Prefs["GetOmdbPoster"] and imdbid.isalnum(): self.getImagesFromOMDB(metadata, imdbid, 98) #return 200 but not downloaded correctly - IMDB has a single poster, downloading through OMDB xml, prefered by mapping file ### fanart.tv - Background, Poster and Banner ### if Prefs['GetFanartTVBackground'] or Prefs['GetFanartTVPoster'] or Prefs['GetFanartTVBanner']: if movie: if tmdbid: self.getImagesFromFanartTV(metadata, tmdbid=tmdbid) elif imdbid: # FanartTV only uses TMDB IDs as a lookup. The Anime List data normally only has IMDB IDs. However, we can convert IMDB IDs to TMDB IDs using TMDB! Log.Info("TMDB ID missing. Attempting to lookup using IMDB ID {imdbid}".format(imdbid=imdbid)) Log.Info("using IMDBID url: " + TMDB_SEARCH_URL_BY_IMDBID % imdbid) local_tmdbid = self.get_json(TMDB_SEARCH_URL_BY_IMDBID %imdbid, cache_time=CACHE_1WEEK * 2)['movie_results'][0]['id'] if local_tmdbid: # TMDB ID lookup was successful Log.Info("TMDB ID found for IMBD ID {imdbid}. tmdbid: '{tmdbid}'".format(imdbid=imdbid, tmdbid=local_tmdbid)) self.getImagesFromFanartTV(metadata, tmdbid=local_tmdbid) else: self.getImagesFromFanartTV(metadata, tvdbid=tvdbid, season=defaulttvdbseason) ### TVDB mode when a season 2 or more exist ############################################################################################################ if not movie and (len(media.seasons)>2 or max(map(int, media.seasons.keys()))>1 or metadata_id_source_core == "tvdb"): Log.Info("using TVDB numbering mode (seasons)" ) if tvdbtitle: metadata.title = tvdbtitle if tvdbRating: metadata.rating = tvdbRating if tvdbOverview: metadata.summary = tvdbOverview if tvdbNetwork: metadata.studio = tvdbNetwork if tvdbContentRating: metadata.content_rating = tvdbContentRating if tvdbFirstAired: metadata.originally_available_at = Datetime.ParseDate( tvdbFirstAired ).date() if tvdbGenre: metadata.genres.clear() for genre in tvdbGenre: metadata.genres.add(genre) Log.Info("TVDB - tvdbGenre: '%s'" % str(tvdbgenre)) list_eps = [] for media_season in media.seasons: metadata.seasons[media_season].summary, metadata.seasons[media_season].title, metadata.seasons[media_season].show,metadata.seasons[media_season].source_title = "#" + tvdbOverview, "#" + tvdbtitle, "#" + tvdbtitle, "#" + tvdbNetwork for media_episode in media.seasons[media_season].episodes: ep, episode_count = "s%se%s" % (media_season, media_episode),0 ##media_episode if defaulttvdbseason=="a" or metadata_id_source in ["tvdb3", "tvdb4"] and media_season != "0" else "s%se%s" % (media_season, media_episode), 0 ##ep, episode_count = media_episode if defaulttvdbseason=="a" or metadata_id_source in ["tvdb3", "tvdb4"] and media_season != "0" else "s%se%s" % (media_season, media_episode), 0 if ep in tvdb_table: metadata.seasons[media_season].episodes[media_episode].directors.clear() metadata.seasons[media_season].episodes[media_episode].writers.clear() if 'Overview' in tvdb_table[ep] and tvdb_table[ep]['Overview']: try: metadata.seasons[media_season].episodes[media_episode].summary = tvdb_table [ep] ['Overview'] except Exception as e: Log.Error("Error adding summary - ep: '%s', media_season: '%s', media_episode: '%s', summary:'%s', Exception: '%s'" % (ep, media_season, media_episode, tvdb_table [ep] ['Overview'], e)) if 'EpisodeName' in tvdb_table[ep] and tvdb_table [ep] ['EpisodeName']: metadata.seasons[media_season].episodes[media_episode].title = tvdb_table [ep] ['EpisodeName'] if 'filename' in tvdb_table[ep] and tvdb_table [ep] ['filename'] and tvdb_table [ep] ['filename'] != "": self.metadata_download (metadata.seasons[media_season].episodes[media_episode].thumbs, TVDB_IMAGES_URL + tvdb_table[ep]['filename'], 1, "TVDB/episodes/"+ os.path.basename(tvdb_table[ep]['filename'])) if 'Director' in tvdb_table[ep] and tvdb_table [ep] ['Director']: for this_director in re.split(',|\|', tvdb_table[ep]['Director']): if this_director not in metadata.seasons[media_season].episodes[media_episode].directors: metadata.seasons[media_season].episodes[media_episode].directors.add(this_director) if 'Writer' in tvdb_table[ep] and tvdb_table [ep] ['Writer']: for this_writer in re.split(',|\|', tvdb_table[ep]['Writer']): if this_writer not in metadata.seasons[media_season].episodes[media_episode].writers: metadata.seasons[media_season].episodes[media_episode].writers.add(this_writer) if 'Rating' in tvdb_table[ep] and tvdb_table [ep] ['Rating']: try: metadata.seasons[media_season].episodes[media_episode].rating = float(tvdb_table [ep] ['Rating']) except Exception as e: Log.Error("float issue: '%s', Exception: '%s'" % (tvdb_table [ep] ['Rating'], e)) #ValueError if 'FirstAired' in tvdb_table[ep] and tvdb_table [ep] ['FirstAired']: match = re.match("([1-2][0-9]{3})-([0-1][0-9])-([0-3][0-9])", tvdb_table [ep] ['FirstAired']) if match: try: metadata.seasons[media_season].episodes[media_episode].originally_available_at = datetime.date(int(match.group(1)), int(match.group(2)), int(match.group(3))) except ValueError, e: Log.Error("TVDB parseAirDate - Date out of range: " + str(e)) for media_item in media.seasons[media_season].episodes[media_episode].items: for item_part in media_item.parts: Log("File: '%s' '%s'" % (ep, item_part.file)) episode_count += 1; list_eps.append(ep) metadata.seasons[media_season].episode_count = episode_count #An integer specifying the number of episodes in the season. if list_eps !="": Log.Debug("List_eps: %s" % str(sorted(list_eps))) Log.Debug("TVDB table: '%s'" % str(tvdb_table)) elif metadata_id_source == "anidb": ### AniDB Serie XML ################################################################################################################################## Log.Info("AniDB mode - AniDB Serie XML: " + ANIDB_HTTP_API_URL + metadata_id_number + ", " + "AniDB/"+metadata_id_number+".xml" ) anime = None #return #if banned return ? try: anime = self.xmlElementFromFile ( ANIDB_HTTP_API_URL + metadata_id_number, "AniDB/"+metadata_id_number+".xml", True, CACHE_1HOUR * 24).xpath('/anime')[0] # Put AniDB serie xml (cached if able) into 'anime' except Exception as e: Log.Error("AniDB Serie XML: Exception raised, probably no return in xmlElementFromFile, Exception: '%s'" % e) if not anime: try: if not metadata.title and tvdbtitle: metadata.title = tvdbtitle except Exception as e: Log.Error("Exception: %s" % e) else: ### AniDB Title ### try: title, orig = self.getAniDBTitle(anime.xpath('/anime/titles/title'), SERIE_LANGUAGE_PRIORITY) except Exception as e: Log.Error("AniDB Title: Exception raised, Exception: '%s'" % e) else: # title, orig = title.encode("utf-8").replace("`", "'"), orig.encode("utf-8").replace("`", "'") if title == str(metadata.title): Log.Info("AniDB title: '%s', original title: '%s', metadata.title '%s'*" % (title, orig, metadata.title)) elif title != "": #If title different but not empty [Failsafe] Log.Info("AniDB title: '%s', original title: '%s', metadata.title '%s'" % (title, orig, metadata.title)) metadata.title = title if movie and orig != "" and orig != metadata.original_title: metadata.original_title = orig # If it's a movie, Update original title in metadata http://forums.plexapp.com/index.php/topic/25584-setting-metadata-original-title-and-sort-title-still-not-possible/ ### AniDB Start Date ### if getElementText(anime, 'startdate') == "": Log.Info("AniDB Start Date: None") elif metadata.originally_available_at == getElementText(anime, 'startdate'): Log.Info("AniDB Start Date: '%s'*" % str(metadata.originally_available_at)) else: metadata.originally_available_at = Datetime.ParseDate( getElementText(anime, 'startdate') ).date() if movie: metadata.year = metadata.originally_available_at.year Log.Info("AniDB Start Date: '%s'" % str(metadata.originally_available_at)) ### AniDB Ratings ### misc = getElementText(anime, 'ratings/permanent') if misc=="": Log.Info("AniDB Ratings: 'None'") elif '.' in misc and float(misc) == metadata.rating: Log.Info("AniDB Ratings: '%s'*" % misc) else: Log.Info("AniDB Ratings: '%s'" % misc); metadata.rating = float( misc ) ### AniDB Genres ### genres = {} for tag in anime.xpath('tags/tag'): this_tag = getElementText(tag, 'name').lower() this_tag_caps = " ".join(string.capwords(tag_part, '-') for tag_part in this_tag.split()) if int(tag.get('weight')) >= (400 if Prefs['MinimumWeight'] == None else int(Prefs['MinimumWeight'])): genres [ this_tag_caps ] = int(tag.get('weight')) sortedGenres = sorted(genres.items(), key=lambda x: x[1], reverse=True) log_string, genres = "AniDB Genres (Weight): ", [] for genre in sortedGenres: genres.append(genre[0].encode("utf-8") ) if sorted(metadata.genres)==sorted(genres): Log.Info(log_string+str(sortedGenres)+"*") else: Log.Info("genres: " + str(sortedGenres) + " " + str(genres)) metadata.genres.clear() for genre in sortedGenres: metadata.genres.add(genre[0]) log_string += "%s (%s) " % (genre[0], str(genre[1])) Log.Info(log_string) ### AniDB Content Rating ### c_source, c_rating, c_genre = 'None', 'None', 'No match' a_movie = True if (movie or anime.xpath('/anime/type')[0].text == "Movie" or tvdbid == 'movie' or "Complete Movie" in [titleText.text for titleText in anime.xpath('episodes/episode/title')]) else False if tvdbContentRating: c_source, c_rating, c_genre = 'TVDB', MOVIE_RATING_MAP[tvdbContentRating] if a_movie else tvdbContentRating, 'Rating flag' elif tvdbid=='hentai': c_source, c_rating, c_genre = 'ScudLee', "X", 'Hentai flag' else: anidb_genres = [getElementText(tag, 'name').lower() for tag in anime.xpath('tags/tag')] result = [(r, g) for r in RESTRICTED_GENRE for g in RESTRICTED_GENRE[r] if g in anidb_genres] # List Comprehension: [word for sentence in text for word in sentence if result: c_source, c_rating, c_genre = 'AniDB', MOVIE_RATING_MAP[result[0][0]] if a_movie else result[0][0], result[0][1] if (None if c_rating=='None' else c_rating) == metadata.content_rating: Log.Info("Content Rating - Source: '%s', Rating: '%s', Genre: '%s'*" % (c_source, c_rating, c_genre)) else: Log.Info("Content Rating - Source: '%s', Rating: '%s' (From '%s'), Genre: '%s'" % (c_source, c_rating, metadata.content_rating, c_genre)); metadata.content_rating = c_rating ### AniDB Collections ### self.anidbCollectionMapping(metadata, anime, anidbid_table) ### AniDB Creator data - Aside from the animation studio, none of this maps to Series entries, so save it for episodes ### log_string, metadata.studio, plex_role = "AniDB Creator data: ", "", {'directors': [], 'producers': [], 'writers': []} roles = { "Animation Work": ["studio", 'studio' , "studio"], "Direction": ["directors", 'directors', "director"], "Series Composition": ["producers", 'producers', "producer"], "Original Work" : ["writers", 'writers', "writer"], "Script" : ["writers", 'writers' , "writer" ], "Screenplay" : ["writers", 'writers' , "writer" ] } if movie: ###github for role in roles [1:3]: roles[role][0].clear() metadata.writers.clear # a = sum(getattr(t, name, 0) for name in "xyz") metadata.producers.clear() metadata.directors.clear() #Log.Debug("before for") #test = {"directors", 'producers', 'writers'} #for role in test: metadata.test[role].clear() #for role in ["directors", 'producers', 'writers']: metadata.role.clear() #role2[role].clear() #TypeError: unhashable type log_string = "AniDB Creator data: " for creator in anime.xpath('creators/name'): for role in roles: if role in creator.get('type'): if roles[ role ][1]=='studio': metadata.studio = creator.text elif movie: if roles[ role ][1]=='directors': metadata.directors.add(creator.text) elif roles[ role ][1]=='writers': metadata.writers.add(creator.text) else: plex_role [ roles[role][1] ].append(creator.text) #not movie #for episodes log_string += "%s is a %s, " % (creator.text, roles[role][2] ) if metadata.studio == "" and mapping_studio == "": error_log['anime-list studio logos'].append("anidbid: %s | Title: '%s' | AniDB and anime-list are both missing the studio" % (WEB_LINK % (ANIDB_SERIE_URL % metadata_id_number, metadata_id_number), title) ) if metadata.studio and mapping_studio and metadata.studio != mapping_studio: error_log['anime-list studio logos'].append("anidbid: %s | Title: '%s' | AniDB has studio '%s' and anime-list has '%s' | " % (WEB_LINK % (ANIDB_SERIE_URL % metadata_id_number, metadata_id_number), title, metadata.studio, mapping_studio) + WEB_LINK % (ANIDB_TVDB_MAPPING_FEEDBACK % ("aid:" + metadata.id + " " + title, String.StripTags( XML.StringFromElement(anime, encoding='utf8'))), "Submit bug report (need GIT account)")) if metadata.studio == "" and mapping_studio: metadata.studio = mapping_studio Log.Info(log_string) ### AniDB Serie/Movie description ### try: description = re.sub(r'http://anidb\.net/[a-z]{1,2}[0-9]+ \[(.+?)\]', r'\1', getElementText(anime, 'description')).replace("`", "'") # Remove wiki-style links to staff, characters etc except Exception as e: description = ""; Log.Error("Exception: %s" % e) if description == "": error_log['AniDB summaries missing'].append("anidbid: %s" % (WEB_LINK % (ANIDB_SERIE_URL % metadata_id_number, metadata_id_number) + " | Title: '%s'" % metadata.title)) if tvdbOverview: description = tvdbOverview; Log.Warn("AniDB series summary is missing but TVDB has one availabe so using it.") if metadata.summary != description and description: metadata.summary = description.replace("`", "'") ### AniDB Posters ### Log.Info("AniDB Poster, url: '%s'" % (ANIDB_PIC_BASE_URL + getElementText(anime, 'picture'))) if getElementText(anime, 'picture') == "": error_log['AniDB posters missing'].append("anidbid: %s" % (WEB_LINK % (ANIDB_SERIE_URL % metadata_id_number, metadata_id_number) + " | Title: '%s'" % metadata.title)) elif Prefs['GetAnidbPoster']: self.metadata_download (metadata.posters, ANIDB_PIC_BASE_URL + getElementText(anime, 'picture'), 99, "AniDB/%s" % getElementText(anime, 'picture')) if not movie: ### TV Serie specific ################################################################################################################# numEpisodes, totalDuration, mapped_eps, missing_eps, missing_specials, ending_table, op_nb = 0, 0, [], [], [], {}, 0 specials = {'S': [0, 'Special'], 'C': [100, 'Opening/Ending'], 'T': [200, 'Trailer'], 'P': [300, 'Parody'], 'O': [400, 'Other']} for episode in anime.xpath('episodes/episode'): ### Episode Specific ########################################################################################### ep_title, main = self.getAniDBTitle (episode.xpath('title'), EPISODE_LANGUAGE_PRIORITY) epNum, eid = episode.xpath('epno')[0], episode.get('id') epNumType = epNum.get('type') season, epNumVal = "1" if epNumType == "1" else "0", epNum.text if epNumType == "1" else str( specials[ epNum.text[0] ][0] + int(epNum.text[1:])) if epNumType=="3": if ep_title.startswith("Ending"): if op_nb==0: op_nb = int(epNum.text[1:])-1 #first type 3 is first ending so epNum.text[1:] -1 = nb openings epNumVal = str( int(epNumVal) +50-op_nb) #shifted to 150 for 1st ending. Log.Info("AniDB specials title - Season: '%s', epNum.text: '%s', epNumVal: '%s', ep_title: '%s'" % (season, epNum.text, epNumVal, ep_title) ) if not (season in media.seasons and epNumVal in media.seasons[season].episodes): #Log.Debug("Season: '%s', Episode: '%s' => '%s' not on disk" % (season, epNum.text, epNumVal) ) current_air_date = getElementText(episode, 'airdate').replace('-','') current_air_date = int(current_air_date) if current_air_date.isdigit() and int(current_air_date) > 10000000 else 99999999 if current_date <= (current_air_date+1): Log.Warn("Episode '%s' is missing in Plex but air date '%s+1' is either missing (99999999) or in the future" % (epNumVal, current_air_date)); continue if epNumType == "1" : missing_eps.append( "s" + season + "e" + epNumVal ) elif epNumType == "2": missing_specials.append("s" + season + "e" + epNumVal ) continue episodeObj = metadata.seasons[season].episodes[epNumVal] ### AniDB Get the correct episode title ### if episodeObj.title == ep_title: Log.Info("AniDB episode title: '%s'*" % ep_title) else: Log.Info("AniDB episode title: '%s'" % ep_title); episodeObj.title = ep_title ### AniDBN turn the YYYY-MM-DD airdate in each episode into a Date ### airdate, originally_available_at = getElementText(episode, 'airdate'), None if airdate: match = re.match("([1-2][0-9]{3})-([0-1][0-9])-([0-3][0-9])", airdate) if match: try: originally_available_at = datetime.date(int(match.group(1)), int(match.group(2)), int(match.group(3))) except ValueError, e: Log.Error("AniDB parseAirDate - Date out of range: " + str(e)) if originally_available_at == episodeObj.originally_available_at: Log.Info("AniDB AirDate '%s'*" % airdate) else: Log.Info("AniDB AirDate '%s'" % airdate); episodeObj.originally_available_at = originally_available_at ### AniDB Duration ### if getElementText(episode, 'length'): duration = int(getElementText(episode, 'length')) * 1000 * 60 # Plex save duration in millisecs, AniDB stores it in minutes if episodeObj.duration == duration: Log.Info("AniDB duration: '%d'*" % duration) else: Log.Info("AniDB duration: '%d'" % duration); episodeObj.duration = duration; if season == "1": numEpisodes, totalDuration = numEpisodes + 1, totalDuration + episodeObj.duration ### AniDB Writers, Producers, Directors ### #Log.Debug("### AniDB Writers, Producers, Directors ### ") episodeObj.writers.clear() episodeObj.producers.clear() episodeObj.directors.clear() for role in plex_role: for person in plex_role[role]: if role=="writers" and person not in episodeObj.writers: episodeObj.writers.add (person) if role=="producers" and person not in episodeObj.producers: episodeObj.producers.add(person) if role=="directors" and person not in episodeObj.directors: episodeObj.directors.add(person) ### Rating ### rating = getElementText(episode, 'rating') #if rating =="": Log.Debug(metadata.id + " Episode rating: ''") #elif rating == episodeObj.rating: Log.Debug(metadata.id + " update - Episode rating: '%s'*" % rating ) if not rating =="" and re.match("^\d+?\.\d+?$", rating): episodeObj.rating = float(rating) #try: float(element) except ValueError: print "Not a float" ### TVDB mapping episode summary ### try: if tvdbid.isdigit(): anidb_ep, tvdb_ep, summary= 's' + season + 'e' + epNumVal, "", "No summary in TheTVDB.com" #epNum if anidb_ep in mappingList and mappingList[anidb_ep] in tvdb_table: tvdb_ep = mappingList [ anidb_ep ] elif 's'+season in mappingList and int(epNumVal) >= int (mappingList['s'+season][0]) and int(epNumVal) <= int(mappingList['s'+season][1]): tvdb_ep = str( int(mappingList['s'+season][2]) + int(epNumVal) ) # season offset + ep number elif defaulttvdbseason=="a" and epNumVal in tvdb_table: tvdb_ep = str( int(epNumVal) + ( int(mappingList [ 'episodeoffset' ]) if 'episodeoffset' in mappingList and mappingList [ 'episodeoffset' ].isdigit() else 0 ) ) elif season=="0": tvdb_ep = "s"+season+"e"+epNumVal else: tvdb_ep = "s"+defaulttvdbseason+"e"+ str(int(epNumVal) + ( int(mappingList [ 'episodeoffset' ]) if 'episodeoffset' in mappingList and mappingList [ 'episodeoffset' ].isdigit() else 0 )) summary = "TVDB summary missing" if tvdb_ep=="" or tvdb_ep not in tvdb_table else tvdb_table [tvdb_ep] ['Overview'].replace("`", "'") if re.match("^Episode [0-9]{1,4}$", episodeObj.title) and tvdb_ep in tvdb_table: ep_title = tvdb_table [tvdb_ep] ['EpisodeName']; episodeObj.title = ep_title Log.Warn("AniDB episode title is missing but TVDB has one availabe so using it.") mapped_eps.append( anidb_ep + ">" + tvdb_ep ) if tvdb_ep in tvdb_table and 'filename' in tvdb_table[tvdb_ep] and tvdb_table[tvdb_ep]['filename']!="": self.metadata_download (episodeObj.thumbs, TVDB_IMAGES_URL + tvdb_table[tvdb_ep]['filename'], 1, "TVDB/episodes/"+ os.path.basename(tvdb_table[tvdb_ep]['filename'])) Log.Info("TVDB mapping episode summary - anidb_ep: '%s', tvdb_ep: '%s', season: '%s', epNumVal: '%s', defaulttvdbseason: '%s', title: '%s', summary: '%s'" %(anidb_ep, tvdb_ep, season, epNumVal, defaulttvdbseason, ep_title, tvdb_table [tvdb_ep] ['Overview'][0:50].strip() if tvdb_ep in tvdb_table else "") ) episodeObj.summary = summary.replace("`", "'") except Exception as e: Log.Error("Issue in 'TVDB mapping episode summary', epNumVal: '%s'", epNumVal) Log.Error("mappingList = %s" % mappingList) Log.Error("Exception: %s" % e) ## End of "for episode in anime.xpath('episodes/episode'):" ### Episode Specific ########################################################################################### ### AniDB Missing Episodes ### #Log.Debug("type: %s , ep1 title: %s" % (anime.xpath('/anime/type')[0].text, anime.xpath('episodes/episode/title')[0].text)) if len(missing_eps)>0 and anime.xpath('/anime/type')[0].text == "Movie" and "Complete Movie" in [titleText.text for titleText in anime.xpath('episodes/episode/title')]: movie_ep_groups = [ {}, {}, {}, {}, {}, {}, {} ] for episode in anime.xpath('episodes/episode'): epNum = episode.xpath('epno')[0] epTitle = episode.xpath('title')[0] epNumType = epNum.get('type') season = "1" if epNumType == "1" else "0" if season == "0": continue epNumVal = "s%se%s" % (season, epNum.text) part_group = -1 if epTitle.text == "Complete Movie": part_group = 0 if epTitle.text.startswith("Part "): part_group = int(epTitle.text[-1]) if epTitle.text[-1].isdigit() else -1 if part_group != -1: movie_ep_groups[part_group][epNumVal] = 'found' #Log.Debug("orig movie_ep_groups: " + str(movie_ep_groups)) #Log.Debug("orig missing_eps: " + str(missing_eps)) for missing_ep in missing_eps: for movie_ep_group in movie_ep_groups: if missing_ep in movie_ep_group.keys(): movie_ep_group[missing_ep] = 'missing' Log.Debug("movie_ep_groups: " + str(movie_ep_groups)) missing_eps = [] for movie_ep_group in movie_ep_groups: if 'found' in movie_ep_group.keys() and 'missing' in movie_ep_group.keys(): for key in movie_ep_group.keys(): if movie_ep_group[key] == 'missing': missing_eps.append(key) Log.Debug("new missing_eps: " + str(missing_eps)) if len(missing_eps)>0: missing_eps = sorted(missing_eps, key=lambda x: int("%d%04d" % (int(x.split('e')[0][1:]), int(x.split('e')[1])))) error_log['Missing Episodes'].append("anidbid: %s | Title: '%s' | Missing Episodes: %s" % (WEB_LINK % (ANIDB_SERIE_URL % metadata_id_number, metadata_id_number), title, str(missing_eps))) if len(missing_specials)>0: missing_specials = sorted(missing_specials, key=lambda x: int("%d%04d" % (int(x.split('e')[0][1:]), int(x.split('e')[1])))) error_log['Missing Specials'].append("anidbid: %s | Title: '%s' | Missing Episodes: %s" % (WEB_LINK % (ANIDB_SERIE_URL % metadata_id_number, metadata_id_number), title, str(missing_specials))) convert = lambda text: int(text) if text.isdigit() else text alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] ### AniDB Final post-episode titles cleanup ### Log.Info("DURATION: %s, numEpisodes: %s" %(str(totalDuration), str(numEpisodes)) ) if numEpisodes: metadata.duration = int(totalDuration) / int(numEpisodes) #if movie getting scrapped as episode number by scanner... ### End of if anime is not None: ### ### HAMA - Load logs, add non-present entried then Write log files to Plug-in /Support/Data/com.plexapp.agents.hama/DataItems ### log_line_separator = "
\r\n" global error_log_lock_sleep for log in error_log: error_log_array, log_prefix, num_of_sleep_sec = {}, "", 0 while error_log_locked[log][0]: Log.Warn("'%s' lock exists. Sleeping 1sec for lock to disappear." % log) num_of_sleep_sec += 1 if num_of_sleep_sec > error_log_lock_sleep: break time.sleep(1) if int(time.time())-error_log_locked[log][1] < error_log_lock_sleep * 2 and num_of_sleep_sec > error_log_lock_sleep: Log.Error("Could not obtain the lock in %ssec & lock age is < %ssec. Skipping log update." % (error_log_lock_sleep, error_log_lock_sleep * 2)); continue error_log_locked[log] = [True, int(time.time())]; Log.Debug("Locked '%s' %s" % (log, error_log_locked[log])) if Data.Exists(log+".htm"): for line in Data.Load(log+".htm").split(log_line_separator): if "|" in line: error_log_array[line.split("|", 1)[0].strip()] = line.split("|", 1)[1].strip() if log == 'TVDB posters missing': log_prefix = WEB_LINK % ("http://thetvdb.com/wiki/index.php/Posters", "Restrictions") + log_line_separator if log == 'Plex themes missing': log_prefix = WEB_LINK % ("https://plexapp.zendesk.com/hc/en-us/articles/201572843","Restrictions") + log_line_separator for entry in error_log[log]: error_log_array[entry.split("|", 1)[0].strip()] = entry.split("|", 1)[1].strip() if error_log[log] == []: if not log in ["Missing Episodes", "Missing Specials"]: keys = ["anidbid: %s" % (WEB_LINK % (ANIDB_SERIE_URL % anidbid, anidbid)), "anidbid: %s" % anidbid, "tvdbid: %s" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid ) ), "tvdbid: %s" % tvdbid] elif not movie and (len(media.seasons)>2 or max(map(int, media.seasons.keys()))>1): keys = ["tvdbid: %s" % (WEB_LINK % (TVDB_SERIE_URL % tvdbid, tvdbid) )] else: keys = ["%sid: %s" % (metadata_id_source_core, WEB_LINK % (ANIDB_SERIE_URL % metadata_id_number if metadata_id_source_core == "anidb" else TVDB_SERIE_URL % metadata_id_number, metadata_id_number) )] for key in keys: if key in error_log_array.keys(): del(error_log_array[key]) Data.Save(log+".htm", log_prefix + log_line_separator.join(sorted([str(key)+" | "+str(error_log_array[key]) for key in error_log_array.keys()], key = lambda x: x.split("|",1)[1] if x.split("|",1)[1].strip().startswith("Title:") and not x.split("|",1)[1].strip().startswith("Title: ''") else int(re.sub("<[^<>]*>", "", x.split("|",1)[0]).strip().split()[1]) ))) error_log_locked[log] = [False, 0]; Log.Debug("Unlocked '%s' %s" % (log, error_log_locked[log])) Log.Info('--- Update end -------------------------------------------------------------------------------------------------') ### Get the tvdbId from the AnimeId ####################################################################################################################### def anidbTvdbMapping(self, metadata, anidb_id, error_log): global AniDB_TVDB_mapping_tree #if not AniDB_TVDB_mapping_tree: AniDB_TVDB_mapping_tree = self.xmlElementFromFile(ANIDB_TVDB_MAPPING, ANIDB_TVDB_MAPPING, False, CACHE_1HOUR * 24) # Load XML file poster_id_array, mappingList = {}, {} for anime in AniDB_TVDB_mapping_tree.iter('anime') if AniDB_TVDB_mapping_tree else []: anidbid, tvdbid, tmdbid, imdbid, defaulttvdbseason, mappingList['episodeoffset'] = anime.get("anidbid"), anime.get('tvdbid'), anime.get('tmdbid'), anime.get('imdbid'), anime.get('defaulttvdbseason'), anime.get('episodeoffset') if tvdbid.isdigit(): poster_id_array [tvdbid] = poster_id_array [tvdbid] + 1 if tvdbid in poster_id_array else 0 # Count posters to have a unique poster per anidbid if anidbid == anidb_id: #manage all formats latter name = anime.xpath("name")[0].text if tvdbid.isdigit(): try: ### mapping list ### for season in anime.iter('mapping') if anime else []: if anime.get("offset"): mappingList[ 's'+season.get("tvdbseason")] = [anime.get("start"), anime.get("end"), anime.get("offset")] for string2 in filter(None, season.text.split(';')): mappingList [ 's' + season.get("anidbseason") + 'e' + string2.split('-')[0] ] = 's' + season.get("tvdbseason") + 'e' + string2.split('-')[1] except Exception as e: Log.Error("mappingList creation exception, Exception: '%s'" % e) elif tvdbid in ("", "unknown"): error_log ['anime-list tvdbid missing'].append("anidbid: %s | Title: '%s' | Has no matching tvdbid ('%s') in mapping file | " % (anidb_id, name, tvdbid) + WEB_LINK % (ANIDB_TVDB_MAPPING_FEEDBACK % ("aid:%s '%s' tvdbid:" % (anidb_id, name), String.StripTags( XML.StringFromElement(anime, encoding='utf8')) ), "Submit bug report")) try: mapping_studio = anime.xpath("supplemental-info/studio")[0].text except: mapping_studio = "" Log.Info("anidb: '%s', tvbdid: '%s', tmdbid: '%s', imbdid: '%s', studio: '%s', defaulttvdbseason: '%s', name: '%s'" % (anidbid, tvdbid, tmdbid, imdbid, mapping_studio, defaulttvdbseason, name) ) anidbid_table = [] for anime2 in AniDB_collection_tree.iter("anime") if AniDB_collection_tree else []: if tvdbid == anime2.get('tvdbid'): anidbid_table.append( anime2.get("anidbid") ) #collection gathering return tvdbid, tmdbid, imdbid, defaulttvdbseason, mappingList, mapping_studio, anidbid_table, poster_id_array [tvdbid] if tvdbid in poster_id_array else {} else: Log.Error("anidbid '%s' not found in file" % anidb_id) error_log['anime-list anidbid missing'].append("anidbid: %s | Title: 'UNKNOWN'" % WEB_LINK % (ANIDB_SERIE_URL % anidbid, anidbid)) return "", "", "", "", [], "", [], "0" ### AniDB collection mapping - complement AniDB movie collection file with related anime AND series sharing the same tvdbid ######################## def anidbCollectionMapping(self, metadata, anime, anidbid_table=[]): global AniDB_collection_tree, SERIE_LANGUAGE_PRIORITY related_anime_list = []; metadata_id_source, metadata_id_number = metadata.id.split('-', 1) for relatedAnime in anime.xpath('/anime/relatedanime/anime'): related_anime_list.append(relatedAnime.get('id')); metadata.collections.clear() for element in AniDB_collection_tree.iter("anime") if AniDB_collection_tree else []: if element.get('anidbid') in related_anime_list + anidbid_table + [metadata_id_number] and metadata_id_source == "anidb": set = element.getparent() title, main = self.getAniDBTitle(set.xpath('titles')[0], SERIE_LANGUAGE_PRIORITY) metadata.collections.add(title) #metadata.collections.clear() Log.Info("anidbid '%s' is part of movie collection: %s', related_anime_list: '%s', " % (metadata_id_number, title, str(related_anime_list))) return Log.Info("anidbid is not part of any collection, related_anime_list: '%s'" % str(related_anime_list)) ### [tvdb4.posters.xml] Attempt to get the ASS's image data ############################################################################################################### def getImagesFromASS(self, metadata, media, tvdbid, movie, num=0): posternum, seasonposternum = 0, 0 if movie: return try: s = media.seasons.keys()[0] e = media.seasons[s].episodes.keys()[0] dir_path = os.path.dirname(media.seasons[s].episodes[e].items[0].parts[0].file) dir_name = os.path.basename(dir_path) if "[tvdb4-" not in dir_name and "tvdb4.id" not in os.listdir(dir_path): Log.Debug("Files are in a season folder (option 1)"); return elif "tvdb4.mapping" in os.listdir(dir_path): Log.Debug("Files are in the series folder and has a mapping file (option 2)"); return else: Log.Debug("Files are in the series folder and has no mapping file (option 3)") except Exception as e: Log.Error("Issues in finding setup info as directories have most likely changed post scan into Plex, Exception: '%s'" % e) try: postersXml = XML.ElementFromURL( ASS_POSTERS_URL, cacheTime=CACHE_1HOUR * 24) except Exception as e: Log.Error("Loading poster XML failed: '%s', Exception: '%s'"% (ASS_POSTERS_URL, e)); return else: Log.Info( "Loaded poster XML: '%s'" % ASS_POSTERS_URL) entry = postersXml.xpath("/tvdb4entries/posters[@tvdbid='%s']" % tvdbid) if not entry: Log.Error("tvdbid '%s' is not found in xml file" % tvdbid); return for line in filter(None, entry[0].text.strip().replace("\r","\n").split("\n")): num += 1; seasonposternum += 1 season, posterURL = line.strip().split("|",1); season = str(int(season)) #str(int(x)) remove leading 0 from number string posterPath = "seasons/%s-%s-%s" % (tvdbid, season, os.path.basename(posterURL)) if movie or season not in media.seasons: continue self.metadata_download (metadata.seasons[season].posters, posterURL, num, "TVDB/"+posterPath) return posternum, seasonposternum ### [banners.xml] Attempt to get the TVDB's image data ############################################################################################################### def getImagesFromTVDB(self, metadata, media, tvdbid, movie, poster_id=1, force=False, defaulttvdbseason_offset="", num=0): posternum, seasonposternum, poster_total = 0, 0, 0 defaulttvdbseason_offset = int(defaulttvdbseason_offset)-1 if defaulttvdbseason_offset.isdigit() else 0 try: bannersXml = XML.ElementFromURL( TVDB_BANNERS_URL % tvdbid, cacheTime=CACHE_1HOUR * 24) # don't bother with the full zip, all we need is the banners except Exception as e: Log.Error("Loading picture XML failed: '%s', Exception: '%s'" %(TVDB_BANNERS_URL % tvdbid, e)); return else: Log.Info( "Loaded picture XML: '%s'" % (TVDB_BANNERS_URL % tvdbid)) for banner in bannersXml.xpath('Banner'): if banner.xpath('BannerType')[0].text=="poster": poster_total +=1 for banner in bannersXml.xpath('Banner'): #rating = banner.xpath('Rating' )[0].text if banner.xpath('Rating') else "" #Language = banner.xpath('Language' )[0].text #if Language not in ['en', 'jp']: continue #id = banner.xpath('id' )[0].text num, bannerType, bannerType2, bannerPath = num+1, banner.xpath('BannerType' )[0].text, banner.xpath('BannerType2')[0].text, banner.xpath('BannerPath' )[0].text if bannerType == 'poster': posternum += 1 if bannerType == 'season' and bannerType2=='season': seasonposternum += 1 season = banner.xpath('Season')[0].text if banner.xpath('Season') else "" if season.isdigit() and defaulttvdbseason_offset!=0: if int(season)-defaulttvdbseason_offset>0: Log.Debug("Setting season '%s' to '%s' with offset of '%s'" % (season, int(season)-defaulttvdbseason_offset, defaulttvdbseason_offset)); season = str( int(season)-defaulttvdbseason_offset ) elif season=="0": Log.Debug("Keeping season '0' as '0'") else: Log.Debug("New season would be <1 (%s-%s) so skipping poster '%s'" % (season, defaulttvdbseason_offset, bannerPath)); continue if movie and not bannerType in ('fanart', 'poster') or season and season not in media.seasons: continue if Prefs['GetTvdbPosters'] and ( bannerType == 'poster' or bannerType2 == 'season' and not movie ) or \ Prefs['GetTvdbFanart' ] and bannerType == 'fanart' or \ Prefs['GetTvdbBanners'] and not movie and ( bannerType == 'series' or bannerType2 == 'seasonwide'): metatype = (metadata.art if bannerType == 'fanart' else \ metadata.posters if bannerType == 'poster' else \ metadata.banners if bannerType == 'series' or bannerType2=='seasonwide' else \ metadata.seasons[season].posters if bannerType == 'season' and bannerType2=='season' else None) if metatype == metadata.posters: rank = 1 if poster_id and poster_total and posternum == divmod(poster_id, poster_total)[1] + 1 else posternum+1 else: rank = num bannerThumbUrl = TVDB_IMAGES_URL + (banner.xpath('ThumbnailPath')[0].text if bannerType=='fanart' else bannerPath) self.metadata_download (metatype, TVDB_IMAGES_URL + bannerPath, rank, "TVDB/"+bannerPath, bannerThumbUrl) return posternum, seasonposternum ### Download TMDB poster and background through IMDB or TMDB ID ########################################################################################## def getImagesFromTMDB(self, metadata, id, num=90): config_dict, images = self.get_json(TMDB_CONFIG_URL, cache_time=CACHE_1WEEK * 2), {} if id.startswith("tt"): Log.Info("using IMDBID url: " + TMDB_SEARCH_URL_BY_IMDBID % id) tmdb_json = self.get_json(TMDB_SEARCH_URL_BY_IMDBID %id, cache_time=CACHE_1WEEK * 2) # Log.Debug("getImagesFromTMDB - by IMDBID - tmdb_json: '%s'" % str(tmdb_json)) for type in ['movie_results', 'tv_results']: if tmdb_json is not None and type in tmdb_json: for index, poster in enumerate(tmdb_json[type]): if Prefs["GetTmdbPoster"] and 'poster_path' in tmdb_json[type][index] and tmdb_json[type][index]['poster_path' ] not in (None, "", "null"): images[ tmdb_json[type][index]['poster_path' ]] = metadata.posters if Prefs["GetTmdbFanart"] and 'backdrop_path' in tmdb_json[type][index] and tmdb_json[type][index]['backdrop_path'] not in (None, "", "null"): images[ tmdb_json[type][index]['backdrop_path']] = metadata.art rank=90 else: Log.Info("using TMDBID url: '%s'" % ((TMDB_MOVIE_IMAGES_URL if metadata.id.startswith("tmdb") else TMDB_SERIE_IMAGES_URL) % id)) tmdb_json = self.get_json(url=(TMDB_MOVIE_IMAGES_URL if metadata.id.startswith("tmdb") else TMDB_SERIE_IMAGES_URL) % id, cache_time=CACHE_1WEEK * 2) if tmdb_json and 'posters' in tmdb_json and len(tmdb_json['posters' ]): for index, poster in enumerate(tmdb_json['posters']): if Prefs["GetTmdbPoster"] and 'file_path' in tmdb_json['posters'][index] and tmdb_json['posters'][index]['file_path'] not in (None, "", "null"): images[ tmdb_json['posters' ][index]['file_path']] = metadata.posters if tmdb_json is not None and 'backdrops' in tmdb_json and len(tmdb_json['backdrops']): for index, poster in enumerate(tmdb_json['backdrops']): if Prefs["GetTmdbFanart"] and 'file_path' in tmdb_json['backdrops'][index] and tmdb_json['backdrops'][index]['file_path'] not in (None, "", "null"): images[ tmdb_json['backdrops'][index]['file_path']] = metadata.art rank=95 if len(images): for filename in images.keys(): if filename: image_url, thumb_url = config_dict['images']['base_url'] + 'original' + filename, config_dict['images']['base_url'] + 'w300' + filename self.metadata_download (images[filename], image_url, rank, "TMDB/%s%s.jpg" % (id, "" if images[filename]==metadata.posters else "-art"), thumb_url) ### Fetch the IMDB poster using OMDB HTTP API ########################################################################################################### def getImagesFromOMDB(self, metadata, imdbid, num=99): Log.Info("imdbid: '%s', url: '%s', filename: '%s'" % (imdbid, OMDB_HTTP_API_URL + imdbid, "OMDB/%s.jpg" % imdbid)) try: OMDB = self.get_json(OMDB_HTTP_API_URL + imdbid, cache_time=CACHE_1WEEK * 56) except Exception as e: Log.Error("Exception - imdbid: '%s', url: '%s', filename: '%s', Exception: '%s'" % (imdbid, OMDB_HTTP_API_URL + imdbid, "OMDB/%s.jpg" % imdbid, e)) else: if OMDB and 'Poster' in OMDB and OMDB['Poster'] not in ("N/A", "", None): self.metadata_download (metadata.posters, OMDB['Poster'], num, "OMDB/%s.jpg" % imdbid) else: Log.Info("No poster to download - " + OMDB_HTTP_API_URL + imdbid) ### Fetch extra images from fanart.tv ################################################################################################################### def getImagesFromFanartTV(self, metadata, tvdbid=None, tmdbid=None, season=0, num=100): Log.Info("Fetching from fanart.tv") if tvdbid: try: FanartTV = self.get_json(FANART_TV_TV_URL.format(tvdbid=tvdbid, api_key=FANART_TV_API_KEY)) # It's a series, grab the list of fanart using the TVDB ID. except Exception as e: Log.Error("Exception - FanartTV - tvdbid: '{tvdbid}', url: '{url}', Exception: '{exception}'".format(tvdbid=tvdbid, url=FANART_TV_TV_URL.format(tvdbid=tvdbid, api_key=FANART_TV_API_KEY), exception=e)) if FanartTV and 'showbackground' in FanartTV and Prefs['GetFanartTVBackground']: Log.Debug("fanart.tv has {count} background images/art".format(count=len(FanartTV['showbackground']))) for art in FanartTV['showbackground']: self.metadata_download(metadata.art, art['url'], num, "FanartTV/series-{filename}.jpg".format(filename=art['id'])) if FanartTV and 'tvposter' in FanartTV and Prefs['GetFanartTVPoster']: Log.Debug("fanart.tv has {count} series posters".format(count=len(FanartTV['tvposter']))) for tvposter in FanartTV['tvposter']: self.metadata_download(metadata.posters, tvposter['url'], num, "FanartTV/series-{filename}.jpg".format(filename=tvposter['id'])) if FanartTV and 'seasonposter' in FanartTV and Prefs['GetFanartTVPoster']: Log.Debug("fanart.tv has {count} season posters".format(count=len(FanartTV['seasonposter']))) for seasonposter in FanartTV['seasonposter']: self.metadata_download(metadata.posters, seasonposter['url'], num, "FanartTV/series-{filename}.jpg".format(filename=seasonposter['id'])) # Add all of the 'season' posters as potential main show posters. Now add season posters to their respective seasons within the show. if seasonposter['season'] == 0: self.metadata_download(metadata.seasons[0].posters, seasonposter['url'], num, "FanartTV/series-{filename}.jpg".format(filename=seasonposter['id'])) # Special elif seasonposter['season'] == season: self.metadata_download(metadata.seasons[1].posters, seasonposter['url'], num, "FanartTV/series-{filename}.jpg".format(filename=seasonposter['id'])) # Non-special. Add any posters to "Season 1" entry if they match this 'actual' season. else: pass if FanartTV and 'tvbanner' in FanartTV and Prefs['GetFanartTVBanner']: Log.Debug("fanart.tv has {count} banners".format(count=len(FanartTV['tvbanner']))) for tvbanner in FanartTV['tvbanner']: self.metadata_download(metadata.banners, tvbanner['url'], num, "FanartTV/series-{filename}.jpg".format(filename=tvbanner['id'])) elif tmdbid: try: FanartTV = self.get_json(FANART_TV_MOVIES_URL.format(tmdbid=tmdbid, api_key=FANART_TV_API_KEY)) # It's a movie, grab the list of fanart using the TMDB ID. except Exception as e: Log.Error("Exception - FanartTV - tmdbid: '{tmdbid}', url: '{url}', Exception: 'movie-{exception}'".format(tmdbid=tmdbid, url=FANART_TV_MOVIES_URL.format(tmdbid=tmdbid, api_key=FANART_TV_API_KEY), exception=e)) if FanartTV and 'moviebackground' in FanartTV and Prefs['GetFanartTVBackground']: Log.Debug("fanart.tv has {count} movie background images/art".format(count=len(FanartTV['moviebackground']))) for art in FanartTV['moviebackground']: self.metadata_download(metadata.art, art['url'], num, "FanartTV/movie-{filename}.jpg".format(filename=art['id'])) if FanartTV and 'movieposter' in FanartTV and Prefs['GetFanartTVPoster']: Log.Debug("fanart.tv has {count} movie posters".format(count=len(FanartTV['movieposter']))) for movieposter in FanartTV['movieposter']: self.metadata_download(metadata.posters, movieposter['url'], num, "FanartTV/movie-{filename}.jpg".format(filename=movieposter['id'])) ######################################################################################################################################################### def metadata_download (self, metatype, url, num=99, filename="", url_thumbnail=None): #if url in metatype:# Log.Debug("url: '%s', num: '%s', filename: '%s'*" % (url, str(num), filename)) # Log.Debug(str(metatype)) # return if url not in metatype: file = None if filename and Data.Exists(filename): ### if stored locally load it try: file = Data.Load(filename) except Exception as e: Log.Warn("could not load file '%s' present in cache, Exception: '%s'" % (filename, e)) if file == None: ### if not loaded locally download it try: file = HTTP.Request(url_thumbnail if url_thumbnail else url, cacheTime=None).content except Exception as e: Log.Error("error downloading, Exception: '%s'" % e); return else: ### if downloaded, try saving in cache but folders need to exist if filename and not filename.endswith("/"): try: Data.Save(filename, file) except Exception as e: Log.Error("could not write filename '%s' in Plugin Data Folder, Exception: '%s'" % (filename, e)); return if file: try: metatype[ url ] = Proxy.Preview(file, sort_order=num) if url_thumbnail else Proxy.Media(file, sort_order=num) # or metatype[ url ] != proxy_item # proxy_item = except Exception as e: Log.Error("issue adding picture to plex - url downloaded: '%s', filename: '%s', Exception: '%s'" % (url_thumbnail if url_thumbnail else url, filename, e)) #metatype.validate_keys( url_thumbnail if url_thumbnail else url ) # remove many posters, to avoid else: Log.Info( "url: '%s', num: '%d', filename: '%s'" % (url, num, filename)) else: Log.Info("url: '%s', num: '%d', filename: '%s'*" % (url, num, filename)) ### get_json file, TMDB API supports only JSON now ###################################################################################################### def get_json(self, url, cache_time=CACHE_1MONTH): try: return JSON.ObjectFromURL(url, sleep=2.0, cacheTime=cache_time) except Exception as e: Log.Error("Error fetching JSON url: '%s', Exception: '%s'" % (url, e)) ### Pull down the XML from web and cache it or from local cache for a given anime ID #################################################################### def xmlElementFromFile (self, url, filename="", delay=True, cache=None): Log.Info("url: '%s', filename: '%s'" % (url, filename)) if delay: time.sleep(4) #2s between anidb requests but 2 threads #### Ban after 160 series if too short, ban also if same serie xml downloaded repetitively, delay for AniDB only for now #try: a = urllib.urlopen(url)#if a is not None and a.getcode()==200: try: result = str(HTTP.Request(url, headers={'Accept-Encoding':'gzip', 'content-type':'charset=utf8'}, timeout=20, cacheTime=cache)) # Loaded with Plex cache, str prevent AttributeError: 'HTTPRequest' object has no attribute 'find' except Exception as e: result = None; Log.Warn("XML issue loading url: '%s', Exception: '%s'" % (url, e)) # issue loading, but not AniDB banned as it returns "Banned" if result and len(result)>1024 and filename: # if loaded OK save else load from last saved file try: Data.Save(filename, result) except Exception as e: Log.Warn("url: '%s', filename: '%s' saving failed, probably missing folder, Exception: '%s'" % (url, filename, e)) elif filename and Data.Exists(filename): # Loading locally if backup exists Log.Info("Loading locally since banned or empty file (result page <1024 bytes)") try: result = Data.Load(filename) except Exception as e: Log.Error("Loading locally failed but data present - url: '%s', filename: '%s', Exception: '%s'" % (url, filename, e)); return if url==ANIDB_TVDB_MAPPING and Data.Exists(ANIDB_TVDB_MAPPING_CUSTOM): # Special case: if importing anidb tvdb mapping, load custom mapping entries first Log.Info("Loading local custom mapping - url: '%s'" % ANIDB_TVDB_MAPPING_CUSTOM) result_custom = Data.Load(ANIDB_TVDB_MAPPING_CUSTOM) result = result_custom[:result_custom.rfind("")-1] + result[result.find("")+len("")+1:] #cut both fiels together removing ending and starting tags to do so if result: element = XML.ElementFromString(result) if str(element).startswith("